0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Modbus TCP的设计与实现

CHANBAEK 来源:木南创智 作者:尹家军 2022-12-13 15:33 次阅读

对于Modbus TCP来说与Modbus RTU和Modbus ASCII有比较大的区别,因为它是运行于以太网链路之上,是运行于TCP/IP协议之上的一种应用层协议。在协议栈的前两个版本中,Modbus TCP作为客户端时也存在一些局限性。我们将对这些不足作一定更新。

1 、存在的不足

在原有的协议栈中,我们所封装的Modbus TCP客户端一个特定的客户端,即它只是一个客户端实例。在通常的应用中不会有什么问题,但在有些应用场合就会显现出它的局限性。

首先,作为一个特定的客户端,若是连接多个服务器目标时,修改服务器参数值的处理变的非常复杂,需要分辨是不同的服务器,不同的变量。当需要从不同的网段操作数据时,我们甚至需要标记不同的网段。

其次,作为一个特定的客户端,如果我们操作的服务器参数相似时,哪怕来自于不同的网段,我们也需要仔细分辨或者传递额外的参数。因为同一客户端的解析函数是同一个。

最后,将多个Modbus TCP服务器通讯都作为唯一的一个特定的服务器来处理,使得各部分混杂在一起,程序结构很不清晰,对象也不明确。

2 、更新设计

考虑到前述的局限性,我们将Modbus TCP客户端及其所访问的Modbus TCP服务器定义为通用的对象,而当我们在具体应用中使用时,再将其特例化为特定的客户端和服务器对象。

首先我们来考虑客户端,原则上我们规划的每一个客户端对象管理我们设备上的一个IP网段的设备。那么在一个特定客户端下,我们可以定义多达253个不同的服务器。如下图所示:

从上图中我们可以发现,我们的目的就是让协议栈支持,多客户端和多服务器,并且在不同客户端下可以访问同网段的多个服务器。接下来我们还需要考虑服务器对象。客户端对服务器的操作无非两类:读服务器信息和写服务器信息。

对于读服务器信息来说,客户端需要发送请求命令,等待服务器返回响应信息,然后客户端解析收到的信息并更新对应的参数值。因为返回的响应消息是没有对应的寄存器地址的,所以要想在解析的时候定位寄存器就必须知道发送的命令,为了便于分辨我们将命令存放在服务器对象中。

而对于写服务器操作,无论写的要求来自于哪里,对于协议栈来说肯定是其它的数据处理进程发过来的,所接到要求后我们需要记录是哪一个客户端管理的哪一个服务器的哪些参数。对于客户端我们不需要分辨,因为每个客户端都是独立的处理进程,但是对于服务器和参数我们就需要分辨。每一个客户端所管理的IP地址的最后一段为0到255,所以我们可以依据来分辨服务器端。而在每一个服务器节点中增加状态标志,用以记录请求状态,而所有服务器端组成链表。

3 、编码实现

我们已经设计了我们的更新,接下来我们就根据这一设计来实现它。我们主要从以下几个方面来操作:第一,实现客户端对象类型和服务器对象类型;第二,客户端对象的实例化及服务器对象的实例化;第三,读服务器参数的客户端操作过程;第四,写服务器参的数客户端操作过程。接下来我们将一一描述之。

3.1 、定义对象类型

与在Modbus RTU和Modbus ASCII一样,在Modbus TCP协议栈的封装中,我们也需要定义客户端对象和服务器对象,自然也免不了要定义这两种类型。

首先我们来定义本地客户端的类型,其成员包括:一个uint32_t的写服务器标志数组;服务器数量字段;服务器顺序字段;本客户端所管理的服务器列表;4个数据更新函数指针。具体定义如下:

1 /* 定义本地TCP客户端对象类型 */
 2 typedef struct LocalTCPClientType{
 3   uint32_t transaction;                                 //事务标识符
 4   uint16_t cmdNumber;                                  //读服务器命令的数量
 5   uint16_t cmdOrder;                                   //当前从站在从站列表中的位置
 6   uint8_t (*pReadCommand)[12];                         //读命令列表
 7   ServerListHeadNode ServerHeadNode;                    //Server对象链表的头节点
 8   UpdateCoilStatusType pUpdateCoilStatus;               //更新线圈量函数
 9   UpdateInputStatusType pUpdateInputStatus;             //更新输入状态量函数
10   UpdateHoldingRegisterType pUpdateHoldingRegister;     //更新保持寄存器量函数
11   UpdateInputResgisterType pUpdateInputResgister;       //更新输入寄存器量函数
12 }TCPLocalClientType;

关于客户端对象类型,在前面的更新设计中已经讲的很清楚了,只有Server对象链表的头节点字段需要说明一下。该字段包括两个类容:第一,服务器链表的头节点指针,用来记录服务器对象列表。第二,记录链表的长度,即服务器节点的数量。具体如下图所示:

还需要定义服务器对象,此服务器对象只是便于客户端而用于表示真是的服务器。客户端的服务器列表中就是此对象。具体结构如下:

1 /* 定义被访问TCP服务器对象类型 */
 2 typedef struct AccessedTCPServerType{
 3   union {
 4     uint32_t ipNumber;
 5     uint8_t ipSegment[4];
 6   }ipAddress;                                           //服务器的IP地址
 7   uint32_t flagPresetServer;                            //写服务器请求标志
 8   WritedCoilListHeadNode pWritedCoilHeadNode;          //可写的线圈量列表
 9   WritedRegisterListHeadNode pWritedRegisterHeadNode;  //可写的保持寄存器列表
10   struct AccessedTCPServerType *pNextNode;              //下一个TCP服务器节点
11 }TCPAccessedServerType;

关于服务器对象有三个字段需要说明一下。首先我们来看一看“读命令列表(uint8_t (*pReadCommand)[12])”字段,它是12个字节,这是由Modbus TCP消息格式决定的。如下:

我们看到协议标识符为0,是因为0就表示Modbus TCP。还有可写的线圈量列表头节点和可写的保持寄存器列表头节点。这两个字段用来表示对线圈和保持寄存器的列表即数量。

3.2 、实例化对象

我们定义了客户端即服务器对象类型,我们在使用时就需要实例化这些对象。一般来说一个IP网段我们将其实例化为一个客户端对象。

TCPLocalClientType hgraClient;

/ 初始化TCP客户端对象 /

InitializeTCPClientObject(&hgraClient,2,hgraServer,NULL,NULL,NULL,NULL);

而一个客户端对象会管理1到253个服务器对象,所以我们可以将多个服务器对象实例组成数组,并将其赋予客户端管理。

TCPAccessedServerType hgraServer[]={{{192,168,0,1},0x00,0x00},{{192,168,1,1},0x00,0x00}};

所以,根据客户端和服务器实例化的条件,我们需要先实例化服务器对象才能完整实例化客户端对象。在客户端的初始化中,我们这里将4的数据处理函数指针初始化为NULL,有一个默认的处理函数会复制给它,该函数是上一版本的延续,在简单应用时简化操作。服务器的上一个发送的命令指针也被赋值为NULL,因为初始时还没有命令发送。

3.3 、读服务器操作

读服务器操作原理上与以前的版本是一样的。按照一定的顺序给服务器发送命令再对收到的消息进行解析。我们对客户端及其所管理的服务器进行了定义,将发送命令保存于服务器对象,将服务器列表保存于客户端对象,所以我们需要对解析函数进行修改。

1 /*解析收到的服务器相应信息*/
 2 void ParsingServerRespondMessage(TCPLocalClientType *client,uint8_t *recievedMessage)
 3 {
 4   /*判断接收到的信息是否有相应的命令*/
 5   int cmdIndex=FindCommandForRecievedMessage(client,recievedMessage);
 6  
 7   if((cmdIndex<0))      //没有对应的请求命令,事务号不相符
 8   {
 9     return;
10   }
11  
12   if((recievedMessage[2]!=0x00)||(recievedMessage[3]!=0x00)) //不是Modbus TCP协议
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)        //数据长度不一致
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)       //请求的数据长度与返回的数据长度不一致
42   {
43     return;
44   }
45  
46   if((fuctionCode>=ReadCoilStatus)&&(fuctionCode<=ReadInputRegister))
47   {
48     HandleServerRespond[fuctionCode-1](client,recievedMessage,startAddress,quantity);
49   }
50 }

