DMA环形缓冲区UART不定长接收:半满中断与IDLE中断竞争条件的精准处理
👁 1 阅读 · 2026-08-27 · 嵌入式
在STM32等嵌入式平台上,使用DMA环形缓冲区实现UART不定长接收是常见做法,但半满中断与IDLE中断的竞争条件常导致数据错乱或丢包。本文深入分析两者触发时序与竞争根源,给出基于状态机与缓冲区分区管理的解决方案,并附完整代码示例与调试建议,帮助开发者构建健壮的接收框架。
```c
// 完整示例代码
#include "stm32f1xx_hal.h"
#define BUF_SIZE 256
typedef struct {
uint8_t data[BUF_SIZE];
volatile uint16_t read_ptr;
volatile uint16_t write_ptr;
volatile uint8_t half_flag;
} RingBuffer;
RingBuffer ring_buf;
uint8_t user_buffer[BUF_SIZE];
void UART_RX_Init(void);
void UART_IDLE_Callback(void);
void DMA_Half_Callback(void);
void Process_RX_Data(void);
void User_RX_Callback(uint8_t *data, uint16_t len);
void UART_RX_Init(void)
{
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1, ring_buf.data, BUF_SIZE);
HAL_DMA_Start_IT(&hdma_uart_rx, (uint32_t)&huart1.Instance->RDR,
(uint32_t)ring_buf.data, BUF_SIZE);
}
void UART_IDLE_Callback(void)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
ring_buf.write_ptr = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_uart_rx);
Process_RX_Data();
}
void DMA_Half_Callback(void)
{
ring_buf.half_flag = 1;
ring_buf.write_ptr = BUF_SIZE / 2;
}
void Process_RX_Data(void)
{
uint16_t data_len;
if (ring_buf.write_ptr >= ring_buf.read_ptr) {
data_len = ring_buf.write_ptr - ring_buf.read_ptr;
} else {
data_len = BUF_SIZE - ring_buf.read_ptr + ring_buf.write_ptr;
}
if (data_len == 0) return;
uint16_t first_len = (data_len > (BUF_SIZE - ring_buf.read_ptr)) ?
(BUF_SIZE - ring_buf.read_ptr) : data_len;
memcpy(user_buffer, &ring_buf.data[ring_buf.read_ptr], first_len);
uint16_t second_len = data_len - first_len;
if (second_len > 0) {
memcpy(user_buffer + first_len, ring_buf.data, second_len);
}
ring_buf.read_ptr = (ring_buf.read_ptr + data_len) % BUF_SIZE;
if (ring_buf.half_flag && (ring_buf.read_ptr == ring_buf.write_ptr)) {
ring_buf.half_flag = 0;
}
User_RX_Callback(user_buffer, data_len);
}
void User_RX_Callback(uint8_t *data, uint16_t len)
{
// 用户处理数据
}
```