0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

rt-thread 驱动篇(二) serialX 理论实现

出出 来源:出出 作者:出出 2022-06-22 09:03 次阅读

前言

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

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

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

注:本串口驱动工作特性请参阅前一篇文章rt-thread 驱动篇(一) serialX 框架理论

STM32 中断模式测试

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

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

250-20ms-2600w.png

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

250-50ms-600w.png

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

1000-80ms-610w.png

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

STM32 DMA模式测试

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

dma-40-10ms-129w.png

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

dma-500-50ms-527w.png

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

dma-500-40ms-261w.png

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

dma-1000-40ms-262w.png

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

dma-1000-40ms-262w-rw.png

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

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

接口详解及移植说明

rtdef.h 添加几个宏定义

添加阻塞打开相关标志

#define RT_DEVICE_OFLAG_BLOCKING        0x000           /**< blocking io mode */
#define RT_DEVICE_OFLAG_NONBLOCKING     0x004           /**< non-blocking io mode */
...

#define RT_DEVICE_CTRL_BLOCKING         0x05            /**< blocking io */

serialX.h

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

#ifndef RT_SERIAL_FIFO_BUFSZ
#define RT_SERIAL_FIFO_BUFSZ            512
#endif
#ifndef RT_SERIAL_DMA_BUFSZ
#define RT_SERIAL_DMA_BUFSZ             32
#endif

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

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

定义一个收发通用 fifo:

struct rt_serial_fifo
{
   rt_uint32_t buf_sz;
   /* software fifo */
   rt_uint8_t *buffer;
   rt_uint16_t put_index, get_index;

   rt_bool_t is_full;
};

重新定义 `rt_serial_device` 定义:

struct rt_serial_device
{
   struct rt_device          parent;
   const struct rt_uart_ops *ops;
   struct serial_configure   config;

   void *serial_rx;   // 串口接收缓存
   void *serial_tx;   // 串口发送缓存

#ifdef RT_SERIAL_USING_DMA       // 串口收发缓存和 DMA 使用的二级缓存分开
   rt_size_t dma_idx_rx;
   rt_uint8_t serial_dma_rx[RT_SERIAL_DMA_BUFSZ]; // DMA 接收缓存
   rt_uint8_t serial_dma_tx[RT_SERIAL_DMA_BUFSZ]; // DMA 发送缓存
#endif

   cb_serial_tx _cb_tx;  // 写过程回调函数指针
   cb_serial_rx _cb_rx;  // 读过程回调函数指针

   struct rt_completion completion_tx;    // 发送完成
   struct rt_completion completion_rx;    // 接收到新数据
};
typedef struct rt_serial_device rt_serial_t;

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

struct rt_uart_ops
{
// 用于配置外设寄存器,引脚功能复用,启用外设等等
   rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
   // 用于使能禁用中断,初始配置 DMA
   rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
// 串口外设写数据寄存器*为空*,把数据放入写数据寄存器。*不为空*,死等
   int (*putc)(struct rt_serial_device *serial, char c);
   // 串口外设读数据寄存器*不为空*,读出读数据寄存器的值。*为空*,返回 -1
   int (*getc)(struct rt_serial_device *serial);
// 启动发送,多数是开启串口外设发送寄存器空中断
   void (*start_tx)(struct rt_serial_device *serial);
// 结束发送,多数是关闭串口外设发送寄存器空中断
   void (*stop_tx)(struct rt_serial_device *serial);

#ifdef RT_SERIAL_USING_DMA
// 判断 DMA 是否在发送过程中,就像上一篇里笔者多次提示的,必须有效检测 DMA 是否在发送数据中
   rt_bool_t (*is_dma_txing)(struct rt_serial_device *serial);
   // 启动 DMA 发送
   void (*start_dma_tx)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size);
   // 停止 DMA 发送
   void (*stop_dma_tx)(struct rt_serial_device *serial);
#endif
// 使能串口外设中断
   void (*enable_interrupt)(struct rt_serial_device *serial);
   // 禁用串口外设中断
   void (*disable_interrupt)(struct rt_serial_device *serial);
};

移植 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 使用宝典(2022-0516更新)

使用完成量进入阻塞漏洞分析

> PS: 谢谢 @HelloBye 的及时纠正,`rt_completion` 不存在本小节描述的漏洞。各位看官可以直接跳过本小节了

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

