久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

專注電子技術(shù)學習與研究
當前位置:單片機教程網(wǎng) >> MCU設(shè)計實例 >> 瀏覽文章

Modbus協(xié)議完全資料與程序解析

作者:劉溫電   來源:本站原創(chuàng)   點擊數(shù):  更新時間:2013年11月25日   【字體:

 

1簡述,modbus是一種工業(yè)用的多設(shè)備之間的主從通信協(xié)議。只要兩臺設(shè)備之間,是采用modbus協(xié)議的主從關(guān)系,并連接到相同網(wǎng)絡,即可互相通信。因為Modbus只是協(xié)議,而且只規(guī)定了數(shù)據(jù)幀,底層連接,可以是232,485或者以太網(wǎng)。設(shè)備一般采用232和485進行通信,因為成本低。當然要是考慮遠距離傳輸和多賣錢的話,也會采用以太網(wǎng),不過應該就會相應復雜一些了。
2模式,modbus有兩種模式,一種叫RTU模式,另一種叫acsii模式,RTU模式是純二進制的,而acsii模式,一個信息中的每8位字節(jié)作為2個ascii字符傳輸?shù),這種模式的主要優(yōu)點時允許字符之間的時間間隔長達1秒,也不會出現(xiàn)錯誤。而較acsii模式,RTU模式的優(yōu)點是用最少的字節(jié),表達更多的內(nèi)容。但同時也要求設(shè)備必須連續(xù)傳輸。
3通訊,modbus屬于主從通訊,可以是一主一從或者一主多從。通訊的方式為主機向從機發(fā)送命令(或者叫請求)從機向主機發(fā)送響應。主機不發(fā)送,從機不返回,一發(fā),一收,不發(fā)不收。而且一個時間,只有一個機器發(fā)送請求或者響應,否則的話,則會出錯。
4信息幀,由于項目上沒有涉及到acsii模式,所以本文只討論RTU模式,不討論acsii模式,以后如果要是用的上,肯定會繼續(xù)討論。用不上,就不討論了。RTU幀,開始時,必須要有3.5個靜止的時間,也就是時間間隔,用來區(qū)分上一幀和下一幀,如果沒有時間間隔的話,則會分辨不出哪里是幀開始,哪里是幀結(jié)束了。3.5個時間間隔依據(jù)波特率不同而不同。同樣,結(jié)束時也需要時間。除了時間以外,還有地址,功能碼,數(shù)據(jù),crc校驗四個部分,每個部分的字節(jié)數(shù)不同,地址功能碼各1個字節(jié),crc是2個字節(jié)其完整表達如下:
      
開始
地址
功能
數(shù)據(jù)
校驗
結(jié)束
3.5t 
1字節(jié) 8b
1字節(jié) 8b
n字節(jié) n*8b
2字節(jié)16b
3.5t 
 
4.1、地址:主要用于區(qū)分從機,在下位機程序中,的宏定義中設(shè)置不同的從機地址。
       #define Modbus_addr 0x01
設(shè)備響應時,第一位也是本機地址。地址的范圍是從0-247,地址0為廣播地址,所有機器均可以識別。
       4.2、功能碼:表示主機要命令這個設(shè)備的什么功能,執(zhí)行什么程序。我看了一下正規(guī)的modbus的功能碼多達24個,不同廠家生產(chǎn)的不同型號的設(shè)備,可能會支持不同的功能碼,所以買之前需要注意一下。具體功能如下:
      
01 讀線圈狀態(tài)            02 讀輸入狀態(tài)            03 讀保持寄存器         04 讀輸入寄存器         05 強制單個線圈
06 預置單個寄存器     07 讀不正常狀態(tài)         08 診斷                    09 程序484                 10 查詢484
11 通訊事件控制         12 通訊事件記錄         13 程序控制器            14 查詢控制器            15 強制多個寄存器
16 預置多個寄存器     17 報告從機id            18 程序884/M84         19 通訊鏈路復位        20 讀通用參考值
21 寫通用參考值         22 Mask Write 4X Register           23 Read/Write 4X Registers          24 Read FIFO 隊列
 
