关于 serialX
上一篇我们基于 serialX 驱动,移植 freemodbus 并填了几个坑。今天换 libmodbus 看看会遇到什么。
测试环境
开发板: NK-980IOT V1.0 的开发板
rt-thread 版本:4.1.1
IDE:keil + env
NUC980 serialX 驱动
之前,笔者介绍 serialX 的时候,曾详细的讲解过 struct rt_uart_ops 接口中的每一个函数的功能。完全按照每一个函数功能定义去做,后面的事情就是水到渠成的。
花了小半天的时间从 drv_uart.c 改成 drv_uartX.c 。
然后使用 serialX 中提供的 测试程序 serialX_test.c 简单测试了一下,收发回环没发现严重问题(没有数据丢失,没有数据错误)。
启用控制台和 finsh 。改成“中断收发读写”模式,试了几个命令,打印结果完整。
可以说,验证了这次写的驱动还是很不错的,是成功的。
启用 libmodbus
env 环境里启用 libmodbus,使能 “Enable libmodbus rtu mode” rtu 模式并使能 “Enable rtu example” rtu 样例程序。
使用命令 pkgs --update 下载 libmodbus 源码。
libmodbus 的源码文件数量比 freemodbus 少很多了。看起来清晰很多。
第一次试编译
生成项目后先尝试编译一次,出现了很多错误。
首先,第一类错误是:
error: #20: identifier "EBADF" is undefined
error: #20: identifier "ECONNRESET" is undefined
...
这几个宏定义在 “errno.h” 头文件,这个文件呢,在某个版本移动过路径,没记错的话也是从 4.0.3 之后开始的。现在的用法是 #include <sys/errno.h>
modbus-rtu.c 修改 #include <errno.h> -> #include <sys/errno.h>
modbus.c 修改 #include <errno.h> -> #include <sys/errno.h>
然后,第二种错误是:
error: #20: identifier "fd_set" is undefined
warning: #223-D: function "FD_ZERO" declared implicitly
warning: #223-D: function "FD_SET" declared implicitly
这几个定义在 “sys/select.h” ,可以 #include <sys/select.h> ,也可以 #include <dfs_select.h>,因为后者文件也只有一行内容就是 #include <sys/select.h>。
modbus.c 添加 #include <sys/select.h>
至此,应该是把所有版本升级引起的兼容错误修正了。
第一次运行测试程序
开始跑测试程序之前,去掉 RS485 的所有相关代码。添加 libmodbus 的 debug modbus_set_debug(ctx, TRUE);。这可以让我们看到很多的有用信息。
和上次的 freemodbus 样例程序不一样的是,这次是读保持寄存器,并不是写寄存器。
测试开始以后,笔者在控制台终端里看到如下信息,
[01][03][00][00][00][0A][C5][CD]
Waiting for a confirmation...
msh >ERROR Unknown error: select
[ 0][read num = -1]<0><0><0><0><0><0><0><0><0><0>
而,在 slave 端的调试信息是:
qt.modbus.lowlevel: (RTU server) Received ADU: "01030000000ac5cd"
qt.modbus: (RTU server) Request PDU: 0x030000000a
qt.modbus: (RTU server) Response PDU: 0x031400110022003300440055006600770088009900aa
qt.modbus.lowlevel: (RTU server) Response ADU: "01031400110022003300440055006600770088009900aa4584"
分析两端的调试信息, slave 端既接收到了请求 ADU,也正常回复了响应 ADU。但是 libmodbus 的 master 端并没有收到任何响应!
问题定位过程
首先确定串口接收中断没触发,也就是给开发板发的数据并没有接收中断;
调出串口外设寄存器发现,中断使能寄存器的值变成 0 了!串口中断使能寄存器被某个力量情况复位了!
再次回到串口驱动测试程序,笔者很确定是使用的中断接收模式,并且可以有接收中断。
问题好像很明显,libmodbus 在打开串口外设的过程中,某些操作清理了串口外设寄存器。
经过详细比对,libmodbus 打开串口设备后使用 termios 配置串口设备的波特率数据位等。而笔者上次在测试 posix 接口时使用的默认配置,并没有修改波特率。为了验证并跟踪问题位置,笔者在 “serialX_posix.c” 里添加 termios 配置函数。
经过一步步代码调试跟踪,得到函数调用链 tcsetattr -> ioctl -> fcntl -> dfs_file_ioctl -> serial_fops_ioctl -> rt_device_control -> rt_serial_control -> nu_uart_configure ,最后这个函数有一句 nu_sys_ip_reset(((nu_uart_t)serial)->rstidx); ,这句会复位中断寄存器的值。
第二次运行测试程序
注释掉这句代码后,再跑 libmodbus 样例程序,
[01][03][00][00][00][0A][C5][CD]
Waiting for a confirmation...
<01><03><14><00><11><00><22><00><33><00><44><00><55><00><66><00><77><00><88><00><99><00><45><84>
[ 11][read num = 10]<0x11><0x22><0x33><0x44><0x55><0x66><0x77><0x88><0x99><0xaa>
终于看到想要的信息了。
问题复盘
使用过 rt-thread 串口驱动框架的人都知道,rt_device_find rt_device_control rt_device_open 是读写串口设备前的三步曲,这三个步骤顺序的不能乱的。rt_device_control和rt_device_open执行先后的问题
也就是说,配置串口波特率数据位等必须在打开设备之前!
我们再看 libmodbus 的做法,先 open ,然后调用 tcsetattr … -> rt_device_control。
那么,笔者删掉的那句代码是错误的吗?好像也不能这么说,它是“有用的”,可能它是出现在了不该出现的地方。
结束语
使用 libmodbus 的代价是很高的,从前边提到过的函数调用过程,就够人头疼的
但是,libmodbus 更适合系统,它只用了一个线程就能跑起来。freemodbus 用了三个(意味着更多的线程栈耗用)。
这次测试,我们发现了两处升级引入的版本兼容错误,以及 open configure 顺序引起的外设工作异常的问题。
原作者:出出啊