serial->ops->enable_interrupt(serial);
ret = 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]( https://gitee.com/thewon/serialX ),求全论坛公测。期待各位大佬提出各种测试方案对它蹂躏。

有问题可以在仓库里提 [issue]( https://gitee.com/thewon/serialX/issues ) ,或者到 rt-thread 官方论坛上进行讨论。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • STM32
    +关注

    关注

    2239

    文章

    10668

    浏览量

    348677
  • 中断
    +关注

    关注

    5

    文章

    884

    浏览量

    41020
  • 串口驱动
    +关注

    关注

    2

    文章

    78

    浏览量

    18349
  • RT-Thread
    +关注

    关注

    31

    文章

    1148

    浏览量

    38861
收藏 人收藏

    评论

    相关推荐

    RT-Thread记录(二、RT-Thread内核启动流程)

    在前面我们RT-Thread Studio工程基础之上讲一讲RT-Thread内核启动流程.
    的头像 发表于 06-20 00:30 4394次阅读
    <b class='flag-5'>RT-Thread</b>记录(二、<b class='flag-5'>RT-Thread</b>内核启动流程)

    RT-Thread NUC97x 移植 LVGL

    不涉及 rt-thread 驱动,但是它是 LVGL 和 rt-thread 的接口。LVGL 在 rt-thread 上运行的基石。
    发表于 07-08 09:37 1278次阅读

    从0到1教你写RT-Thread之新建工程

    仿真,然后我们再开始一步一步地教大家把 RT-Thread 内核从 0 到 1 写出来,让大家彻底搞懂 RT-Thread的内部实现和设计的哲学思想。最后我们再把 RT-Thread
    发表于 11-18 14:17 1164次阅读

    RT-Thread ssd1306驱动

    RT-Thread 驱动ssd1306
    的头像 发表于 04-21 10:08 26.4w次阅读
    <b class='flag-5'>RT-Thread</b> ssd1306<b class='flag-5'>驱动</b>

    【原创精选】RT-Thread征文精选技术文章合集

    软件包)NO2 专栏作者 :出出简介:rt-thread 研究。1. rt-thread 驱动rt-thread
    发表于 07-26 14:56

    RT-Thread编程指南

    RT-Thread编程指南——RT-Thread开发组(2015-03-31)。RT-Thread做为国内有较大影响力的开源实时操作系统,本文是RT-Thread实时操作系统的编程指南
    发表于 11-26 16:06 0次下载

    RT-Thread开发,如何有效学习RT-Thread的五个步骤

    RT-Thread推出RT-Thread Inside战略开放RT-Thread开发平台授权合作,与硬件十万个为什么合作首次推出第一款RT-Inside的开发板——iBox物联网开发套
    的头像 发表于 09-25 09:55 3.4w次阅读
    <b class='flag-5'>RT-Thread</b>开发,如何有效学习<b class='flag-5'>RT-Thread</b>的五个步骤

    RT-Thread Studio驱动SD卡

    总结前言硬件平台:RT-Thread ART-Pi STM32H750XBH6开发板 H750开发板开发软件:RT-Thread Studio参考博客:RT-Thread Studio使用SDIO
    发表于 12-27 19:13 20次下载
    <b class='flag-5'>RT-Thread</b> Studio<b class='flag-5'>驱动</b>SD卡

    RT-Thread AI kit开源:轻松实现一键部署AI模型至 RT-Thread

    RT-AK 是 RT-Thread 团队为 RT-Thread 实时操作系统所开发的 AI 套件,能够一键将 AI 模型部署到 RT-Thread 项目中,让用户可以 在统一...
    发表于 01-25 18:18 3次下载
    <b class='flag-5'>RT-Thread</b> AI kit开源:轻松<b class='flag-5'>实现</b>一键部署AI模型至 <b class='flag-5'>RT-Thread</b>

    RT-Thread开源作品秀】基于RT-Thread的星务平台研究

    本作品为了验证星务软件在RT-Thread系统运行的可行性,底层是否能够驱动星务软件,同时扩展RT-Thread应用范围。ART-Pi作为卫星下位机,...
    发表于 01-25 18:26 5次下载
    【<b class='flag-5'>RT-Thread</b>开源作品秀】基于<b class='flag-5'>RT-Thread</b>的星务平台研究

    RT-Thread全球技术大会:RT-Thread对POSIX的实现情况介绍

    RT-Thread全球技术大会:RT-Thread对POSIX的实现情况介绍             审核编辑:彭静
    的头像 发表于 05-27 16:52 1559次阅读
    <b class='flag-5'>RT-Thread</b>全球技术大会:<b class='flag-5'>RT-Thread</b>对POSIX的<b class='flag-5'>实现</b>情况介绍

    RT-Thread学习笔记 RT-Thread的架构概述

    RT-Thread 简介 作为一名 RTOS 的初学者,也许你对 RT-Thread 还比较陌生。然而,随着你的深入接触,你会逐渐发现 RT-Thread 的魅力和它相较于其他同类型 RTOS
    的头像 发表于 07-09 11:27 3985次阅读
    <b class='flag-5'>RT-Thread</b>学习笔记 <b class='flag-5'>RT-Thread</b>的架构概述

    RT-Thread文档_RT-Thread 简介

    RT-Thread文档_RT-Thread 简介
    发表于 02-22 18:22 5次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>RT-Thread</b> 简介

    RT-Thread文档_RT-Thread SMP 介绍与移植

    RT-Thread文档_RT-Thread SMP 介绍与移植
    发表于 02-22 18:31 7次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>RT-Thread</b> SMP 介绍与移植

    送《RT-Thread设备驱动开发指南》,手慢无!

    过程中的第一选择!本书 也是RT-Thread第8本相关书籍了,本期我们将抽奖送给大家! 抽奖请移步文末↓↓↓ 随着RT-Thread被更广泛地应用于行业中,开发者对嵌入式驱动开发的需求越来越强烈,他们
    的头像 发表于 03-19 23:00 1032次阅读