新聞中心

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

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

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

評(píng)論


技術(shù)專(zhuān)區(qū)

關(guān)閉