打印

基于EncEthernet的FreeModbus-TCP 在stm32上的移植与测试

[复制链接]
6023|35
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Diyer2015|  楼主 | 2017-11-28 10:33 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
昨天移植好了modbus-RTU,今晚开始在EncEthernet上的free modbus-TCP的移植,使用的开发板为火牛开发板,stm32f103+enc28j60网络方案。主流的TCP/IP协议栈包括uIP、LwIP等,EncEthernet协议栈是一款比较简单的协议栈,由厂家提供在stm32的开发板已经移植好,所以就直接使用,其他的协议的移植方法应该都大同小异。

相关帖子

沙发
Diyer2015|  楼主 | 2017-11-28 10:47 | 只看该作者
一、相关知识
       Modbus TCP/IP数据帧除了TCP已经有的包头外,还有modbus TCP协议数据单元(ADU),包括MBAP帧头以及与RTU数据内容相同的应用数据单元(PDU),地址码除外。

使用特权

评论回复
板凳
Diyer2015|  楼主 | 2017-11-28 10:50 | 只看该作者
数据原理如下:

1.jpg (28.78 KB )

1.jpg

使用特权

评论回复
地板
Diyer2015|  楼主 | 2017-11-28 10:50 | 只看该作者
其中与单纯的TCP/IP或是modbus-RTU相比,多的内容就是一个MBAP报文头,这是个什么东西,规定了什么内容呢?先来看看都包含哪些东西。

使用特权

评论回复
5
Diyer2015|  楼主 | 2017-11-28 10:50 | 只看该作者
MBAP报文

2.jpg (73.49 KB )

2.jpg

使用特权

评论回复
6
Diyer2015|  楼主 | 2017-11-28 10:51 | 只看该作者
可以看出来,MBAP报文头主要添加了以下附加信息,为了识别是请求还是响应而设置的事务元标识符、为了判断协议类型设置的协议标识符、为了区分可变长度数据帧结束的数据帧长度、还有用于标识从站地址,与RTU不同的是,从地址放在了MBAP帧头里。

使用特权

评论回复
7
Diyer2015|  楼主 | 2017-11-28 10:52 | 只看该作者
二、代码移植
       前两天已经基于BARE工程移植好RTU模式,仿照相应思路实现TCP的一些函数功能,在mbtcp.c中可以发现,包括TCP初始化(xMBTCPPortInit)、TCP启动(eMBTCPStart)、TCP停止(eMBTCPStop)、TCP接收一个数据包(xMBTCPPortGetRequest)、TCP发送一个数据包(xMBTCPPortSendResponse)等,为了实现free-modbus与EncEthernet对接,在port文件夹下建立porttcp.c文件,在其中包含头文件ip_arp_udp_tcp.h。

使用特权

评论回复
8
Diyer2015|  楼主 | 2017-11-28 10:53 | 只看该作者
(1) xMBTCPPortInit( ucTCPPort )
       这是以太网TCP端口初始化函数,怎么觉得参数有点少呢,绑定TCP端口至少需要mac地址、ip地址以及端口地址吧,这里面只与端口有关,看来只能把他们隐藏了。
       于是加入
       enc28j60Init(mymac);
       enc28j60PhyWrite(PHLCON,0x476);
       init_ip_arp_udp_tcp(mymac,myip,mywwwport);
       这个几个函数作为TCP端口初始化。

使用特权

评论回复
9
Diyer2015|  楼主 | 2017-11-28 10:53 | 只看该作者
(2) eMBTCPStart
      其实在EncEthernet中只要进行了协议栈的初始化,就已经启动了协议栈,可直接使用。

使用特权

评论回复
10
Diyer2015|  楼主 | 2017-11-28 10:54 | 只看该作者
  (3) eMBTCPStop
这个函数是作为TCP端口关闭的函数,其实在modbus中调用它的是eMBClose,而在协议中没有调用eMBClose把modbus给关掉,所以这个函数不用去实现。

使用特权

评论回复
11
Diyer2015|  楼主 | 2017-11-28 10:58 | 只看该作者
(4) xMBTCPPortGetRequestTCP接收一个数据包
调用enc28j60PacketReceive(BUFFER_SIZE, buf)进行判断,当然希望移植的modbus除了能处理modbus-TCP包外还能对一些正常数据包进行响应,比如arp请求、ping命令等,所以在之后添加了包头验证,当确定传入包是有数据的TCP/IP包才返回TRUE。

使用特权

评论回复
12
Diyer2015|  楼主 | 2017-11-28 10:59 | 只看该作者
  (5) xMBTCPPortSendResponse TCP 发送一个数据包
封装xMBTCPPortSendResponse发送写好的TCP包即可。
接口部分移植完毕,下面软件仿真一下看看modbusTCP运行的流程。
       先看main函数:
       eMBErrorCode    eStatus;
       SystemInit();
       SPI_Enc28j60_Init();
       eStatus = eMBTCPInit(502 );
       eStatus =eMBEnable(  );
       for( ;; ){
         ( void)eMBPoll(  );
         usRegInputBuf[0]++;
      }

