圖文詳解Modbus-RTU協(xié)議
前世今生
照例簡單說下這個(gè)協(xié)議的歷時(shí),Modicon公司于1979年制定了Modbus協(xié)議標(biāo)準(zhǔn),并用在其PLC產(chǎn)品上。后來Modicon公司被施耐德收購。已成為一種事實(shí)標(biāo)準(zhǔn)協(xié)議,同時(shí)也被IEC-61158工業(yè)通信總線規(guī)范收錄于type 15子集。所謂一流的企業(yè)做標(biāo)準(zhǔn),二流的企業(yè)做品牌,三流的企業(yè)做產(chǎn)品。這些標(biāo)準(zhǔn)國人都基本是使用者,而非締造者,所以使用一下,產(chǎn)品上印個(gè)標(biāo)志,做做相關(guān)的測試認(rèn)證都要給老外交錢。這里只是順帶牢騷幾句,與本文想說的無關(guān)。打住!Modbus的應(yīng)用除了常見的過程控制系統(tǒng),在其他很多領(lǐng)域都有其身影,比如一些樓宇控制,消防控制等等都有大量的產(chǎn)品采用Modbus協(xié)議,因?yàn)檫@個(gè)協(xié)議實(shí)現(xiàn)簡單,工作可靠,還是標(biāo)準(zhǔn)化的協(xié)議!Modbus分很多實(shí)現(xiàn)版本,總的來說是一種應(yīng)用層協(xié)議。從OSI七層模型來看,位于第七層應(yīng)用層。它定義了在不同類型的總線或網(wǎng)絡(luò)上連接的設(shè)備之間提供 ”客戶端/服務(wù)器“ 通信。對于使用串口的版本,也定義了layer 1 和 layer 2,實(shí)現(xiàn)在主站和一個(gè)或多個(gè)從站之間交換MODBUS 報(bào)文。具體有哪些版本呢?其實(shí)主要分兩種:- Modbus RTU(Remote Terminal Unit 遠(yuǎn)程終端單元):這種方式常采用RS-485做為物理層,一般利用芯片的串口實(shí)現(xiàn)數(shù)據(jù)報(bào)文的收發(fā),報(bào)文數(shù)據(jù)采用二進(jìn)制數(shù)據(jù)進(jìn)行通信。
- Modbus ASCII :報(bào)文使用 ASCII 字符。ASCII 格式使用縱向冗余校驗(yàn)和。Modbus ASCII 報(bào)文由冒號 (":")開始 和換行符 (CR/LF) 結(jié)尾構(gòu)成。
- Modbus TCP/IP 或 Modbus TCP :這是一種 Modbus 變體版本,使用 TCP/IP 網(wǎng)絡(luò)進(jìn)行通信,通過 502 端口進(jìn)行連接。報(bào)文不需要校驗(yàn)和計(jì)算,因?yàn)橐蕴W(wǎng)底層已經(jīng)實(shí)現(xiàn)了CRC32 數(shù)據(jù)完整性校驗(yàn)。
- Modbus over TCP/IP 或 Modbus over TCP 或 Modbus RTU/IP :這也是一種 Modbus 變體,與 Modbus TCP 的不同之處在于,與 Modbus RTU 一樣,校驗(yàn)和包含在報(bào)文中。
- Modbus UDP:也有在UDP上傳輸Modbus報(bào)文的,不過需要做錯(cuò)誤重傳機(jī)制,這么干的應(yīng)該不多。
Modbu標(biāo)準(zhǔn)
概況
Modbus-串口版本基本定義了物理層、鏈路層以及應(yīng)用層:物理層可以使用485或232, 這里EIA/TIA都是標(biāo)準(zhǔn)協(xié)會的簡稱,也常寫成RS-485/RS-232。- RS-485:半雙工收發(fā)接口,這是最為常用的modbus物理層,信號采用差分電平編碼,用一對雙絞線現(xiàn)場布線,抗干擾性能也不錯(cuò)
- RS-422:全雙工收發(fā)接口,這種物理層也有比較多的應(yīng)用,信號采用差分電平編碼,需要兩對雙絞線現(xiàn)場布線,抗干擾性能也不錯(cuò)。與RS-485相比,其優(yōu)勢在于可以實(shí)現(xiàn)全雙工,通信的效率高些,所需要的代價(jià)就是現(xiàn)場布線需要兩對雙絞線,增加了一定的成本。
- RS-232:全雙工收發(fā)接口,這個(gè)基本用在點(diǎn)對點(diǎn)通信場景下,不適合多點(diǎn)拓?fù)溥B接,采用共模電平編碼,一般需要Rxd/Txd/Gnd三根線連接。
鏈路層
單播與廣播
modbus從鏈路控制的角度屬于主(Master)/從(Slave)方式,比較簡單。對介質(zhì)的訪問控制相當(dāng)于時(shí)分復(fù)用。通訊總是由主站發(fā)起,但可分為單播和廣播兩種方式,單播就是主站向特定的從站發(fā)出通訊請求,廣播是向總線所有的設(shè)備發(fā)起通訊請求。看下面兩個(gè)圖就比較清楚了:- 單播(unicast): 報(bào)文中的地址字段指定所需要訪問的設(shè)備,該設(shè)備收到請求后作出對應(yīng)的應(yīng)答。
- 廣播(Broadcast):主站向總線所有設(shè)備發(fā)出廣播報(bào)文,所有從設(shè)備都不做應(yīng)答,報(bào)文中地址為0則為廣播請求:
尋址
?modbus-RTU從設(shè)備都具有一個(gè)單字節(jié)地址,其地址分配定義為:- 地址0:廣播地址,所有的從設(shè)備必須處理廣播報(bào)文。
- 1-247:從設(shè)備地址,主設(shè)備是沒有地址的,這一點(diǎn)需要注意。
- 248-255:保留地址
報(bào)文結(jié)構(gòu)
前面說過,通信模式是主/從方式,也即主請求、從應(yīng)答的方式。無論主請求報(bào)文,還是從應(yīng)答報(bào)文其結(jié)構(gòu)都是如下圖這樣的:- 地址:取值范圍是0-247,如果是0,就是主站廣播報(bào)文;如果是1-247,則有可能是主站請求或者從站應(yīng)答。
- 功能碼:也就是報(bào)文命令,代表主站對從站的操作,讀或者寫
- 數(shù)據(jù):數(shù)據(jù)字段,主請求報(bào)文,從應(yīng)答報(bào)文會有所差異。也就是說假設(shè)抓取總線報(bào)文,如何區(qū)分是主站請求還是從站應(yīng)答,則需要通過數(shù)據(jù)字段進(jìn)行區(qū)分了。
- CRC校驗(yàn):采樣CRC16,16位循環(huán)冗余校驗(yàn)。
主站狀態(tài)機(jī)
鏈路層一個(gè)最最重要的職責(zé)就是對通訊介質(zhì)的管理,如果沒有介質(zhì)的管理,就不能成其為總線。Modbus如何進(jìn)行介質(zhì)管理呢?前面介紹了從鏈路管理的角度來看,總線介質(zhì)上發(fā)送報(bào)文的有兩種設(shè)備,一種是主設(shè)備,另一種是從設(shè)備。對于主設(shè)備來說,它會有兩種報(bào)文會向總線介質(zhì)發(fā)送:一種是廣播報(bào)文,另一種是單播報(bào)文。那么究竟是怎么控制介質(zhì)的呢?其實(shí)很簡單,看看下面這個(gè)狀態(tài)機(jī)就很清楚了:圖中的事件的產(chǎn)生,將會觸發(fā)主設(shè)備鏈路狀態(tài)機(jī)從一個(gè)狀態(tài)遷移到另一個(gè)狀態(tài),再事件觸發(fā)后,還伴隨動作需要執(zhí)行。- 空閑:主設(shè)備處于空閑態(tài)。如果此時(shí)應(yīng)用程序需要發(fā)送一個(gè)從設(shè)備請求,就會切換到等待應(yīng)答狀態(tài);如果此時(shí)應(yīng)用需要發(fā)送總線廣播,此時(shí),主設(shè)備就切換到廣播延時(shí)。
- 等待應(yīng)答:在等待應(yīng)答狀態(tài)時(shí),主設(shè)備將等待來自從設(shè)備的應(yīng)答報(bào)文,如果接收到從站的報(bào)文,則進(jìn)入應(yīng)答處理。如果等待超時(shí)則進(jìn)入錯(cuò)誤處理狀態(tài)機(jī);在等待過程中,狀態(tài)機(jī)不發(fā)生遷移。
- 廣播延時(shí):如果主設(shè)備需要發(fā)送廣播報(bào)文,則發(fā)送完就進(jìn)入廣播延時(shí)狀態(tài)。這里為什么要延時(shí)呢?延時(shí)的設(shè)計(jì)目的就是留給從設(shè)備一點(diǎn)時(shí)間去處理接收到的廣播請求。如果主設(shè)備沒有這個(gè)延時(shí),那么如果應(yīng)用馬上在發(fā)一個(gè)請求,則從設(shè)備有可能來不及處理。但是從設(shè)備只做接收處理,任何從設(shè)備都不可以對廣播報(bào)文進(jìn)行應(yīng)答。
從站狀態(tài)機(jī)
對于從設(shè)備來說,只接收主設(shè)備請求或者發(fā)送應(yīng)答,因此從設(shè)備的狀態(tài)機(jī)就更簡單了。從設(shè)備的狀態(tài)機(jī)很簡單,系統(tǒng)一上電就進(jìn)入空閑狀態(tài),空閑態(tài)一直監(jiān)聽總線報(bào)文,當(dāng)收到一個(gè)完整的報(bào)文時(shí),首先校驗(yàn)報(bào)文的正確性,再檢查報(bào)文是否是發(fā)給該設(shè)備的,如果是請求本設(shè)備的,則先完成請求的操作,然后準(zhǔn)備好應(yīng)答報(bào)文,如果出錯(cuò)則將出錯(cuò)信息發(fā)送給主站。如果收到的是主站廣播請求,則僅僅處理相應(yīng)請求,不做任何應(yīng)答。字節(jié)編碼格式
報(bào)文以字節(jié)為單位,每個(gè)字節(jié)再物理層又以什么格式出現(xiàn)再總線呢?其字節(jié)編碼格式為:1個(gè)起始位,8個(gè)數(shù)據(jù)位,1個(gè)校驗(yàn)位,1個(gè)停止位。校驗(yàn)位可選擇奇校驗(yàn)、偶校驗(yàn)、或無校驗(yàn),有一個(gè)地方標(biāo)準(zhǔn)特別說明了一下,如果選擇無校驗(yàn),會采用2個(gè)停止位。介質(zhì)管理
對于幀的時(shí)間管理,其實(shí)就是對介質(zhì)的沖突管理,modbus-RTU對于介質(zhì)管理規(guī)定了2個(gè)重要的時(shí)間參數(shù),以實(shí)現(xiàn)成幀、沖突管理等。來看看下面這幾個(gè)圖:這個(gè)圖可以用于斷幀,也就時(shí)判斷是否接收到一個(gè)完整的幀,因此只需要使用一個(gè)定時(shí)器在每次收到一個(gè)字節(jié)后,就重啟一個(gè)3.5字節(jié)定時(shí)器,如果這個(gè)3.5字節(jié)定時(shí)器中斷了,就證明收到了一個(gè)Modbus報(bào)文,至于這個(gè)報(bào)文是不是正確的報(bào)文,可以在進(jìn)一步根據(jù)幀格式進(jìn)行校驗(yàn)。另外還規(guī)定了報(bào)文需要連續(xù)發(fā)送,字節(jié)間隔不得超過1.5字節(jié)時(shí)間。上面對于介質(zhì)管理所規(guī)定得時(shí)分復(fù)用,可以用一個(gè)狀態(tài)機(jī)來描述:當(dāng)T3.5定時(shí)器超時(shí)后,對于modbus-RTU來說,幀校驗(yàn)采用CRC-16。對于CRC-16得實(shí)現(xiàn),標(biāo)準(zhǔn)給出了查表法得實(shí)現(xiàn)栗子:查表法:static?unsigned?char?auchCRCHi[]?=?{?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,
0x40,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,
0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,
0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,
0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,
0x40,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,?0xC0,
0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,
0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,
0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,
0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,
0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,
0xC0,?0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,
0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,
0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,
0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,
0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,?0x40,?0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,
0x00,?0xC1,?0x81,?0x40,?0x01,?0xC0,?0x80,?0x41,?0x01,?0xC0,?0x80,?0x41,?0x00,?0xC1,?0x81,
0x40?}?;?
?
static?char?auchCRCLo[]?=?{?
0x00,?0xC0,?0xC1,?0x01,?0xC3,?0x03,?0x02,?0xC2,?0xC6,?0x06,?0x07,?0xC7,?0x05,?0xC5,?0xC4,
0x04,?0xCC,?0x0C,?0x0D,?0xCD,?0x0F,?0xCF,?0xCE,?0x0E,?0x0A,?0xCA,?0xCB,?0x0B,?0xC9,?0x09,
0x08,?0xC8,?0xD8,?0x18,?0x19,?0xD9,?0x1B,?0xDB,?0xDA,?0x1A,?0x1E,?0xDE,?0xDF,?0x1F,?0xDD,
0x1D,?0x1C,?0xDC,?0x14,?0xD4,?0xD5,?0x15,?0xD7,?0x17,?0x16,?0xD6,?0xD2,?0x12,?0x13,?0xD3,
0x11,?0xD1,?0xD0,?0x10,?0xF0,?0x30,?0x31,?0xF1,?0x33,?0xF3,?0xF2,?0x32,?0x36,?0xF6,?0xF7,
0x37,?0xF5,?0x35,?0x34,?0xF4,?0x3C,?0xFC,?0xFD,?0x3D,?0xFF,?0x3F,?0x3E,?0xFE,?0xFA,?0x3A,
0x3B,?0xFB,?0x39,?0xF9,?0xF8,?0x38,?0x28,?0xE8,?0xE9,?0x29,?0xEB,?0x2B,?0x2A,?0xEA,?0xEE,
0x2E,?0x2F,?0xEF,?0x2D,?0xED,?0xEC,?0x2C,?0xE4,?0x24,?0x25,?0xE5,?0x27,?0xE7,?0xE6,?0x26,
0x22,?0xE2,?0xE3,?0x23,?0xE1,?0x21,?0x20,?0xE0,?0xA0,?0x60,?0x61,?0xA1,?0x63,?0xA3,?0xA2,
0x62,?0x66,?0xA6,?0xA7,?0x67,?0xA5,?0x65,?0x64,?0xA4,?0x6C,?0xAC,?0xAD,?0x6D,?0xAF,?0x6F,
0x6E,?0xAE,?0xAA,?0x6A,?0x6B,?0xAB,?0x69,?0xA9,?0xA8,?0x68,?0x78,?0xB8,?0xB9,?0x79,?0xBB,
0x7B,?0x7A,?0xBA,?0xBE,?0x7E,?0x7F,?0xBF,?0x7D,?0xBD,?0xBC,?0x7C,?0xB4,?0x74,?0x75,?0xB5,
0x77,?0xB7,?0xB6,?0x76,?0x72,?0xB2,?0xB3,?0x73,?0xB1,?0x71,?0x70,?0xB0,?0x50,?0x90,?0x91,
0x51,?0x93,?0x53,?0x52,?0x92,?0x96,?0x56,?0x57,?0x97,?0x55,?0x95,?0x94,?0x54,?0x9C,?0x5C,
0x5D,?0x9D,?0x5F,?0x9F,?0x9E,?0x5E,?0x5A,?0x9A,?0x9B,?0x5B,?0x99,?0x59,?0x58,?0x98,?0x88,
0x48,?0x49,?0x89,?0x4B,?0x8B,?0x8A,?0x4A,?0x4E,?0x8E,?0x8F,?0x4F,?0x8D,?0x4D,?0x4C,?0x8C,
0x44,?0x84,?0x85,?0x45,?0x87,?0x47,?0x46,?0x86,?0x82,?0x42,?0x43,?0x83,?0x41,?0x81,?0x80,
0x40?};?
unsigned?short?CRC16?(?unsigned?char?*puchMsg,?unsigned?short?usDataLen?)??
{?
?unsigned?char?uchCRCHi?=?0xFF?;??/*?高字節(jié)初始化值???*/?
?unsigned?char?uchCRCLo?=?0xFF?;??/*?低字節(jié)初始化值???*/?
?unsigned?uIndex?;??
??
?while?(usDataLen--)?
?{?
??uIndex?=?uchCRCLo?^?*puchMsg ?;???
??uchCRCLo?=?uchCRCHi?^?auchCRCHi[uIndex]?;?
??uchCRCHi?=?auchCRCLo[uIndex]?;?
?}?
?return?(uchCRCHi?<8?|?uchCRCLo)?;?
}?
應(yīng)用層
Client/Server模型
前面OSI模型說到了,應(yīng)用層通信可以看成是client/server方式,或許會與前面說得主從模式攪合在一起,導(dǎo)致理解起來費(fèi)勁。其實(shí)這里client/server是從應(yīng)用得角度描述得,modbus-RTU中,主設(shè)備其應(yīng)用層就是client側(cè),而slave設(shè)備就是應(yīng)用的server。modbus標(biāo)準(zhǔn)文檔有種把簡單問題復(fù)雜描述之嫌。其實(shí)就是這樣一個(gè)簡單的圖:- 無錯(cuò)誤:Client(主站)向從站發(fā)出請求,Server(從站)執(zhí)行命令請求的操作,然后發(fā)送應(yīng)答給Client(主站),這里的操作,有可能是讀取參數(shù),設(shè)置參數(shù),或者執(zhí)行某個(gè)動作,具體取決于產(chǎn)品怎么設(shè)計(jì)。
- 有錯(cuò)誤:Client(主站)向從站發(fā)出請求,Server(從站)檢測到錯(cuò)誤,然后發(fā)送異常應(yīng)答給Client(主站)。這里的錯(cuò)誤,有可能是讀取失敗,寄存器地址非法,寫失敗,執(zhí)行動作失敗等。
數(shù)據(jù)模型
Modbus將采用大端字節(jié)順序傳輸報(bào)文,什么意思呢?比如一個(gè)16位數(shù)據(jù)0x55AA,先傳輸高字節(jié)0x55,再傳輸?shù)妥止?jié)0xAA。Modbus將數(shù)據(jù)抽象成四張表:看到這個(gè)表,可能會讓人覺得費(fèi)解,我的設(shè)備里哪來什么線圈?這大概是modbus協(xié)議原本是Modicon公司針對其PLC產(chǎn)品開發(fā)的協(xié)議,與其特殊的工業(yè)PLC控制編程有很大的關(guān)系。作為使用modbus協(xié)議進(jìn)行應(yīng)用開發(fā)而言,則不必費(fèi)力研究為什么叫這些名字。這四個(gè)表本質(zhì)上就是將應(yīng)用數(shù)據(jù)規(guī)劃為離散位開關(guān)量,以及寄存器變量,其中線圈與保持寄存器表為可讀可寫,其他兩個(gè)表為只讀。這個(gè)四個(gè)表中將應(yīng)用數(shù)據(jù)都利用寄存器地址進(jìn)行索引。地址范圍為0x0000-0xFFFF。需要理解的是,這里的地址與芯片的地址空間完全是兩個(gè)概念,把它簡單理解成modbus可以索引0x0000-0xFFFF這么多個(gè)用戶應(yīng)用16位數(shù)據(jù)即可。其中有的可能是開關(guān)量,有的可能利用兩個(gè)連續(xù)寄存器對應(yīng)用戶的浮點(diǎn)數(shù),字符串等等,都有可能。這些地址是modbus請求命令中的一個(gè)字段,比如使用的最最頻繁的兩條命令,就是0x03,0x10兩條命令:下圖來自modbus標(biāo)準(zhǔn)中的0x03號命令的請求以及應(yīng)答定義:又比如0x10號命令:這兩條命令中的Starting Address就是上面這4個(gè)表中寄存器對應(yīng)的地址。Modbus命令
modbus-RTU支持的命令或者叫操作碼,就如下面這個(gè)表:其中最為常用的命令是0x03,0x04,0x10號命令,一般的應(yīng)用而言,單個(gè)位開關(guān)量通信效率不免低下,現(xiàn)在很多產(chǎn)品開發(fā)已很少使用。其實(shí)對于這樣的離散量也完全可以直接放在輸入寄存器表以及保持寄存器表中。modbus對于用戶應(yīng)用并沒有嚴(yán)格的規(guī)定。用戶可以自由進(jìn)行寄存器地址(或叫索引) 映射。總結(jié)一下
modbus-RTU是一種比較簡單、可靠的協(xié)議,本文梳理了一下標(biāo)準(zhǔn)中一些比較重要的點(diǎn)。下文將分享一下,如何在單片機(jī)中實(shí)現(xiàn)幾條常用命令。—END—往期精彩推薦,點(diǎn)擊即可閱讀▲萬變不離其宗之單片機(jī)串口問題?▲萬變不離其宗之I2C總線要點(diǎn)總結(jié)▲萬變不離其宗之SPI總線要點(diǎn)總結(jié)▲長文圖解工業(yè)HART總線協(xié)議?▲RS-485總線,這篇很詳細(xì)