對(duì)于Modbus TCP來(lái)說(shuō)與Modbus RTU和Modbus ASCII有比較大的區(qū)別,因?yàn)樗沁\(yùn)行于以太網(wǎng)鏈路之上,是運(yùn)行于TCP/IP協(xié)議之上的一種應(yīng)用層協(xié)議。在協(xié)議棧的前兩個(gè)版本中,Modbus TCP作為客戶(hù)端時(shí)也存在一些局限性。我們將對(duì)這些不足作一定更新。
1 、存在的不足
在原有的協(xié)議棧中,我們所封裝的Modbus TCP客戶(hù)端一個(gè)特定的客戶(hù)端,即它只是一個(gè)客戶(hù)端實(shí)例。在通常的應(yīng)用中不會(huì)有什么問(wèn)題,但在有些應(yīng)用場(chǎng)合就會(huì)顯現(xiàn)出它的局限性。
首先,作為一個(gè)特定的客戶(hù)端,若是連接多個(gè)服務(wù)器目標(biāo)時(shí),修改服務(wù)器參數(shù)值的處理變的非常復(fù)雜,需要分辨是不同的服務(wù)器,不同的變量。當(dāng)需要從不同的網(wǎng)段操作數(shù)據(jù)時(shí),我們甚至需要標(biāo)記不同的網(wǎng)段。
其次,作為一個(gè)特定的客戶(hù)端,如果我們操作的服務(wù)器參數(shù)相似時(shí),哪怕來(lái)自于不同的網(wǎng)段,我們也需要仔細(xì)分辨或者傳遞額外的參數(shù)。因?yàn)橥豢蛻?hù)端的解析函數(shù)是同一個(gè)。
最后,將多個(gè)Modbus TCP服務(wù)器通訊都作為唯一的一個(gè)特定的服務(wù)器來(lái)處理,使得各部分混雜在一起,程序結(jié)構(gòu)很不清晰,對(duì)象也不明確。
2 、更新設(shè)計(jì)
考慮到前述的局限性,我們將Modbus TCP客戶(hù)端及其所訪問(wèn)的Modbus TCP服務(wù)器定義為通用的對(duì)象,而當(dāng)我們?cè)诰唧w應(yīng)用中使用時(shí),再將其特例化為特定的客戶(hù)端和服務(wù)器對(duì)象。
首先我們來(lái)考慮客戶(hù)端,原則上我們規(guī)劃的每一個(gè)客戶(hù)端對(duì)象管理我們?cè)O(shè)備上的一個(gè)IP網(wǎng)段的設(shè)備。那么在一個(gè)特定客戶(hù)端下,我們可以定義多達(dá)253個(gè)不同的服務(wù)器。如下圖所示:
從上圖中我們可以發(fā)現(xiàn),我們的目的就是讓協(xié)議棧支持,多客戶(hù)端和多服務(wù)器,并且在不同客戶(hù)端下可以訪問(wèn)同網(wǎng)段的多個(gè)服務(wù)器。接下來(lái)我們還需要考慮服務(wù)器對(duì)象??蛻?hù)端對(duì)服務(wù)器的操作無(wú)非兩類(lèi):讀服務(wù)器信息和寫(xiě)服務(wù)器信息。
對(duì)于讀服務(wù)器信息來(lái)說(shuō),客戶(hù)端需要發(fā)送請(qǐng)求命令,等待服務(wù)器返回響應(yīng)信息,然后客戶(hù)端解析收到的信息并更新對(duì)應(yīng)的參數(shù)值。因?yàn)榉祷氐捻憫?yīng)消息是沒(méi)有對(duì)應(yīng)的寄存器地址的,所以要想在解析的時(shí)候定位寄存器就必須知道發(fā)送的命令,為了便于分辨我們將命令存放在服務(wù)器對(duì)象中。
而對(duì)于寫(xiě)服務(wù)器操作,無(wú)論寫(xiě)的要求來(lái)自于哪里,對(duì)于協(xié)議棧來(lái)說(shuō)肯定是其它的數(shù)據(jù)處理進(jìn)程發(fā)過(guò)來(lái)的,所接到要求后我們需要記錄是哪一個(gè)客戶(hù)端管理的哪一個(gè)服務(wù)器的哪些參數(shù)。對(duì)于客戶(hù)端我們不需要分辨,因?yàn)槊總€(gè)客戶(hù)端都是獨(dú)立的處理進(jìn)程,但是對(duì)于服務(wù)器和參數(shù)我們就需要分辨。每一個(gè)客戶(hù)端所管理的IP地址的最后一段為0到255,所以我們可以依據(jù)來(lái)分辨服務(wù)器端。而在每一個(gè)服務(wù)器節(jié)點(diǎn)中增加狀態(tài)標(biāo)志,用以記錄請(qǐng)求狀態(tài),而所有服務(wù)器端組成鏈表。
3 、編碼實(shí)現(xiàn)
我們已經(jīng)設(shè)計(jì)了我們的更新,接下來(lái)我們就根據(jù)這一設(shè)計(jì)來(lái)實(shí)現(xiàn)它。我們主要從以下幾個(gè)方面來(lái)操作:第一,實(shí)現(xiàn)客戶(hù)端對(duì)象類(lèi)型和服務(wù)器對(duì)象類(lèi)型;第二,客戶(hù)端對(duì)象的實(shí)例化及服務(wù)器對(duì)象的實(shí)例化;第三,讀服務(wù)器參數(shù)的客戶(hù)端操作過(guò)程;第四,寫(xiě)服務(wù)器參的數(shù)客戶(hù)端操作過(guò)程。接下來(lái)我們將一一描述之。
3.1 、定義對(duì)象類(lèi)型
與在Modbus RTU和Modbus ASCII一樣,在Modbus TCP協(xié)議棧的封裝中,我們也需要定義客戶(hù)端對(duì)象和服務(wù)器對(duì)象,自然也免不了要定義這兩種類(lèi)型。
首先我們來(lái)定義本地客戶(hù)端的類(lèi)型,其成員包括:一個(gè)uint32_t的寫(xiě)服務(wù)器標(biāo)志數(shù)組;服務(wù)器數(shù)量字段;服務(wù)器順序字段;本客戶(hù)端所管理的服務(wù)器列表;4個(gè)數(shù)據(jù)更新函數(shù)指針。具體定義如下:
1 /* 定義本地TCP客戶(hù)端對(duì)象類(lèi)型 */
2 typedef struct LocalTCPClientType{
3 uint32_t transaction; //事務(wù)標(biāo)識(shí)符
4 uint16_t cmdNumber; //讀服務(wù)器命令的數(shù)量
5 uint16_t cmdOrder; //當(dāng)前從站在從站列表中的位置
6 uint8_t (*pReadCommand)[12]; //讀命令列表
7 ServerListHeadNode ServerHeadNode; //Server對(duì)象鏈表的頭節(jié)點(diǎn)
8 UpdateCoilStatusType pUpdateCoilStatus; //更新線(xiàn)圈量函數(shù)
9 UpdateInputStatusType pUpdateInputStatus; //更新輸入狀態(tài)量函數(shù)
10 UpdateHoldingRegisterType pUpdateHoldingRegister; //更新保持寄存器量函數(shù)
11 UpdateInputResgisterType pUpdateInputResgister; //更新輸入寄存器量函數(shù)
12 }TCPLocalClientType;
關(guān)于客戶(hù)端對(duì)象類(lèi)型,在前面的更新設(shè)計(jì)中已經(jīng)講的很清楚了,只有Server對(duì)象鏈表的頭節(jié)點(diǎn)字段需要說(shuō)明一下。該字段包括兩個(gè)類(lèi)容:第一,服務(wù)器鏈表的頭節(jié)點(diǎn)指針,用來(lái)記錄服務(wù)器對(duì)象列表。第二,記錄鏈表的長(zhǎng)度,即服務(wù)器節(jié)點(diǎn)的數(shù)量。具體如下圖所示:
還需要定義服務(wù)器對(duì)象,此服務(wù)器對(duì)象只是便于客戶(hù)端而用于表示真是的服務(wù)器。客戶(hù)端的服務(wù)器列表中就是此對(duì)象。具體結(jié)構(gòu)如下:
1 /* 定義被訪問(wèn)TCP服務(wù)器對(duì)象類(lèi)型 */
2 typedef struct AccessedTCPServerType{
3 union {
4 uint32_t ipNumber;
5 uint8_t ipSegment[4];
6 }ipAddress; //服務(wù)器的IP地址
7 uint32_t flagPresetServer; //寫(xiě)服務(wù)器請(qǐng)求標(biāo)志
8 WritedCoilListHeadNode pWritedCoilHeadNode; //可寫(xiě)的線(xiàn)圈量列表
9 WritedRegisterListHeadNode pWritedRegisterHeadNode; //可寫(xiě)的保持寄存器列表
10 struct AccessedTCPServerType *pNextNode; //下一個(gè)TCP服務(wù)器節(jié)點(diǎn)
11 }TCPAccessedServerType;
關(guān)于服務(wù)器對(duì)象有三個(gè)字段需要說(shuō)明一下。首先我們來(lái)看一看“讀命令列表(uint8_t (*pReadCommand)[12])”字段,它是12個(gè)字節(jié),這是由Modbus TCP消息格式?jīng)Q定的。如下:
我們看到協(xié)議標(biāo)識(shí)符為0,是因?yàn)?就表示Modbus TCP。還有可寫(xiě)的線(xiàn)圈量列表頭節(jié)點(diǎn)和可寫(xiě)的保持寄存器列表頭節(jié)點(diǎn)。這兩個(gè)字段用來(lái)表示對(duì)線(xiàn)圈和保持寄存器的列表即數(shù)量。
3.2 、實(shí)例化對(duì)象
我們定義了客戶(hù)端即服務(wù)器對(duì)象類(lèi)型,我們?cè)谑褂脮r(shí)就需要實(shí)例化這些對(duì)象。一般來(lái)說(shuō)一個(gè)IP網(wǎng)段我們將其實(shí)例化為一個(gè)客戶(hù)端對(duì)象。
TCPLocalClientType hgraClient;
/ 初始化TCP客戶(hù)端對(duì)象 /
InitializeTCPClientObject(&hgraClient,2,hgraServer,NULL,NULL,NULL,NULL);
而一個(gè)客戶(hù)端對(duì)象會(huì)管理1到253個(gè)服務(wù)器對(duì)象,所以我們可以將多個(gè)服務(wù)器對(duì)象實(shí)例組成數(shù)組,并將其賦予客戶(hù)端管理。
TCPAccessedServerType hgraServer[]={{{192,168,0,1},0x00,0x00},{{192,168,1,1},0x00,0x00}};
所以,根據(jù)客戶(hù)端和服務(wù)器實(shí)例化的條件,我們需要先實(shí)例化服務(wù)器對(duì)象才能完整實(shí)例化客戶(hù)端對(duì)象。在客戶(hù)端的初始化中,我們這里將4的數(shù)據(jù)處理函數(shù)指針初始化為NULL,有一個(gè)默認(rèn)的處理函數(shù)會(huì)復(fù)制給它,該函數(shù)是上一版本的延續(xù),在簡(jiǎn)單應(yīng)用時(shí)簡(jiǎn)化操作。服務(wù)器的上一個(gè)發(fā)送的命令指針也被賦值為NULL,因?yàn)槌跏紩r(shí)還沒(méi)有命令發(fā)送。
3.3 、讀服務(wù)器操作
讀服務(wù)器操作原理上與以前的版本是一樣的。按照一定的順序給服務(wù)器發(fā)送命令再對(duì)收到的消息進(jìn)行解析。我們對(duì)客戶(hù)端及其所管理的服務(wù)器進(jìn)行了定義,將發(fā)送命令保存于服務(wù)器對(duì)象,將服務(wù)器列表保存于客戶(hù)端對(duì)象,所以我們需要對(duì)解析函數(shù)進(jìn)行修改。
1 /*解析收到的服務(wù)器相應(yīng)信息*/
2 void ParsingServerRespondMessage(TCPLocalClientType *client,uint8_t *recievedMessage)
3 {
4 /*判斷接收到的信息是否有相應(yīng)的命令*/
5 int cmdIndex=FindCommandForRecievedMessage(client,recievedMessage);
6
7 if((cmdIndex<0)) //沒(méi)有對(duì)應(yīng)的請(qǐng)求命令,事務(wù)號(hào)不相符
8 {
9 return;
10 }
11
12 if((recievedMessage[2]!=0x00)||(recievedMessage[3]!=0x00)) //不是Modbus TCP協(xié)議
13 {
14 return;
15 }
16
17 if(recievedMessage[7]>0x04) //功能碼大于0x04則不是讀命令返回
18 {
19 return;
20 }
21
22 uint16_t mLength=(recievedMessage[4]<<8)+recievedMessage[4];
23 uint16_t dLength=(uint16_t)recievedMessage[8];
24 if(mLength!=dLength+3) //數(shù)據(jù)長(zhǎng)度不一致
25 {
26 return;
27 }
28
29 FunctionCode fuctionCode=(FunctionCode)recievedMessage[7];
30
31 if(fuctionCode!=client->pReadCommand[cmdIndex][7])
32 {
33 return;
34 }
35
36 uint16_t startAddress=(uint16_t)client->pReadCommand[cmdIndex][8];
37 startAddress=(startAddress<<8)+(uint16_t)client->pReadCommand[cmdIndex][9];
38 uint16_t quantity=(uint16_t)client->pReadCommand[cmdIndex][10];
39 quantity=(quantity<<8)+(uint16_t)client->pReadCommand[cmdIndex][11];
40
41 if(quantity*2!=dLength) //請(qǐng)求的數(shù)據(jù)長(zhǎng)度與返回的數(shù)據(jù)長(zhǎng)度不一致
42 {
43 return;
44 }
45
46 if((fuctionCode>=ReadCoilStatus)&&(fuctionCode<=ReadInputRegister))
47 {
48 HandleServerRespond[fuctionCode-1](client,recievedMessage,startAddress,quantity);
49 }
50 }
解析函數(shù)的主要部分是在檢查接收到的消息是否是合法的Modbus TCP消息。檢查沒(méi)問(wèn)題則調(diào)用協(xié)議站解析。而最后調(diào)用的數(shù)據(jù)處理函數(shù)則是我們需要在具體應(yīng)用中編寫(xiě)。在前面客戶(hù)端初始化時(shí),回調(diào)函數(shù)我們初始化為NULL,實(shí)際在協(xié)議占中有弱化的函數(shù)定義,需要針對(duì)具體的寄存器和變量地址實(shí)現(xiàn)操作。
3.4 、寫(xiě)服務(wù)器操作
寫(xiě)服務(wù)器操作則是在其它進(jìn)程請(qǐng)求后,我們標(biāo)識(shí)需要寫(xiě)的對(duì)象再統(tǒng)一處理。對(duì)具體哪個(gè)服務(wù)器的寫(xiě)標(biāo)識(shí)存于客戶(hù)端實(shí)例。而該服務(wù)器的哪些變量需要寫(xiě)則記錄在服務(wù)器實(shí)例中。
所以在進(jìn)程檢測(cè)到需要寫(xiě)一個(gè)服務(wù)器時(shí)則置位對(duì)應(yīng)的位,即改變flagWriteServer中的對(duì)應(yīng)位。而需要寫(xiě)該服務(wù)器的哪些變量則標(biāo)記flagPresetCoil和flagPresetReg的對(duì)應(yīng)位。修改這些標(biāo)識(shí)都在其它請(qǐng)求更改的進(jìn)程中實(shí)現(xiàn),而具體的寫(xiě)操作則在本客戶(hù)端進(jìn)程中,檢測(cè)到標(biāo)志位的變化統(tǒng)一執(zhí)行。
這部分不修改協(xié)議棧的代碼,因?yàn)楦鞣?wù)器及各變量都只與具體對(duì)象相關(guān)聯(lián),所以在具體的應(yīng)用中修改。
4 、回歸驗(yàn)證
借鑒前面Modbus ASCII和Modbus RTU的回歸測(cè)試經(jīng)驗(yàn),我們?cè)O(shè)計(jì)兩個(gè)網(wǎng)段、每網(wǎng)段包括一個(gè)客戶(hù)端及兩個(gè)服務(wù)器的網(wǎng)絡(luò)結(jié)構(gòu)。但考慮到我們只是功能性驗(yàn)證,所以我們?cè)O(shè)計(jì)相對(duì)簡(jiǎn)單的服務(wù)器。所以我們?cè)O(shè)計(jì)的網(wǎng)絡(luò)為:協(xié)議棧建立2個(gè)客戶(hù)端,每個(gè)客戶(hù)端管理同一網(wǎng)段的2個(gè)服務(wù)器,每個(gè)服務(wù)器有8個(gè)線(xiàn)圈及2個(gè)保持寄存器。具體結(jié)構(gòu)如圖:
從上圖我們知道,該Modbus網(wǎng)關(guān)需要實(shí)現(xiàn)一個(gè)Modbus服務(wù)器用于和上位的通訊;需要實(shí)現(xiàn)兩個(gè)Modbus客戶(hù)端用于和下位的通訊。
在這個(gè)實(shí)驗(yàn)中,讀操作沒(méi)有什么需要說(shuō)的,只需要發(fā)送命令解析返回消息即可。所以我們中點(diǎn)描述一下為了方便操作,在需要寫(xiě)的連續(xù)段,我們只要找到第一個(gè)請(qǐng)求寫(xiě)的位置后,就將后續(xù)連續(xù)可寫(xiě)數(shù)據(jù)一次性寫(xiě)入。
告之: 源代碼可上Github下載:https://github.com/foxclever/Modbus
-
MODBUS
+關(guān)注
關(guān)注
28文章
1805瀏覽量
76985 -
TCP
+關(guān)注
關(guān)注
8文章
1353瀏覽量
79068 -
RTU
+關(guān)注
關(guān)注
0文章
412瀏覽量
28676 -
協(xié)議棧
+關(guān)注
關(guān)注
2文章
141瀏覽量
33629
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論