使用特权

评论回复
13
Diyer2015|  楼主 | 2017-11-28 11:00 | 只看该作者
与modbus-RTU模式类似,只是多了对ENC28J60SPI端口的初始化函数,使用eStatus = eMBTCPInit( 502 )对TCP/IP协议栈进行初始化,进入到eMBPoll()。到这里的时候发现在   if( xMBPortEventGet(&eEvent ) == TRUE )条件一直为假,也就是事件队列没有事件,RTU模式下是定时器触发的,TCP模式下到底应该谁去触发它呢? TCP模式没有控制状态转换状态机有木有!没有东西修改队列检索事件的函数xMBPortEventGet,就不能被eMBPoll周期性地调用!这可是个大问题。

使用特权

评论回复
14
Diyer2015|  楼主 | 2017-11-28 11:00 | 只看该作者
由于网络数据包这里并不是用的中断方式,所以只能有以下解决方法:在eMBTCPStart中加上一个xMBPortEventPost(EV_FRAME_RECEIVED),那么这样进入到eMBPoll的时候就会调用eMBTCPReceiveàxMBTCPPortGetRequest去读取数据包,如果是modbus-TCP包的话就把返回MB_ENOERR,再对MBAP帧头进行判断,查看协议类型,跳过MBAP帧头,传递了正确的数据包后进入EV_EXECUTE状态就会调用相应的函数进行处理,如果不是广播帧则返回处理后的TCP数据包,调用完xMBTCPPortSendResponse再发送事件xMBPortEventPost(EV_FRAME_RECEIVED)即可。如果不是想要的TCP数据包,在xMBTCPPortGetRequest里面也加入xMBPortEventPost(EV_FRAME_RECEIVED),进入下一轮查询,这样可以响应ARP、PING命令的数据包等。

使用特权

评论回复
15
Diyer2015|  楼主 | 2017-11-28 11:01 | 只看该作者
测试下ping命令,设置开发板ip地址为222.28.40.18,与我的电脑接在同一个路由器上,可以看到返回的响应了!

3.jpg (89.13 KB )

3.jpg

使用特权

评论回复
16
Diyer2015|  楼主 | 2017-11-28 11:02 | 只看该作者
三、运行流程
       不过还不能高兴的太早,能测试通ping只能是TCP/IP的功能,那么接收以太网发来的modbus-TCP帧后是如何处理的呢?接下来分析一下,既然modbus-TCP发出来的数据包时TCP/IP包对modbus数据帧的封装,那么在调用peMBFrameReceiveCur(xMBTCPPortGetRequest)获得的数据包是带各种包头的,要把带以太网帧头、IP头、TCP头去掉才行,除此以外还需要实现TCP协议的三次握手功能,这还得在xMBTCPPortGetRequest中修改,主要去判断数据长度、是否为 arp请求、是否为空ip包、对ping命令做出响应、实现TCP协议的三次握手功能、以及获得modbus-TCP的MBAP帧头。

使用特权

评论回复
17
Diyer2015|  楼主 | 2017-11-28 11:03 | 只看该作者
通过对代码的整体分析可以得出数据流在modbus协议栈是这样流动的,如图所示:

4.jpg (73.11 KB )

4.jpg

使用特权

评论回复
18
Diyer2015|  楼主 | 2017-11-28 11:04 | 只看该作者
数据接收:
       由于在eMBPoll中数据是逐渐调用的,这里不妨从数据接收的源头开始理清思路。当一帧数据到来之后,通过enc28j60PacketReceive接收到了一个完整的TCP包,包括以太网头、IP头、TCP头以及modbusTCP/IP 应用数据单元ADU。接着被xMBTCPPortGetRequest调用去掉了各种header,然后经过eMBTCPReceive(peMBFrameReceiveCur)对MBAP的分析,将指向MBAP报文头的指针传到eMBPoll:EV_FRAME_RECEIVED中。

使用特权

评论回复
19
Diyer2015|  楼主 | 2017-11-28 11:06 | 只看该作者
数据处理:
       在eMBPoll :EV_EXECUTE状态下调用对应的处理函数,这里以读入寄存器状态为例,调用的是eMBFuncReadInputRegister,对命令内容进行分析译码,调用eMBRegInputCB 填好请求的数据。

使用特权

评论回复
20
Diyer2015|  楼主 | 2017-11-28 11:06 | 只看该作者
数据发送:
       如果不是广播包的话,需要对数据进行回复,调用eMBTCPSend(peMBFrameSendCur)填好MBAP帧头的长度信息,再传给xMBTCPPortSendResponse enc28j60PacketSend填好header以及校验发送出去。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

63

主题

1615

帖子

13

粉丝