解析函数的主要部分是在检查接收到的消息是否是合法的Modbus TCP消息。检查没问题则调用协议站解析。而最后调用的数据处理函数则是我们需要在具体应用中编写。在前面客户端初始化时,回调函数我们初始化为NULL,实际在协议占中有弱化的函数定义,需要针对具体的寄存器和变量地址实现操作。

3.4 、写服务器操作

写服务器操作则是在其它进程请求后,我们标识需要写的对象再统一处理。对具体哪个服务器的写标识存于客户端实例。而该服务器的哪些变量需要写则记录在服务器实例中。

所以在进程检测到需要写一个服务器时则置位对应的位,即改变flagWriteServer中的对应位。而需要写该服务器的哪些变量则标记flagPresetCoil和flagPresetReg的对应位。修改这些标识都在其它请求更改的进程中实现,而具体的写操作则在本客户端进程中,检测到标志位的变化统一执行。

这部分不修改协议栈的代码,因为各服务器及各变量都只与具体对象相关联,所以在具体的应用中修改。

4 、回归验证

借鉴前面Modbus ASCII和Modbus RTU的回归测试经验,我们设计两个网段、每网段包括一个客户端及两个服务器的网络结构。但考虑到我们只是功能性验证,所以我们设计相对简单的服务器。所以我们设计的网络为:协议栈建立2个客户端,每个客户端管理同一网段的2个服务器,每个服务器有8个线圈及2个保持寄存器。具体结构如图:

从上图我们知道,该Modbus网关需要实现一个Modbus服务器用于和上位的通讯;需要实现两个Modbus客户端用于和下位的通讯。

在这个实验中,读操作没有什么需要说的,只需要发送命令解析返回消息即可。所以我们中点描述一下为了方便操作,在需要写的连续段,我们只要找到第一个请求写的位置后,就将后续连续可写数据一次性写入。

告之: 源代码可上Github下载https://github.com/foxclever/Modbus

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • MODBUS
    +关注

    关注

    28

    文章

    1760

    浏览量

    76825
  • TCP
    TCP
    +关注

    关注

    8

    文章

    1348

    浏览量

    78982
  • RTU
    RTU
    +关注

    关注

    0

    文章

    404

    浏览量

    28619
  • 协议栈
    +关注

    关注

    2

    文章

    140

    浏览量

    33608
收藏 人收藏

    评论

    相关推荐

    Modbus TCP通信报文解析

    Modbus TCP是在TCP/IP网络上运行的Modbus实现,旨在允许Modbus ASC
    发表于 09-20 15:55 2.8w次阅读
    <b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>通信报文解析

    Modbus TCPModbus RTU的实现

    使用ZLSN2040、NETCOM2040实现Modbus TCPModbus RTU的转化。1.Modbus
    发表于 08-10 10:04

    怎么实现嵌入式ModbusTCP网关的设计?

     本文设计和实现了一种嵌入式协议转换网关,通过Modbus/TCP协议将传统的串行链路通信的Modbus现场总线与以太网相连。
    发表于 06-01 06:10

    如何快速实现Modbus RTU和Modbus TCP协议转换?

    整合起来监控管理,目前上位机大部分用的Modbus TCP协议,而现场设备有大批量使用的是Modbus RTU协议,要实现Modbus
    发表于 08-18 18:36

    基于Modbus TCP协议实现PC机与PLC的串行通信

    本文对modbus tcp协议以及modbus tcp串行通信在智能楼宇工程中的应用作了简单介绍,重点分析研究了modbus
    发表于 08-13 14:47 6992次阅读
    基于<b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>协议<b class='flag-5'>实现</b>PC机与PLC的串行通信

    Modbus_TCP驱动组件设计与实现_史运涛

    Modbus_TCP驱动组件设计与实现_史运涛
    发表于 03-19 11:27 1次下载

    Modbus/TCP通讯配置

    MODBUSTCP 是简单的、中立厂商的用于管理和控制自动化设备的MODBUS 系列通讯协议的派生产品,它覆盖了使用TCP/IP 协议的Intranet和Internet环境中MODBUS 报文
    发表于 09-30 09:13 38次下载
    <b class='flag-5'>Modbus</b>/<b class='flag-5'>TCP</b>通讯配置

    汇川PLC AM401以太网通讯ModBus TCP实现

    本人近十年的工作都与工业软件相关、其中工控系统开发过程中有一个必要环节就是跟各大厂商的PLC进行通讯,而对于从互联网行业跨入工业互联网行业的从业人员来说要实现各型号PLC通讯还是需要 一个过程的,随着PLC的需求和应用越来越广泛,本文就介绍一下上位机软件与汇川PLC-AM401的通讯
    发表于 04-17 15:55 4次下载
    汇川PLC AM401以太网通讯<b class='flag-5'>ModBus</b> <b class='flag-5'>TCP</b><b class='flag-5'>实现</b>

    PLC转MQTT OPC UA网关实现工业设备与阿里云平台的互联互通

    、MQTT、Modbus TCP实现高效上行和下行通信,同时满足PLC远程上传下载和远程编程需求。 其硬件接口包括2路或6路RS485/RS232串口、网口、4G/WiFi接口,灵活适配各设备,与阿里云
    的头像 发表于 08-01 11:57 357次阅读
    PLC转MQTT OPC UA网关<b class='flag-5'>实现</b>工业设备与阿里云平台的互联互通

    CAN从站转Modbus TCP主站协议网关配置详情

    ​ CAN转Modbus TCP 如何实现高效且稳定的连接与通信,这一问题常常让众多业内人士感到困惑不已。在此,为大家专门解读这个难题。 远创智控YC-CCLKIE-RTU 型设备有着极为卓越的表现
    的头像 发表于 08-28 11:28 258次阅读
    CAN从站转<b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>主站协议网关配置详情

    Profibus-DP主站转Modbus-TCP协议网关(YC-DPM-TCP

    Profibus-DP转Modbus-TCP如何实现高效且稳定的连接与通信,很多朋友想要知道这个问题的答案。现在作者为大家详细解读这一难题。远创智控YC-DPM-TCP型设备可以帮助大家很好
    的头像 发表于 08-30 10:23 138次阅读
    Profibus-DP主站转<b class='flag-5'>Modbus-TCP</b>协议网关(YC-DPM-<b class='flag-5'>TCP</b>)

    EtherCAT转Modbus-TCP协议网关(Modbus-TCP转EtherCAT)

    EtherCAT转Modbus-TCP实现网络协议互通是众人关注焦点,远创智控YC-ECT-TCP能够很轻松解决这个问题。在这里作者将从该设备的主要功能、技术参数、性能优势、配置方法等几个方面详细
    的头像 发表于 09-04 09:59 205次阅读
    EtherCAT转<b class='flag-5'>Modbus-TCP</b>协议网关(<b class='flag-5'>Modbus-TCP</b>转EtherCAT)

    Profibus DP转Modbus TCP协议网关

    Profibus DP转Modbus TCP 实现网络协议互通这一问题备受众人瞩目,而 远创智控YC-DPM-TCP 可以轻松化解这一难题。接下来,作者将从该设备的主要功能、技术参数、
    的头像 发表于 09-04 21:54 128次阅读
    Profibus DP转<b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>协议网关

    EtherCAT转Modbus TCP协议网关(JM-ECT-TCP

    JM-ECT-TCP网关实现EtherCAT网络与Modbus TCP网络之间的数据通讯,即将Modbus
    的头像 发表于 09-07 17:05 298次阅读
    EtherCAT转<b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>协议网关(JM-ECT-<b class='flag-5'>TCP</b>)

    MODBUS TCP 转 CANOpen

    产品概述 SG-TCP-COE-210 网关可以实现将 CANOpen 接口设备连接到 MODBUS TCP 网络中。用户不需要了解具体的 CANOpen 和
    的头像 发表于 09-24 13:59 227次阅读
    <b class='flag-5'>MODBUS</b> <b class='flag-5'>TCP</b> 转 CANOpen