前面咱們已經詳細講解過Modbus協議棧的開發過程,而且利用協議棧封裝了Modbus RTU主站和從站,Modbus TCP服務器與客戶端,Modbus ASCII主站與從站應用。但在使用過程當中,咱們發現一些使用不便和受限的地方,因此咱們就想要更新一下協議棧,主要是應用站的封裝。git
1、存在的侷限性github
在原有的協議棧中,咱們所封裝的Modbus RTU主站是一個特定的主站,即它只是一個主站。在一般的應用中不會有什麼問題,但在有些應用場合就會顯現出它的侷限性。數組
首先,做爲一個特定的主站,帶多個從站時,寫從站的處理變的很是複雜,須要分辨不一樣的從站,不一樣的變量。當有多個端口時,還須要分辨不一樣的端口。服務器
其次,做爲一個特定的主站,帶多個從站時,讀從站的處理,即便是不一樣的端口也不能分辨相同站地址的從站。由於同一主站的解析函數是同一個,因此即便在不一樣端口也很難分辨,除非在解析前傳遞端口信息,這實際上是將多餘的信息傳遞到協議棧。這樣作不但程序不夠明晰也缺少通常性。函數
最後,將全部的Modbus從站通信都做爲惟一的一個特定從站來處理,使得各部分混雜在一塊兒,程序結構很不清晰,對象也不明確。ui
2、更新設計spa
考慮到前述的侷限性,咱們將主站及其帶訪問的從站定義爲通用的對象,而當咱們在具體應用中使用時,再將其特例化爲特定的主站和從站對象。設計
首先咱們來考慮主站,原則上咱們規劃的每個主站對象對應咱們設備上的一個端口,那麼在同一端口下,也就是在一個特定主站下,咱們能夠定義多個地址不一樣的從站,但不一樣端口下不受影響。以下圖所示:指針
從上圖中咱們能夠發現,咱們的目的就是讓協議棧支持,多主站和多從站,而且在不一樣主站下,從站的地址重複不受影響。接下來咱們還須要考慮從站對象。主站對從站的操做無非兩類:讀從站信息和寫從站信息。code
對於讀從站信息來講,主站須要發送請求命令,等待從站返回響應信息,而後主站解析收到的信息並更新對應的參數值。有兩點須要咱們考慮,第一返回的響應消息是沒有對應的寄存器地址的,因此要想在解析的時候定位寄存器就必須知道發送的命令,爲了便於分辨咱們將命令存放在從站對象中。第二在解析響應時,若是兩條命令的響應相似是無法分辨的,因此咱們還須要記住上一條命令是什麼。也存儲於從站對象中。
而對於寫從站操做,不管寫的要求來自於哪裏,對於協議棧來講確定是其它的數據處理進程發過來的,所接到要求後咱們須要記錄是哪個主站管理的哪個從站的哪些參數。對於主站咱們不須要分辨,由於每一個主站都是獨立的處理進程,可是對於從站和參數咱們就須要分辨。每個主站能夠帶的站地址爲0到255,但0和255已有定義,因此實際是1到254個。因此咱們使用一個256位的變量,每位對應站號來標誌其是否有須要寫的請求。記錄於主站,具體以下:
而每一個從站的寫參數請求標誌則存儲於各個從站對象,由於不一樣的從站可能有很大區別,存儲於各個從站更加靈活。
3、如何實現
咱們已經設計了咱們的更新,但具體如何實現它呢?咱們主要從如下幾個方面來實現它。第一,實現主站對象類型和從站對象類型。第二,主站對象的實例化及從站對象的實例化。第三,讀從站的主站操做過程。第四,寫從站的主站操做過程。接下來咱們將一一描述之。
3.1、定義對象類型
在Modbus RTU協議棧的封裝中,咱們須要定義主站對象和從站對象,天然也須要定義這兩種類型。至於其功能前述已經描述過。
首先咱們來定義本地主站的類型,其成員包括:一個uint32_t的寫從站標誌數組;從站數量字段;從站順序字段;本主站所管理的從站列表;4個數據更新函數指針。具體定義以下:
1 /* 定義本地RTU主站對象類型 */ 2 typedef struct LocalRTUMasterType{ 3 uint32_t flagWriteSlave[8]; //寫一個站控制標誌位,最多256個站,與站地址對應。 4 uint16_t slaveNumber; //從站列表中從站的數量 5 uint16_t readOrder; //當前從站在從站列表中的位置 6 RTUAccessedSlaveType *pSlave; //從站列表 7 UpdateCoilStatusType pUpdateCoilStatus; //更新線圈量函數 8 UpdateInputStatusType pUpdateInputStatus; //更新輸入狀態量函數 9 UpdateHoldingRegisterType pUpdateHoldingRegister; //更新保持寄存器量函數 10 UpdateInputResgisterType pUpdateInputResgister; //更新輸入寄存器量函數 11 }RTULocalMasterType;
關於主站對象類型,在前面的更新設計中已經講的很清楚了,只有兩個須要說明一下。第一,從站列表是用來記錄本主站所管理的從站對象。第二,readOrder字段表示爲當前訪問從站在列表中的位置,而slaveNumber是從站對象的數量,即列表的長度。具體以下圖所示:
還須要定義從站對象,此從站對象只是便於主站而用於表示真實的從站。主站的從站列表中就是此對象。具體結構以下:
1 /* 定義被訪問RTU從站對象類型 */ 2 typedef struct AccessedRTUSlaveType{ 3 uint8_t stationAddress; //站地址 4 uint8_t cmdOrder; //當前命令在命令列表中的位置 5 uint16_t commandNumber; //命令列表中命令的總數 6 uint8_t (*pReadCommand)[8]; //讀命令列表 7 uint8_t *pLastCommand; //上一次發送的命令 8 uint32_t flagPresetCoil; //預置線圈控制標誌位 9 uint32_t flagPresetReg; //預置寄存器控制標誌位 10 }RTUAccessedSlaveType;
關於從站對象有兩個字段須要說一下,就是flagPresetCoil和flagPresetReg字段。這兩個字段用來表示對線圈和保持寄存器的寫請求。
3.2、實例化對象
咱們定義了主站即從站對象類型,咱們在使用時就須要實例化這些對象。通常來講一個硬件端口咱們將其實例化爲一個主站對象。
RTULocalMasterType hgraMaster;
/*初始化RTU主站對象*/
InitializeRTUMasterObject(&hgraMaster,2,hgraSlave,NULL,NULL,NULL,NULL);
而一個主站對象會管理1到254個從站對象,因此咱們能夠將多個從站對象實例組成數組,並將其賦予主站管理。
RTUAccessedSlaveType hgraSlave[]={{1,0,2,slave1ReadCommand,NULL,0x00,0x00},{2,0,2,slave2ReadCommand,NULL,0x00,0x00}};
因此,根據主站和從站實例化的條件,咱們須要先實例化從站對象才能完整實例化主站對象。在主站的初始化中,咱們這裏將4的數據處理函數指針初始化爲NULL,有一個默認的處理函數會複製給它,該函數是上一版本的延續,在簡單應用時簡化操做。從站的上一個發送的命令指針也被賦值爲NULL,由於初始時尚未命令發送。
3.3、讀從站操做
讀從站操做原理上與之前的版本是同樣的。按照必定的順序給從站發送命令再對收到的消息進行解析。咱們對主站及其所管理的從站進行了定義,將發送命令保存於從站對象,將從站列表保存於主站對象,因此咱們須要對解析函數進行修改。
1 /*解析收到的服務器相應信息*/ 2 /*uint8_t *recievedMessage,接收到的消息列表*/ 3 /*uint8_t *command,發送的讀操做命令,若爲NULL則在命令列表中查找*/ 4 void ParsingSlaveRespondMessage(RTULocalMasterType *master,uint8_t *recievedMessage,uint8_t *command) 5 { 6 int i=0; 7 int j=0; 8 uint16_t startAddress; 9 uint16_t quantity; 10 uint8_t *cmd=NULL; 11 12 /*若是不是讀操做的反回信息不須要處理*/ 13 if(recievedMessage[1]>0x04) 14 { 15 return; 16 } 17 18 /*判斷功能碼是否有誤*/ 19 FunctionCode fuctionCode=(FunctionCode)recievedMessage[1]; 20 if (CheckFunctionCode(fuctionCode) != MB_OK) 21 { 22 return; 23 } 24 25 /*校驗接收到的信息是否有錯*/ 26 uint16_t byteCount=recievedMessage[2]; 27 bool chechMessageNoError=CheckRTUMessageIntegrity(recievedMessage,byteCount+5); 28 if(!chechMessageNoError) 29 { 30 return; 31 } 32 33 if((command==NULL)||(!CheckMessageAgreeWithCommand(recievedMessage,command))) 34 { 35 while(i<master->slaveNumber) 36 { 37 if(master->pSlave[i].stationAddress==recievedMessage[0]) 38 { 39 break; 40 } 41 i++; 42 } 43 44 if(i>=master->slaveNumber) 45 { 46 return; 47 } 48 49 if((master->pSlave[i].pLastCommand==NULL)||(!CheckMessageAgreeWithCommand(recievedMessage,master->pSlave[i].pLastCommand))) 50 { 51 j=FindCommandForRecievedMessage(recievedMessage,master->pSlave[i].pReadCommand,master->pSlave[i].commandNumber); 52 53 if(j<0) 54 { 55 return; 56 } 57 58 cmd=master->pSlave[i].pReadCommand[j]; 59 } 60 else 61 { 62 cmd=master->pSlave[i].pLastCommand; 63 } 64 } 65 else 66 { 67 cmd=command; 68 } 69 70 startAddress=(uint16_t)cmd[2]; 71 startAddress=(startAddress<<8)+(uint16_t)cmd[3]; 72 quantity=(uint16_t)cmd[4]; 73 quantity=(quantity<<8)+(uint16_t)cmd[5]; 74 75 if((fuctionCode>=ReadCoilStatus)&&(fuctionCode<=ReadInputRegister)) 76 { 77 HandleSlaveRespond[fuctionCode-1](master,recievedMessage,startAddress,quantity); 78 } 79 }
解析函數的主要部分是在檢查接收到的消息是不是合法的Modbus RTU消息。檢查沒問題則調用協議站解析。而最後調用的數據處理函數則是咱們須要在具體應用中編寫。在前面主站初始化時,回調函數咱們初始化爲NULL,實際在協議棧中有弱化的函數定義,須要針對具體的寄存器和變量地址實現操做。
3.4、寫從站操做
寫從站操做則是在其它進程請求後,咱們標識須要寫的對象再統一處理。對具體哪一個從站的寫標識存於主站實例。而該從站的哪些變量須要寫則記錄在從站實例中。
因此在進程檢測到須要寫一個從站時則置位對應的位,即改變flagWriteSlave中的對應位。而須要寫該站的哪些變量則標記flagPresetCoil和flagPresetReg的對應位。修改這些標識都在其它請求更改的進程中實現,而具體的寫操做則在本主站進程中,檢測到標誌位的變化統一執行。
這部分不修改協議棧的代碼,由於各站及各變量都至於具體對象相關聯,因此在具體的應用中修改。
4、迴歸驗證
爲了驗證咱們前面的更新設計是符合要求的,咱們設計一個難度較高的實驗系統。這一實驗系統包括Modbus網關,上位Modbus主站以及下位的Modbus從站。咱們所要實現的是Modbus網關部分,其具體結構圖設計以下:
從上圖咱們知道,該Modbus網關須要實現一個Modbus從站用於和上位的通信;須要實現兩個Modbus主站用於和下位的通信。
在這個實驗中,讀操做沒有什麼須要說的,只須要發送命令,解析返回消息便可。因此咱們重點描述一下寫操做,爲了方便操做,在須要寫的連續段,咱們只要找到第一個請求寫的位置後,就將後續連續可寫數據一次性寫入。修改寫標誌位的代碼以下:
1 /* 寫從站寄存器控制 */ 2 static void WriteSlaveRegisterControll(uint16_t startAddress,uint16_t endAddress) 3 { 4 if((12<=startAddress)&&(startAddress<=71)&&(12<=endAddress)&&(endAddress<=71)) 5 { 6 ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[0].stationAddress,true); 7 8 if((startAddress<=12)&&(13<=endAddress)) 9 { 10 hgraMaster.pSlave[0].flagPresetReg|=0x01; 11 } 12 if((startAddress<=14)&&(15<=endAddress)) 13 { 14 hgraMaster.pSlave[0].flagPresetReg|=0x02; 15 } 16 if((startAddress<=16)&&(17<=endAddress)) 17 { 18 hgraMaster.pSlave[0].flagPresetReg|=0x04; 19 } 20 if((startAddress<=18)&&(19<=endAddress)) 21 { 22 hgraMaster.pSlave[0].flagPresetReg|=0x08; 23 } 24 if((startAddress<=20)&&(21<=endAddress)) 25 { 26 hgraMaster.pSlave[0].flagPresetReg|=0x10; 27 } 28 if((startAddress<=22)&&(23<=endAddress)) 29 { 30 hgraMaster.pSlave[0].flagPresetReg|=0x20; 31 } 32 if((startAddress<=24)&&(25<=endAddress)) 33 { 34 hgraMaster.pSlave[0].flagPresetReg|=0x40; 35 } 36 if((startAddress<=26)&&(27<=endAddress)) 37 { 38 hgraMaster.pSlave[0].flagPresetReg|=0x80; 39 } 40 41 if((startAddress<=32)&&(32<=endAddress)) 42 { 43 hgraMaster.pSlave[0].flagPresetReg|=0x100; 44 } 45 if((startAddress<=33)&&(33<=endAddress)) 46 { 47 hgraMaster.pSlave[0].flagPresetReg|=0x200; 48 } 49 if((startAddress<=34)&&(34<=endAddress)) 50 { 51 hgraMaster.pSlave[0].flagPresetReg|=0x400; 52 } 53 if((startAddress<=35)&&(35<=endAddress)) 54 { 55 hgraMaster.pSlave[0].flagPresetReg|=0x800; 56 } 57 if((startAddress<=36)&&(36<=endAddress)) 58 { 59 hgraMaster.pSlave[0].flagPresetReg|=0x1000; 60 } 61 if((startAddress<=37)&&(37<=endAddress)) 62 { 63 hgraMaster.pSlave[0].flagPresetReg|=0x2000; 64 } 65 if((startAddress<=38)&&(38<=endAddress)) 66 { 67 hgraMaster.pSlave[0].flagPresetReg|=0x4000; 68 } 69 if((startAddress<=39)&&(39<=endAddress)) 70 { 71 hgraMaster.pSlave[0].flagPresetReg|=0x8000; 72 } 73 if((startAddress<=40)&&(40<=endAddress)) 74 { 75 hgraMaster.pSlave[0].flagPresetReg|=0x10000; 76 } 77 if((startAddress<=41)&&(41<=endAddress)) 78 { 79 hgraMaster.pSlave[0].flagPresetReg|=0x20000; 80 } 81 if((startAddress<=42)&&(42<=endAddress)) 82 { 83 hgraMaster.pSlave[0].flagPresetReg|=0x40000; 84 } 85 if((startAddress<=43)&&(43<=endAddress)) 86 { 87 hgraMaster.pSlave[0].flagPresetReg|=0x80000; 88 } 89 if((startAddress<=44)&&(44<=endAddress)) 90 { 91 hgraMaster.pSlave[0].flagPresetReg|=0x100000; 92 } 93 if((startAddress<=45)&&(45<=endAddress)) 94 { 95 hgraMaster.pSlave[0].flagPresetReg|=0x200000; 96 } 97 if((startAddress<=46)&&(46<=endAddress)) 98 { 99 hgraMaster.pSlave[0].flagPresetReg|=0x400000; 100 } 101 if((startAddress<=47)&&(47<=endAddress)) 102 { 103 hgraMaster.pSlave[0].flagPresetReg|=0x800000; 104 } 105 106 if((startAddress<=52)&&(55<=endAddress)) 107 { 108 hgraMaster.pSlave[0].flagPresetReg|=0x1000000; 109 } 110 if((startAddress<=56)&&(59<=endAddress)) 111 { 112 hgraMaster.pSlave[0].flagPresetReg|=0x2000000; 113 } 114 if((startAddress<=60)&&(63<=endAddress)) 115 { 116 hgraMaster.pSlave[0].flagPresetReg|=0x4000000; 117 } 118 if((startAddress<=64)&&(67<=endAddress)) 119 { 120 hgraMaster.pSlave[0].flagPresetReg|=0x8000000; 121 } 122 } 123 124 if((72<=startAddress)&&(startAddress<=131)&&(72<=endAddress)&&(endAddress<=131)) 125 { 126 ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[1].stationAddress,true); 127 128 if((startAddress<=72)&&(73<=endAddress)) 129 { 130 hgraMaster.pSlave[1].flagPresetReg|=0x01; 131 } 132 if((startAddress<=74)&&(75<=endAddress)) 133 { 134 hgraMaster.pSlave[1].flagPresetReg|=0x02; 135 } 136 if((startAddress<=76)&&(77<=endAddress)) 137 { 138 hgraMaster.pSlave[1].flagPresetReg|=0x04; 139 } 140 if((startAddress<=78)&&(79<=endAddress)) 141 { 142 hgraMaster.pSlave[1].flagPresetReg|=0x08; 143 } 144 if((startAddress<=80)&&(81<=endAddress)) 145 { 146 hgraMaster.pSlave[1].flagPresetReg|=0x10; 147 } 148 if((startAddress<=82)&&(83<=endAddress)) 149 { 150 hgraMaster.pSlave[1].flagPresetReg|=0x20; 151 } 152 if((startAddress<=84)&&(85<=endAddress)) 153 { 154 hgraMaster.pSlave[1].flagPresetReg|=0x40; 155 } 156 if((startAddress<=86)&&(87<=endAddress)) 157 { 158 hgraMaster.pSlave[1].flagPresetReg|=0x80; 159 } 160 161 if((startAddress<=92)&&(92<=endAddress)) 162 { 163 hgraMaster.pSlave[1].flagPresetReg|=0x100; 164 } 165 if((startAddress<=93)&&(93<=endAddress)) 166 { 167 hgraMaster.pSlave[1].flagPresetReg|=0x200; 168 } 169 if((startAddress<=94)&&(94<=endAddress)) 170 { 171 hgraMaster.pSlave[1].flagPresetReg|=0x400; 172 } 173 if((startAddress<=95)&&(95<=endAddress)) 174 { 175 hgraMaster.pSlave[1].flagPresetReg|=0x800; 176 } 177 if((startAddress<=96)&&(96<=endAddress)) 178 { 179 hgraMaster.pSlave[1].flagPresetReg|=0x1000; 180 } 181 if((startAddress<=97)&&(97<=endAddress)) 182 { 183 hgraMaster.pSlave[1].flagPresetReg|=0x2000; 184 } 185 if((startAddress<=98)&&(98<=endAddress)) 186 { 187 hgraMaster.pSlave[1].flagPresetReg|=0x4000; 188 } 189 if((startAddress<=99)&&(99<=endAddress)) 190 { 191 hgraMaster.pSlave[1].flagPresetReg|=0x8000; 192 } 193 if((startAddress<=100)&&(100<=endAddress)) 194 { 195 hgraMaster.pSlave[1].flagPresetReg|=0x10000; 196 } 197 if((startAddress<=101)&&(101<=endAddress)) 198 { 199 hgraMaster.pSlave[1].flagPresetReg|=0x20000; 200 } 201 if((startAddress<=102)&&(102<=endAddress)) 202 { 203 hgraMaster.pSlave[1].flagPresetReg|=0x40000; 204 } 205 if((startAddress<=103)&&(103<=endAddress)) 206 { 207 hgraMaster.pSlave[1].flagPresetReg|=0x80000; 208 } 209 if((startAddress<=104)&&(104<=endAddress)) 210 { 211 hgraMaster.pSlave[1].flagPresetReg|=0x100000; 212 } 213 if((startAddress<=105)&&(105<=endAddress)) 214 { 215 hgraMaster.pSlave[1].flagPresetReg|=0x200000; 216 } 217 if((startAddress<=106)&&(106<=endAddress)) 218 { 219 hgraMaster.pSlave[1].flagPresetReg|=0x400000; 220 } 221 if((startAddress<=107)&&(107<=endAddress)) 222 { 223 hgraMaster.pSlave[1].flagPresetReg|=0x800000; 224 } 225 226 if((startAddress<=112)&&(115<=endAddress)) 227 { 228 hgraMaster.pSlave[1].flagPresetReg|=0x1000000; 229 } 230 if((startAddress<=116)&&(119<=endAddress)) 231 { 232 hgraMaster.pSlave[1].flagPresetReg|=0x2000000; 233 } 234 if((startAddress<=120)&&(123<=endAddress)) 235 { 236 hgraMaster.pSlave[1].flagPresetReg|=0x4000000; 237 } 238 if((startAddress<=124)&&(127<=endAddress)) 239 { 240 hgraMaster.pSlave[1].flagPresetReg|=0x8000000; 241 } 242 } 243 244 if((132<=startAddress)&&(startAddress<=191)&&(131<=endAddress)&&(endAddress<=191)) 245 { 246 ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[0].stationAddress,true); 247 } 248 249 if((192<=startAddress)&&(startAddress<=251)&&(192<=endAddress)&&(endAddress<=251)) 250 { 251 ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[1].stationAddress,true); 252 } 253 }
而後在主站對象的進程中檢測標誌位,根據標誌位的狀態來實現操做,具體的操做代碼很簡單,且不具廣泛性,在此不貼出。
5、幾點注意
雖然咱們對主站對象和從站對象進行了封裝,但咱們在使用時人須要注意一些問題。
(1)、4個回調函數的定義,這4個回調函數用於處理從粘返回的信息,對應Modbus定義的四種數據,須要根據主站對象管理的從站狀況來實現。
(2)、對於寫操做標識符,通常都是在請求進程置位,在主站對象所在的進程檢測並操做,而後復位。
告之:源代碼可上Github下載:https://github.com/foxclever/Modbus
歡迎關注: