編者說:
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
提問方式:以上程序有啥不懂的可以隨時向我提問哈,用微信掃描下方二維碼我會在第一時間給大家回覆的,謝謝。