一、为什么需要半满中断与空闲中断协同?
在STM32中使用DMA接收串口数据时,传统做法是每收到一个字节就触发中断,但这会带来巨大的CPU开销。HAL库提供了两种更高效的机制:
- 半满中断(Half Transfer):当DMA缓冲区接收数据达到一半时触发,适合处理定长或流式数据。
- 空闲中断(IDLE):当串口总线在一个字节时间内无新数据时触发,适合处理不定长数据帧。
单独使用任一种都有局限:仅用半满中断无法及时处理短帧;仅用空闲中断在高速大数据量时可能因频繁中断而丢包。协同处理能兼顾实时性与效率:半满中断负责批量搬运,空闲中断负责帧尾判定。
二、协同处理原理
假设DMA接收缓冲区大小为BUFFER_SIZE,工作流程如下:
- DMA循环模式接收数据,计数器
NDTR递减。 - 当接收量达到
BUFFER_SIZE/2时触发半满中断,此时可处理前半部分数据(或仅作标记)。 - 当总线空闲时触发IDLE中断,此时根据
NDTR计算已接收数据长度,处理剩余数据。 - 在中断服务程序中重置DMA计数器或重新启动接收,避免数据覆盖。
关键点:半满中断与空闲中断共享同一个DMA缓冲区,需通过状态变量协调读写位置,防止数据竞争。
三、CubeMX配置步骤
- 使能USART1,模式为Asynchronous,波特率115200。
- 在NVIC中使能USART1全局中断。
- 添加DMA通道:USART1_RX,模式为Circular,数据宽度Byte,优先级High。
- 生成代码后,在
main.c中手动开启IDLE中断和DMA接收。
四、完整代码示例
#define RX_BUFFER_SIZE 256
uint8_t rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_len = 0;
volatile uint8_t rx_flag = 0;
void USART1_Init(void)
{
// CubeMX已生成基础配置,此处补充中断开启
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
// 串口中断服务函数
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
// 空闲中断处理
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) {
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
// 停止DMA以获取准确计数
HAL_UART_DMAStop(&huart1);
uint16_t remaining = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
rx_len = RX_BUFFER_SIZE - remaining;
rx_flag = 1;
// 重新启动DMA接收
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
}
}
// DMA半满/完成回调(可选,用于流式处理)
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
// 前半部分数据已就绪,可在此处理
// 注意:不要在此重启DMA,否则会破坏循环模式
}
}
// 主循环中处理数据
void Process_Data(void)
{
if (rx_flag) {
rx_flag = 0;
// 处理rx_buffer中长度为rx_len的数据
// 例如:解析协议、存入队列等
}
}
五、丢包排查指南
实际项目中常遇到“数据收不全”问题,按以下顺序排查:
- 检查DMA模式:必须为Circular,否则一次接收后停止。
- 检查中断优先级:USART中断优先级应高于或等于DMA中断,避免IDLE响应延迟。
- 检查缓冲区大小:若数据帧长度接近缓冲区大小,半满中断与空闲中断可能冲突,建议缓冲区为最大帧长的2倍以上。
-
检查
HAL_UART_DMAStop调用时机:在IDLE中断中停止DMA会丢失后续字节,若数据流连续,应改用__HAL_DMA_DISABLE并快速重启,或使用双缓冲。 -
检查
__HAL_UART_CLEAR_IDLEFLAG顺序:必须先清标志再处理数据,否则可能重复进入中断。 -
检查编译器优化:
rx_len和rx_flag需加volatile,防止被优化。 - 使用逻辑分析仪:确认实际波形与波特率误差,误差>3%会导致误码丢包。
六、注意事项
- 半满中断与空闲中断同时使能时,若在空闲中断中重启DMA,需确保半满回调不会访问已失效的缓冲区。
- 对于高速连续数据流,建议采用“双缓冲+空闲中断”方案,避免停止DMA造成的窗口丢失。
- 调试时可在IDLE中断中翻转GPIO,用示波器观察中断响应时间,评估系统负载。
- HAL库的
HAL_UART_Receive_DMA在循环模式下不会自动重装计数器,需在IDLE中断中手动重启。
通过合理协同半满与空闲中断,并遵循上述排查方法,可构建出稳定可靠的串口接收框架,满足大多数嵌入式应用需求。
暂无评论,来抢沙发~