基于serialX串口驱动移植freemodbus

关于 serialx
之前,笔者写过多篇 serialx 的文章,已经把它的原理和理念完完全全明明白白讲了,包括它的优势以及使用它需要注意的方面和可能遇到的问题。
到目前为止,笔者只介绍了 finsh/msh 使用 serialx ,实现了中断接收中断发送模式打开串口设备,这次尝试让笔者坚信了即便使用 dma 收发也能在 finsh 里应付自如。今天我们尝试一下在 freemodbus 里使用 serialx 。
注:笔者写过文章,讨论在控制台或者 finsh 里完全的中断收发或者完全的 dma 收发会有哪些问题。有兴趣的大家可以找找看。
测试环境
笔者手里有一块儿 nk-980iot v1.0 的开发板,这是去年 rt-thread 论坛搞的测评活动送的。一方面,它吃灰很久了,另一方面,serialx 的驱动很久没新增了。趁此机会,笔者把 nuc980 的 serialx 驱动加上,然后在此基础上调试出 console 和 finsh,最后移植 freemodbus 试试会遇到什么问题。
nuc980 serialx 驱动
之前,笔者介绍 serialx 的时候,曾详细的讲解过 struct rt_uart_ops 接口中的每一个函数的功能。完全按照每一个函数功能定义去做,后面的事情就是水到渠成的。
花了小半天的时间从 drv_uart.c 改成 drv_uartx.c 。
然后使用 serialx 中提供的 测试程序 serialx_test.c 简单测试了一下,收发回环没发现严重问题(没有数据丢失,没有数据错误)。
启用控制台和 finsh 。改成“中断收发读写”模式,试了几个命令,打印结果完整。
可以说,验证了这次写的驱动还是很不错的,是成功的。
启用 freemodbus
env 环境里启用 freemodbus,打开 modbus master 并添加 master 的测试程序。
使用命令 pkgs --update 下载 freemodbus 源码。
打开项目后,我们可以看到 “sample_mb_master.c” “port” 开头的以及几个 “mb” 开头的。”sample_mb_master.c” 文件是测试样例程序,”port” 开头的文件是 freemodbus 在 rt-thread 系统上的接口,剩余的是 freemodbus 的核心程序。
这里面 “sample_mb_master.c” “portserial_m.c” 这两个文件是今天我们最关心的。我们可以打开看看这俩文件都做了啥。
“sample_mb_master.c”
这个文件的主要工作是创建了两个线程。
一个用来循环调用 embmasterpoll 函数,这个函数是 freemodbus 工作引擎。不循环执行这个函数 freemodbus 跑不起来。
另一个模式应用层线程,用来发送多寄存器写请求(在这个样例程序里只用这个来做演示)。
“portserial_m.c”
这个文件里是串口设备操作相关的。比如初始化、打开、关闭,写数据到串口设备,从串口设备读数据等等。
xmbmasterportserialinit
笔者第一次打开这个文件,满眼看到的 serial->config. serial->ops->configure 等操作把我惊呆了。
初始化过程最后创建了子线程 serial_soft_trans_irq ,它的工作就是发送数据。
serial_soft_trans_irq
发送子线程入口函数,这个函数只关心 event_serial_trans_start 事件,接收到事件后调用 xmbmasterrtutransmitfsm 函数,如果数据没发送完,调用 xmbmasterportserialputbyte->xmbmasterportserialputbyte 往串口写 1 个字节数据。循环执行直到把所有需要发送的数据写完。
第一次测试
经过简单浏览后,笔者想尽快运行程序,做进一步观察。
计算机端,笔者用一个 qt5 写的 modbus slave 终端。如果一切顺利,nk-980iot 开发板上发的多寄存器写请求会对 qt5 modbus slave 终端里的数据修改,并界面上显示到数据变化。
很幸运,笔者这一步也很顺利。
进一步测试
因为自带的 master 样例程序仅仅测试了一个多寄存器写,而且,我们可以看到原来只对 embmasterreqwritemultipleholdingregister 返回错误代码计数,并没有区分判断会出现哪种错误,我们把这个错误码处理一下
rt_uint32_t err_no = 0, err_reg = 0, err_arg = 0, err_data = 0, err_tout = 0;switch (error_code) {case mb_mre_no_err: err_no++;break;case mb_mre_no_reg: err_reg++;break;case mb_mre_ill_arg: err_arg++;break;case mb_mre_rev_data: err_data++;break;case mb_mre_timedout: err_tout++;break;default: rt_kprintf(n:%d; r:%d; a:%d; d:%d; t:%dn, err_no, err_reg, err_arg, err_data, err_tout);break;}这几种返回码,只有 mb_mre_no_err 是正常和完美的,如果出现其它几个都是有问题的。
笔者经过关闭 slave 端,可以测试出现 mb_mre_timedout 。但是,经常还会出现 mb_mre_rev_data 这个错误!!!
mb_mre_rev_data 错误跟踪
这个错误字面含义是,有接收数据,但是接收的数据有异常!我们上面的测试只有一个“多寄存器写”,master 发写请求了以后,只有可能收到成功响应(mb_mre_no_err),或者错误响应信息(mb_mre_no_reg mb_mre_ill_arg)。不应该会出现 slave 给 master 主动发请求的。
为了观察 slave 端接收和发送数据,笔者计划把 slave 端的调试打开,检查一下它是不是回复响应出现错误了。
因为笔者的 slave 端是 qt 写的,当前使用的这个 exe 是很久之前用 qt5.9 版本编译出来的。笔者需要重新编译一下这个 exe 程序(现在安装的版本有 qt5.12 qt5.15 两个)。编译出来新程序之后,笔者得到如下调试信息(无论是 qt5.12 还是 qt5.15),
qt.modbus.lowlevel: (rtu server) received adu: 01100000000408000f0049000200003574
qt.modbus: (rtu server) request pdu: 0x100000000408000f004900020000
qt.modbus: (rtu server) response pdu: 0x1000000004
qt.modbus.lowlevel: (rtu server) response adu: 011000000004c1ca
qt.modbus.lowlevel: (rtu server) received adu: 011000000004080013004b000200009175
qt.modbus: (rtu server) request pdu: 0x1000000004080013004b00020000
qt.modbus: (rtu server) response pdu: 0x1000000004
qt.modbus.lowlevel: (rtu server) response adu: 011000000004c1ca
qt.modbus.lowlevel: (rtu server) received adu: 011000000004080027004d000200006cb6
qt.modbus: (rtu server) request pdu: 0x1000000004080027004d00020000
qt.modbus: (rtu server) response pdu: 0x1000000004
qt.modbus.lowlevel: (rtu server) response adu: 011000000004c1ca
slave 断得到了完整正确的 adu,响应的 adu 也是正常的。接下来再回过头看看开发板接收响应 adu 时得到的数据是什么。
在 embmasterrtureceive 函数体内 if 条件语句之前添加一句代码 od_mem((unsigned int)ucmasterrturcvbuf, (unsigned int)ucmasterrturcvbuf + usmasterrcvbufferpos);。这句代码将在控制台打印输出接收到的 adu 。
笔者看到了很多 “01040004 70f9c1” “01040004 70f9f9” 或者其它数据,但是正确的应该是 “01100000 0004c1ca”,很多次才出现一次正确的。为什么从串口发出去的数据是正确的,能被远端正常接收,远端响应回来的数据就出现接收错误了?!
为了确定开发板和驱动工作还是正常的,笔者又切换到 serialx 的测试程序,用收发回环测试一遍没有问题,再测试 freemodbus 接收仍然异常!
修改 xmbmasterportserialinit
前边笔者说过,当看到 serial->config. serial->ops->configure 等操作时惊呆了。这一步,让上帝的归上帝吧。
serial_dev = rt_device_find(uart_name);if(serial_dev == rt_null){ /* can not find uart */ return false;}/* set serial configure parameter */uart_conf.baud_rate = ulbaudrate;/* set serial configure */rt_device_control(serial_dev, rt_device_ctrl_config, &uart_conf);/* open serial device */if (!rt_device_open(serial_dev, rt_device_oflag_rdwr | rt_device_flag_int_rx | rt_device_flag_int_tx)) { rt_device_set_rx_indicate(serial_dev, serial_rx_ind);} else { return false;}全局变量 static struct rt_serial_device *serial; 改成 static rt_device_t serial_dev = rt_null;。所有使用 serial 这个变量的地方全换成使用 serial_dev 。
这样修改了以后,一切变得明朗了起来。
总结
除了上面提到的那个问题,笔者还遇到一种情况,slave 端接收串口数据经常出现断帧,而且字符接收间隔长达 10+ms 。这种情况,当笔者将 rt_device_write(serial_dev, 0, pucbyte, 1); 改成 rt_device_write(serial_dev, 0, pucbyte, sz); 后得到解决。但是,之后再也没复现。
得益于 serialx 的框架理念,我们可以从“一个字节一个字节的写”提升到“写一批字节”。而且放到发送缓存里的数据,无论使用中断发送还是 dma 发送都不会对应用层带来任何压力。
另一方面,读 modbus adu 的时候,其实也可以“读一批字节”。不是一个字节中断,read 一个字节,下一个接收字节中断,再 read 一个字节…
freemodbus 还有很多可圈可点的地方。但是拿它来验证 serialx 驱动可能是最简单的一个了。
最后,笔者把修改过后的 ‘portserial_m.c’ 文件上传上来,但是请大家注意,不止这一个文件需要修改,但是其它修改都是因为这个文件引起的。

N930X音乐芯片在儿童学步车上的应用
用于下一代汽车无刷直流系统的汽车功率集成模块
荣耀30系列影像升级,AI精彩瞬间三大功能即将上线
盛思锐SCD30智能传感器进行室内空气质量监测
如何做到用铜线控制远端负载的电压
基于serialX串口驱动移植freemodbus
Fluke Calibration 5322A 电器安全测试仪具有更好的防泄漏设计
俄罗斯开发出首个量子计算机电源 质量不亚于国外同类产品
电源适配器实现高效率低功耗才是硬道理,这颗IC就能搞定
运算放大器应用电路之积分电路
Linux 5.6将支持速度高达40Gbps的USB4
大华视频图像信息数据库的特点和应用解决方案分析
高通发布Wi-Fi 6E芯片 可同时支持超过2,000个用户的平台
无需兼容汽车、手机独立运行的Android Auto:让你的车秒变”特斯拉“
爱立信和中国电信完成与现有4G基站的互操作等基本业务验证
华为mate9和华为p10哪个好?华为mate9和华为p10评测对比
企业是如何解决数据存储的这一难题的
如何制作一个usb双母头接口
基于磁性连接的自密封模块化PDMS微流控系统设计实现
RTX2080Super性能将超过顶级半专业卡TitanXp