1. 程式人生 > >Modbus庫開發筆記之二:Modbus消息幀的生成

Modbus庫開發筆記之二:Modbus消息幀的生成

不同的 command dwr 分別是 slave 識別碼 align 數據格式 .com

前面我們已經對Modbus的基本事務作了說明,也據此設計了我們將要實現的主從站的操作流程。這其中與Modbus直接相關的就是Modbus消息幀的生成。Modbus消息幀也是實現Modbus通訊協議的根本。

1Modbus消息幀分析

MODBUS協議在不同的物理鏈路上的消息幀有一些差異,但我們分析一下就會發現,在這些不同的消息幀中具有一下相同的部分,這對我們實現統一的數據操作非常重要,具體描述如下:

1)、簡單協議數據單元

MODBUS協議定義了一個與基礎通信層無關的簡單協議數據單元(PDU)。簡單協議數據單元的結構如下:

技術分享

PDU是一個與具體的傳輸網絡無關的部分,包含功能碼和數據。對於特定總線或網絡上的 MODBUS 協議只是在PDU的基礎上在應用數據單元(ADU)上引入一些附加域。

數據單元部分的開發是最基本的部分,主要是2各方面的類容:一是生成客戶端(主站)訪問服務器(從站)的命令部分;二是生成服務器(從站)響應客戶端(主站)回復部分。

2)、RTU的應用數據單元

對於在串行鏈路上運行的Modbus協議,其應用數據單元(ADU)是在PDU的基礎上,在前面加上地址域,後面加上數據校驗。格式如下圖所示:

技術分享

地址域就是所訪問從站的地址,為一個8位無符號數,取值0-255,但0和255有固定含義不能使用。CRC校驗采用的是CRC16校驗方式。

3)、TCP的應用數據單元

在以太網鏈路上運行的Modbus協議,其應用數據單元(ADU)是在PDU的基礎上添加上MBAP報文頭形成的,具體格式如下圖:

技術分享

對於MBAP 報文頭,包括下列域:

長度

描述

客戶機

服務器

事務元標識符

2 個字節

MODBUS 請求/響應事務處理的識別碼

客戶機啟動

服務器從接收的請求中重新復制

協議標識符

2 個字節

0=MODBUS 協議

客戶機啟動

服務器從接收的請求中重新復制

長度

2 個字節

以下字節的數量

客戶機啟動(請求)

服務器(響應)啟動

單元標識符

1 個字節

串行鏈路或其它總線上連接的遠程從站的識別碼

客戶機啟動

服務器從接收的請求中重新復制

從上表中可知報文頭為 7 個字節長:

事務處理標識符:用於事務處理配對。在響應中,MODBUS 服務器復制請求的事務處理標識符。
協議標識符:用於系統內的多路復用。通過值 0 識別 MODBUS 協議。

長度:長度域是下一個域的字節數,包括單元標識符和數據域。

單元標識符:為了系統內路由,使用這個域。專門用於通過以太網TCP-IP 網絡和MODBUS串行鏈路之間的網關對MODBUS或MODBUS+串行鏈路從站的通信。說的簡單點就是串行鏈路中的地址域。MODBUS客戶機在請求中設置這個域,在響應中服務器必須利用相同的值返回這個域。

2、數據幀的具體組成分析

從以上對簡單協議基本數據元、RTU應用數據單元和TCP應用數據單元報文格式的分析,我們發現對於基本數據單元部分已一致的,所以我們可以考慮來分層封裝協議操作部分:

技術分享

最開始實現Modbus基本數據單元,這是數據公用部分與具體的應用無關,只需要封裝一次,對於這部分的開發只需要按照Modbus的標準協議來開發就好,本次我們計劃實現的功能有8個:

功能碼

名稱

實現

描述

0x01

讀線圈

對可讀寫型的狀態量進行讀取

0x02

讀離散輸入

對只讀型的狀態量進行讀取

0x03

讀保持寄存器

對可讀寫型的寄存器量進行讀取

0x04

讀輸入寄存器

對只讀型的寄存器量進行讀取

0x05

寫單個線圈

對單個的讀寫型的狀態量進行寫入

0x06

寫單個寄存器

對單個的讀寫型的寄存器量進行寫入

0x0F

寫多個線圈

對多個的讀寫型的狀態量進行寫入

0x10

寫多個寄存器

對多個的讀寫型的寄存器量進行寫入

這8個也是Modbus協議所定義的最主要的功能,現在對這幾種功能碼的報文格式描述如下:

1)讀線圈0x01

讀線圈就是都一種可以寫的開關量,因為Modbus協議起源於PLC應用,而線圈是對PLC的DO輸出的稱呼,一般適用於主站對從站下達操作命令。讀這種具有讀寫功能的狀態量的數據格式如下:

技術分享

其下發的命令格式為:域名+功能碼+起始地址+數量。

2)讀離散輸入0x02

讀狀態輸入是讀取一種只讀開關量信號,對應於PLC中的數字輸入量。讀取這種只讀型開關輸入量的格式如下:

技術分享

其下發的命令格式為:域名+功能碼+起始地址+數量。

3)讀保持寄存器0x03

保持寄存器就是指可以讀寫的16位數據,通過單個或多個保持寄存器可以用來表示各種數據,如8位整數、16為整數、32位整數、64位整數以及單雙精度浮點數等。讀取保持寄存器的報文格式如下:

技術分享

其下發的命令格式為:域名+功能碼+起始地址+數量。

4)讀輸入寄存器0x04

輸入寄存器是一種只讀形式的16位數據。通過單個或多個輸入寄存器可以表示8位整數、16為整數、32位整數、64位整數以及單雙精度浮點數等。讀取輸入寄存器的報文格式如下:

