教程简介 MODBUS是一种基于主从结构的控制协议,由一个主机和多个从机组成。 MODBUS是施耐德电气于1979年为可编程逻辑控制器(PLC)通信所开发并发布的协议,至今已有相当长的历史。 以前它主要基于串口,但由于物联网的发展,基于TCP的MODBUS现在被越来越广泛地使用。恩智浦的i.MX RT 系列控制器的SDK中没有相关的demo,本文的目标是向大家介绍如何在SDK中的lwip_ping_freertos例程基础上添加MODBUS TCP。
MODBUS 协议栈的下载地址:
https://www.embedded-experts.at/en/freemodbus/
测试软件QModMaster 的下载地址:
https://sourceforge.net/projects/qmodmaster/
我在本次教程使用了MIMXRT1024-EVK 作为硬件平台,MIMXRT1024-EVK 和电脑都连接到一台路由器组成一个测试网络。使用 MCUXpresso IDE 作为开发环境,MIMXRT1024-EVK SDK 版本是 2.12.1。 一、文件复制
下载完协议栈后,先解压,随后将其中的 modbus 目录完整复制到 lwip_ping_freertos 工程目录下,添加 modbus 及其所有子文件夹到工程的 include paths 中。
在协议栈里有一个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.2 Modbus协议栈初始化
这个进程首先是等 DHCP 拿到路由器给的 IP 地址,随后就可以初始化 modbus 协议,并采用轮询方式等待连接。
3.3 编写各种命令对应的程序
三、验证 连接所有线路后编译下载。我们的程序会在串口打印出路由器给它分配的 IP 地址。
打 开 QModMaster, Modbus 模式选 TCP;
选项->Modbus TCP->从机 IP 填获得的 IP 地址,端口是 502;功能码选Read Holding Register (0x03),起始地址 100;
寄存器数量选 6,Data Format 选 16 进制; 按连接按钮,QModMaster 就能和我们的板子连上,按钮变成连接起来的样子;按旁边读写按钮,就能读出register的值(见程序vTask_HoldingRegister())。 一切顺利,移植成功!
QModMaster有一个总线监视器窗口,可以看到收发的数据。
审核编辑 :李倩
全部0条评论
快来发表一下你的评论吧 !