三、STM32F103标准库DMA+USART空闲中断实现高效数据透传

张开发
2026/4/16 22:22:39 15 分钟阅读

分享文章

三、STM32F103标准库DMA+USART空闲中断实现高效数据透传
1. 为什么需要DMAUSART空闲中断方案在嵌入式开发中串口通信是最基础也最常用的外设之一。但很多新手在实现不定长数据接收时常常会遇到数据丢失、接收不完整的问题。传统的串口中断接收方式需要为每个字节都触发中断当数据量大时会导致CPU频繁中断严重影响系统整体性能。我在工业传感器采集项目中就踩过这个坑。当时用普通串口中断接收Modbus协议数据结果发现当传感器数量增加到8个时系统响应速度明显变慢还出现了数据包截断的情况。后来改用DMA空闲中断的方案CPU占用率直接从70%降到了5%而且再也没出现过数据丢失。这种方案的核心优势在于DMA自动搬运数据不需要CPU参与每个字节的传输解放处理器资源空闲中断精准判帧通过串口总线空闲状态判断一帧数据接收完成不受数据长度限制双缓冲机制DMA循环接收内存缓冲区切换实现数据无缝衔接2. 硬件与开发环境搭建2.1 硬件准备清单我用的是最常见的STM32F103C8T6最小系统板成本不到20元。你需要准备STM32F103开发板兼容Blue PillUSB转TTL模块推荐CH340G芯片杜邦线若干可选逻辑分析仪调试时序用硬件连接示意图[STM32] [USB-TTL] PA9(TX) ------ RX PA10(RX) ------ TX GND ------- GND2.2 开发环境配置推荐使用Keil MDK标准库的组合这是最稳定的开发环境。我测试过CubeMX生成的代码发现对DMA空闲中断的支持不够完善。具体配置步骤安装Keil MDK-ARM 5.30下载STM32F10x标准外设库3.5.0版本新建工程时选择Device: STM32F103C8Use Standard Peripheral Library勾选USART1和DMA模块注意不要使用HAL库标准库对DMA的控制更直接在高速数据传输时更可靠。3. 核心代码实现详解3.1 DMA双缓冲配置技巧先来看DMA接收部分的配置这是整个方案的关键。我优化过的配置代码如下#define USART1_REC_LEN 256 uint8_t USART1_RX_BUF[USART1_REC_LEN]; // 接收缓冲区 uint8_t USART1_RX_TEMP[USART1_REC_LEN]; // 临时缓冲区 void DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 使能DMA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 配置DMA接收通道 DMA_DeInit(DMA1_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)USART1_RX_BUF; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize USART1_REC_LEN; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_VeryHigh; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel5, DMA_InitStructure); // 启用DMA传输完成中断 DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE); DMA_Cmd(DMA1_Channel5, ENABLE); }这段代码有几个关键点需要注意使用Normal模式而非Circular模式便于精确控制缓冲区设置DMA_Priority为VeryHigh确保数据传输及时性内存地址自增外设地址固定串口数据寄存器3.2 空闲中断处理逻辑空闲中断的配置和处理是整个方案的灵魂所在。当串口总线空闲时间超过一个字节传输时间时就会触发空闲中断。我的处理流程如下void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_IDLE) ! RESET) { // 必须读取SR和DR寄存器来清除空闲中断标志 USART1-SR; USART1-DR; // 计算接收到的数据长度 uint16_t len USART1_REC_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); // 将数据拷贝到临时缓冲区 memcpy(USART1_RX_TEMP, USART1_RX_BUF, len); // 重新配置DMA DMA_Cmd(DMA1_Channel5, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel5, USART1_REC_LEN); DMA_Cmd(DMA1_Channel5, ENABLE); // 处理数据如透传到其他串口 Process_Received_Data(USART1_RX_TEMP, len); } }这里有个重要细节必须在中断服务函数开始时读取SR和DR寄存器否则空闲中断标志无法清除。我在早期版本中就因为这个疏忽导致系统不断进入中断。4. 数据透传实战优化4.1 DMA发送配置数据接收完成后通常需要将数据转发到其他设备。使用DMA发送可以最大限度降低CPU负载void USART1_DMA_Send(uint8_t *data, uint16_t len) { // 等待上次发送完成 while(DMA_GetCmdStatus(DMA1_Channel4) ! DISABLE); // 配置DMA发送 DMA_Cmd(DMA1_Channel4, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel4, len); DMA1_Channel4-CMAR (uint32_t)data; DMA_Cmd(DMA1_Channel4, ENABLE); // 使能USART DMA发送请求 USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); }4.2 流量控制策略在高波特率(≥115200)下建议加入硬件流控或软件流控机制。我在项目中实现的简单流控方案接收方缓冲区满时通过特定GPIO发送停止信号发送方检测到停止信号后暂停发送缓冲区有空闲时接收方发送继续信号// 硬件流控配置示例 USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_RTS_CTS;5. 常见问题与调试技巧5.1 DMA传输不稳定的解决方法在实际项目中我遇到过DMA偶尔丢失数据的问题。通过逻辑分析仪捕获发现是时钟配置问题。解决方案确保系统时钟配置正确RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 8MHz*972MHzDMA时钟必须使能RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);检查缓冲区地址对齐#pragma pack(1) // 取消字节对齐5.2 空闲中断无法触发的排查步骤如果发现空闲中断不触发可以按照以下步骤排查确认USART_ITConfig中使能了空闲中断USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);检查NVIC中断优先级配置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0;用示波器检查串口波形确认确实有总线空闲时段6. 性能测试与优化建议在我的测试平台上STM32F10372MHz不同方案的性能对比方案115200bps CPU占用率最大稳定速率普通中断65%460800bpsDMA空闲中断8%1.5MbpsDMA空闲中断双缓冲5%2Mbps对于要求更高的场景可以进一步优化使用内存到内存的DMA加速数据处理将缓冲区放在CCM RAM64KB中减少访问延迟采用DMA循环模式配合半传输中断在最近的一个工业网关项目中这套方案稳定运行了超过180天没有出现任何通信故障。关键是要做好异常处理比如DMA传输超时检测、缓冲区溢出保护等。

更多文章