一、DMA半传输与全传输中断原理
STM32的DMA控制器支持两种关键中断:
- 半传输中断(HT):当DMA传输数据量达到设定缓冲区大小的一半时触发。
- 全传输中断(TC):当DMA传输数据量达到设定缓冲区大小时触发。
以串口接收为例,若配置DMA接收缓冲区长度为BUFFER_SIZE,则:
- 接收前
BUFFER_SIZE/2字节时触发HT中断; - 接收满
BUFFER_SIZE字节时触发TC中断。
利用这两个中断,可实现双缓冲(Ping-Pong) 机制:在HT中断中处理前半缓冲区数据,在TC中断中处理后半缓冲区数据,从而避免数据覆盖,实现连续不间断接收。
二、HAL库中的中断回调与协同配置
HAL库为DMA传输提供了三个回调函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
-
HAL_UART_RxCpltCallback对应TC中断; -
HAL_UART_RxHalfCpltCallback对应HT中断。
协同处理的关键:在HT和TC回调中分别处理缓冲区的前半段和后半段,并重置相应的读指针。注意,HAL库的DMA接收在循环模式下会自动重载计数器,无需手动重启。
三、配置步骤(以UART+DMA接收为例)
-
CubeMX配置:
- 启用USART1,模式为Asynchronous;
- 在DMA Settings中添加USART1_RX,模式为Circular,数据宽度Byte;
- 使能USART1全局中断和DMA中断(在NVIC中勾选USART1 global interrupt)。
-
代码初始化:
#define BUFFER_SIZE 64 uint8_t rx_buf[BUFFER_SIZE]; // 启动DMA接收(循环模式) HAL_UART_Receive_DMA(&huart1, rx_buf, BUFFER_SIZE); -
实现回调函数:
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 处理前半缓冲区 rx_buf[0] ~ rx_buf[BUFFER_SIZE/2 - 1] process_data(rx_buf, BUFFER_SIZE/2); } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 处理后半缓冲区 rx_buf[BUFFER_SIZE/2] ~ rx_buf[BUFFER_SIZE - 1] process_data(&rx_buf[BUFFER_SIZE/2], BUFFER_SIZE/2); } }
四、完整代码示例(不定长数据接收)
以下代码实现基于空闲中断(IDLE)与DMA HT/TC的协同,适用于不定长数据帧接收:
#define RX_BUF_SIZE 128
uint8_t rx_buf[RX_BUF_SIZE];
volatile uint16_t last_pos = 0;
void start_reception(void) {
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能空闲中断
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
}
// 空闲中断处理(在USART1_IRQHandler中调用)
void USART1_IRQHandler(void) {
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
uint16_t current_pos = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
if (current_pos != last_pos) {
// 处理从last_pos到current_pos的数据
process_frame(&rx_buf[last_pos], current_pos - last_pos);
last_pos = current_pos;
}
}
HAL_UART_IRQHandler(&huart1);
}
// HT回调:处理前半段
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
process_frame(rx_buf, RX_BUF_SIZE/2);
last_pos = RX_BUF_SIZE/2;
}
}
// TC回调:处理后半段
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
process_frame(&rx_buf[RX_BUF_SIZE/2], RX_BUF_SIZE/2);
last_pos = 0; // 循环缓冲区,重置位置
}
}
五、常见丢包问题排查
-
中断优先级冲突:
- DMA中断与UART中断优先级需合理设置,避免高优先级中断长时间阻塞导致DMA溢出。
- 建议DMA中断优先级高于UART中断。
-
缓冲区大小不足:
- 若数据处理耗时较长,应增大缓冲区或提高处理速度。
- 使用双缓冲时,确保半缓冲区处理时间小于接收半缓冲区数据的时间。
-
未及时清除中断标志:
- 在回调中避免耗时操作,尽快返回。
- 检查是否因未清除IDLE标志导致重复进入中断。
-
DMA模式配置错误:
- 循环模式下DMA自动重载,无需手动重启;正常模式需在TC回调中重新启动接收。
- 确认
HAL_UART_Receive_DMA调用后DMA使能位已置位。
-
内存对齐与Cache问题(针对Cortex-M7等):
- 若使用D-Cache,需确保DMA缓冲区位于非缓存区域或手动维护Cache一致性。
-
串口波特率与DMA请求不匹配:
- 过高波特率可能导致DMA请求丢失,可降低波特率或使用硬件流控。
六、注意事项
- 在HT和TC回调中避免调用阻塞函数(如
HAL_Delay)。 - 使用
__HAL_DMA_GET_COUNTER获取当前传输计数时,注意该寄存器在循环模式下会重载。 - 若同时使用HT和TC,需确保数据处理逻辑不会重复处理同一段数据。
- 调试时可通过翻转GPIO或打印时间戳观察中断触发时序。
通过合理协同HT与TC中断,并结合空闲中断,可构建高效可靠的DMA数据接收框架,显著降低CPU负载并提升系统实时性。
暂无评论,来抢沙发~