技術分享

其下發的命令格式為:域名+功能碼+起始地址+數量。

5)寫單個線圈0x05

寫單個線圈量就是對單個的可讀寫的開關量進行操作,但是其並非是直接寫“0”或者“1”,而是在需要寫“1”時發送0xFF00;而在需要寫“0”時發送0x0000,其具體的報文格式如下:

技術分享

其下發的命令格式為:域名+功能碼+輸出地址+輸出值。命令的具體內容與讀操作有區別但,格式卻是完全一樣,在編程時實際讀和寫可以封裝在一起。

6)寫單個寄存器0x06

寫單個寄存器就是對單個的保持寄存器進行操作,數據的格式依然是一樣的,實際應用中只適用於對16位整型數據的操作,對於浮點數等則不可以。

技術分享

其下發的命令格式為:域名+功能碼+輸出地址+輸出值。命令的具體內容與讀操作有區別但,格式卻是完全一樣,在編程時實際讀和寫可以封裝在一起。

7)寫多個線圈0x0F

寫多個線圈的操作對象與寫單個線圈是完全一樣的,不同的是數量和操作值,特別是值,寫“1”就是“1”,寫“0”就是 “0”,這是與寫單個線圈的區別。

技術分享

其下發的命令格式為:域名+功能碼+起始地址+輸出數量+字節數+輸出值。命令報文與前面的幾種讀寫操作有較大的區別,必須要單獨處理。

8)寫多個寄存器0x10

寫多個寄存器的就是對多個可讀寫寄存器同時進行操作,數據報文的格式與寫多個線圈是一致的。

技術分享

其下發的命令格式為:域名+功能碼+起始地址+輸出數量+字節數+輸出值。

3、基本數據單元的編程

經過上面的分析,我們發現不論是在什麽樣的物理鏈路上實現的應用數據,器基本數據段都是相同的。其實加上域名段的格式也是相同的,所以我們就將

域名+PDU一起作為最基本的數據單元來實現。

對於基本數據單元的實現由分為2種情況:一是作為主站(客戶端)時,對從站(服務器)的下發命令;二是作為從站(服務器)時,對主站(客戶端)命令的響應。所以我們將這兩種情況分別封裝為2個基礎函數:

(1)、作為RTU主站(TCP客戶端)時,生成讀寫RTU從站(TCP服務器)對象的命令:

uint16_t GenerateReadWriteCommand(ObjAccessInfo objInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

參數分別是PDU單元的基本信息,寫對象的對應數據,以及生成的命令字節。而返回值則是生成的命令的長度。

(2)、作為從站(服務器)時,生成主站讀訪問的響應。對於響應因為寫操作的響應實際上就是復制主站(客戶端)的命令的一部分,所以我們實際需要生成的響應是包括0x01、0x02、0x03、0x04功能碼的情形。

uint16_t GenerateMasterAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

參數分別是接收到的信息,讀取的對象的數據,以及返回的響應消息。而返回值則是返回的響應消息的長度。

4RTU應用數據單元的編程

對於RTU應用數據單元來說,其報文格式就是:“域名+PDU+CRC”,而域名+PDU我們在上一節中已經實現了,所以要實現RTU的數據單元實際上我們只需要加上CRC校驗就已經完成了。

對於RTU數據單元的實現由分為2種情況:一是作為主站時,對從站的下發命令;二是作為從站時,對主站命令的響應。所以我們將這兩種情況分別封裝為2個基礎函數:

(1)、作為RTU主站時,生成讀寫RTU從站對象的命令:

/*生成讀寫從站數據對象的命令,命令長度包括2個校驗字節*/

uint16_t SyntheticReadWriteSlaveCommand(ObjAccessInfo slaveInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

參數分別是從站基本信息,下發的數據列表,以及最終生成的命令數組。返回值是是命令的長度。

(2)、作為從站時,生成主站讀訪問的響應:

/*生成從站應答主站的響應*/

uint16_t SyntheticSlaveAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

參數分別是接收到的信息,返回的數據列表,生成的響應信息列表。返回值是響應信息列表的長度。

5TCP應用數據單元的編程

而對於TCP應用數據單元來說,與RTU類式,起報文格式是:“MBAP頭+PDU”,而PDU單元就是前面定義的,所以只需要加上MBAP頭部就可以了,事實上MBAP頭部的實現格式是固定的。

對於TCP應用數據單元的實現同樣分為2中情況:一是作為客戶端時,對服務器的下發命令;二是作為服務器時,對客戶端命令的響應。所以我們將這兩種情況分別封裝為2個基礎函數:

(1)、作為TCP客戶端時,生成讀寫TCP服務器對象的命令:

/*生成讀寫服務器對象的命令*/

uint16_t SyntheticReadWriteTCPServerCommand(ObjAccessInfo objInfo,bool *statusList,uint16_t *registerList,uint8_t *commandBytes)

(2)、作為(服務器時,生成客戶端讀寫訪問的響應:

/*合成對服務器訪問的響應,返回值為命令長度*/

uint16_t SyntheticServerAccessRespond(uint8_t *receivedMessage,bool *statusList,uint16_t *registerList,uint8_t *respondBytes)

6、結束語

其實到這裏我們對Modbus基本協議已經基本實現,甚至使用這些基本操作也能實現Modbus的通訊。事實上很多人在應用寫的Modbus通訊協議比這還要簡單,也能實現部分的Modbus通訊功能。當然這不是我們的目標,否則就不需要專門開發庫了,我們要進一步封裝,讓其更通用也更易用才是我們需要的。

Modbus庫開發筆記之二:Modbus消息幀的生成