ModBus-RTU通訊協議編程

編者說:

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中斷
           }

協議層代碼執行流程如下所述:

  1. 根據接收數據的第一位來判斷設備地址是否匹配。
  2. 進行CRC校驗,判斷數據通信是否出錯
  3. 根據接收數據的第二位(功能碼)來確定要執行的操作。0x03 or 0x06。
  4. 根據相應的功能碼來執行相應的處理函數。

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

 

提問方式:以上程序有啥不懂的可以隨時向我提問哈,用微信掃描下方二維碼我會在第一時間給大家回覆的,謝謝。 

發佈了16 篇原創文章 · 獲贊 31 · 訪問量 8792
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章