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

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

3天内不再提示

使用协议栈实现Modbus TCP客户端应用

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

自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例。所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,这一篇中我们将解说如何使用协议栈实现一个Modbus TCP客户端。

1 、何为TCP客户端

Modbus协议是一个主从协议,那肯定就有主站和从站之分,在Modbus TCP中亦称之为客户端与服务器。所谓TCP客户端其功能基本与RTU主站一样,RTU主站会向从站发起数据请求,同样的TCP客户端也会向服务器发起请求。也就是说在Modbus TCP模式下客户端亦是发起通讯的一方。

对于TCP客户端来说,自己并不会产生数据,它的数据均是从服务器获取,为了得到数据就必须向服务器发起数据请求。在Modbus TCP协议中,服务器一般也不会主动向外发送数据,服务器需要根据客户端的数据请求来决定是否发送数据、发送哪些数据。这一过程如下图所示:

从上图我们不难看出,首先客户端要主动发起数据请求,客户端发起的数据请求需要告诉服务器它请求的数据有哪些。服务器收到这个数据请求后,服务器解析客户端的请求并按照客户端的请求返回数据。客户端收到数据响应后解析数据,这样就完成了客户端与服务器之间的一次数据通讯。

需要注意的是,Modbus TCP与Modbus RTU不同的是有一个专用的MBAP报文头来识别Modbus应用数据单元。这一报文头由7个字节组成:

这种MBAP报文头虽然也是用来识别Modbus数据域,但还是与串行链路上使用的MODBUS RTU应用数据单元有一些差别,具体如下:

1 、用MBAP报文头中的单个字节单元标识符取代MODBUS串行链路上通常使用的MODBUS从地址域。这个单元标识符用于设备的通信,这些设备使用单个 IP 地址支持多个独立MODBUS 终端单元,例如:网桥、路由器和网关。

2 、使用接收者可以验证的方式来构造所有MODBUS请求和响应。对于MODBUS PDU有固定长度的功能码来说,仅功能码就足够了。对于在请求或响应中携带一个可变数据的功能码来说,数据域包括字节数。

3 、使用TCP上传送MODBUS数据域时,即使将报文分成多个信息包来传输,可在MBAP报文头上携带附加长度信息,这样接收者就能够识别报文的完整性。

2 、如何实现TCP客户端

我们已经简单的描述了基于TCP/IP的Modbus数据通讯,在此基础上我们将进一步描述基于协议栈的Modbus TCP客户端的实现。

在协议栈中,我们已经实现了TCP客户端的数据请求命令的合成以及响应数据的解析,所以我们使用协议栈时就是要控制何时将协议栈合成的客户端请求命令发出以及如何解析数据响应进而得到想要的数据的过程。

在我们的协议栈中实现了0x01、0x02、0x03、0x04、0x05、0x06、0x0F以及0x10等功能码。也就是说TCP客户端对象可以生成面向这些功能码的服务器数据请求。也可以解析面向这些功能码的服务器数据响应。可以表示为下图所示:

从上图我们很清楚,协议栈已经实现了面向这些功能码的数据请求命令的生成以及数据响应消息的解析。我们使用协议栈时需要做的就是要告诉协议栈我要生成哪些数据请求命令以及如何解析数据响应消息。

2.1 、生成数据请求

作为客户端需要主动向服务器发起操作,所以我们要控制客户端生成可以被服务器识别的命令序列。在协议栈中已经封装了生成客户端访问服务器的命令序列的函数,其原型如下:

/ 生成访问服务器的命令 /

uint16_t CreateAccessServerCommand(TCPLocalClientType*client,ObjAccessInfo objInfo,void *dataList,uint8_t *commandBytes)

这个函数有4个参数,分别是:

TCPLocalClientType *client,所发起访问的本地客户端对象。

ObjAccessInfo objInfo,用于生成访问命令的信息,如站地址、功能码等。

void *dataList,如果是写操作,则对应需要写的数据列表,线圈为bool量、寄存器为uint16_t型无符号整数。

uint8_t *commandBytes是生成的命令序列

而返回值则是生成的命令序列的长度。在我们需要生成访问服务器的命令时,调用这个函数就可实现。不过一定要注意生成的命令序列的长度,定义commandBytes对象时长度一定要足够。

2.2 、解析数据响应

当客户端收到服务器返回的响应信息后,客户端需要对消息进行解析,并决定需要进行的操作。在协议栈中封装了对服务器响应消息的解析函数,该函数的原型如下:

/ 解析收到的服务器相应信息 /

void ParsingServerRespondMessage(TCPLocalClientType client,uint8_trecievedMessage)

这一解析函数包含2个参数,TCPLocalClientType client是本地客户端对象;而uint8_trecievedMessage为接收到的服务器响应消息。本函数会注意核对任务号、协议代码、功能码、数据完整性等,检验正确的消息会被解析,并根据消息来操作相应的数据对象,比如读的是服务器的保持寄存器,则根据读的起始地址和数量以及数据对象的类型来解析之。

3 TCP****客户端编码

我们讲述了客户端所要进行的工作以及协议栈中封装好的面向客户端的操作函数,接下来我们将基于协议栈来实现一个简单的Modbus TCP客户端实例。

3.1 、定义TCP客户端对象

在开始实现客户端的相关操作前,我们需要先声明并实例化部分用于Modbus TCP客户端操作的对象。

首先需要定义用于本地操作的本地客户端,也就是我们要实现的客户端对象。具体的声明如下:TCPLocalClientType mbClient;

其次需要声明一个或者多个服务器对象,这些服务器对象是我们所要实现的客户端所管理的服务器对象。具体的声明如下:TCPAccessedServerType mbServer;

同时需要定义一个用于存放读操作命令的数组,定义一个写服务器操作的线圈量对象数组和一个寄存器量对象数组,具体如下:

uint8_t readCommand[10][12];
WritedCoilListNode coilList[3]={{0,0,0,1},{1,0,0,1},{2,0,0,0}};
WritedRegisterListNoderegisterList[3]={{0,0,0,1,1},{1,0,0,1,2},{2,0,0,0,0}};

接下来还需要对客户端对象和服务器对象进行初始化和实例化。而上述的数组将作为参数用于客户端对象的初始化和服务器对象的实例化。

此外还要定义4个函数指针用于对从服务器读取回来的数据进行更新,这几个函数的原型如下:

/*更新读回来的线圈状态*/
typedef void (*UpdateCoilStatusType)(uint8_t salveAddress,uint16_tstartAddress,uint16_t quantity,bool *stateValue);


/*更新读回来的输入状态值*/
typedef void (*UpdateInputStatusType)(uint8_t salveAddress,uint16_tstartAddress,uint16_t quantity,bool *stateValue);


/*更新读回来的保持寄存器*/
typedef void (*UpdateHoldingRegisterType)(uint8_t salveAddress,uint16_tstartAddress,uint16_t quantity,uint16_t *registerValue);


/*更新读回来的输入寄存器*/
typedef void (*UpdateInputResgisterType)(uint8_t salveAddress,uint16_tstartAddress,uint16_t quantity,uint16_t *registerValue);

这几个函数的实现要根据具体的参数来实现。定义好这些对象后,我们就可以对客户端进行初始化和对服务器进行实例化。

/*初始化TCP客户端对象*/
InitializeTCPClientObject(&mbClient, NULL, NULL, UpdateHoldingRegister, NULL);
/* 实例化TCP服务器对象 */
InstantiateTCPServerObject(&mbServer,          //要实例化的服务器对象
    &mbClient,             //服务器所属本地客户端对象
    192,                     //IP地址第1段
    168,                     //IP地址第2段
    183,                     //IP地址第3段
    130,                     //IP地址第4段
    502,
    1,
    rCmd,
    0,              //可写线圈量节点的数量
    NULL,          //写线圈列表
    0,          //可写寄存器量节点的数量
    NULL);  //写寄存器列表

在这一示例中,我们只定义了一个服务器所以只需要实例化一个就可以了,实例化函数会自动将服务器添加到客户端管理的服务器列表中。

3.2 、生成客户端数据请求

作为客户端需要首先发起请求。在前一节我们已经讲述了生成客户端请求的函数。我们只需要调用该函数就可以了,但该函数需要一些参数,我们先来看看这些参数是否准备就绪。

第一个参数是客户端对象,在前面的描述中我们已经生命并初始化完成了这一对象所以直接使用就好。

第二个参数是要生成请求的信息,其定义为一个结构体变量。

/*定义用于传递要访问从站(服务器)的信息*/
typedef struct{
  uint8_t unitID;
  FunctionCode functionCode;
  uint16_t startingAddress;
  uint16_t quantity;
}ObjAccessInfo;

所以我们需要定义一个该类型变量,并根据我们的操作要求给其赋值。我们假设要实现对站地址为1的服务器对象的保持寄存器从0-9共10个寄存器地址的读取则可实现为:

ObjAccessInfo tObj={1,ReadHoldingRegister,0x00,10};

第三个参数为数据列表,读服务器时无数据列表以NULL输入。第四个参数则为生成的读服务器数据的请求命令,我们按要求定义即可使用,于是我们就可以调用该函数生成相应的命令了。

uint16_t sendLengh;
uint8_t sendCommand[12];
sendLengh=CreateAccessServerCommand(&tClient,tObj,NULL,sendCommand);

这一例子中我们是读取服务器保持寄存器的数据,如果我们写服务器对应数据,这只要将dataList组织好,作为参数传入就好,不过要注意返回的命令的长度。生成访问服务器的命令后,作为客户端主动发送相应的命令后等待服务器响应。

3.3 、解析服务器数据响应

客户端接收到服务器的返回信号后,就会调用解析函数对消息进行解析并根据具体的消息对数据对象进行更新。解析函数非常简单仅有两个参数,一个是本地客户端对象,一个是接收到的响应消息。

ParsingServerRespondMessage(&tClient,recievedMessage);

解析函数会根据消息内容执行相应的操作。如在这个实例中,我们读取了服务器的保持寄存器起始地址为0的10个寄存器,所以解析函数会调用保持寄存器数据处理函数来更新数据,最终其实就是以回调的方式执行。在这里我们将需要实现更新读回来的保持寄存器的参数的函数。

/*更新读回来的保持寄存器*/
void UpdateHoldingRegisterForClient(uint8_tserverAddress,uint16_t startAddress,uint16_t quantity,uint16_t *registerValue)
{
  uint16_tstartRegister=HoldingResterEndAddress+1;
 
 if(serverAddress==130)
  {
   startRegister=startAddress;
  }
 
  for(inti=0;i

至此我们实际已经实现了一个简单的Modbus TCP客户端。不过我们还要说明一下,使用不同的协议栈时,解析函数中的recievedMessage参数的具体的形式需要调整。

4 TCP****客户端小结

我们使用协议栈完成了一个简单的Modbus TCP客户端应用实例。同样的我们可以使用相关的Modbus测试软件测试这一示例。我们使用Modbus Slave模拟Modbus TCP服务器应用,然后使用我们实现的客户端与之通讯,以验证我们的客户端。

客户端接收到的服务器反馈如下图:

上图说明我们基于协议栈实现的简单Modbus TCP客户端是正确的。在使用协议栈实现Modbus TCP客户端时,我们需要注意,协议栈封装了Modbus TCP客户端,使得在同一台设备上支持在不同的接口实现不同的客户端,也就是在同一设备可以实现多个客户端以管理不同的服务器。具体的实现可以根据协议栈进一步发挥。

最后我们来总结一下使用协议栈实现Modbus TCP客户端应用的步骤,以方便大家使用协议栈实现Modbus TCP客户端应用。

第一步,使用Modbus TCP客户端对象类型声明一个Modbus TCP客户端对象。然后对这个Modbus TCP客户端对象进行初始化。初始化Modbus TCP客户端对象时。需要指定所管理的服务器的数量,服务器列表以及更新数据的回调函数指针。

第二步,生成访问Modbus TCP客户端的数据请求列表。这个数据请求列表是按每一台服务器来划分的,将列表的指针存在对应的服务器本地对象中。然后在需要的时候发送相应的数据请求。

第三步,解析接收的服务器数据响应。协议栈已经定义好了解析函数,只需传入消息就可自动解析。但是更新数据的回调函数必须根据具体的变量来编写。可以每台Modbus TCP客户端独立编写,也可使用默认的函数。

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

    关注

    28

    文章

    1802

    浏览量

    76959
  • TCP
    TCP
    +关注

    关注

    8

    文章

    1353

    浏览量

    79057
  • 客户端
    +关注

    关注

    1

    文章

    290

    浏览量

    16684
  • 协议栈
    +关注

    关注

    2

    文章

    141

    浏览量

    33628
收藏 人收藏

    评论

    相关推荐

    Linux网络编程-TCP客户端如何获取要连接的服务IP?

    本篇介绍了在TCP通信中,客户端通过UDP广播,实现自动获取服务的IP地址,并进行TCP连接的具体方法,并通过代码
    的头像 发表于 09-27 08:56 3673次阅读
    Linux网络编程-<b class='flag-5'>TCP</b><b class='flag-5'>客户端</b>如何获取要连接的服务<b class='flag-5'>端</b>IP?

    Modbus TCP的设计与实现

    对于Modbus TCP来说与Modbus RTU和Modbus ASCII有比较大的区别,因为它是运行于以太网链路之上,是运行于TCP/I
    的头像 发表于 12-13 15:33 2168次阅读
    <b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>的设计与<b class='flag-5'>实现</b>

    使用协议实现Modbus TCP服务器应用

    自从开源了我们自己开发的Modbus协议之后,有很多朋友建议我针对性的做几个示例。所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,这一篇中我们来简述如何使用协议
    的头像 发表于 12-13 16:23 1561次阅读
    使用<b class='flag-5'>协议</b><b class='flag-5'>栈</b><b class='flag-5'>实现</b><b class='flag-5'>Modbus</b> <b class='flag-5'>TCP</b>服务器应用

    求助!用labview的TCP传输协议传输的图片不能在客户端实时...

    求助!我编了用labviewTCP传输协议进行图像采集传输的TCP客户端和服务客户端和服务
    发表于 12-04 17:09

    labview TCP客户端

    最近在做一个labview 客户端测试小程序,服务器采用MFC编写,客户端采用TCP侦听函数,通信可以连接,数据也正确,但是服务器检测发送判断失败,个人推测是不是
    发表于 06-30 23:15

    协议介绍--TCP/IP

    ,和SOCKET API。以及DNS,PING等直接调用的函数。5)本协议可以完成客户机,服务器,UDP客户机,UDP服务器。还可以方便完成DNS
    发表于 09-03 15:03

    Modbus TCPModbus RTU的实现

    设施,而大批量的Modbus RTU设备已经存在。用一个统一的Modbus TCP客户端软件去访问所有的Modbus设备,统一、集中式管理将
    发表于 08-10 10:04

    TCP通信时服务如何接收客户端的数据?

    毕设采用的是TCP协议,组员做的是下位机,C编程,WiFi模块工作处于客户端。我负责上位机,Labview使用tcp协议时服务
    发表于 04-14 14:49

    如何使用Socket实现TCP客户端

    本教程介绍了如何编写一个基于 socket 编程实现TCP 客户端。我们先将 socket 编程的流程列出来,然后给出具体的实例。
    发表于 03-30 07:07

    怎样去设计嵌入式LWIP网络客户端

    嵌入式LWIP网络客户端设计教程本文引用地址: 嵌入式技术的兴起使得传统的基于PC机的互联网技术优势不再,嵌入式网络客户端与服务技术成为热点,而该技术需要移植性高、占用资源小的协议
    发表于 08-06 06:46

    JAVA教程之TCP客户端

    JAVA教程之TCP客户端,很好的JAVA的资料,快来学习吧
    发表于 04-11 17:28 7次下载

    大神教你:嵌入式LWIP网络客户端设计

    嵌入式技术的兴起使得传统的基于PC机的互联网技术优势不再,嵌入式网络客户端与服务技术成为热点,而该技术需要移植性高、占用资源小的协议,轻量级TC
    发表于 05-24 16:20 1986次阅读
     大神教你:嵌入式LWIP网络<b class='flag-5'>客户端</b>设计

    TCP回响客户端:RAW API接口

    TCP回响客户端例程(RAW API)
    的头像 发表于 07-05 00:31 3932次阅读
    <b class='flag-5'>TCP</b>回响<b class='flag-5'>客户端</b>:RAW API接口

    基于LwIP的TCP客户端设计

    上一篇我们基于LwIP协议的RAW API实现了一个TCP服务器的简单应用,接下来一节我们来实现一个T
    的头像 发表于 12-14 15:12 2276次阅读
    基于LwIP的<b class='flag-5'>TCP</b><b class='flag-5'>客户端</b>设计

    基于LwIP的HTTP客户端设计

    前面我们实现TCP服务器和客户端的简单应用,接下来我们实现一个基于TCP协议的应用
    的头像 发表于 12-14 15:19 2822次阅读
    基于LwIP的HTTP<b class='flag-5'>客户端</b>设计