1.前言

    使用modbus有些时间了,期间使用过modbus RTU也使用过modbus TCP,通过博文和大家分享一些MODBUS TCP的东西。在嵌入式中实现TCP就需要借助一个以太网协议栈,在这里我选择最简单的uIP协议栈。uIP协议栈简单易用方便上手,相比于LwIP无论是移植还是使用难度都低些,这样就可以把更多的精力花在modbus tcp协议本身而不必花大量的时间研究以太网协议栈。modbus协议栈为freemodbus

 

【其他有用的博文】

    【1】uIP学习笔记

    【2】MODBUS协议整理——汇总 

 

【工程代码】

    示例代码托管于GitHub——【Github Clone

    如果有问题我会及时更新。

【使用说明】

    【1】工具链为IAR 6.5

    【2】从机IP为固定IP 192.168.1.15,请保证从机和路由器位于同一个网段中。

    【3】modbus tcp的侦听端口号为502

 

2.MODBUS TCP注意点

2.1 主机和从机、服务端和客户端

freemodbus modbus TCP 学习笔记

图1 MODBUS请求响应模型

【在modbus协议中】

主机发送modbus请求,从机根据请求内容向主机返回响应。在modbus协议中,主机总是主动方,从机总是被动方。

【在网络应用中】

在网络应用中存在客户端和服务器端,客户端(例如浏览器)发送请求到服务器,服务器向客户端返回内容(例如HTML文本)。

【在modbus tcp中】

主机是客户端,而从机是服务器端。千万不要以为服务器端重要,主机也重要,所以主机就是服务器端。

 

2.2 是否可以多主机

    通过前面的分析,主机为客户端那么modbus tcp支持多个主机,在一个局域网中可存在多个主机和多个从机。从机的连接能力(连接主机的数量)由uIP的最大TCP连接个数决定。

 

2.3 modbus TCP协议简述

modbus TCP和modbus RTU基本相同,但是也存在一些区别

    【1】从机地址变得不再重要,多数情况下忽略。从某种意义上说从机地址被IP地址取代

    【2】CRC校验变得不再重要,甚至可以忽略。由于TCP数据包中已经存在校验,为了不重复造轮子,modbus TCP干脆取消了CRC校验。

 

modbus TCP和modbus RTU的区别可使用下图概括

freemodbus modbus TCP 学习笔记

图2 modbus TCP数据包和modbus RTU数据包比较

 

在modbus TCP中包含一个MBAP头,该头包含以下几个部分

区域

长度

描述

客户端

服务器

传输标志

2字节

MODBUS 请求和响应传输过程中

***

客户端生成

应答时复制该值

协议标志

2字节

Modbus协议默认为0

客户端生成

应答时复制该值

长度

2字节

剩余部分的长度

客户端生成

应答时由服务器端生成

单元标志

1字节

从机标志(从机地址)

客户端生成

应答时复制该值

【注意】

【1】传输标志可理解为***,防止MODBUS TCP通信错位,例如后发生的响应先到了主机,而早发生的响应后到主机

【2】单元标志可理解为从机地址,此时已经不再重要

 

2.4 modbus tcp 和 TCP IP的关系

    modbus TCP可以理解为发生在TCP上的应用层协议,既然是TCP协议那么一个完整的MODBUS TCP报文必然包括TCP首部,IP首部和Ethernet首部。

    下面就通过uIP协议栈来实现modbus TCP

 

3.代码实现

3.1 侦听502端口

[cpp] view plain copy

 

  1. BOOL  
  2. xMBTCPPortInit( USHORT usTCPPort )  
  3. {  
  4.     BOOL bOkay = FALSE;  
  5.      
  6.     USHORT usPort;  
  7.     if( usTCPPort == 0 )  
  8.     {  
  9.         usPort = MB_TCP_DEFAULT_PORT;  
  10.     }  
  11.     else  
  12.     {  
  13.         usPort = (USHORT)usTCPPort;  
  14.     }  
  15.      
  16.     // 侦听端口 502端口  
  17.     uip_listen(HTONS(usPort));  
  18.      
  19.     bOkay = TRUE;  
  20.     return bOkay;  
  21. }  

    【代码说明】

    【1】uip_listen(HTONS(usPort)) 侦听502端口,注意大小端变化。

 

3.2 uIP循环处理——porttcp.c

[cpp] view plain copy

 

  1. void uip_modbus_appcall(void)  
  2. {  
  3.     if(uip_connected())  
  4.     {  
  5.         PRINTF("connected!\r\n");  
  6.     }  
  7.      
  8.     if(uip_closed())  
  9.     {  
  10.         PRINTF("closed\r\n");  
  11.     }  
  12.      
  13.     if(uip_newdata())  
  14.     {  
  15.         PRINTF("request!\r\n");  
  16.         // 获得modbus请求  
  17.         memcpy(ucTCPRequestFrame, uip_appdata, uip_len );  
  18.         ucTCPRequestLen = uip_len;  
  19.         // 向 modbus poll发送消息  
  20.         xMBPortEventPost( EV_FRAME_RECEIVED );  
  21.     }  
  22.      
  23.     if(uip_poll())  
  24.     {  
  25.         if(bFrameSent)  
  26.         {  
  27.             bFrameSent = FALSE;  
  28.             // uIP发送Modbus应答数据包  
  29.             uip_send( ucTCPResponseFrame , ucTCPResponseLen );  
  30.         }  
  31.     }  
  32. }  

    【代码说明】

    【1】uip_newdata()返回为True表示存在新的数据

    【2】复制uip_appdate中的数据到ucTCPRequestFrame,该变量为全局变量,通过该全部变量”中转“到modbus处理的缓冲区中。然后向modbus协议栈发送消息,消息内容为EV_FRAME_RECEIVED 。由于没有使用操作系统

    【3】如果处理完成则通过uip_send发送响应。

    【4】ucTCPRequestFrame和ucTCPResponseFrame均为全局数组,用于和modbus缓冲区交换数据。

static UCHAR ucTCPRequestFrame[MB_TCP_BUF_SIZE];

static USHORT ucTCPRequestLen;

static UCHAR ucTCPResponseFrame[MB_TCP_BUF_SIZE];

static USHORT ucTCPResponseLen;

 

3.3 modbus 接收处理

[cpp] view plain copy

 

  1. BOOL  
  2. xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )  
  3. {  
  4.     *ppucMBTCPFrame = &ucTCPRequestFrame[0];  
  5.     *usTCPLength = ucTCPRequestLen;  
  6.      
  7.     /* Reset the buffer. */  
  8.     ucTCPRequestLen = 0;  
  9.     return TRUE;  
  10. }  

    【代码说明】

    【1】** ppucMBTCPFrame为一个指向数据的指针,而*ppucMBTCPFrame可以指向一个数组,在这里可把ucTCPRequestFrame复制给该变量,配合usTCPLength,那么从uIP接收到的内容就”转移“到freemodbus中。

 

3.4 modbus 发送处理

[cpp] view plain copy

 

  1. BOOL  
  2. xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )  
  3. {  
  4.     memcpy( ucTCPResponseFrame , pucMBTCPFrame , usTCPLength);  
  5.     ucTCPResponseLen = usTCPLength;  
  6.      
  7.     bFrameSent = TRUE; // 通过uip_poll发送数据  
  8.     return bFrameSent;  
  9. }  

【代码说明】

【1】把传入的内容 pucMBTCPFrame复制给ucTCPResponseFrame,并设置bFrameSent为True,那么在下一次uip_poll时便会把响应发送会主机。

 

4.测试与分析

    【1】连接从机

    选择IP地址为192.168.1.15,端口号为502

freemodbus modbus TCP 学习笔记

图3 打开modbus tcp连接

    【2】尝试读出保持寄存器

freemodbus modbus TCP 学习笔记

图4 读取保持寄存器

    【3】抓包分析

    请使用ip.addr == 192.168.1.15 表达式过滤报文,其中192.168.1.100为PC机,此处为modbus 主机

    【简单分析】

    【1】115行为modbus主机请求,此时传输标志为25.

    【2】116行为modbus从机给出的TCP应答,TCP应答为TCP协议规定的内容,TCP应答中不包含modbus 响应

    【3】117行为modbus从机响应,此时传输标志依然为25.

    【4】118行为modbus主机 TCP应答,同16行。

freemodbus modbus TCP 学习笔记

图5 抓包分析

转摘:http://blog.csdn.net/xukai871105/article/details/21652287

相关文章: