前面我們已經(jīng)分析了Modbus RTU的更新設(shè)計(jì)和具體實(shí)現(xiàn)(如果不清楚可查看前一篇文章)。其實(shí)Modbus ASCII與Modbus RTU都是基于串行鏈路實(shí)現(xiàn)的,所以有很多的共同點(diǎn),基于此,這篇文章我們只討論與Modbus RTU所不同的部分。
1 、更新設(shè)計(jì)
關(guān)于原來的協(xié)議棧在Modbus ASCII主站應(yīng)用時(shí)所存在的局限性與Modbus RTU也是一樣的,所以我們不分析它的不足,只討論更新設(shè)計(jì)。我們將主站及其所訪問的從站定義為通用的對(duì)象,而當(dāng)我們?cè)诰唧w應(yīng)用中使用時(shí),再將其特例化為特定的主站和從站對(duì)象。
首先我們來考慮主站,原則上我們規(guī)劃的每一個(gè)主站對(duì)象對(duì)應(yīng)我們?cè)O(shè)備上的一個(gè)端口,這里所說端口就是指串口。那么在同一端口下,也就是在一個(gè)特定主站下,我們可以定義多個(gè)地址不同的從站。而在不同的端口上可以具有地址相同的從站。如下圖所示:
從上圖中我們可以發(fā)現(xiàn),我們的目的就是讓協(xié)議棧支持,多主站和多從站,并且在不同主站下,從站的地址重復(fù)不受影響。從上圖看視乎一個(gè)主站對(duì)象可以同時(shí)管理254個(gè)從站對(duì)象,事實(shí)上還要受到帶載能力的影響。
接下來我們還需要考慮從站對(duì)象。主站對(duì)從站的操作無非兩類:讀從站信息和寫從站信息。對(duì)于讀從站信息來說,主站需要發(fā)送請(qǐng)求命令,等待從站返回響應(yīng)信息,然后主站解析收到的信息并更新對(duì)應(yīng)的參數(shù)值。有兩點(diǎn)需要我們考慮,第一返回的響應(yīng)消息是沒有對(duì)應(yīng)的寄存器地址的,所以要想在解析的時(shí)候定位寄存器就必須知道發(fā)送的命令,為了便于分辨我們將命令存放在從站對(duì)象中。第二在解析響應(yīng)時(shí),如果兩條命令的響應(yīng)類似是沒法分辨的,所以我們還需要記住上一條命令是什么。也存儲(chǔ)于從站對(duì)象中。
而對(duì)于寫從站操作,無論寫的要求來自于哪里,對(duì)于協(xié)議棧來說肯定是其它的數(shù)據(jù)處理進(jìn)程發(fā)過來的,所接到要求后我們需要記錄是哪一個(gè)主站管理的哪一個(gè)從站的哪些參數(shù)。對(duì)于主站我們不需要分辨,因?yàn)槊總€(gè)主站都是獨(dú)立的處理進(jìn)程,但是對(duì)于從站和參數(shù)我們就需要分辨。每一個(gè)主站可以帶的站地址為0到255,但0和255已有定義,所以實(shí)際是1到254個(gè)。所以我們使用一個(gè)256位的變量,每位對(duì)應(yīng)站號(hào)來標(biāo)志其是否有需要寫的請(qǐng)求。記錄于主站,具體如下:
事實(shí)上,我們不可能會(huì)用到256個(gè)標(biāo)志位,因?yàn)镸odbus ASCII本身就是為簡(jiǎn)單應(yīng)用而設(shè)定的。我們使用256個(gè)標(biāo)志位,主要是考慮到站地址的取值范圍,方便軟件操作而定的。還有每個(gè)從站的寫參數(shù)請(qǐng)求標(biāo)志,我們將其存儲(chǔ)于各個(gè)從站對(duì)象,因?yàn)椴煌膹恼究赡苡泻艽髤^(qū)別,存儲(chǔ)于各個(gè)從站更加靈活方便。
2 、編碼實(shí)現(xiàn)
我們已經(jīng)設(shè)計(jì)了我們的更新,接下來我們就根據(jù)這一設(shè)計(jì)來實(shí)現(xiàn)它。我們主要從以下幾個(gè)方面來操作:第一,實(shí)現(xiàn)主站對(duì)象類型和從站對(duì)象類型;第二,主站對(duì)象的實(shí)例化及從站對(duì)象的實(shí)例化;第三,讀從站的主站操作過程;第四,寫從站的主站操作過程。接下來我們將一一描述之。
2.1 、定義對(duì)象類型
與在Modbus RTU一樣,在Modbus ASCII協(xié)議棧的封裝中,我們也需要定義主站對(duì)象和從站對(duì)象,自然也免不了要定義這兩種類型。
首先我們來定義本地主站的類型,其成員包括:一個(gè)uint32_t的寫從站標(biāo)志數(shù)組;從站數(shù)量字段;從站順序字段;本主站過管理的從站列表;4個(gè)數(shù)據(jù)更新函數(shù)指針。具體定義如下:
1 /* 定義本地ASCII主站對(duì)象類型 */
2 typedef struct LocalASCIIMasterType{
3 uint32_t flagWriteSlave[8]; //寫一個(gè)站控制標(biāo)志位,最多256個(gè)站,與站地址對(duì)應(yīng)。
4 uint16_t slaveNumber; //從站列表中從站的數(shù)量
5 uint16_t readOrder; //當(dāng)前從站在從站列表中的位置
6 ASCIIAccessedSlaveType *pSlave; //從站列表
7 UpdateCoilStatusType pUpdateCoilStatus; //更新線圈量函數(shù)
8 UpdateInputStatusType pUpdateInputStatus; //更新輸入狀態(tài)量函數(shù)
9 UpdateHoldingRegisterType pUpdateHoldingRegister; //更新保持寄存器量函數(shù)
10 UpdateInputResgisterType pUpdateInputResgister; //更新輸入寄存器量函數(shù)
11 }ASCIILocalMasterType;
關(guān)于主站對(duì)象類型,在前面的更新設(shè)計(jì)中已經(jīng)講的很清楚了,只有兩個(gè)字段需要說明一下。第一,從站列表是用來記錄本主站所管理的從站對(duì)象。第二,readOrder字段表示為當(dāng)前訪問從站在列表中的位置,而slaveNumber是從站對(duì)象的數(shù)量,即列表的長(zhǎng)度。具體如下圖所示:
還需要定義從站對(duì)象,此從站對(duì)象只是便于主站而用于表示真是的從站。主站的從站列表中就是此對(duì)象。具體結(jié)構(gòu)如下:
1 /* 定義被訪問ASCII從站對(duì)象類型 */
2 typedef struct AccessedASCIISlaveType{
3 uint8_t stationAddress; //站地址
4 uint8_t cmdOrder; //當(dāng)前命令在命令列表中的位置
5 uint16_t commandNumber; //命令列表中命令的總數(shù)
6 uint8_t (*pReadCommand)[17]; //讀命令列表
7 uint8_t *pLastCommand; //上一次發(fā)送的命令
8 uint32_t flagPresetCoil; //預(yù)置線圈控制標(biāo)志位
9 uint32_t flagPresetReg; //預(yù)置寄存器控制標(biāo)志位
10 }ASCIIAccessedSlaveType;
關(guān)于從站對(duì)象有三個(gè)字段需要說明一下。首先我們來看一看“讀命令列表(uint8_t (*pReadCommand)[17])”字段,與Modbus RTU不同,它是17個(gè)字節(jié),這是由Modbus ASCII消息格式?jīng)Q定的。如下:
還有就是flagPresetCoil和flagPresetReg字段。這兩個(gè)字段用來表示對(duì)線圈和保持寄存器的寫請(qǐng)求。
2.2 、實(shí)例化對(duì)象
我們定義了主站即從站對(duì)象類型,我們?cè)谑褂脮r(shí)就需要實(shí)例化這些對(duì)象。一般來說一個(gè)硬件端口我們將其實(shí)例化為一個(gè)主站對(duì)象。
ASCIILocalMasterType hgraMaster;
/ 初始化ASCII主站對(duì)象 /
InitializeASCIIMasterObject(&hgraMaster,2,hgraSlave,NULL,NULL,NULL,NULL);
而一個(gè)主站對(duì)象會(huì)管理1到254個(gè)從站對(duì)象,所以從站對(duì)象我們可以將多個(gè)從站對(duì)象實(shí)例組成數(shù)組,并將其賦予主站管理。
ASCIIAccessedSlaveType hgraSlave[]={{1,0,2,slave1ReadCommand,NULL,0x00,0x00},{2,0,2,slave2ReadCommand,NULL,0x00,0x00}};
所以,根據(jù)主站和從站實(shí)例化的條件,我們需要先實(shí)例化從站對(duì)象才能完整實(shí)例化主站對(duì)象。在主站的初始化中,我們這里將4的數(shù)據(jù)處理函數(shù)指針初始化為NULL,有一個(gè)默認(rèn)的處理函數(shù)會(huì)復(fù)制給它,該函數(shù)是上一版本的延續(xù),在簡(jiǎn)單應(yīng)用時(shí)簡(jiǎn)化操作。從站的上一個(gè)發(fā)送的命令指針也被賦值為NULL,因?yàn)槌跏紩r(shí)還沒有命令發(fā)送。
2.3 、讀從站操作
讀從站操作原理上與以前的版本是一樣的。按照一定的順序給從站發(fā)送命令再對(duì)收到的消息進(jìn)行解析。我們對(duì)主站及其所管理的從站進(jìn)行了定義,將發(fā)送命令保存于從站對(duì)象,將從站列表保存于主站對(duì)象,所以我們需要對(duì)解析函數(shù)進(jìn)行修改。
1 /*解析收到的服務(wù)器相應(yīng)信息*/
2 void ParsingAsciiSlaveRespondMessage(AsciiLocalMasterType *master,uint8_t *recievedMessage, uint8_t *command,uint16_t rxLength)
3 {
4 int i=0;
5 int j=0;
6 uint8_t *cmd=NULL;
7
8 /*判斷是否為Modbus ASCII消息*/
9 if (0x3A != recievedMessage[0])
10 {
11 return ;
12 }
13
14 /*判斷消息是否接收完整*/
15 if ((rxLength < 17) || (recievedMessage[rxLength - 2] != 0x0D) || (recievedMessage[rxLength - 1] != 0x0A))
16 {
17 return ;
18 }
19
20 uint16_t length = rxLength - 3;
21 uint8_t hexMessage[256];
22
23 if (!CovertAsciiMessageToHex(recievedMessage + 1, hexMessage, length))
24 {
25 return ;
26 }
27
28 /*校驗(yàn)接收到的數(shù)據(jù)是否正確*/
29 if (!CheckASCIIMessageIntegrity(hexMessage, length/2))
30 {
31 return ;
32 }
33
34 /*判斷功能碼是否有誤*/
35 FunctionCode fuctionCode = (FunctionCode)hexMessage[1];
36 if (CheckFunctionCode(fuctionCode) != MB_OK)
37 {
38 return;
39 }
40
41 if ((command == NULL)||(!CheckMessageAgreeWithCommand(recievedMessage, command)))
42 {
43 while(islaveNumber)
44 {
45 if(master->pSlave[i].stationAddress==hexMessage[0])
46 {
47 break;
48 }
49 i++;
50 }
51
52 if(i>=master->slaveNumber)
53 {
54 return;
55 }
56
57 if((master->pSlave[i].pLastCommand==NULL)||(!CheckMessageAgreeWithCommand(recievedMessage,master->pSlave[i].pLastCommand)))
58 {
59 j=FindAsciiCommandForRecievedMessage(recievedMessage,master->pSlave[i].pReadCommand,master->pSlave[i].commandNumber);
60
61 if(j<0)
62 {
63 return;
64 }
65
66 cmd=master->pSlave[i].pReadCommand[j];
67 }
68 else
69 {
70 cmd=master->pSlave[i].pLastCommand;
71 }
72 }
73 else
74 {
75 cmd=command;
76 }
77
78 uint8_t hexCommand[256];
79 CovertAsciiMessageToHex(cmd + 1, hexCommand, 14);
80
81 uint16_t startAddress = (uint16_t)hexCommand[2];
82 startAddress = (startAddress << 8) + (uint16_t)hexCommand[3];
83 uint16_t quantity = (uint16_t)hexCommand[4];
84 quantity = (quantity << 8) + (uint16_t)hexCommand[5];
85
86 if ((fuctionCode >= ReadCoilStatus) && (fuctionCode <= ReadInputRegister))
87 {
88 HandleAsciiSlaveRespond[fuctionCode - 1](master,hexMessage,startAddress,quantity);
89 }
90 }
解析函數(shù)的主要部分是在檢查接收到的消息是否是合法的Modbus ASCII消息。檢查沒問題則調(diào)用協(xié)議站解析。而最后調(diào)用的數(shù)據(jù)處理函數(shù)則是我們需要在具體應(yīng)用中編寫。在前面主站初始化時(shí),回調(diào)函數(shù)我們初始化為NULL,實(shí)際在協(xié)議占中有弱化的函數(shù)定義,需要針對(duì)具體的寄存器和變量地址實(shí)現(xiàn)操作。特別要說明的是,解析Modbus ASCII消息時(shí),在去除開始字符和結(jié)束字符后,需要將ASCII碼轉(zhuǎn)化為二進(jìn)制數(shù)才能完成解析。
2.4 、寫從站操作
寫從站操作則是在其它進(jìn)程請(qǐng)求后,我們標(biāo)識(shí)需要寫的對(duì)象再統(tǒng)一處理。對(duì)具體哪個(gè)從站的寫標(biāo)識(shí)存于主站實(shí)例。而該從站的哪些變量需要寫則記錄在從站實(shí)例中。
所以在進(jìn)程檢測(cè)到需要寫一個(gè)從站時(shí)則置位對(duì)應(yīng)的位,即改變flagWriteSlave中的對(duì)應(yīng)位。而需要寫該站的哪些變量則標(biāo)記flagPresetCoil和flagPresetReg的對(duì)應(yīng)位。修改這些標(biāo)識(shí)都在其它請(qǐng)求更改的進(jìn)程中實(shí)現(xiàn),而具體的寫操作則在本主站進(jìn)程中,檢測(cè)到標(biāo)志位的變化統(tǒng)一執(zhí)行。
這部分不修改協(xié)議棧的代碼,因?yàn)楦髡炯案髯兞慷贾劣诰唧w對(duì)象相關(guān)聯(lián),所以在具體的應(yīng)用中修改。
3 、回歸驗(yàn)證
考慮到Modbus ASCII和Modbus RTU的相似性,我們?cè)O(shè)計(jì)同樣的的網(wǎng)絡(luò)結(jié)構(gòu)。但考慮到Modbus ASCII一般用于小數(shù)據(jù)量通訊,所以我們?cè)O(shè)計(jì)相對(duì)簡(jiǎn)單的從站。所以我們?cè)O(shè)計(jì)的網(wǎng)絡(luò)為:協(xié)議棧建立2個(gè)主機(jī),每個(gè)主機(jī)管理2個(gè)從站,每個(gè)從站有8個(gè)線圈及2個(gè)保持寄存器。具體結(jié)構(gòu)如圖:
從上圖我們知道,該Modbus網(wǎng)關(guān)需要實(shí)現(xiàn)一個(gè)Modbus從站用于和上位的通訊;需要實(shí)現(xiàn)兩個(gè)Modbus主站用于和下位的通訊。
在這個(gè)實(shí)驗(yàn)中,讀操作沒有什么需要說的,只需要發(fā)送命令解析返回消息即可。所以我們中點(diǎn)描述一下為了方便操作,在需要寫的連續(xù)段,我們只要找到第一個(gè)請(qǐng)求寫的位置后,就將后續(xù)連續(xù)可寫數(shù)據(jù)一次性寫入。修改寫標(biāo)志位的代碼如下:
1 /* 修改從站線圈量使能控制 */
2 static void PresetSlaveCoilControll(uint16_t startAddress,uint16_t endAddress)
3 {
4 if((8<=startAddress)&&(startAddress<=15)&&(8<=endAddress)&&(endAddress<=15))
5 {
6 ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[0].stationAddress,true);
7
8 if((startAddress<=8)&&(8<=endAddress))
9 {
10 hgraMaster.pSlave[0].flagPresetCoil|=0x01;
11 }
12 if((startAddress<=9)&&(9<=endAddress))
13 {
14 hgraMaster.pSlave[0].flagPresetCoil|=0x02;
15 }
16 if((startAddress<=10)&&(10<=endAddress))
17 {
18 hgraMaster.pSlave[0].flagPresetCoil|=0x04;
19 }
20 if((startAddress<=11)&&(11<=endAddress))
21 {
22 hgraMaster.pSlave[0].flagPresetCoil|=0x08;
23 }
24 if((startAddress<=12)&&(12<=endAddress))
25 {
26 hgraMaster.pSlave[0].flagPresetCoil|=0x10;
27 }
28 if((startAddress<=13)&&(13<=endAddress))
29 {
30 hgraMaster.pSlave[0].flagPresetCoil|=0x20;
31 }
32 if((startAddress<=14)&&(14<=endAddress))
33 {
34 hgraMaster.pSlave[0].flagPresetCoil|=0x40;
35 }
36 if((startAddress<=15)&&(15<=endAddress))
37 {
38 hgraMaster.pSlave[0].flagPresetCoil|=0x80;
39 }
40 }
41
42 if((16<=startAddress)&&(startAddress<=23)&&(16<=endAddress)&&(endAddress<=23))
43 {
44 ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[1].stationAddress,true);
45 if((startAddress<=16)&&(16<=endAddress))
46 {
47 hgraMaster.pSlave[1].flagPresetCoil|=0x01;
48 }
49 if((startAddress<=17)&&(17<=endAddress))
50 {
51 hgraMaster.pSlave[1].flagPresetCoil|=0x02;
52 }
53 if((startAddress<=18)&&(18<=endAddress))
54 {
55 hgraMaster.pSlave[1].flagPresetCoil|=0x04;
56 }
57 if((startAddress<=19)&&(19<=endAddress))
58 {
59 hgraMaster.pSlave[1].flagPresetCoil|=0x08;
60 }
61 if((startAddress<=20)&&(20<=endAddress))
62 {
63 hgraMaster.pSlave[1].flagPresetCoil|=0x10;
64 }
65 if((startAddress<=21)&&(21<=endAddress))
66 {
67 hgraMaster.pSlave[1].flagPresetCoil|=0x20;
68 }
69 if((startAddress<=22)&&(22<=endAddress))
70 {
71 hgraMaster.pSlave[1].flagPresetCoil|=0x40;
72 }
73 if((startAddress<=23)&&(23<=endAddress))
74 {
75 hgraMaster.pSlave[1].flagPresetCoil|=0x80;
76 }
77 }
78
79 if((24<=startAddress)&&(startAddress<=31)&&(24<=endAddress)&&(endAddress<=31))
80 {
81 ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[0].stationAddress,true);
82 if((startAddress<=24)&&(24<=endAddress))
83 {
84 hgpjMaster.pSlave[0].flagPresetCoil|=0x01;
85 }
86 if((startAddress<=25)&&(25<=endAddress))
87 {
88 hgpjMaster.pSlave[0].flagPresetCoil|=0x02;
89 }
90 if((startAddress<=26)&&(26<=endAddress))
91 {
92 hgpjMaster.pSlave[0].flagPresetCoil|=0x04;
93 }
94 if((startAddress<=27)&&(27<=endAddress))
95 {
96 hgpjMaster.pSlave[0].flagPresetCoil|=0x08;
97 }
98 if((startAddress<=28)&&(28<=endAddress))
99 {
100 hgpjMaster.pSlave[0].flagPresetCoil|=0x10;
101 }
102 if((startAddress<=29)&&(29<=endAddress))
103 {
104 hgpjMaster.pSlave[0].flagPresetCoil|=0x20;
105 }
106 if((startAddress<=30)&&(30<=endAddress))
107 {
108 hgpjMaster.pSlave[0].flagPresetCoil|=0x40;
109 }
110 if((startAddress<=31)&&(31<=endAddress))
111 {
112 hgpjMaster.pSlave[0].flagPresetCoil|=0x80;
113 }
114 }
115
116 if((32<=startAddress)&&(startAddress<=39)&&(32<=endAddress)&&(endAddress<=39))
117 {
118 ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[1].stationAddress,true);
119 if((startAddress<=32)&&(32<=endAddress))
120 {
121 hgpjMaster.pSlave[1].flagPresetCoil|=0x01;
122 }
123 if((startAddress<=33)&&(33<=endAddress))
124 {
125 hgpjMaster.pSlave[1].flagPresetCoil|=0x02;
126 }
127 if((startAddress<=34)&&(34<=endAddress))
128 {
129 hgpjMaster.pSlave[1].flagPresetCoil|=0x04;
130 }
131 if((startAddress<=35)&&(35<=endAddress))
132 {
133 hgpjMaster.pSlave[1].flagPresetCoil|=0x08;
134 }
135 if((startAddress<=36)&&(36<=endAddress))
136 {
137 hgpjMaster.pSlave[1].flagPresetCoil|=0x10;
138 }
139 if((startAddress<=37)&&(37<=endAddress))
140 {
141 hgpjMaster.pSlave[1].flagPresetCoil|=0x20;
142 }
143 if((startAddress<=38)&&(38<=endAddress))
144 {
145 hgpjMaster.pSlave[1].flagPresetCoil|=0x40;
146 }
147 if((startAddress<=39)&&(39<=endAddress))
148 {
149 hgpjMaster.pSlave[1].flagPresetCoil|=0x80;
150 }
151 }
152
153 }
與Modbus RTU一樣也是在請(qǐng)求修改進(jìn)程中置位索要寫的從站的寫請(qǐng)求標(biāo)志位和對(duì)應(yīng)參數(shù)的寫請(qǐng)求標(biāo)志位。然后在主站對(duì)象的進(jìn)程中檢測(cè)標(biāo)志位,根據(jù)標(biāo)志位的狀態(tài)來實(shí)現(xiàn)操作。
告之: 源代碼可上Github下載:https://github.com/foxclever/Modbus
-
MODBUS
+關(guān)注
關(guān)注
28文章
1935瀏覽量
78478 -
ASCII
+關(guān)注
關(guān)注
5文章
172瀏覽量
35520 -
RTU
+關(guān)注
關(guān)注
0文章
427瀏覽量
29078 -
協(xié)議棧
+關(guān)注
關(guān)注
2文章
145瀏覽量
33920
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
使用協(xié)議棧實(shí)現(xiàn)Modbus ASCII從站應(yīng)用

Modbus TCP通信報(bào)文解析

esp32 idf支持modbus ascii協(xié)議的主機(jī)模式嗎?
Modbus TCP轉(zhuǎn)Modbus RTU的實(shí)現(xiàn)
Modbus協(xié)議的理解
什么是ascii碼,什么叫ascii碼
講解一下MODBUS的應(yīng)用,MODBUS系統(tǒng)框架圖

PCS7下ASCII模式Modbus Master的實(shí)現(xiàn)
常見的幾種Modbus網(wǎng)關(guān)介紹
使用協(xié)議棧實(shí)現(xiàn)Modbus ASCII主站應(yīng)用

ModBus RTU、ASCII、TCP,選哪種模式更好?
什么是Modbus ASCII?Modbus是如何工作的?

MODBUS ASCII傳輸模式介紹

評(píng)論