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

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

3天内不再提示

rt-thread 驱动篇(六)serialX弊端及解决方法

出出 来源:出出 作者:出出 2022-06-20 11:43 次阅读

前言

控制台,做为一种人机交互接口,相较于其他接口(显示器、网络终端),可能是最简单的。它耗用资源少,容易配置,几乎是任何芯片会自带的外设。而且可以很容易和计算机建立连接。因而,串口控制台可能是程序员进行人机交互的首选。

启用控制台,可以帮我们在系统全速运行时窥探系统运行状况。可以监测其它外设或者组件初始化过程。

一月份,笔者在论坛发的 serialX 串口驱动反响很大,应该会让很多人眼前一亮(老王卖瓜)。当初,决定费力研究它的初衷很简单 —— 应用和驱动弱耦合、真阻塞非阻塞特性。这两个月来,笔者一直在实际项目中使用它,而且应用到了控制台上,同时发现了一些问题。

控制台串口问题汇总

问题一、任务调度器启动前 `rt_kprintf` 死循环到 tx 函数

问题原因是,笔者打开控制台串口设定的 flag 是阻塞写方式,无论是中断发送还是 DMA 发送都依赖中断。但是任务调度器启动前是关全局中断的,这样导致触发发送失败,发送缓冲区满了以后再有写动作就会永久死到 tx 函数里。

> 应对之策:任务调度器启动前只能使用 poll 发送模式。任务调度器启动后或临启动时切换到中断或 DMA 模式。

问题二、出现异常后,`rt_kprintf` 无输出

这个现象和上一条有点儿类似。不同的是,虽然全局中断是开着的,但是串口中断优先级不足,导致控制台设备停止工作。

arm9 架构上出现 Undef SWI PAbt DBbt 等等 trap 后,串口外设中断级别不足,导致这些 trap 中的 printf 输出失效。

> 应对之策:unset 控制台串口设备,或者将控制台串口切换到 poll 发送模式。

问题三、`rt_hw_interrupt_disable` 之后 `rt_kprintf` 无输出

这个和“问题一”是一样的,根本原因是,非 poll 模式必须有中断它才能工作,关中断以后设备停止工作。

> 应对之策:尽量减少关中断时间;避免在关中断之后写串口设备。

问题四、遇到 `_rt_scheduler_stack_check` 也会停止输出

因为 `_rt_scheduler_stack_check` 函数最后先关全局中断,然后进入 while 死循环。这个时候串口中断肯定也失效了。

> 应对之策:关全局中断前,先 flush 串口设备。让串口把 “stack overflow” 的提示信息输出完。

问题五、打断点后 `rt_kprintf` 输出不完整,部分数据没输出到控制台

因为 debug 断点停止的时候,前边 printf 缓存的数据可能还没来得及送到串口移位寄存器cpu时钟被断点打断停止运行了,导致部分数据没输出。继续运行程序就可以出现剩余信息输出。这是非阻塞设备的特性。

以上这些问题是所有非 poll 非阻塞设备输出都会遇到的现象。在 RTOS 系统里,应用程序不可避免地要和中断打交道,了解中断对我们编程思想的影响很重要。


完整解决方案

rt_device 增加 flush 接口

flush 接口对带缓存设备是极其有用的,无论是阻塞还是非阻塞模式,我们总有需求要求*在某个代码节点设备的缓存已经是空的*,*或者要求实现通信同步*。

`struct rt_device` 增加 `flush` 接口

struct rt_device
{
...
   rt_err_t  (*flush)  (rt_device_t dev);
...
};

serialX.c 添加 `flush` 回调函数实现 `static rt_err_t rt_serial_flush(struct rt_device *dev)` ,用于等待串口驱动层发送缓存发完数据。另外底层外设也增加 flush 接口,用于等待串口发送寄存器中的*最后一个字节数据*被搬到了移位发送寄存器中。

console 添加 unset flush 控制台设备接口

void rt_console_unset_device()
{
   if (_console_device != RT_NULL)
   {
       /* close old console device */
       rt_device_close(_console_device);
       _console_device = RT_NULL;
   }
}
RT_WEAK void rt_hw_console_flush()
{
   /* empty console output */
}
void rt_console_flush()
{
#ifdef RT_USING_DEVICE
   if (_console_device == RT_NULL)
   {
       rt_hw_console_flush();
   }
   else
   {
       rt_device_flush(_console_device);
   }
#else
   rt_hw_console_flush();
#endif /* RT_USING_DEVICE */
}

有 set 也有 unset, 不是吗? unset 是为了调用 `rt_hw_console_output` 而不是 `rt_device_write` 输出打印信息。

`rt_console_flush` 既考虑启用设备框架也考虑未启用设备框架两种情况。`rt_device_flush(_console_device)` 会调用上文的 `rt_serial_flush` ;`rt_hw_console_flush` 和 `rt_hw_console_output` 类似用于不使用设备框架,自定义 `rt_kprintf` 底层接口时要实现的。视实际情况实现 `rt_hw_console_flush` 。例如 NUC970 UART 自带了 FIFO ,需要实现 `rt_hw_console_flush`

> 如果使用了 DMA 模式,底层实现 flush 还是有点儿难度的。需要花点儿心思。

延迟 `rt_console_set_device` 调用

挪到任务调度器启动前,那么之前的控制台输出怎么实现?答案是使用 `rt_hw_console_output`。如上所说,第一次使用 poll 模式打开控制台串口,到这里临启动任务调度器的时候再次用 中断/DMA 模式打开控制台串口也可以。但是,多次用不同模式打开同一个设备会引入另外的问题,要不要先关闭上次的 open 呢?假如之前没有打开过呢?

   /* Set the shell console output device */
#ifdef RT_USING_CONSOLE
   rt_console_flush();
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
   /* start scheduler */
   rt_system_scheduler_start();

这时候,我们的 `rt_console_set_device` 可以用任何模式打开控制台串口设备

   if (rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM
                       | RT_DEVICE_FLAG_INT_RX
                       | RT_DEVICE_FLAG_INT_TX
                       ) == RT_EOK) {
       _console_device = new_device;
   }

或者,先用 poll 模式 set console device

       /* set new console device */
       if (rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM
                       ) == RT_EOK) {
           _console_device = new_device;
       }

当第二次 reset 的时候,需要先 unset (用到了上面提到的 `rt_console_unset_device`),因为 `rt_console_set_device` 不允许重复 set 同一个设备,也没法修改打开设备的参数。写另外一个 set api 也就变的必要了

       if (rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM
                       | RT_DEVICE_FLAG_INT_RX
                       | RT_DEVICE_FLAG_INT_TX
                       ) == RT_EOK) {
           _console_device = new_device;
       }

但是,我为什么不喜欢这种方式呢?

1. board 初始化阶段需要初始化系统时钟、倍频 cpu 时钟、 `rt_hw_systick_init`、 `rt_system_heap_init`、 `rt_hw_pin_init`、还有 `rt_hw_usart_init` 设备,可能还有 `rt_console_set_device`。为了能第一时间使用上控制台串口,`rt_hw_usart_init` 必须尽早执行,然后是 `rt_console_set_device` 。
2. 但是 uart 设备可能用到动态申请内存,这样就必然要求 `rt_system_heap_init` 先于 `rt_hw_usart_init` 。
3. 初始化系统时钟、倍频 cpu 时钟、 `rt_hw_systick_init`、 `rt_system_heap_init`、 `rt_hw_usart_init` 。也只能这样了,前边几步的串口打印需求就忽略了吧,`rt_system_heap_init`->`rt_memheap_init` 里的 `RT_DEBUG_LOG` 调试信息就忽略了吧。

如果不着急用串口设备,先简单初始化串口外设,让 `rt_hw_console_output` 以最快的速度工作起来。如此一来,初始化流程可能就可以变成,初始化系统时钟、倍频 cpu 时钟、 **`rt_hw_console_init`**、 `rt_hw_systick_init`、... 。后面是初始化顺序都无关紧要了,而且所有的打印信息需求都可以满足。最后在任务调度器启动前选择某个串口设备做控制台串口,将会避免前文说到的*问题一*。

进入不可恢复状态的处理

- 以 `_rt_scheduler_stack_check` 为例,关中断前先 flush 控制台。

   rt_console_flush();
   level = rt_hw_interrupt_disable();
   while (level);

- 还比如 SWI 异常,`rt_hw_cpu_shutdown` 也会关中断,进入 while 死循环。先 unset 控制台,使用 `rt_hw_console_output` 进行 poll 输出之后的输出需求。

void rt_hw_trap_swi(struct rt_hw_register *regs)
{
   rt_console_unset_device();
   rt_hw_show_register(regs);   rt_kprintf("software interrupt\n");
   rt_hw_cpu_shutdown();
}

**注:为避免因中断优先级,引起串口设备中断得不到响应,在中断响应里切忌调用 `rt_console_flush` 函数**

结束

控制台串口在系统中扮演着极其重要的角色,对其处理不当,会引起各种依赖问题。有人就有疑虑了,做其它通信用时 serialX 会不会存在同样的隐患?笔者保证,您不在中断响应里调用 `rt_device_flush` 就不会出现以上所有列出来的问题。

笔者下一篇计划聊聊内核启动流程的问题,虽然之前发过一篇文章 [rt-thread 系统启动及 SysTick 初始化流程优化可行性分析]( https://club.rt-thread.org/ask/article/2881.html ),里面提了一种可能的系统启动流程,当时只是一种想法,并不系统。再写一篇,笔者希望把需要考虑的问题以及优缺点系统化地说明白,可能还会提及控制台串口设备,以及控制台对内核启动流程的影响。


相关文章:

rt-thread 驱动篇(一) serialX 框架理论

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

rt-thread 驱动篇(三) serialX 压力测试

rt-thread 驱动篇(四)serialX 多架构适配

rt-thread 驱动篇(五)serialX 小试牛刀

审核编辑:汤梓红

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

    关注

    12

    文章

    1206

    浏览量

    55384
  • 控制台
    +关注

    关注

    0

    文章

    85

    浏览量

    10349
  • 串口
    +关注

    关注

    14

    文章

    1551

    浏览量

    76439
  • RT-Thread
    +关注

    关注

    31

    文章

    1285

    浏览量

    40093
  • serialX
    +关注

    关注

    0

    文章

    7

    浏览量

    808
收藏 人收藏

    评论

    相关推荐

    RT-Thread记录(一、版本开发环境及配合CubeMX)

    RT-Thread 学习记录的第一文章,RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及
    的头像 发表于 06-20 00:28 5230次阅读
    <b class='flag-5'>RT-Thread</b>记录(一、版本开发环境及配合CubeMX)

    RT-Thread NUC97x 移植 LVGL

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

    基于RT-Thread的SPI通讯

    驱动层的驱动。(rt-thread的设备 I/O 模型有设备管理层、设备驱动框架层、设备驱动层),我写过一
    的头像 发表于 08-22 09:28 1705次阅读

    RT-Thread ssd1306驱动

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

    RT-Thread设备驱动开发指南基础—以先楫bsp的hwtimer设备为例

    RT-Thread设备驱动开发指南》书籍是RT-thread官方出品撰写,系统讲解RT-thread IO设备驱动开发
    的头像 发表于 02-20 16:01 1686次阅读
    <b class='flag-5'>RT-Thread</b>设备<b class='flag-5'>驱动</b>开发指南基础<b class='flag-5'>篇</b>—以先楫bsp的hwtimer设备为例

    RT-Thread驱动开发指南进阶-动手驱动先楫未适配的外设LCD

    经过上一的《《RT-Thread设备驱动开发指南》基础--以先楫bsp的hwtimer设备为例》阐述,可以大致了解到RT-thread
    的头像 发表于 02-25 11:04 2518次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>驱动</b>开发指南进阶<b class='flag-5'>篇</b>-动手<b class='flag-5'>驱动</b>先楫未适配的外设LCD

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

    rt-thread 驱动serialX弊端解决方法
    发表于 07-26 14:56

    RT-Thread编程指南

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

    RT-Thread Studio驱动SD卡

    RT-Thread Studio驱动SD卡前言一、创建基本工程1、创建Bootloader2、创建项目工程二、配置RT-Thread Settings三、代码分析1.引入库2.读入数据四、效果验证
    发表于 12-27 19:13 20次下载
    <b class='flag-5'>RT-Thread</b> Studio<b class='flag-5'>驱动</b>SD卡

    rt-thread 驱动(五)serialX 小试牛刀

    终于来到了 serialX 的实践,期待很久了。
    的头像 发表于 06-16 11:29 4534次阅读
    <b class='flag-5'>rt-thread</b> <b class='flag-5'>驱动</b><b class='flag-5'>篇</b>(五)<b class='flag-5'>serialX</b> 小试牛刀

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

    RT-Thread 简介 作为一名 RTOS 的初学者,也许你对 RT-Thread 还比较陌生。然而,随着你的深入接触,你会逐渐发现 RT-Thread 的魅力和它相较于其他同类型 RTOS
    的头像 发表于 07-09 11:27 4547次阅读
    <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 9次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>RT-Thread</b> SMP 介绍与移植

    基于RT-Thread Studio学习

    前期准备:从官网下载 RT-Thread Studio,弄个账号登陆,开启rt-thread学习之旅。
    的头像 发表于 05-15 11:00 3933次阅读
    基于<b class='flag-5'>RT-Thread</b> Studio学习

    RT-Thread设备驱动开发指南》基础--以先楫bsp的hwtimer设备为例

    一、概述(一)RT-Thread设备驱动RT-Thread设备驱动开发指南》书籍是RT-thread官方出品撰写,系统讲解
    的头像 发表于 02-24 08:16 1636次阅读
    《<b class='flag-5'>RT-Thread</b>设备<b class='flag-5'>驱动</b>开发指南》基础<b class='flag-5'>篇</b>--以先楫bsp的hwtimer设备为例