雖然看著功能很多,但實際上有用的,只有01 02 03 04 05 06 15 和16功能碼。
       4.3、數(shù)據(jù)區(qū),根據(jù)功能碼的不同數(shù)據(jù)的長度是不同的。
       4.4、crc校驗 包含兩個字節(jié),發(fā)送端發(fā)送時,一幀的所有數(shù)據(jù)統(tǒng)一計算出一個crc校驗碼,然后加在一幀的最后兩位中,然后等到發(fā)送到接收端時接收端重新計算一次除最后兩位的一幀所有數(shù)據(jù),然后根據(jù)兩個數(shù)據(jù)的對比,來判斷接收到的數(shù)據(jù)是否正確。
       5、程序,以下位機為程序?qū)ο,主要使用c語言編寫,首先,先從變量入手,既然modbus接受以幀為單位,所以就要設(shè)置兩個緩沖區(qū),用來接收數(shù)據(jù),我們這里使用數(shù)組來存儲接收來的數(shù)據(jù)Modbus_send_buf[Modbus_max_send_buf];//數(shù)據(jù)發(fā)送緩沖 和 Modbus_recevie_buf[Modbus_max_recevie_buf];//數(shù)據(jù)接收緩沖 ,其中Modbus_max_send_buf,和Modbus_max_recevie_buf ,為宏定義,這樣可以方便的修改一幀最大的存儲數(shù)據(jù)。有了發(fā)送接收緩沖,就可以寫中斷函數(shù)了,進入中斷后,首先做一些必要的工作,清ES ,判斷IR,清IR,做完后,就可以開始接收數(shù)據(jù)了,但有個問題?如果設(shè)備處于空閑狀態(tài),那么接收數(shù)據(jù)后按命令執(zhí)行,但如果當設(shè)備正在執(zhí)行指令的時候,則不應該再繼續(xù)的接收指令,那樣的話,會讓程序進入混亂狀態(tài)。所以要在基礎(chǔ)工作做完后,增加一個判斷,來確定設(shè)備的忙閑。if((Modbus_cmd_flag == 0) && (Modbus_exe_flag == 0)),判斷完以后就可以繼續(xù)下面的工作了。如果通訊中包含奇偶校驗的話,那么則判斷奇偶校驗。下面就是接收數(shù)據(jù)。Modbus_recevie_buf[Modbus_recevie_count] = SBUF; ,將接收來的數(shù)據(jù)存入數(shù)組并記錄存入的數(shù)據(jù)個數(shù)Modbus_recevie_count,由于modbus是通過時間來判斷一幀的結(jié)束的,所以在程序中,必須要有一個定時器函數(shù),這個定時器用來判斷程序是正在接受,還是已經(jīng)接受完成了。所以中斷的最后所做的是計數(shù)器自加Modbus_recevie_count++;,定時器清0 Modbus_timeout_cnt = 0;   ,將設(shè)備狀態(tài)轉(zhuǎn)入接收狀態(tài)Modbus_recevie_flag = 1;。此時,串口中斷的工作就完成了。
          下面開始分析定時器,定時器的目的其實就1個,判斷一幀是否接收完畢,如果完畢,則進入下一步。在定時器中斷函數(shù)中,首先要對定時器值進行初始化,這個就不多說了,然后是判斷程序是否處于接受狀態(tài)if(Modbus_recevie_flag == 1),這個狀態(tài)只有在串口中斷函數(shù)中才會被置位,其他的情況不會被置位。若程序不是接收狀態(tài),則直接跳出定時器中斷,若程序處于接收狀態(tài),則定時計數(shù)自加Modbus_timeout_cnt++;,自加后進入判斷if(Modbus_timeout_cnt >= Modbus_max_timeout_cnt),判斷的值即為modbus接收一幀傳輸完成所需要的時間間隔。至于是多少時間,可以通過修改Modbus_max_timeout_cnt來確定。可以將定時器終端設(shè)置為1ms1次,在9600的情況下將超時時間設(shè)為4,#define Modbus_max_timeout_cnt   4,這樣如果串口中斷不在接收數(shù)據(jù)時,定時計數(shù)將不會清0,當?shù)竭_設(shè)定的超時時間后即判斷接收結(jié)束,轉(zhuǎn)向命令解析狀態(tài)。
       接收來的數(shù)據(jù)可以經(jīng)過一個函數(shù)來執(zhí)行,同時也可以經(jīng)過兩個函數(shù),解析與執(zhí)行兩步來分別執(zhí)行。我喜歡后者,因為這樣可以把解析的過程和執(zhí)行的過程分開來寫。程序顯得更加清晰與明朗。
       在主函數(shù)中就執(zhí)行1個函數(shù),
while(1)
      {
             Modbus_proc();
       }
