参考资料常用名词缩写3.1 UART 简介3.2 DMA 简介3.3 UART 结合 DMA实现数据收发3.3.1 配置方法3.3.2 中断组合以及Overflow异常3.3.3 中断优先级设置3.4 环形缓冲区的使用3.5 通讯机制对比3.5.1 UART 接收方式对比3.5.2 UART 发送方式对比
本节首先简介UART和DMA功能(以STM32为例,STM32基因种自带这两个功能);接着详解了参数配置,4种中断组合情况以及中断优先级设置,并引入环形缓冲区,提高内存使用率。最后,对比了所有通讯机制并指出串口收发最优方案(轮询、中断、RTOS)。
参考资料
常用名词缩写
- DMA: 直接内存访问控制
- HT: DMA传输一半事件标志 Half-Transfer Complete DMA event/flag
- TC: DMA传输完成事件标志 Transfer Complete DMA event/flag
- UART: 通用异步接收发送器 Universal Asynchronous Receiver Transmitter
- USART: 通用同步异步接收发送器 Universal Synchronous Asynchronous Receiver Transmitter
- TX: 传输
- RX: 接收
- RTO: 接收器超时 UART事件标志 Receiver Timeout UART event/flag
3.1 UART 简介
STM32 系统有 UART、USART 和 LPUART 等串口通信外设,LPUART 为低功耗外设,可以在芯片停止模式下运行,USART支持同步操作。本节涉及的通讯机制,和外设特性无关,故下面使用UART说明,但要提的是,该机制适用于以上三种外设。
STM32芯片上UART支持多种收发模式,比如
- 轮询模式(Poll Mode), 通过定时检查状态位,判断字符是否被收发。数据需要快速读出,避免字节丢失。这个模式简单易用,但复杂应用中,读取不及时,容易丢失字节,高负载下响应延迟。更适用于低波特率,9600以下。
- 中断模式(Interrupt Mode),串口触发中断,通过硬件中断响应数据到达时间。该模式嵌入式中应用广泛,响应速度快,对115200波特率支持良好,最高支持~921600波特率。但高速度通讯下,频繁进入中断服务例程,系统性能。
- DMA模式(DMA Mode),串口数据直接从寄存器传输到用户内存,由硬件层级完成这件事,应用层无需处理数据接收过程,只专注数据处理过程。该模式很方便集成到操作系统中,支持极高的波特率 >1Mbps,可以通过提高缓存大小,应对数据突发情况。但是DMA传输前必须提前知道传输字节数,如果通信失败,DMA 可能不会通知应用程序所有已传输的字节。
STM32 UART 具备检测RX线路未激活检测能力,通过两种方式实现:
- 线路空闲事件(IDLE LINE event)
RX线路空闲状态通过判断线路逻辑电平(通常是高电平)确定,并且要监测一定的时间窗口(上一次接收到字节后,持续一帧时间空闲)。帧时间基于波特率。更高的波特率意味着单个字节的帧时间更短。
- 接收器超时事件(RTO (Receiver Timeout) event)
当线路处于空闲状态达到可编程时间时触发。它由固件完全配置。
以上两种方式都可以触发中断,这是高效接收操作的重要特性。
不是所有STM32系列均支持空闲中断和接收器超时中断,对于常用系列,支持情况:
STM32 Family | IDLE LINE EVENT | RTO EVENT |
STM32F0xx | ✅ | ✅ |
STM32F1xx | ✅ | ❌ |
STM32F3xx | ✅ | ✅ |
STM32F4xx | ✅ | ❌ |
STM32F7xx | ✅ | ✅ |
STM32H7xx | ✅ | ✅ |
STM32L4xx | ✅ | ✅(USART/UART)
❌(LPUART) |
STM32G0xx | ✅ | ✅ |
STM32G4xx | ✅ | ✅ |
下面通过一个例子说明以上概念,你可以在这里找到出处。
上位机使用115200波特率传输3个字节数据,当数据传输完成,单片机连续接收这3个字节。当接收总线空闲一帧,会触发空闲中断,数据回环发回上位机。

