新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > Modbus協(xié)議完全資料與程序解析

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

作者: 時間:2016-12-01 來源:網(wǎng)絡 收藏
1簡述,modbus是一種工業(yè)用的多設備之間的主從通信協(xié)議。只要兩臺設備之間,是采用modbus協(xié)議的主從關系,并連接到相同網(wǎng)絡,即可互相通信。因為Modbus只是協(xié)議,而且只規(guī)定了數(shù)據(jù)幀,底層連接,可以是232,485或者以太網(wǎng)。設備一般采用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é),表達更多的內容。但同時也要求設備必須連續(xù)傳輸。
3通訊,modbus屬于主從通訊,可以是一主一從或者一主多從。通訊的方式為主機向從機發(fā)送命令(或者叫請求)從機向主機發(fā)送響應。主機不發(fā)送,從機不返回,一發(fā),一收,不發(fā)不收。而且一個時間,只有一個機器發(fā)送請求或者響應,否則的話,則會出錯。
4信息幀,由于項目上沒有涉及到acsii模式,所以本文只討論RTU模式,不討論acsii模式,以后如果要是用的上,肯定會繼續(xù)討論。用不上,就不討論了。RTU幀,開始時,必須要有3.5個靜止的時間,也就是時間間隔,用來區(qū)分上一幀和下一幀,如果沒有時間間隔的話,則會分辨不出哪里是幀開始,哪里是幀結束了。3.5個時間間隔依據(jù)波特率不同而不同。同樣,結束時也需要時間。除了時間以外,還有地址,功能碼,數(shù)據(jù),crc校驗四個部分,每個部分的字節(jié)數(shù)不同,地址功能碼各1個字節(jié),crc是2個字節(jié)其完整表達如下:
開始
地址
功能
數(shù)據(jù)
校驗
結束
3.5t
1字節(jié) 8b
1字節(jié) 8b
n字節(jié) n*8b
2字節(jié)16b
3.5t
4.1、地址:主要用于區(qū)分從機,在下位機程序中,的宏定義中設置不同的從機地址。
#define Modbus_addr 0x01
設備響應時,第一位也是本機地址。地址的范圍是從0-247,地址0為廣播地址,所有機器均可以識別。
4.2、功能碼:表示主機要命令這個設備的什么功能,執(zhí)行什么程序。我看了一下正規(guī)的modbus的功能碼多達24個,不同廠家生產(chǎn)的不同型號的設備,可能會支持不同的功能碼,所以買之前需要注意一下。具體功能如下:
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、程序,以下位機為程序對象,主要使用c語言編寫,首先,先從變量入手,既然modbus接受以幀為單位,所以就要設置兩個緩沖區(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ù)了,但有個問題?如果設備處于空閑狀態(tài),那么接收數(shù)據(jù)后按命令執(zhí)行,但如果當設備正在執(zhí)行指令的時候,則不應該再繼續(xù)的接收指令,那樣的話,會讓程序進入混亂狀態(tài)。所以要在基礎工作做完后,增加一個判斷,來確定設備的忙閑。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是通過時間來判斷一幀的結束的,所以在程序中,必須要有一個定時器函數(shù),這個定時器用來判斷程序是正在接受,還是已經(jīng)接受完成了。所以中斷的最后所做的是計數(shù)器自加Modbus_recevie_count++;,定時器清0 Modbus_timeout_cnt = 0; ,將設備狀態(tài)轉入接收狀態(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來確定??梢詫⒍〞r器終端設置為1ms1次,在9600的情況下將超時時間設為4,#define Modbus_max_timeout_cnt 4,這樣如果串口中斷不在接收數(shù)據(jù)時,定時計數(shù)將不會清0,當?shù)竭_設定的超時時間后即判斷接收結束,轉向命令解析狀態(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ù)
上一頁 1 2 下一頁

評論


技術專區(qū)

關閉