這個函數(shù)是經(jīng)過打包的兩個函數(shù),進入這個函數(shù)
void Modbus_proc()
{
           Modbus_cmd();
           Modbus_exe();   
}
可以看到,程序分為cmd解析,exe執(zhí)行。
Cmd   命令解析函數(shù)
有這么幾個問題是需要判斷的,命令解析狀態(tài),接收來的數(shù)據(jù)個數(shù),crc,地址,這幾個問題是命令解析時需要注意的,順序可以稍做變化。但最好是這個順序。
首先判斷程序是否處于命令解析狀態(tài)if(Modbus_cmd_flag == 1)。命令解析狀態(tài)標志只有在超時后置位,其他情況下不置位。之后是判斷接收數(shù)據(jù)是否大于4字節(jié),if(Modbus_recevie_count > 4)。當程序接收數(shù)據(jù)小于4字節(jié)則說明接收發(fā)生錯誤,拋棄它。下一步則是判斷crc校驗,由于crc在一幀的最后兩位,所以crc應該取緩沖的最后兩位
modbus_crc_h=Modbus_recevie_buf[Modbus_recevie_count-2];                    
modbus_crc_l = Modbus_recevie_buf[Modbus_recevie_count-1];
然后將取來的數(shù)據(jù)合并成一個16位數(shù)據(jù),得到接收的crc
modbus_crc = ((unsigned int)(modbus_crc_h) << 8) | modbus_crc_l;
重新計算1幀的crc,得到自己的crc
modbus_crc_b = crc16(Modbus_recevie_buf,Modbus_recevie_count - 2);
最后進行對比,將自己算的crc和接收的crc進行比較,來判斷接收的數(shù)據(jù)是否正確。
if( modbus_crc_b == modbus_crc )
在crc判斷正確后,就可以判斷地址了
if(Modbus_recevie_buf[0] == Modbus_addr)      // Modbus_addr為一個宏定義的本機地址,若多機可以在此處修改。
當?shù)刂,crc,等全判斷正確以后,就可以判斷最重要的功能碼了。由于功能碼很多,所以1可以用宏定義來定義功能碼增加程序的可讀性,2可以利用switch來命令的模式
 
#define Modbus_read_coil 0x01 //功能碼01 讀可讀寫數(shù)字量寄存器(線圈狀態(tài)):
switch (Modbus_recevie_buf[1])
{
case Modbus_read_coil:
Modbus_mode = Modbus_read_coil;
break;
       ……
     default:                //非法命令準備報異常                               
    return ;
    break;
}
       Modbus_exe_flag = 1;
       解析后,將執(zhí)行標志置位即可。
Exe 執(zhí)行函數(shù),
執(zhí)行函數(shù)在解析函數(shù)后面,而不是在里面,所以,若沒有解析,照樣可以進入執(zhí)行函數(shù),但由于執(zhí)行函數(shù)中有判斷執(zhí)行標志位if( modbus_crc_b == modbus_crc ),所以若標志為0,則直接退出函數(shù)。若標志為1,則執(zhí)行Modbus_mode中對應的函數(shù)函數(shù)中依然用switch來選擇具體功能函數(shù)
 
switch(Modbus_mode)      //通過判斷模式來進行對響應的發(fā)送
{
case Modbus_read_coil:
read_coil_proc();   
break;
       ……
default:
return;
break;
}
這樣的做的話,就可以吧解析函數(shù),執(zhí)行函數(shù)和具體的實施函數(shù)分開來弄,層次多多少少要清晰一些
下面就是針對01,02,03,04,05,06,15,16幾個功能碼的執(zhí)行及返回進行說明
在說明各功能函數(shù)之前,先說說響應。
上面說的那兩個函數(shù)只不過是對一幀的外圍進行解析與判斷,至于具體的參數(shù),還需要功能函數(shù)去解析與返回,功能函數(shù)要做的事情有3個,1個是參數(shù)的解析,2是執(zhí)行,3是返回響應。
先說響應,響應是有特點的,第一個字節(jié)肯定是自己的本機地址,第二個字節(jié)肯定是功能碼,最后兩個字節(jié)肯定是crc校驗,所以說,在發(fā)送緩沖中,基本上4個字節(jié)已經(jīng)定死了
 
Modbus_send_buf[0] = Modbus_addr;        
Modbus_send_buf[1] = Modbus_read_input_reg;  //相應的功能碼,每個功能寒暑都不一樣
再經(jīng)過執(zhí)行函數(shù)最后算crc
modbus_crc = crc16(Modbus_send_buf,temp);    //計算發(fā)送crc數(shù)據(jù)
Modbus_send_buf[temp] = modbus_crc >> 8;     //計算
temp++;                                                         
Modbus_send_buf[temp] = modbus_crc & 0xff;   //return num 高位
      
5.1 01 讀線圈狀態(tài)
#define Modbus_read_coil 0x01 
       其實表面上挺難理解的,啥線圈啥的,但你仔細看看就可以了解,就是讀輸出數(shù)字量,如果你寫下位機的話,其實就是控制讀取輸出io,說白了,就是把目前的io輸出狀態(tài)返回給主機。這些io連接的可能是繼電器,也可能是一些開關(guān)之類的東西,也就是些數(shù)字信號。讀數(shù)字輸出信號。
計算機發(fā)送命令:[設(shè)備地址] [命令號01] [起始寄存器地址高8位] [低8位] [讀取的寄存器數(shù)高8位] [低8位]
設(shè)備響應:[設(shè)備地址] [命令號01] [返回的字節(jié)個數(shù)][數(shù)據(jù)1][數(shù)據(jù)2]...[數(shù)據(jù)n][CRC校驗的低8位] [CRC校驗的高8位]
簡單的說就是返回所有的輸出io的值,放在一個或者幾個字節(jié)里,可以用判斷的方法來實現(xiàn),當然,也可以用與或的方式實現(xiàn)。
    if(P1_0 == 1)
    {
        temp |= (1<<8);
    }
    else
    {
        temp &= (1<<8);
    }
temp的值放入第四個緩沖區(qū),當然這根據(jù)設(shè)備的io口,編程時就已經(jīng)確定了的。接下來就可以進行crc計算了。最后發(fā)送即可。
Modbus_send_buf[3] = temp;
modbus_crc = crc16(Modbus_send_buf,4);
Modbus_send_buf[4] = modbus_crc >> 8;   
Modbus_send_buf[5] = modbus_crc & 0xff;      //return num 高位 
 
 
5.2 02 讀只可讀數(shù)字量寄存器(輸入狀態(tài))
基本上和01意思差不多,只不過這個功能碼返回的數(shù)據(jù)是輸入io的數(shù)據(jù),和01的區(qū)別是01可讀可改,而02只可讀不可改。也就是輸入的狀態(tài)。數(shù)據(jù)不可由設(shè)備本身控制。程序方面和01程序一樣。
 
5.3 03讀可讀寫模擬量寄存器(保持寄存器)
說簡單點就是讀da,da屬于模擬量,也可以輸出,但是以模擬量的方式來進行傳輸?shù)?/div>
計算機發(fā)送命令:[設(shè)備地址] [命令號03] [起始寄存器地址高8位] [低8位] [讀取的寄存器數(shù)高8位] [低8位] [CRC校驗的低8位] [CRC校驗的高8位]
設(shè)備響應:[設(shè)備地址] [命令號03] [返回的字節(jié)個數(shù)][數(shù)據(jù)1][數(shù)據(jù)2]...[數(shù)據(jù)n][CRC校驗的低8位] [CRC校驗的高8位]
其中返回字節(jié)個數(shù),為讀取寄存器數(shù)乘2
寫程序時,首先要注意數(shù)據(jù)個數(shù),temp = Modbus_recevie_buf[5];一般寄存器個數(shù)不會超過255,個數(shù)取讀取寄存器個數(shù)的低八位即可。返回即乘2,temp = temp << 1;,下面要做的就是一個循環(huán)for(i = 0;i < temp ; i += 2),把需要的數(shù)據(jù)放入發(fā)送數(shù)組。其內(nèi)容是
Modbus_send_buf[i+3]=(data_v&0xff00)>>8;
Modbus_send_buf[i+4]=data_v&0x0ff;
由于幀的前面3個是地址,功能碼,和返回字節(jié)個數(shù),所以循環(huán)從第四個數(shù)據(jù)開始存放。data_v為讀取的數(shù)據(jù),在程序中還需要其他語句配合。比如:data_v = updateValue();
    循環(huán)后就可以進入crc校驗了可以利用返回字節(jié)數(shù)來確定crc的校驗個數(shù)temp = temp + 3;,最后計算發(fā)送字節(jié)的個數(shù)
    send_cnt = Modbus_recevie_buf[5]*2 + 5 ; //數(shù)據(jù)發(fā)送個數(shù)   數(shù)據(jù)+地址+命令+返回數(shù)據(jù)個數(shù)+crc低+crc高
       最后將數(shù)據(jù)發(fā)送出去即可。
   
    5.4 04讀只可讀模擬量寄存器(輸入寄存器)
       和03的區(qū)別是04就是讀ad,ad輸入輸入模擬兩,只能讀,不能改,同樣也是以模擬兩的方式來進行傳輸?shù)。其程?span>       與03類似
      
       5.5 05寫數(shù)字量(線圈狀態(tài))
       05則是修改io口輸出狀態(tài),數(shù)字量輸出。
