STM32 串口 (DMA + 空闲中断 + 环形缓冲区)
1. 基本概念
-
UART 空闲中断(IDLE):
- 当串口 RX 线上 连续一段时间没有数据接收,USART 外设触发 空闲中断。
- 空闲中断的主要作用是通知数据传输完成或当前帧结束。
-
DMA 接收模式:
- DMA(Direct Memory Access) 自动将串口接收到的数据存储到指定缓冲区。
- CPU 不再需要逐字节处理接收数据,提高效率。
HAL_UARTEx_ReceiveToIdle_DMA
:启动 DMA 接收,支持接收数据直到触发 空闲中断。
-
环形缓冲区:
- 通过固定大小的缓冲区 + 读写指针 实现数据的循环存储。
- 用于连续接收数据,解决 DMA 数据处理问题。
- 读写指针逻辑:
- 写指针:指向新接收数据的位置。
- 读指针:指向待处理数据的位置。
2. 流程概述
-
初始化 UART 和 DMA:
- 配置 UART 和 DMA。
- 启用 DMA 接收并启动
HAL_UARTEx_ReceiveToIdle_DMA
。
-
串口接收数据:
- 数据通过 DMA 存储到 DMA 缓冲区
uart_rx_dma_buffer
。 - 串口数据未停止时,DMA 自动接收,CPU 不参与。
- 数据通过 DMA 存储到 DMA 缓冲区
-
触发空闲中断:
- 当 RX 线上 超过一个字节时间没有数据接收,触发 空闲中断(IDLE)。
- 调用
USARTx_IRQHandler
。
-
中断处理:
- 在
USARTx_IRQHandler
中调用HAL_UART_IRQHandler
。 - HAL 库检测到 IDLE 中断,触发回调函数
HAL_UARTEx_RxEventCallback
。
- 在
-
回调函数处理接收数据:
- 在
HAL_UARTEx_RxEventCallback
中:- 计算接收到的数据长度。
- 将 DMA 缓冲区的数据拷贝到 环形缓冲区。
- 清空 DMA 缓冲区,准备下一次接收。
- 重新启动 DMA 接收
HAL_UARTEx_ReceiveToIdle_DMA
。
- 在
-
主循环读取数据:
- 通过环形缓冲区的 读写指针 提取接收到的数据,进行处理。
3. 代码展示
初始化 UART 和 DMA
void MX_USART1_UART_Init(void) {huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;HAL_UART_Init(&huart1);// 启动 DMA 接收HAL_UARTEx_ReceiveToIdle_DMA(&huart1, uart_rx_dma_buffer, sizeof(uart_rx_dma_buffer));__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); // 禁用半满中断
}
中断服务函数
void USART1_IRQHandler(void) {HAL_UART_IRQHandler(&huart1); // 处理 UART 中断
/*不同的触发,跳转不同的回调函数,若是空闲中断触发,跳转的是 *`HAL_UARTEx_RxEventCallback`*//* 调用HAL库的串口中断处理函数 */HAL_UARTEx_ReceiveToIdle_DMA(&huart1, uart_rx_dma_buffer, sizeof(uart_rx_dma_buffer));/* 重新启动DMA接收,确保下一次接收正常进行 */ __HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
}
回调函数:处理空闲中断
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {if (huart->Instance == USART1) {if (!ringbuffer_is_full(&usart_rb)) {ringbuffer_write(&usart_rb, uart_rx_dma_buffer, Size); // 将数据写入环形缓冲区}memset(uart_rx_dma_buffer, 0, sizeof(uart_rx_dma_buffer)); // 清空 DMA 缓冲区}
}
4. 环形缓冲区实现
环形缓冲区结构
-
ringbuffer_t
结构体用于管理缓冲区:
buffer
:实际存储数据的数组。r
:读指针。w
:写指针。itemCount
:当前缓冲区内的数据量。
typedef struct {uint32_t w; // 写指针uint32_t r; // 读指针uint8_t buffer[RINGBUFFER_SIZE]; // 数据存储缓冲区uint32_t itemCount; // 当前缓冲区数据量
} ringbuffer_t;
函数功能
-
初始化环形缓冲区:
- 清零缓冲区、读写指针和数据量。
void ringbuffer_init(ringbuffer_t *rb);
-
写入数据:
- 检查缓冲区是否已满,未满时将数据写入。
int8_t ringbuffer_write(ringbuffer_t *rb, uint8_t *data, uint32_t num);
-
读取数据:
- 检查缓冲区是否为空,非空时读取数据。
int8_t ringbuffer_read(ringbuffer_t *rb, uint8_t *data, uint32_t num);
-
缓冲区状态检查:
ringbuffer_is_full
:检查缓冲区是否已满。ringbuffer_is_empty
:检查缓冲区是否为空。
5. 工作顺序总结
HAL_UARTEx_ReceiveToIdle_DMA
启动 DMA 接收。- UART 接收到数据,DMA 将数据存入缓冲区。
- 空闲中断(IDLE)触发,调用
USART1_IRQHandler
。 HAL_UART_IRQHandler
检测到空闲中断,自动调用HAL_UARTEx_RxEventCallback
。- 在回调函数中:
- 处理接收到的数据(写入环形缓冲区)。
- 重新启动 DMA 接收(调用
HAL_UARTEx_ReceiveToIdle_DMA
)。
- 主循环处理数据:调用
uart_proc
,从环形缓冲区中提取数据并处理
6. 图文总结
UART RX --> DMA 接收数据 --> RX 空闲中断 --> USART1_IRQHandler --> HAL_UARTEx_RxEventCallback| ||---- 重新启动 DMA 接收 ----------------|
数据 --> 写入环形缓冲区 --> 主循环读取数据 --> 用户处理
本系统实现了 STM32 串口 DMA 接收 + 空闲中断 + 环形缓冲区,旨在高效接收和处理串口不定长数据,保证数据的完整性与实时性。其核心工作流程如下:
- DMA 接收:通过调用
HAL_UARTEx_ReceiveToIdle_DMA
函数启动 DMA 模式接收,将接收到的数据自动存储到 DMA 缓冲区,减少 CPU 干预,提高效率。 - 空闲中断触发:当串口 RX 线超过一个字节时间没有新数据输入时,触发 空闲中断(IDLE),并在中断中调用
HAL_UART_IRQHandler
,进一步触发HAL_UARTEx_RxEventCallback
回调函数。 - 数据存储:
在回调函数HAL_UARTEx_RxEventCallback
中,将 DMA 缓冲区接收到的数据写入 环形缓冲区,通过ringbuffer_write
函数实现数据的安全存储,防止数据丢失。随后清空 DMA 缓冲区,重新启动 DMA 接收,确保数据连续接收。 - 数据处理:
在主循环中,调用uart_proc
函数,通过ringbuffer_read
从环形缓冲区读取数据进行处理。环形缓冲区通过读写指针和数据计数机制实现数据的循环存储与读取,适用于不定长、连续数据接收的场景。 - 环形缓冲区管理:
ringbuffer_is_full
和ringbuffer_is_empty
用于判断缓冲区状态。ringbuffer_write
和ringbuffer_read
分别实现数据的写入与读取,确保缓冲区数据有序管理,防止数据丢失。
系统特点
- 高效性:DMA 自动接收数据,减少 CPU 开销。
- 实时性:通过 UART 空闲中断实时捕获数据接收完成状态。
- 可靠性:使用环形缓冲区管理数据,确保数据存储稳定,避免数据丢失。
- 适用性:适合高频率、不定长数据的串口通信场景。