天天看點

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

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

ModBus-RTU通訊協定程式設計

繼續閱讀