前面咱们已经详细讲解过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
欢迎关注: