freemodbus modbus TCP 学习笔记

1.前言
    使用modbus有些时间了,期间使用过modbus RTU也使用过modbus TCP,通过博文和大家分享一些MODBUS TCP的东西。在嵌入式中实现TCP就需要借助一个以太网协议栈,在这里我选择最简单的uIP协议栈。uIP协议栈简单易用方便上手,相比于LwIP无论是移植还是使用难度都低些,这样就可以把更多的精力花在modbus tcp协议本身而不必花大量的时间研究以太网协议栈。modbus协议栈为freemodbus
 
【其他有用的博文】
    【1】uIP学习笔记
 
【工程代码】
    示例代码托管于GitHub——【Github Clone
    如果有问题我会及时更新。
【使用说明】
    【1】工具链为IAR 6.5
    【2】从机IP为固定IP 192.168.1.15,请保证从机和路由器位于同一个网段中。
    【3】modbus tcp的侦听端口号为502
 
2.MODBUS TCP注意点
2.1 主机和从机、服务端和客户端
图1 MODBUS请求响应模型
【在modbus协议中】
主机发送modbus请求,从机根据请求内容向主机返回响应。在modbus协议中,主机总是主动方,从机总是被动方。
【在网络应用中】
在网络应用中存在客户端和服务器端,客户端(例如浏览器)发送请求到服务器,服务器向客户端返回内容(例如HTML文本)。
【在modbus tcp中】
主机是客户端,而从机是服务器端。千万不要以为服务器端重要,主机也重要。
【在modbus rtu中】
主机(Master)是客户端(Client),而从机(Slave)是服务器端(Server)。
 
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的区别可使用下图概括
图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! ");  
  6.     }  
  7.      
  8.     if(uip_closed())  
  9.     {  
  10.         PRINTF("closed ");  
  11.     }  
  12.      
  13.     if(uip_newdata())  
  14.     {  
  15.         PRINTF("request! ");  
  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
图3 打开modbus tcp连接
    【2】尝试读出保持寄存器
图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行。
图5 抓包分析
转摘:http://blog.csdn.net/xukai871105/article/details/21652287
原文地址:https://www.cnblogs.com/axinno1/p/8521481.html