- 一个bit接收时间 1/115200 = 8.68us,一个字节 8.68*8 = 69.4 us,每个字节数据之间会有间隔(2bit左右),这里总体上,我们以一个字节 ~100us,3个字节耗时~300us传输完
- 接收完成,总线进入高电平空闲状态(上图中黄色部分),串口RX总线在一帧时间窗口内(约100us)持续判断状态。若超时仍未接收数据,进入工作状态,则触发空闲中断(上图绿色剪头时刻)。
3.2 DMA 简介
DMA介入下,无需CPU参与,提供外设和存储器之间高速传输方式,将CPU从数据的复制和存储中解放出来。
STM32中DMA可配置为常规模式(normal mode)和循环模式(circular mode)
- 常规模式:该模式下传输完成则停止,再次启动需要手动控制,发送数据可配置此模式。
- 循环模式:该模式下传输完成后,按相同配置自动启动下一次传输,接收数据可配置此模式。
串口数据传输过程,有两个中断会被触发:
- 达到半传输: DMA 数据传输达到一半时 HTIF 标志位被置 1,如果使能 HTIE 中断控制位将产生达到半传输中断。
- 传输完成: DMA 数据传输完成时 TCIF 标志位被置 1,如果使能 TCIE 中断控制位将产生传输完成中断。
在传输开始之前,必须将通过 DMA 硬件传输的元素数量写入相关的 DMA 寄存器。
详情参考: DMA 使用指南 。
3.3 UART 结合 DMA实现数据收发
每个 STM32 至少有一个 (
1
) UART IP 和至少一个 ( 1
) DMA 控制器。这就是我们成功进行数据传输所需的一切。我么可以通过 DMA 实现非常高效的传输系统。数据发送相对直截了当,设置好数据指针,数据长度后发送,而数据接收情况更加复杂。这是因为接收数据不定长,串口协议并不指定该信息。(当然我们可以在高层协议上,指定长度信息,这个我们可以参考上一节内容,但本节重点在于实现可靠高效的uart通信)。
本节首先介绍UART + DMA应用于串口接收和发送下配置方法,接着分析中断组合的4种情况,还有处理不及时,造成的overflow异常情况。最后最终指出多中断限情况下竞态保护问题-抢占优先级设置。
3.3.1 配置方法
方向 | 方法 | 配置细节 | 备注 |
串口接收 | UART + DMA | 1. 设置 DMA 寄存器数据长度(以20 bytes说明))
2. 设置 DMA 寄存器地址 memory & peripheral 地址
3. 设置 DMA 方向 peripheral-to-memory
4. 设置 DMA为循环模式 circular mode
5. 启用DMA 和 UART 的接收模式,DMA等待 UART接收字符并传到数组中
6. 接收10字节,触发 DMA HT 中断
7. 接收20字节,触发 DMA TC 中断
8. 接收线空闲或者超时触发空闲(IDLE)或接收超时(RTO)中断 | 1. 为了高效接收数据,所有事件需快速响应
2. 因为串口接收数据长度未知,频率未知,设置为循环读模式
3. 数据长度视串口波特率和应用层数据处理效率而定(中断通知,RTOS或轮询) |
串口发送 | UART + DMA | 1. 设置 DMA 寄存器数据长度(以发送长度为准)
2. 设置 DMA 寄存器地址 memory & peripheral 地址
3. 设置 DMA 方向 memory-to-peripheral
4. 设置 DMA为常规模式 normal mode
5. 启用DMA 和 UART 的发送模式,DMA等待 UART接收字符并传到数组中
6. 所有字节从内存传输到串口,触发发送完成TC中断
| 1. 相较于接收,发送更为简单,因为发送的内存,数据长度提前知道。
2. TC中断产生于DMA将内存最后一个字节传输到UART,而非UART将所有数据传输到GPIO口 |
- 串口初始化和DMA常规配置不在表格说明
3.3.2 中断组合以及Overflow异常
由于我们结合串口和DMA,串口接收程序会在串口空闲中断,以及DMA HT和TC中调用。因此我们梳理一下 DMA HT/TC 和 UART IDLE 不同组合特点,以及串口接收程序应该怎么写。
下图描述了
4
种可能的情况,以及一种异常情况,解释了为什么 HT/TC 事件在应用中是必要的。
图中符号含义如下:
- R:应用程序读取内存数据位置
- W:DMA在内存中写数据位置,随着DMA写新字节提升
- HT:DMA传输一半发生中断标志
- TC:DMA传输完成发生中断标志
- I: UART发生空闲中断标志
图中DMA配置如下:
- 循环模式
- 数据长度配置为20字节
- 接收10字节,连续触发HT事件
- 接收20字节,连续触发TC事件
各种情况描述如下:
情况 | 描述 |
CASE A | 1. DMA 接收 10 字节数据存入内存,更新 W 位置,触发 HT 中断和 IDLE 中断
2. 中断从内存读取处理数据处理,更新 R 位置 |
CASE B | 1. DMA 继续接收 10 字节数据,更新 W 位置,触发 TC 中断和 IDLE 中断
2. 中断从内存上一次位置继续读取,直到内存底部
3. 由于DMA 设置循环模式,内存位置更新为内存起始位置 |
CASE C | 该情况是 DMA 接收10字节数据,但未与HT 或TC 中断对齐
1. 接收6个字节,触发HT中断,中断处理6个字节(从上一次R位置)
2. 继续接收4个字节,触发空闲中断,中断继续处理剩余4个字节 |
CASE D | 该情况是 DMA OverFlow 模式下接收数据,并未与HT 或TC 中断对齐
1. 接收4个字节,触发TC中断,中断处理4个字节(从上一次R位置)
2. 继续接收6个字节,触发空闲中断,从内存起始位置开始处理 |
⚠️CASE E | 该情况发生在只有空闲事件处理数据
1. 数据突发30字节,应用处理不够快,DMA处理覆盖了10字节(内存20字节)
2. 数据的前10个字节,是被突发数据覆盖的最后10个字节
3. 避免此类情况,可以轮询速度快于 20 字节突发传输 ;或通过使用 TC 和 HT 事件 |
下面代码片段给出串口接收例子,主要处理内存线性接收(由于三种中断中都会唤醒接收程序,线性接收也是分段接收的)和内存溢出两种情况。
3.3.3 中断优先级设置
Cortex-M NVIC中断控制器可以实现不同中断优先级控制,对于UART和DMA拥有不同中断线(稍后也有中断服务例程),其优先级可以通过软件配置。
处理函数不是线程安全的或可重入的,因此要避免DMA 和 UART 中断相互抢占,导致相同处理函数usart_rx_check 中读取位置被破坏,导致应用程序使用错误的数据。
因此应用程序必须确保,DMA 和 UART 中断使用相同的抢先优先级级别。
3.4 环形缓冲区的使用
环形缓冲区是一种有效的数据缓存机制,特别适合连续数据流的处理。其特点是采用固定大小的缓冲区,通过循环使用缓冲区的空间,可以避免数据丢失和提高效率。
- 数据流管理:串口通信通常涉及生产者(发送数据的设备)和消费者(接收数据的设备)。环形缓冲区可以有效管理这两者之间的数据流,确保接收方不会因数据到达速度不一致而丢失信息。
- 提高效率:通过使用环形缓冲区,数据可以被连续存储而无需频繁地进行内存分配和释放。这种机制减少了处理延迟,提高了数据传输的效率。
- 防止数据溢出:在高数据传输速率下,接收方可能来不及处理所有数据。环形缓冲区允许接收的数据存储在缓冲区中,直至应用程序准备好处理这些数据,从而降低数据溢出的风险。
- 异步处理:环形缓冲区支持异步通信,允许接收方在数据到达时进行其他操作,而不是阻塞等待数据的到来。这在实时系统中尤为重要。
- 数据顺序保持:环形缓冲区能确保数据按顺序存储和读取,这对许多应用程序(如数据日志和实时监控)是至关重要的。
实现示例
在这个示例中,数据写入和读取都以环形方式进行,有效利用了空间并避免了数据冲突。
3.5 通讯机制对比
3.5.1 UART 接收方式对比
序号 | 方式 | 特点 | 优点 | 缺点 |
1 | Polling for changes | 1. DMA负责将接收到数据转移到内存
2. 应用使用轮询方式读取改变 | 1. 无 UART IDLE设备可用
2. 无中断,无需考虑优先级和竞态条件 | 1. 应用层需周期处理数据
2. 低功耗模式下不可能运行 |
2 | Polling for changes with operating system | 1. 仍是轮询
2. RTOS线程完成处理 | 1. 无 UART IDLE设备可用
2. 无中断,无需考虑优先级和竞态条件
3. 易执行,单线程,无需考虑线程同步 | 1. 应用层需周期处理数据
2. 低功耗模式下不可能运行
3. 数据处理线程需占用一定内存资源 |
3 | UART IDLE line detection + DMA HT&TC interrupts | 1. 串口空闲中断+ DMA TC/HT 中断通知
2. 中断中完成数据接收和处理 | 1. 无需轮询获知数据改变
2. 进入低功耗模式,以 提高电池续航 | 1. 中断中数据读取和处理,可能造成中断延迟 |
4 | USART Idle line detection + DMA HT&TC interrupts with RTOS | 1. 串口空闲中断+ DMA TC/HT 中断通知
2. 中断处理线程化 | 1. 中断中不处理数据,只通知线程,避免造成中断延迟
2. 创建单独线程处理数据,等待中线程阻塞,释放CPU
| 线程和消息队列(或信号量)会消耗一定内存
1. 需考虑线程同步手段
2. 需考虑竞态条件保护 |
- 方法3和4, 由于同时会有两个中断服务程序(串口空闲中断和DMA中断),为了避免相互抢占,造成的竞态问题(更改读指针),需要设置相同的抢占优先级。
- UART 接收推荐使用方法4 ,即综合使用空闲中断+DMA+中断处理线程化。
3.5.2 UART 发送方式对比
UART 发送方式推荐使用环形缓冲区+DMA常规模式方式。
- 应用层和底层传输之间使用 ringbuffer 来作为缓存,主要可以实现如下功能:
- 解耦:高层和低层之间的解耦使得高层不必等待低层的传输完成,从而提高整体系统的响应能力。
- 提高效率:高层可以持续写入数据,而低层可以在其闲置时处理传输,这样可以更高效地利用系统资源。
- 平衡负载:环形缓冲区能够平衡高层产生数据的速度与低层处理速度之间的差异,防止因数据流量过大而导致的数据丢失。
- 流控支持:环形缓冲区的存在简化了流控机制的实现,能够根据缓冲区的状态来动态调整数据传输速率。
- 传输完成,触发DMA TC中断,应用再次发送数据。
↩️ 返回 开发框架09-高效可靠串口通讯