乐趣区

关于嵌入式:RTT串口V1版本的使用分析及问题排查指南一

本文由 RT-Thread 论坛用户 123 原创公布:https://club.rt-thread.org/as…

RTT 串口 V1 版本的应用剖析及问题排查指南(一)

简述

无论是刚接触 RT-Thread 的老手,还是教训老道的大牛们,他们应用 RT-Thread 的时候,应用最频繁最宽泛的外设,想必也非串口设施莫属。

回忆大家在移植一个新的 BSP 或者芯片时,如何验证是否移植胜利呢?是的,msh 控制台窗口走一波 RTT 的 logo 信息,输入胜利了就根本代表移植胜利了。如下所示:

 \ | /
- RT -     Thread Operating System
 / | \     4.0.4 build Dec  32 2021
 2006 - 2021 Copyright by rt-thread team
msh >

例如 finsh 组件,以命令行的形式实现人机交互的性能,在我的项目开发调试中有着无足轻重的作用,也是开发者们应用最为频繁的组件。

又例如 ULOG 组件,AT 组件,ymodem 组件,RT_Link 组件等,其底层数据流都有串口的形迹。因而,作为应用最频繁,覆盖面最宽泛的串口设施,如果把它搞懂,那将会在你的我的项目开发中锦上添花,或对嵌入式零碎也会有更粗浅的了解。

既然串口设施如此重要,受众人群又如此之多,应用范畴如此之广,那么有必要去理一理串口框架,汇总一下问题所在,为宽广开发者们指一下解决问题的方向。这也是这篇文章的次要工作。

本文会先联合 STM32 为平台,以串口 V1 版本做剖析阐明(第一局部),并总结遇到串口方面的问题该如何解决的办法(第二局部)。

(在此须要解释一下,串口 V1 版本 这个名字非官方冠名,是自己为了辨别 串口 V2 版本 而长期起的名字,当然后续会有串口 V2 版本的介绍阐明,快马加鞭更新中)

因为串口 V1 版本曾经历经多年的开发与迭代,也被少数开发者整顿成文,广为流传,本文若从新对其做粗疏的剖析显然是在节约各位看官的宝贵时间。因而,本文旨在总结串口应用过程中遇到的问题,弱化剖析串口的执行流程,如需具体的串口流程剖析,可移步文档核心串口章节,或者自行上网搜寻,置信你肯定能找到适合的答案。那么废话不多说,开整。

串口设施应用阐明

串口流程剖析分为两局部,一部分是串口驱动,另一部分是串口框架,用户(应用层)应用串口时,是依照下图的模型进行操作的:

首先须要应用 Device 框架(源码地位在 src/device.c 中),什么是 Device 框架,比方用到的 rt_device_open/close/read/write/control() 等 API 操作接口,就是应用的 Device 框架的接口。本文中不做深入探讨,理解即可。

其次是 UART 设施驱动框架(源码地位在 components\drivers\serial\serial.c),串口设施驱动框架实现了 Device 框架的操作方法的接口rt_serial_open/close/read/write/control()。举个例子,例如 Device 框架的rt_device_open() 接口是关上一个设施对象,而对应到串口框架上,就是对接到了 rt_serial_open()

而后是串口设施驱动(源码地位在 bsp/xxx/drivers/drv_usart.c),串口设施驱动负责实例化串口设施。这一层调用了 rt_hw_serial_register() 函数注册串口设施到操作系统中,也是与串口硬件间接打交道的媒介,这一层将会看到串口硬件的配置、读写寄存器、中断的操作等。

上面这张图将介绍串口设施的应用序列:

串口各个模式的流程剖析

串口框架目前适配了三种硬件工作模式,即串口轮询(发送 / 接管)、串口中断(发送 / 接管)、串口 DMA(发送 / 接管)。上面别离对三种模式进行介绍:

一:轮询模式

轮询模式即调用串口 接管 / 发送 的 API 后将始终占用 CPU 资源直到数据收发实现才返回,应用时须要用户被动调用 接管 / 发送 的 API 接口才会执行相应的操作。

轮询接管

应用程序调用 rt_device_read() 接收数据,轮询接管模式下会调用到底层串口驱动提供的 getc 接口,每次接管一个字节,如果接管不到数据将始终占用 CPU 资源。

轮询发送

应用程序调用 rt_device_write() 发送数据,轮询发送模式下会最终会调用到底层串口驱动提供的 putc 接口,每次发送一个字节,循环发送直到待发送的数据发送实现。

轮询接管和发送调用关系如下图所示:

二:中断模式

中断模式下收发数据时,将数据的收发过程放在中断中进行,不再继续占用 CPU 资源,应用程序只用负责将数据写入串口数据寄存器,而后线程就会让出资源,空出了程序期待串口硬件收发数据的工夫。留神一点:中断接管和发送时,每次在中断中操作的还是单个字节。

中断接管

串口硬件接管到一个字节的数据后会触发中断并调用串口驱动框架的 rt_hw_serial_isr() 函数,此函数会将这一字节数据写入环形缓冲区,用户设置的回调函数也会被调用。应用程序读取数据理论是从环形缓冲区读取。

中断接管调用关系如下图所示:

中断发送

串口框架负责调用底层 putc 接口向串口寄存器写入待发送的数据,而后期待此数据发送实现的信号,此时以后线程会被挂起。上一个数据发送实现后会调用串口驱动框架的 rt_hw_serial_isr() 函数,此函数会发送实现信号唤醒发送数据线程,并重置实现信号状态为未实现。线程运行后会向串口寄存器写入下一个数据,而后期待实现信号,反复上一个流程,直到缓冲区数据发送实现。

该过程与中断接管流程很相像,再次不再贴图赘叙。

三:DMA 模式

DMA 模式下收发数据与中断模式很相像,能够粗略的认为,两种模式惟一不同的就是,中断模式每次收发数据为单字节,而 DMA 模式则是多个字节进行收发的。

DMA 接管

应用 DMA 接管模式时,首先应用程序关上串口设施会指定关上标记为 RT_DEVICE_FLAG_DMA_RX,此时串口驱动框架会创立环形缓冲区并调用串口驱动层 control 接口使能 DMA 接管实现中断。

DMA 接管调用关系如下图所示:

这里再啰嗦一句,在 DMA 中断解决流程中,有三种 DMA 接管中断,别离是 DMA 闲暇中断(IDLE)、DMA 半满中断(HFIT)和 DMA 满中断(TCIT),这三个中断相辅相成,最初对立交由串口框架中断处理函数 rt_hw_serial_isr(RT_SERIAL_EVENT_RX_DMA_DONE)中做解决。其中 DMA 闲暇中断是在 uart_isr 中触发的,另外两个是在 DMA 接管回调中触发的,这两个接管回调是在 DMA 接管使能启动的时候,由 HAL 库注册的,用户无需关怀这两个 DMA 接管回调的注册逻辑,只需晓得,如果用户不想应用这两个 DMA 接管回调的时候,只有把函数外面的执行代码正文掉即可。相似下图这样:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    struct stm32_uart *uart;
    RT_ASSERT(huart != NULL);
    // uart = (struct stm32_uart *)huart;
    // dma_isr(&uart->serial);
}

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
    struct stm32_uart *uart;
    RT_ASSERT(huart != NULL);
    // uart = (struct stm32_uart *)huart;
    // dma_isr(&uart->serial);
}

(至于为什么会有用户无需应用该回调函数的利用场景,这里先按下不表,后边会具体详细分析,而且这部分会很重要,该机制在串口 V2 上照样应用)

DMA 发送

应用程序发送数据流程如下图所示,应用程序调用 rt_device_write() 发送数据,最终待发送数据的地址和大小会被放入数据队列,如果此时 DMA 闲暇,就会开始发送数据。当 DMA 发送实现触发中断时,rt_hw_serial_isr() 函数会判断数据队列是否有待传输的数据并开始下次 DMA 传输,用户设置的回调函数也会被调用。

由上图咱们能够看出,在应用 DMA 发送时,串口框架并未应用环形缓冲区,然而咱们又晓得,DMA 发送时,必定是须要缓冲区来存放数据的,那么 DMA 发送时候的数据块,寄存在哪里呢?这里就须要留神了,DMA 发送时候,数据缓冲区是由用户应用层定义的,在传给串口框架的时候,是将应用层定义的数据缓冲区的指针传递过去。如果在发送过程中,该缓冲区的内容被意外批改了,那么将会导致 DMA 发送的数据呈现谬误。咱们能够再联合上面的图持续阐明:

串口相干问题解析

篇幅无限,本节内容见 RTT 串口 V1 版本的应用剖析及问题排查指南(二)

退出移动版