UART 不定长接收:DMA 环形缓冲区下的帧边界与噪声毛刺精确区分策略
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式系统中,使用 DMA 环形缓冲区实现 UART 不定长接收是提升实时性与 CPU 效率的常用手段,但如何从连续数据流中精确区分有效帧与噪声毛刺,一直是开发者面临的痛点。本文深入剖析帧边界定义、噪声毛刺特征,并给出基于空闲检测、超时判定与数据校验的三层过滤机制,配合完整代码示例,助你构建鲁棒的接收方案。
# 引言
在 STM32 等嵌入式平台上,UART 接收数据常采用 DMA + 环形缓冲区,以减轻 CPU 负担并支持不定长帧。然而,总线上的电气噪声、干扰或发送端的不稳定,可能产生短促的毛刺信号,这些毛刺若被误判为有效数据,将导致帧解析错乱。因此,精确区分帧边界与噪声毛刺,是保证通信可靠性的关键。
# 帧边界与噪声毛刺的本质
## 帧边界定义
帧边界通常由以下两种方式之一界定:
- **空闲线检测**:总线空闲超过一定时间(如 3 个字节时间),认为一帧结束。
- **帧头/帧尾标志**:使用特定字符(如 0xAA 0x55)作为帧起始,0x0D 0x0A 作为结束。
## 噪声毛刺特征
- **持续时间极短**:通常小于 1 个位时间,甚至只有几十纳秒。
- **幅度异常**:可能超出有效电平范围,但 UART 接收器可能将其采样为有效边沿。
- **无规律性**:不遵循帧格式,且不会产生完整的起始位+数据位+停止位序列。
# 区分策略:三层过滤机制
## 第一层:硬件层过滤
- **使能 UART 的接收超时中断(RTO)**:若在设定时间内未收到新数据,触发中断,可标记帧结束。
- **配置 DMA 传输完成中断**:当 DMA 传输达到设定长度(如缓冲区满)时,可强制处理,但需结合超时判断。
- **使用 UART 的噪声检测位(NE)和帧错误位(FE)**:在接收中断中检查这些标志,若置位则丢弃当前字节。
## 第二层:软件层过滤——空闲线检测与超时判定
利用定时器(如基本定时器)或 UART 自身的空闲检测功能,实现帧超时判定。
- **空闲检测**:STM32 的 UART 支持 IDLE 中断,当总线空闲一个字节时间后触发。
- **超时判定**:在每次收到新字节时重置定时器,若定时器溢出(如超过 10ms),则认为当前帧结束。
## 第三层:数据校验——帧格式与 CRC
- **帧头校验**:检查帧头是否符合预设值,不符合则丢弃。
- **长度字段校验**:帧中携带长度信息,若实际接收长度与长度字段不符,则判定为噪声干扰。
- **CRC 校验**:对帧数据进行 CRC 计算,若错误则丢弃整帧。
# 代码实现示例
以下以 STM32F4 + HAL 库为例,展示如何结合 DMA 环形缓冲区、空闲中断和定时器超时实现帧边界识别。
## 1. 初始化配置
```c
// 定义环形缓冲区大小
#define RX_BUF_SIZE 256
// 环形缓冲区结构
volatile uint8_t rx_buf[RX_BUF_SIZE];
volatile uint16_t rx_head = 0;
volatile uint16_t rx_tail = 0;
// UART 句柄
UART_HandleTypeDef huart2;
// 定时器句柄(用于超时检测)
TIM_HandleTypeDef htim6;
void MX_UART2_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart2);
// 使能 UART 空闲中断
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
}
void MX_TIM6_Init(void)
{
// 定时器配置为 1ms 中断,用于超时检测
htim6.Instance = TIM6;
htim6.Init.Prescaler = 72-1; // 72MHz/72=1MHz
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 1000-1; // 1ms
HAL_TIM_Base_Init(&htim6);
HAL_TIM_Base_Start_IT(&htim6);
}
// 启动 DMA 接收
void UART_RX_Start(void)
{
HAL_UART_Receive_DMA(&huart2, (uint8_t*)rx_buf, RX_BUF_SIZE);
}
```
## 2. 中断处理
```c
// UART 中断回调(空闲中断)
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
// 检查空闲中断标志
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
// 更新环形缓冲区头指针
rx_head = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx);
// 停止定时器,表示帧接收完成
HAL_TIM_Base_Stop_IT(&htim6);
// 处理接收到的帧(见下方处理函数)
Process_Frame();
// 重新启动 DMA 接收
HAL_UART_Receive_DMA(huart, (uint8_t*)rx_buf, RX_BUF_SIZE);
}
}
}
// 定时器超时中断(若空闲中断未触发,则超时处理)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6)
{
// 超时,强制处理当前数据
rx_head = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&huart2.hdmarx);
Process_Frame();
// 重新启动 DMA 接收
HAL_UART_Receive_DMA(&huart2, (uint8_t*)rx_buf, RX_BUF_SIZE);
HAL_TIM_Base_Stop_IT(&htim6);
}
}
```
## 3. 帧处理与噪声过滤
```c
void Process_Frame(void)
{
uint16_t len = (rx_head >= rx_tail) ? (rx_head - rx_tail) : (RX_BUF_SIZE - rx_tail + rx_head);
if (len == 0) return;
// 检查帧头(例如 0xAA 0x55)
if (rx_buf[rx_tail] != 0xAA || rx_buf[(rx_tail+1)%RX_BUF_SIZE] != 0x55)
{
// 不是有效帧头,丢弃并移动尾指针
rx_tail = (rx_tail + 1) % RX_BUF_SIZE;
return;
}
// 检查长度字段(假设帧格式:AA 55 len data... CRC)
uint8_t frame_len = rx_buf[(rx_tail+2)%RX_BUF_SIZE];
if (len < frame_len + 5) // 帧头2+长度1+数据+CRC2
{
// 数据不完整,等待更多数据(但此处已触发空闲,可能噪声导致)
// 可丢弃或标记错误
rx_tail = (rx_tail + len) % RX_BUF_SIZE;
return;
}
// 计算 CRC 并校验(此处省略具体 CRC 实现)
if (Check_CRC(rx_buf, rx_tail, frame_len+3))
{
// 有效帧,处理数据
Handle_Valid_Frame(&rx_buf[rx_tail], frame_len+5);
}
else
{
// CRC 错误,丢弃
}
// 更新尾指针
rx_tail = (rx_tail + frame_len + 5) % RX_BUF_SIZE;
}
```
# 注意事项
- **环形缓冲区溢出**:当 DMA 接收速度大于处理速度时,缓冲区可能溢出,需在中断中及时处理,或增加缓冲区大小。
- **空闲中断与超时定时器协同**:空闲中断适合正常帧结束,但若发送方连续发送多帧且间隔极短,可能无法触发空闲,此时定时器超时作为兜底。
- **噪声毛刺的硬件抑制**:在硬件设计上,可增加 RC 滤波或使用 RS485 收发器,减少毛刺产生。
- **DMA 配置**:确保 DMA 工作在循环模式,且缓冲区大小需为 2 的幂,便于取模运算。
- **临界区保护**:在中断中修改 rx_head 和 rx_tail 时,主循环读取时需关闭中断,防止数据竞争。
# 总结
通过硬件空闲检测、软件超时判定和帧格式校验三层机制,可以有效地从 DMA 环形缓冲区中提取有效帧,并过滤掉噪声毛刺。实际应用中,还需根据具体场景调整超时时间和帧格式,以达到最佳平衡。希望本文能为你提供实用的参考。