i.MX RT1024: 移植 MODBUS TCP协议栈教程
来源:恩智浦MCU加油站发布时间:2023-05-313008浏览
询问 AIMODBUS 协议栈的下载地址:
https://www.embedded-experts.at/en/freemodbus/
测试软件QModMaster 的下载地址:
https://sourceforge.net/projects/qmodmaster/

在协议栈里有一个demo 目录,里面是modbus 被移植到各个厂商的处理器上的demo, 可惜没有i.MX RT。仔细对比各个 demo 就可以发现,原来这些 demo 有很多就是基于freertos 和 lwip 的。我们就从 MCF5235 TCP 下手,它既有freertos 又有lwip,完美符合我们的项目。可以把它的 port 目录复制到我们项目的modbus 目录下。把其中的文件全部涵盖进来。
二、修改程序接下来首先是在 lwip_ping_freertos.c 中include “mb.h”。这个文件包含了 freemodbus 协议栈提供的所有接口函数。
1.程序结构
Lwip 协议栈为用户应用程序的编写提供 3 种编程接口:
第一种是Raw Callback API,这种方式下协议栈与用户程序间通过回调函数实现通信。而且协议栈同应用程序处在同一个进程中,彼此间的执行都会互相制约。
第二种是 Sequential API 方式,用户向内核注册回调函数,并通过直接调用内核 UDP 或TCP 相关操作函数来完成应用程序的编写。在这种方式下,协议栈内核运行于进程 tcpip_thread, 而应用程序进程也是一个单独的进程。独立的进程结构可以使协议栈和应用程序的执行互不影响。通过使用邮箱和信号量等机制,内核进程可以直接将数据递交给应用程序邮箱中然后继续执行,不必阻塞等待。
第三种是使用 BSD socket 函数进行应用程序开发。本来这是最简单的方式,但是由于BSD socket 函数在实现上高度抽象,不适合小型嵌入式TCP/IP 应用,所以 lwip 里的socket 函数并不是非常完整。
为实现与 lwip 协议栈之间的相互调用,freemodbus 协议栈采用的是sequential API 方式。具体的调用顺序如下:在 mb.c 中提供了 eMBTCPInit() 函数, 这个函数调用eMBTCPDoInit()函数, 随后层层调用下去,最后在 xMBTCPPortInit()函数中调用 tcp_bind()。这个函数是 lwip 的 tcp.c 提供的。下图反映出了 modbus 协议栈的层次结构。

最下层的tcp_bind()函数的作用是将一个连接结构与本地 IP地址addr和端口号 port 进行绑定。作为服务器端程序,执行这一步操作是必要的,服务器必须与熟知端口进行绑定才能接受客户端的连接请求。这里可以看到,freemodbus协议栈并没有以最标准的方式调用netconn_bind()进行绑定,而是直接调用 tcp_bind()。原因应该是这样做可以免去IPC 通信过程,在无操作系统的环境下也能工作。
tcp_listen()的任务是让 tcp 内核监听这个端口;tcp_accept()是为新的连接注册一个回调函数prvxMBTCPPortAccept()。在tcpip_thread()进程监听到有效连接后会回调这个函数。而这个函数会把 modbus 的数据包从 tcp 数据包中复制出来,随后发送 mailbox 信息给modbus 的进程,用来处理消息。
2. 修改接口


随后是修改porttcp.c。在这个文件中的 prvxMBTCPPortReceive()函数有一个致命问题必须被改正。这个函数是 tcpip_thread()核心进程在收到 modbus 包后的回调函数。当数据处理完成后它会调用pbuf_free()函数释放pbuf包并返回 ERR_OK。tcpip_thread()核心进程收到ERR_OK就知道数据已经处理完了,就可以放心踏实的干别的事情了。然而不知为何,这里的 prvxMBTCPPortReceive() 函数在某些时候释放了pbuf却并不返回 ERR_OK。于是tcpip_thread()核心进程在没有收到正确的返回值时会认为数据包没有被处理,会把这个实际已经废弃的包暂存下来,下次再处理。等到下次处理时就会产生 pbuf错误,使得整个程序被锁死。
以下是修改的地方:
3. 上层代码编写
接下去是为上层应用写的示例代码。以下 code 全部都是在 lwip_ping_freertos.c 中。3.1 Tcpip协议栈初始化我没有指定 IP 地址,而是由路由器来分配。

3.2Modbus协议栈初始化

这个进程首先是等 DHCP 拿到路由器给的 IP 地址,随后就可以初始化 modbus 协议,并采用轮询方式等待连接。
3.3编写各种命令对应的程序



打 开 QModMaster, Modbus 模式选 TCP;选项->Modbus TCP->从机 IP 填获得的 IP 地址,端口是 502;功能码选Read Holding Register (0x03),起始地址 100;
寄存器数量选 6,Data Format 选 16 进制;按连接按钮,QModMaster 就能和我们的板子连上,按钮变成连接起来的样子;按旁边读写按钮,就能读出register的值(见程序vTask_HoldingRegister())。一切顺利,移植成功!
QModMaster有一个总线监视器窗口,可以看到收发的数据。
恩智浦MCU加油站
这是由恩智浦官方运营的公众号,着重为您推荐恩智浦MCU的产品信息、开发技巧、教程文档、培训课程等内容。

长按二维码,关注我们
END
更多恩智浦AI-IoT市场和产品信息,邀您同时关注“NXP客栈”微信公众号

NXP客栈
恩智浦致力于打造安全的连接和基础设施解决方案,为智慧生活保驾护航。
长按二维码,关注我们
新闻来源:恩智浦MCU加油站,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
