編者說:
ModBus通信協定結構簡單,程式設計友善,在工業應用現場被廣泛使用,特别是PLC應用場合。需要指出的是,ModBus隻是一種通信協定,即裝置之間的資料限制方式,使用時需要有底層的驅動程式支援,例如,序列槽通訊。序列槽通信使用簡單,在ModBus協定中應用廣泛。在信号的傳輸方式上又分為RS-232通信,RS-485通信,這種區分隻是在資料的傳輸方式方作劃分,底層的驅動程式完全一樣。需要長距離、長距離、可靠性高的傳輸方式時,我們就選擇RS-485通信,需要短距離、高速率通信時,我們就用RS-232通信。
這篇部落客要講述ModBus-RTU通信協定的程式設計方法,實作時采用序列槽通信,RS-232的傳輸方式,采用的單片機為TMS320F280049C。
(1)ModBus通信協定簡介
Modbus協定可以說是工業自動化領域應用最為廣泛的通訊協定,因為他的開放性、可擴充性和标準化使它成為一個通用工業标準。有了它,不同廠商的産品可以簡單可靠的接入網絡,實作系統的集中監控,分散控制功能。
目前Modbus規約主要使用的是ASCII, RTU, TCP等,并沒有規定實體層。目前Modbus常用的接口形式主要有RS-232C,RS485,RS422,也有使用RJ45接口的,ModBus的ASCII, RTU協定則在此基礎上規定了消息、資料的結構、指令和應答的方式。ModBus資料通信采用Master/Slave方式(主/從),即Master端發出資料請求消息,Slave端接收到正确消息後就可以發送資料到Master端以響應請求;Master端也可以直接發消息修改Slave端的資料,實作雙向讀寫。
Modbus協定需要對資料進行校驗,串行協定中除有奇偶校驗外,ASCII模式采用LRC校驗,RTU模式采用16位CRC校驗,但TCP模式沒有額外規定校驗,因為TCP協定是一個面向連接配接的可靠協定。另外,Modbus采用主從方式定時收發資料,在實際使用中如果某Slave站點斷開後(如故障或關機),Master端可以診斷出來,而當故障修複後,網絡又可自動接通。是以,Modbus協定的可靠性較好。
(2)ModBus資訊幀結構
當裝置之間進行通信時,都是以資訊幀的結構進行資料之間的傳送。資訊幀即資料的組成方式,一個資訊幀由多位位元組資料組成,具體格式如下所示(這裡主要講述RTU格式的資訊幀):
開始 | 裝置位址 | 功能碼 | 資料位 | 校驗位 | 終止 |
T1-T2-T3-T4 | 8bit | 8bit | N * 8bit | 16bit | T1-T2-T3-T4 |
RTU模式中,資訊開始至少需要有3.5個字元的靜止時間,依據使用的波特率,很容易計算這個靜止的時間(如下圖中的T1-T2-T3-T4)。接着,第一個區的資料為裝置位址。
裝置位址:當與主機進行通信的裝置有多個時,主機通過裝置位址号來選擇與哪個裝置進行通信。裝置位址号為1位元組。
功能碼:當主機與從機通信時,主機通過功能碼來選擇對從機進行什麼操作。部分功能碼列出如下表所示,詳細的功能碼清單請查閱相關資料。功能碼資料長度為1位元組
功能碼 | 名稱 | 作用 |
0x03 | 讀取保持寄存器 | 讀取保持寄存器資料 |
0x06 | 預置單寄存器 | 将資料寫入到某寄存器 |
資料位:要傳送的具體資料,由多個位元組組成。
校驗位:主機或從機可用校驗碼進行判别接收資訊是否出錯。錯誤校驗采用CRC-16校驗方法。CRC校驗的算法後邊講解。
(3)ModBus功能碼詳解
1. 0x03号指令,讀可讀寫模拟量寄存器(保持寄存器):
主機發送指令格式:
裝置号 | 功能碼 | 起始寄存器位址高八位 | 起始寄存器位址低八位 | 讀取數高八位 | 讀取數低八位 | CRC H | CRC L |
例:【01】【03】【00】【12】【00】【03】【CRC高】【CRC低】
1)裝置位址即為0x01。
2)功能碼0x03。
3)0x00 0x12,要讀取寄存器的高八位位址和低八位位址。
4)0x00 0x03,讀取寄存器的數量,此處為讀取3個寄存器。
5)CRC H,CRC L。
從機響應格式:
裝置号 | 功能碼 | 傳回位元組個數 | 資料1 | .... | 資料N | CRC H | CRC L |
例:【01】【03】【03】【12】【01】【03】【CRC高】【CRC低】
1)裝置位址即為0x01。
2)功能碼0x03,功能為讀取保持寄存器值。
3)0x03,要傳回的資料數量,此處為3。
4)0x12 0x01 0x03,傳回的資料。
5)CRC H,CRC L。
2. 0x06号指令,寫單個模拟量寄存器(預置寄存器):
主機發送指令格式:
裝置号 | 功能碼 | 預置寄存器位址高八位 | 預置寄存器位址低八位 | 預置資料高八位 | 預置資料低八位 | CRC H | CRC L |
例:【01】【06】【00】【01】【00】【03】【CRC高】【CRC低】
1)裝置位址即為0x01。
2)功能碼0x06,預置單個模拟量寄存器。
3)0x00 0x01,要預置的寄存器的高八位位址和低八位位址。
4)0x00 0x03,預置的資料,此處為一個16bit的資料。
5)CRC H,CRC L。
從機響應格式:
如果成功把計算機發送的指令原樣傳回,否則不響應。
(4)序列槽通信程式配置
序列槽通信程式即為ModBus協定的實體層代碼。序列槽發送程式時,每次發送的資料量為1個位元組,發送方式為9600,N,8,1。對應的DSP TMS320F280049C的序列槽配置程式如下所示:
1)接收RX和發送TX引腳配置程式
void SCI_GPIO_Init(void)
{
EALLOW;
GpioCtrlRegs.GPADIR.bit.GPIO11 = 0; //配置為輸入
GpioCtrlRegs.GPAPUD.bit.GPIO11 = 1; //配置為推挽輸出
GpioCtrlRegs.GPAMUX1.bit.GPIO11 = 2; //配置SCI-GPIO作為使用
GpioCtrlRegs.GPAGMUX1.bit.GPIO11 = 0x01;
GpioCtrlRegs.GPADIR.bit.GPIO12 = 1; //配置為輸出
GpioCtrlRegs.GPAPUD.bit.GPIO12 = 1; //配置為推挽輸出
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 2; //配置SCI-GPIO作為使用
GpioCtrlRegs.GPAGMUX1.bit.GPIO12 = 0x01;
EDIS;
}
2)序列槽外設配置程式
void InitSCI(void)
{
SCI_GPIO_Init();
// ScibRegs.SCIFFRX.bit.RXFFIENA = 1; //接收FIFO中斷使能
// ScibRegs.SCIFFRX.bit.RXFFIL = 1; //接收FIFO深度
// ScibRegs.SCIFFRX.bit.RXFFINTCLR = 1; //接收FIFO中斷清零
// ScibRegs.SCIFFRX.bit.RXFIFORESET = 1; //複位FIFO
ScibRegs.SCICCR.all = 0x0007; // 1 stop bit, No loopback
// No parity, 8 char bits,
// async mode, idle-line protocol
ScibRegs.SCICTL1.bit.RXENA = 1;
ScibRegs.SCICTL1.bit.TXENA = 1;
ScibRegs.SCICTL2.bit.RXBKINTENA = 1; //接收中斷使能
//
// SCIA at 9600 baud
// @LSPCLK = 25 MHz (100 MHz SYSCLK) HBAUD = 0x01 and LBAUD = 0x44.
//
ScibRegs.SCIHBAUD.all = 0x0001;
ScibRegs.SCILBAUD.all = 0x0044;
ScibRegs.SCICTL1.all = 0x0023; // Relinquish SCI from Reset
}
(5)ModBus協定層程式配置
ModBus協定層程式主要是進行資料的處理,處理方式主要按照協定的要求來配置。此部落格所寫的協定為ModBus-RTU協定。
1)資料的接收處理
資料的接收主要采用中斷的方式,使用的是序列槽的接收中斷。序列槽中斷是逐位元組進行資料接收的,而裝置之間每次通信是多位元組的,是以,在每次接收裝置傳來的資料時,需要設定定時器計數,以保證每次接收資料的完整性。資料接收部分的代碼如下所示:
__interrupt void SCIbISR(void)
{
PieCtrlRegs.PIEIER1.bit.INTx1 = 0; //關閉ADC中斷
if(Rx_Stop_Flag == 0)
{
CpuCounter = CpuTimer0.RegsAddr->TIM.all; //讀取定時器0的計數值
CpuTimer0.RegsAddr->TCR.bit.TSS = 0; //開啟定時器計數
}
ScibRegs.SCIFFRX.bit.RXFFINTCLR = 1; //接收FIFO中斷清零
unsigned char res = ScibRegs.SCIRXBUF.bit.SAR;
if((CpuCounter>=50000000)&&(Rx_Stop_Flag == 0)) //500mS
{
Rx_Stop_Flag = 0;
RS232_RXBuf[RS232_RXCount++] = res;
}
else
{
CpuTimer0.RegsAddr->TCR.bit.TSS = 1; //停止定時器計數
CpuTimer0.RegsAddr->TIM.all = CpuTimer0.RegsAddr->PRD.all; //重裝載計數器
Rx_Stop_Flag = 1; //标志置一,停止接收資料
GpioDataRegs.GPBCLEAR.bit.GPIO33 = 1;
}
PieCtrlRegs.PIEACK.bit.ACK9 = 1;
PieCtrlRegs.PIEIER1.bit.INTx1 = 1;
}
程式中Rx_Stop_Flag為資料接收标志,一次資料接收完成後,此标志會置1。下一步,程式會按照該标志是否置1來進行ModBus-RTU程式的執行。
2)協定層程式的處理
如果Rx_Stop_Flag标志置1。程式會進行資料處理,此部分的程式為ModBus協定層的程式。協定層部分代碼如下所示:
if(Rx_Stop_Flag)
{
PieCtrlRegs.PIEIER1.bit.INTx1 = 0; //關閉ADC中斷
if(RS232_RXBuf[0] == RS232_addr) //位址碼判斷
{
CRC_Cal = CRC16(RS232_RXBuf,RS232_RXCount-2); //根據接收到的資料計算得到的CRC
RX_CRC = RS232_RXBuf[RS232_RXCount-1]|(((Uint16)RS232_RXBuf[RS232_RXCount-2])<<8); //接收資料最後兩位的CRC
if(CRC_Cal == RX_CRC) //CRC檢驗正确
{
switch(RS232_RXBuf[1])
{
case 0x03: //功能碼0x03
{
ModBus_Solve_03();
break;
}
case 0x06: //功能碼0x06
{
ModBus_Solve_06();
break;
}
}
}
else //CRC校驗出錯
{
}
}
RS232_RXCount=0; //接收計數清零
Rx_Stop_Flag=0; //停止接收标志清零
PieCtrlRegs.PIEIER1.bit.INTx1 = 1; //開啟ADC中斷
}
協定層代碼執行流程如下所述:
- 根據接收資料的第一位來判斷裝置位址是否比對。
- 進行CRC校驗,判斷資料通信是否出錯
- 根據接收資料的第二位(功能碼)來确定要執行的操作。0x03 or 0x06。
- 根據相應的功能碼來執行相應的處理函數。
3)功能函數代碼的編寫
此部分的代碼主要根據功能碼由裝置執行相應的功能。例如,0x03要求裝置傳回一些資料(傳回目前電壓、電流等),0x06要求裝置執行一些操作(繼電器動作,輸出電壓、電流的調整等)。
0x03功能碼處理函數如下:
void ModBus_Solve_03(void) //上位機讀取指令
{
Uint16 RX_Number;
Uint16 RX_Command;
Uint16 i;
RX_Command = ((Uint16)RS232_RXBuf[2]<<8)|RS232_RXBuf[3]; //要讀取寄存器的位址
RX_Number = ((Uint16)RS232_RXBuf[4]<<8)|RS232_RXBuf[5]; //讀取位元組個數
// RS232_TXBuf[0] = RS232_RXBuf[0]; //位址碼
// RS232_TXBuf[1] = RS232_RXBuf[1]; //功能碼
// RS232_TXBuf[2] = RS232_RXBuf[2]; //寄存器位址高位
// RS232_TXBuf[3] = RS232_RXBuf[3]; //寄存器位址低位
// RS232_TXBuf[4] = RS232_RXBuf[4]; //讀取位元組個數 高位
// RS232_TXBuf[5] = RS232_RXBuf[5]; //讀取位元組個數 低位
RS232_TXBuf[0] = RS232_RXBuf[0]; //位址碼
RS232_TXBuf[1] = RS232_RXBuf[1]; //功能碼
RS232_TXBuf[2] = RX_Number; //傳回位元組個數為2
for(i=0;i<RX_Number;i++)
{
RS232_TXBuf[3+i] = i; //傳回寄存器值
}
Uint16 CRC_Cal = CRC16(RS232_TXBuf,3+i);
RS232_TXBuf[3+i] = (CRC_Cal>>8)&0xFF;
RS232_TXBuf[4+i] = (CRC_Cal)&0xFF;
RS232_SendBuf(RS232_TXBuf,5+i);
}
0x06功能碼處理函數如下:
void ModBus_Solve_06(void) //上位機寫入指令 ModBus--預置單寄存器 [位址碼] [功能碼] [寄存器高位位址] [寄存器低位位址] [高八位資料] [低八位資料] [CRC H] [CRC L]
{
Uint16 RX_Command;
RX_Command = ((Uint16)RS232_RXBuf[2]<<8)|RS232_RXBuf[3];
switch(RX_Command)
{
case 0x0001: //設定Buck電壓
{
break;
}
case 0x0002: //繼電器切換
{
break;
}
case 0x0003: //Buck輸出禁止
{
break;
}
case 0x0004: //輸出雙脈沖
{
break;
}
case 0x0005: //IGBT PWM信号禁止
{
break;
}
case 0x0006: //
{
break;
}
}
RS232_TXBuf[0] = RS232_RXBuf[0];
RS232_TXBuf[1] = RS232_RXBuf[1];
RS232_TXBuf[2] = RS232_RXBuf[2];
RS232_TXBuf[3] = RS232_RXBuf[3];
RS232_TXBuf[4] = RS232_RXBuf[4];
RS232_TXBuf[5] = RS232_RXBuf[5];
Uint16 CRC_Cal = CRC16(RS232_TXBuf,6);
RS232_TXBuf[6] = (CRC_Cal>>8)&0xFF;
RS232_TXBuf[7] = (CRC_Cal)&0xFF;
RS232_SendBuf(RS232_TXBuf,8);
}
4)CRC校驗函數的編寫
CRC校驗的原理為:當從機發送資料時,從機會根據發送的資料,采用CRC校驗算法計算出一個16bit的資料,CRC_H、CRC_H,然後從機裝置會将16位的CRC資料附帶在要發送的資料後邊,将此資料作為一個資料幀發送給主機。主機接收到資料後,将CRC資料位前面的資料采用相同的CRC校驗算法進行計算,将計算得到的16bit的資料與接收到的CRC位的資料作比較,比較結果一緻即為資料傳輸正确,否則,資料傳輸失敗。
CRC校驗算法為:
unsigned short CRC16(unsigned char *puchMsg, unsigned short usDataLen) //puchMsg ; /* 要進行CRC校驗的消息 //usDataLen ; /* 消息中位元組數 */
{
unsigned char uchCRCHi = 0xFF ; /* 高CRC位元組初始化 */
unsigned char uchCRCLo = 0xFF ; /* 低CRC 位元組初始化 */
unsigned uIndex ; /* CRC循環中的索引 */
while (usDataLen--) /* 傳輸消息緩沖區 */
{
uIndex = uchCRCHi ^ *puchMsg++ ; /* 計算CRC */
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex];
uchCRCLo = auchCRCLo[uIndex];
}
return ((uchCRCHi << 8) | uchCRCLo);
}
完整的實驗代碼我已上傳到CSDN,需要的可自行下載下傳哈,下載下傳連結如下所示:
https://download.csdn.net/download/fanxianyan1993/12058062
提問方式:以上程式有啥不懂的可以随時向我提問哈,用微信掃描下方二維碼我會在第一時間給大家回複的,謝謝。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxierR1T4RzVZVTNXlFco5mYoplMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLyczM4MjNzcTM4ETMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)