計算機發(fā)送命令:[設(shè)備地址] [命令號05] [需下置的寄存器地址高8位] [低8位] [下置的數(shù)據(jù)高8位] [低8位] [CRC校驗的低8位] [CRC校驗的高8位]
設(shè)備響應:若執(zhí)行成功,則原樣返回
寫程序時,首先確定需要修改的io口,然后根據(jù)0xff00或0x0000來置位或清零該數(shù)據(jù)位。執(zhí)行完成后,將接收到的數(shù)據(jù)重新發(fā)送即可 Uart0_senddata(Modbus_recevie_buf,8);
 
5.6 06寫單個模擬量寄存器(保持寄存器)
06為修改設(shè)備da數(shù)據(jù),模擬量傳輸數(shù)據(jù)。
計算機發(fā)送命令:[設(shè)備地址] [命令號06] [需下置的寄存器地址高8位] [低8位] [下置的數(shù)據(jù)高8位] [低8位] [CRC校驗的低8位] [CRC校驗的高8位]
設(shè)備響應:若執(zhí)行成功,原樣返回即可
 
5.7 16主機設(shè)置寄存器
簡單的說,就是一次設(shè)置多個da,以一個偏移量為準,一次設(shè)置多個輸出模擬里量
計算機發(fā)送命令:[設(shè)備地址] [命令號10] [開始地址高8位] [低8位] [寄存器個數(shù)高8位] [低8位] [第一個寄存器數(shù)據(jù)高][第一個寄存器數(shù)據(jù)低][第二個寄存器數(shù)據(jù)高][第二個寄存器數(shù)據(jù)低]……[CRC校驗的低8位] [CRC校驗的高8位]
命令響應:功能碼[0x10],寄存器起始地址高字節(jié),低字節(jié),要寫的寄存器數(shù)量的高字節(jié),低字節(jié),CRC校驗低字節(jié),高字節(jié)
在程序中,首先要獲取寄存器個數(shù)
num = Modbus_recevie_buf[6] - 2;
然后進入循環(huán),一次把寄存器數(shù)據(jù)提取出來for(i = 0; i < num; i = i + 2)
在循環(huán)的內(nèi)部提取數(shù)據(jù)temp = (((unsigned int)(Modbus_recevie_buf[i+7])<<8)|(Modbus_recevie_buf[i+8]));
 
以上就是我在項目中涉及到的一點modbus的通訊的下位機程序,不全,但總體的思路,接收數(shù)據(jù)并解析,解析后提取數(shù)據(jù)在設(shè)備上加載或采集,然后再按照響應的方式發(fā)送回去。
下回改進的方向,1,增加功能碼2,增加宏定義及編譯定義,3增加單片主機的程序,和pc主從機的程序。4,增加ascii的程序,和rtu同時設(shè)置。Pc機程序,采用c#號編寫。

 

完整的程序請參考:http://m.zg4o1577.cn/bbs/dpj-23230-1.html

 

相關(guān)文章

主站蜘蛛池模板: 91麻豆精品国产91久久久更新资源速度超快 | 久久久www成人免费无遮挡大片 | 99这里只有精品视频 | 91精品国产91久久综合桃花 | 日本久久福利 | 国产高清免费视频 | 99精品免费视频 | 日本又色又爽又黄又高潮 | 成人精品一区 | 免费成人高清 | 成人精品一区二区三区 | 日韩av免费看 | 免费在线看a | 天天天插 | 国产激情91久久精品导航 | 国产精品欧美日韩 | 天天看天天摸天天操 | 国产一区二区精品在线 | 亚洲精品视频三区 | 伊人久久伊人 | 一区二区视频在线观看 | 看真人视频一级毛片 | 亚洲人成人一区二区在线观看 | av三级| 国产精品一区二区欧美黑人喷潮水 | 日本精品在线播放 | 中文字幕在线人 | 亚洲国产成人av好男人在线观看 | 国产精品国产三级国产a | 99久久婷婷国产综合精品首页 | 黄免费在线| 国产成人精品av | 日韩资源 | 久精品久久 | 日韩免费视频一区二区 | 亚洲视频欧美视频 | 国产精品18久久久久久白浆动漫 | 99久久国产综合精品麻豆 | 日韩精品久久久久 | 免费一级毛片 | 久久天天躁狠狠躁夜夜躁2014 |