RT-Thread 驱动篇 之 serialX 全网公测

19503fe1a99cb2cd1d406480c7a686a3.png


作者:RT-Thread论坛 出出啊

前言

“serialX” 我起的名字,起个名字想破头。

在前一篇文章里,大致提出了我的串口驱动框架理论。里面做了一些对串口驱动特性的幻想。也在 NUC970 芯片下通过了中断模式的实践验证。但是,因为 NUC970 的 uart 自带 fifo 。用它测试效果好,并不能真正说明驱动框架通过验证了。

然后,紧接着笔者在 STM32F429 完成了中断和 DMA 两种模式。今天,我把一些测试结果和移植说明发出来,征求全网公测。

测试配置:DMA 二级缓存 32 个字节,串口收发缓存各 512 字节。

注:本串口驱动工作特性请参阅前一篇文章rt-thread 驱动篇 之 串口驱动框架剖析性能提升

STM32 中断模式测试

以下是三组连续发收测试:

1、定时间隔20ms,发送250字节数据,持续发送2600w,接收发送数据量相等

3fd38f929268eb992165fdad927c535c.png

2、定时间隔50ms,发送250字节数据,持续发送600w,接收发送数据量相等

46d126f88be65a31c749109597b5686f.png

3、定时间隔80ms,发送1000字节数据,持续发送600w,接收发送数据量相等

e5b310f55ae65aece6303077c6af3f30.png

注:刚刚跟我们小伙伴求证了一下,串口调试助手的定时间隔是固定周期。如果是这样的,以上测试是有意义的,如果不是,那就没达到串口带宽上限。

STM32 DMA模式测试

1、读写测试,串口调试助手定时 10ms ,发送40字节数据,持续发送129w

911f4cfceb53a66311e18fc25ebb81ac.png

2、串口调试助手定时 50ms ,发送500字节数据,持续发送527w

ed7f19d61d6a0e41882559a6dd406428.png

3、串口调试助手定时 40ms ,发送500字节数据,持续发送261w

ce414beebbf2a09a87cb3a970f88c7c6.png

4、串口调试助手定时 40ms ,发送1000字节数据,持续发送262w

4d70e9759688e5e67787ac623ffa0135.png

串口调试助手上发送和接收数量不相等,接着我在代码中添加了个断点,单独发送了一个字节 ‘Z’ 。

b735dada32f0ff5c4deee861679d4534.png

代码中接收和发送数量相等,都等于串口调试助手的接收量。这个缺少的部分是串口调试助手发送失败数量,还是串口驱动接收丢失了?

接下来,修改成中断接收发送模式,其它不做修改进行相同的测试,也是有数量差。进一步检查串口驱动里,接收缓存有溢出现象。应用层没来得及把数据取走,就删掉了最旧的数据。

接口详解及移植说明

rtdef.h 添加几个宏定义

添加一个强制 inline 宏定义

#define rt_forceinline static __attribute__((always_inline))

添加阻塞打开相关标志()

1#define RT_DEVICE_OFLAG_BLOCKING        0x000           /**< blocking io mode */
2#define RT_DEVICE_OFLAG_NONBLOCKING     0x004           /**< non-blocking io mode */
3
4...
5
6#define RT_DEVICE_CTRL_BLOCKING         0x05            /**< blocking io */
serialX.h

添加串口驱动缓存和 DMA 二级缓存大小定义(放弃使用 RT_SERIAL_RB_BUFSZ):

1#ifndef RT_SERIAL_FIFO_BUFSZ
2#define RT_SERIAL_FIFO_BUFSZ            512
3#endif
4
5#ifndef RT_SERIAL_DMA_BUFSZ
6#define RT_SERIAL_DMA_BUFSZ             32
7#endif

串口接收和发送使用的缓存大小是一样的,如果想改变串口缓存大小,请修改 RT_SERIAL_FIFO_BUFSZ 的值。

如果想改变 DMA 二级缓存大小,请修改 RT_SERIAL_DMA_BUFSZ 的值。

定义一个收发通用 fifo:

1struct rt_serial_fifo
 2{
 3    rt_uint32_t buf_sz;
 4    /* software fifo */
 5    rt_uint8_t *buffer;
 6
 7    rt_uint16_t put_index, get_index;
 8
 9    rt_bool_t is_full;
10};

重新定义 rt_serial_device 定义:

1struct rt_serial_device
 2{
 3    struct rt_device          parent;
 4
 5    const struct rt_uart_ops *ops;
 6    struct serial_configure   config;
 7
 8    void *serial_rx;            // 串口接收缓存
 9    void *serial_tx;            // 串口发送缓存
10
11#ifdef RT_SERIAL_USING_DMA                            // 串口收发缓存和 DMA 使用的二级缓存分开
12    rt_size_t dma_idx_rx;
13    rt_uint8_t serial_dma_rx[RT_SERIAL_DMA_BUFSZ];    // DMA 接收缓存
14    rt_uint8_t serial_dma_tx[RT_SERIAL_DMA_BUFSZ];    // DMA 发送缓存
15#endif
16
17    cb_serial_tx _cb_tx;        // 写过程回调函数指针
18    cb_serial_rx _cb_rx;        // 读过程回调函数指针
19
20    struct rt_completion completion_tx;                // 发送完成
21    struct rt_completion completion_rx;                // 接收到新数据
22};
23typedef struct rt_serial_device rt_serial_t;

串口驱动通用框架和硬件底层接口定义

1struct rt_uart_ops
 2{
 3    // 用于配置外设寄存器,引脚功能复用,启用外设等等
 4    rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
 5    // 用于使能禁用中断,初始配置 DMA
 6    rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
 7    // 串口外设写数据寄存器*为空*,把数据放入写数据寄存器。*不为空*,死等
 8    int (*putc)(struct rt_serial_device *serial, char c);
 9    // 串口外设读数据寄存器*不为空*,读出读数据寄存器的值。*为空*,返回 -1
10    int (*getc)(struct rt_serial_device *serial);
11
12    // 启动发送,多数是开启串口外设发送寄存器空中断
13    void (*start_tx)(struct rt_serial_device *serial);
14    // 结束发送,多数是关闭串口外设发送寄存器空中断
15    void (*stop_tx)(struct rt_serial_device *serial);
16
17#ifdef RT_SERIAL_USING_DMA
18    // 判断 DMA 是否在发送过程中,就像上一篇里笔者多次提示的,必须有效检测 DMA 是否在发送数据中
19    rt_bool_t (*is_dma_txing)(struct rt_serial_device *serial);
20    // 启动 DMA 发送
21    void (*start_dma_tx)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size);
22    // 停止 DMA 发送
23    void (*stop_dma_tx)(struct rt_serial_device *serial);
24#endif
25    // 使能串口外设中断
26    void (*enable_interrupt)(struct rt_serial_device *serial);
27    // 禁用串口外设中断
28    void (*disable_interrupt)(struct rt_serial_device *serial);
29};

移植 serialX 到新芯片上,必须按照 rt_uart_ops 的定义实现上述几个接口。函数功能不能随意更改。

rt_hw_serial_isr

这个中断只接收 RT_SERIAL_EVENT_RX_IND RT_SERIAL_EVENT_RX_IND RT_SERIAL_EVENT_RX_DMADONE RT_SERIAL_EVENT_TX_DMADONE 四种中断状态。

  • RT_SERIAL_EVENT_RX_IND 接收寄存器不空中断

  • RT_SERIAL_EVENT_TX_DONE 发送寄存器空中断,为了兼容自带 fifo 的芯片,event 参数的高三字节代表 fifo 容量

  • RT_SERIAL_EVENT_RX_DMADONE 串口接收 DMA 中断。这个可以兼容接收半传输和全传输等多种中断。event 参数的高三字节代表 DMA fifo 接收数据数量(1-RT_SERIAL_DMA_BUFSZ)。

  • RT_SERIAL_EVENT_TX_DMADONE 串口发送 DMA 中断。这个应该保证 DMA 发送完本次 DMA 缓存中的所有数据,也就是对于 stm32 芯片是 DMA 计数达到 0。

使用注意

  • RT_SERIAL_FIFO_BUFSZ RT_SERIAL_DMA_BUFSZ 两个的定义和实际是否合适,小数据量通信可以定义小点儿,数据量大的情况适当调整这两个值。

  • rt_uart_ops 接口定义,功能实现必须匹配。

  • 阻塞模式,收发是一致的。默认是阻塞模式。想使用非阻塞模式请 open 的时候添加 RT_DEVICE_OFLAG_NONBLOCKING flag。

  • 使用 RT_DEVICE_FLAG_INT RT_DEVICE_FLAG_DMA_RX RT_DEVICE_FLAG_INT_TX RT_DEVICE_FLAG_DMA_TX 四个 open flag 指定收发模式,是用中断还是 DMA。

  • 特别提醒,非阻塞模式下,read 可能返回 0。write 返回值可能不是目标写入 size。read/write 还可能返回 RT_EXXX 错误值。

  • 特别提醒,阻塞模式下,read 返回值可能不是期望数据量 size。笔者也曾经提供过可靠处理流数据的方案,详见 rt-thread 使用宝典(2021-1210更新)

使用完成量进入阻塞漏洞分析
PS: 谢谢  @HelloBye  的及时纠正, rt_completion  不存在本小节描述的漏洞。各位看官可以直接跳过本小节了。

串口驱动里有几个阻塞点,进入阻塞都使用的 rt_completion ,如下代码:

1serial->ops->enable_interrupt(serial);
2ret = rt_completion_wait(&(serial->completion_rx), RT_WAITING_FOREVER);// 或者 serial->completion_tx

首先开中断,调用 rt_completion_wait 等待完成量进入阻塞。这样是有个漏洞的,当开中断后有个串口中断,中断处理函数里调用 rt_completion_done 是没有任何反应的,rt_completion_done 直接返回退出。

进而回到原线程才执行 rt_completion_wait。之后,如果有第二次接收(或发送)中断发生时才会结束上一次的阻塞。但是,第二次什么时候出现也就是个未知数了。即便前一次可能已经收全了全部想要的数据,但是会不定期阻塞下去。

解决方法有两个:一、不用永久阻塞,换成 10ms 或者几 ms 等待;二、用二值信号量替代。

但是!!!我没有用上述方法中的任何一个进行改进,原因是:

第一种方法无疑要引入一个循环,rt_completion_wait 超时返回的时候循环继续阻塞。还有就是等待时间没有理论支持。最重要的是用循环方式补漏洞的方式不美观。

没使用二值信号量的原因是,rt-thread 的信号量实现没有真正的“二值”,如果中断已经多次 release 了,然后应用层才来一次 take,之后还可能成功 take 多次(虽然应该是要阻塞的,但是实际不阻塞,反而会循环 take 多次)。

结束语

现笔者将打码开放出来 gitee 仓库 serialX,求全论坛公测。期待各位大佬提出各种测试方案对它蹂躏。

有问题可以在仓库里提 issue ,或者到 rt-thread 官方论坛上进行讨论。

267304af878a6a6bf63557619fb6365c.png

2cbbfe3a9a2a13d5962b65490207dfa4.gif

👇 点击阅读原文进入论坛

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值