前面我們已經(jīng)分析了Modbus RTU的更新設計和具體實現(xiàn)(如果不清楚可查看前一篇文章)。其實Modbus ASCII與Modbus RTU都是基于串行鏈路實現(xiàn)的,所以有很多的共同點,基于此,這篇文章我們只討論與Modbus RTU所不同的部分。
1 、更新設計
關于原來的協(xié)議棧在Modbus ASCII主站應用時所存在的局限性與Modbus RTU也是一樣的,所以我們不分析它的不足,只討論更新設計。我們將主站及其所訪問的從站定義為通用的對象,而當我們在具體應用中使用時,再將其特例化為特定的主站和從站對象。
首先我們來考慮主站,原則上我們規(guī)劃的每一個主站對象對應我們設備上的一個端口,這里所說端口就是指串口。那么在同一端口下,也就是在一個特定主站下,我們可以定義多個地址不同的從站。而在不同的端口上可以具有地址相同的從站。如下圖所示:
從上圖中我們可以發(fā)現(xiàn),我們的目的就是讓協(xié)議棧支持,多主站和多從站,并且在不同主站下,從站的地址重復不受影響。從上圖看視乎一個主站對象可以同時管理254個從站對象,事實上還要受到帶載能力的影響。
接下來我們還需要考慮從站對象。主站對從站的操作無非兩類:讀從站信息和寫從站信息。對于讀從站信息來說,主站需要發(fā)送請求命令,等待從站返回響應信息,然后主站解析收到的信息并更新對應的參數(shù)值。有兩點需要我們考慮,第一返回的響應消息是沒有對應的寄存器地址的,所以要想在解析的時候定位寄存器就必須知道發(fā)送的命令,為了便于分辨我們將命令存放在從站對象中。第二在解析響應時,如果兩條命令的響應類似是沒法分辨的,所以我們還需要記住上一條命令是什么。也存儲于從站對象中。
而對于寫從站操作,無論寫的要求來自于哪里,對于協(xié)議棧來說肯定是其它的數(shù)據(jù)處理進程發(fā)過來的,所接到要求后我們需要記錄是哪一個主站管理的哪一個從站的哪些參數(shù)。對于主站我們不需要分辨,因為每個主站都是獨立的處理進程,但是對于從站和參數(shù)我們就需要分辨。每一個主站可以帶的站地址為0到255,但0和255已有定義,所以實際是1到254個。所以我們使用一個256位的變量,每位對應站號來標志其是否有需要寫的請求。記錄于主站,具體如下:
事實上,我們不可能會用到256個標志位,因為Modbus ASCII本身就是為簡單應用而設定的。我們使用256個標志位,主要是考慮到站地址的取值范圍,方便軟件操作而定的。還有每個從站的寫參數(shù)請求標志,我們將其存儲于各個從站對象,因為不同的從站可能有很大區(qū)別,存儲于各個從站更加靈活方便。
2 、編碼實現(xiàn)
我們已經(jīng)設計了我們的更新,接下來我們就根據(jù)這一設計來實現(xiàn)它。我們主要從以下幾個方面來操作:第一,實現(xiàn)主站對象類型和從站對象類型;第二,主站對象的實例化及從站對象的實例化;第三,讀從站的主站操作過程;第四,寫從站的主站操作過程。接下來我們將一一描述之。
2.1 、定義對象類型
與在Modbus RTU一樣,在Modbus ASCII協(xié)議棧的封裝中,我們也需要定義主站對象和從站對象,自然也免不了要定義這兩種類型。
首先我們來定義本地主站的類型,其成員包括:一個uint32_t的寫從站標志數(shù)組;從站數(shù)量字段;從站順序字段;本主站過管理的從站列表;4個數(shù)據(jù)更新函數(shù)指針。具體定義如下:
1 /* 定義本地ASCII主站對象類型 */
2 typedef struct LocalASCIIMasterType{
3 uint32_t flagWriteSlave[8]; //寫一個站控制標志位,最多256個站,與站地址對應。
4 uint16_t slaveNumber; //從站列表中從站的數(shù)量
5 uint16_t readOrder; //當前從站在從站列表中的位置
6 ASCIIAccessedSlaveType *pSlave; //從站列表
7 UpdateCoilStatusType pUpdateCoilStatus; //更新線圈量函數(shù)
8 UpdateInputStatusType pUpdateInputStatus; //更新輸入狀態(tài)量函數(shù)
9 UpdateHoldingRegisterType pUpdateHoldingRegister; //更新保持寄存器量函數(shù)
10 UpdateInputResgisterType pUpdateInputResgister; //更新輸入寄存器量函數(shù)
11 }ASCIILocalMasterType;
關于主站對象類型,在前面的更新設計中已經(jīng)講的很清楚了,只有兩個字段需要說明一下。第一,從站列表是用來記錄本主站所管理的從站對象。第二,readOrder字段表示為當前訪問從站在列表中的位置,而slaveNumber是從站對象的數(shù)量,即列表的長度。具體如下圖所示:
還需要定義從站對象,此從站對象只是便于主站而用于表示真是的從站。主站的從站列表中就是此對象。具體結構如下:
1 /* 定義被訪問ASCII從站對象類型 */
2 typedef struct AccessedASCIISlaveType{
3 uint8_t stationAddress; //站地址
4 uint8_t cmdOrder; //當前命令在命令列表中的位置
5 uint16_t commandNumber; //命令列表中命令的總數(shù)
6 uint8_t (*pReadCommand)[17]; //讀命令列表
7 uint8_t *pLastCommand; //上一次發(fā)送的命令
8 uint32_t flagPresetCoil; //預置線圈控制標志位
9 uint32_t flagPresetReg; //預置寄存器控制標志位
10 }ASCIIAccessedSlaveType;
關于從站對象有三個字段需要說明一下。首先我們來看一看“讀命令列表(uint8_t (*pReadCommand)[17])”字段,與Modbus RTU不同,它是17個字節(jié),這是由Modbus ASCII消息格式?jīng)Q定的。如下:
還有就是flagPresetCoil和flagPresetReg字段。這兩個字段用來表示對線圈和保持寄存器的寫請求。
2.2 、實例化對象
我們定義了主站即從站對象類型,我們在使用時就需要實例化這些對象。一般來說一個硬件端口我們將其實例化為一個主站對象。
ASCIILocalMasterType hgraMaster;
/ 初始化ASCII主站對象 /
InitializeASCIIMasterObject(&hgraMaster,2,hgraSlave,NULL,NULL,NULL,NULL);
而一個主站對象會管理1到254個從站對象,所以從站對象我們可以將多個從站對象實例組成數(shù)組,并將其賦予主站管理。
ASCIIAccessedSlaveType hgraSlave[]={{1,0,2,slave1ReadCommand,NULL,0x00,0x00},{2,0,2,slave2ReadCommand,NULL,0x00,0x00}};
所以,根據(jù)主站和從站實例化的條件,我們需要先實例化從站對象才能完整實例化主站對象。在主站的初始化中,我們這里將4的數(shù)據(jù)處理函數(shù)指針初始化為NULL,有一個默認的處理函數(shù)會復制給它,該函數(shù)是上一版本的延續(xù),在簡單應用時簡化操作。從站的上一個發(fā)送的命令指針也被賦值為NULL,因為初始時還沒有命令發(fā)送。
2.3 、讀從站操作
讀從站操作原理上與以前的版本是一樣的。按照一定的順序給從站發(fā)送命令再對收到的消息進行解析。我們對主站及其所管理的從站進行了定義,將發(fā)送命令保存于從站對象,將從站列表保存于主站對象,所以我們需要對解析函數(shù)進行修改。
1 /*解析收到的服務器相應信息*/
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 /*校驗接收到的數(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消息。檢查沒問題則調用協(xié)議站解析。而最后調用的數(shù)據(jù)處理函數(shù)則是我們需要在具體應用中編寫。在前面主站初始化時,回調函數(shù)我們初始化為NULL,實際在協(xié)議占中有弱化的函數(shù)定義,需要針對具體的寄存器和變量地址實現(xiàn)操作。特別要說明的是,解析Modbus ASCII消息時,在去除開始字符和結束字符后,需要將ASCII碼轉化為二進制數(shù)才能完成解析。
2.4 、寫從站操作
寫從站操作則是在其它進程請求后,我們標識需要寫的對象再統(tǒng)一處理。對具體哪個從站的寫標識存于主站實例。而該從站的哪些變量需要寫則記錄在從站實例中。
所以在進程檢測到需要寫一個從站時則置位對應的位,即改變flagWriteSlave中的對應位。而需要寫該站的哪些變量則標記flagPresetCoil和flagPresetReg的對應位。修改這些標識都在其它請求更改的進程中實現(xiàn),而具體的寫操作則在本主站進程中,檢測到標志位的變化統(tǒng)一執(zhí)行。
這部分不修改協(xié)議棧的代碼,因為各站及各變量都至于具體對象相關聯(lián),所以在具體的應用中修改。
3 、回歸驗證
考慮到Modbus ASCII和Modbus RTU的相似性,我們設計同樣的的網(wǎng)絡結構。但考慮到Modbus ASCII一般用于小數(shù)據(jù)量通訊,所以我們設計相對簡單的從站。所以我們設計的網(wǎng)絡為:協(xié)議棧建立2個主機,每個主機管理2個從站,每個從站有8個線圈及2個保持寄存器。具體結構如圖:
從上圖我們知道,該Modbus網(wǎng)關需要實現(xiàn)一個Modbus從站用于和上位的通訊;需要實現(xiàn)兩個Modbus主站用于和下位的通訊。
在這個實驗中,讀操作沒有什么需要說的,只需要發(fā)送命令解析返回消息即可。所以我們中點描述一下為了方便操作,在需要寫的連續(xù)段,我們只要找到第一個請求寫的位置后,就將后續(xù)連續(xù)可寫數(shù)據(jù)一次性寫入。修改寫標志位的代碼如下:
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一樣也是在請求修改進程中置位索要寫的從站的寫請求標志位和對應參數(shù)的寫請求標志位。然后在主站對象的進程中檢測標志位,根據(jù)標志位的狀態(tài)來實現(xiàn)操作。
告之: 源代碼可上Github下載:https://github.com/foxclever/Modbus
-
MODBUS
+關注
關注
28文章
1805瀏覽量
76999 -
ASCII
+關注
關注
5文章
172瀏覽量
35103 -
RTU
+關注
關注
0文章
413瀏覽量
28680 -
協(xié)議棧
+關注
關注
2文章
141瀏覽量
33632
發(fā)布評論請先 登錄
相關推薦
評論