STM32F446RE UART DMA循环接收触发传输错误中断问题排查
问题
我正在对STM32F446RE Nucleo-64开发板进行寄存器级编程,希望通过DMA以循环模式实现UART数据接收。每次使用Tera Term终端以115200波特率通过PC虚拟COM口发送字节到STM32时,都会触发DMA传输错误中断。我使用的是UART2,其引脚通过ST-Link微控制器连接到USB,可通过PC虚拟COM口访问。
我怀疑配置的使能顺序存在问题,当前使用的顺序为:UART RX EN >> DMAR EN >> DMA Stream EN >> UART EN。请问这是UART配置DMA接收的正确顺序吗?
以下是已实现的代码:
main.c
#include <stdint.h> #include <string.h> #include "stm32f446xx.h" void HT_Complete_callback(void); void FT_Complete_callback(void); void TE_error_callback(void); void FE_error_callback(void); void DME_error_callback(void); void IDLE_IRQ_callback(void); void DMA_init(void); void DMA_Config (uint32_t srcAdd, uint32_t destAdd, uint16_t datasize); void UART2_init (void); void UART2_config (void); #define RXSIZE 20 #define pDest (SRAM2_BASE + 0x800) int main(void) { UART2_init (); DMA_init(); DMA_Config (USART2->DR,(uint32_t) pDest, RXSIZE); UART2_config (); /* Loop forever */ while (1) { } } void UART2_init (void) { RCC_TypeDef* pRCC = RCC; GPIO_TypeDef* pGPIOA = GPIOA; // 1. Enable RCC Clock for UART and GPIOA pins PA2-TX and PA3-RX pRCC->AHB1ENR |= (1 << 0); pRCC->APB1ENR |= (1 << 17); // 2. Configure PA2 and PA3 pins as alternate function pins moder register pGPIOA->MODER |= (0x2 << 4); pGPIOA->MODER |= (0x2 << 6); pGPIOA->AFR[0] |= (7<<8); pGPIOA->AFR[0] |= (7<<12); // Enable internal pull-up for PA2 pGPIOA->PUPDR |= (0x1 << 4); // Enable internal pull-up for PA3 pGPIOA->PUPDR |= (0x1 << 6); } void UART2_config (void) { USART_TypeDef* pUART2 = USART2; DMA_Stream_TypeDef* pRXStream = DMA1_Stream5; //RX pUART2->CR1 = 0x00; // Clear ALL pUART2->CR1 |= (1 << 4); // Enable IDLE Interrupt NVIC_EnableIRQ(USART2_IRQn); //Configure baud rate pUART2->BRR = 0x8B; // 10. Enable RX engine pUART2->CR1 |= (1 << 2); pUART2->CR3 |= (1 << 6); //DMR bit enable // 4. Enable the DMA1 stream 5 pRXStream->CR &= ~(1<<0); while((pRXStream->CR & (1<<0))); pRXStream->CR |= 1<<0; // 4. Enable the DMA1 stream 5 // 11. Enable UART engine pUART2->CR1 |= (1 << 13); } void HT_Complete_callback(void) { while (1); //TODO: Implement callback } void FT_Complete_callback(void) { while (1); //TODO: Implement callback } void DMA_init(void) { // 0. Pointers for the registers RCC_TypeDef* pRCC = RCC; //pointer to RCC_Typedef DMA_Stream_TypeDef* pRXStream = DMA1_Stream5; //RX // 1. Enable DMA1 Clock pRCC->AHB1ENR |= (1 << 21); pRXStream->CR = 0x0; //clear reg before config pRXStream->CR |= (1 << 27); // channel 4 select // 2. Enable DMA Interrupts DMA1->HIFCR = 0x00; //clear flags first pRXStream->CR |= (1<<1); // TCIE, HTIE, TEIE, DMEIE Enabled pRXStream->CR |= (1<<2); pRXStream->CR |= (1<<3); pRXStream->CR |= (1<<4); NVIC_EnableIRQ(DMA1_Stream5_IRQn); // 3. Set the Data Direction pRXStream->CR |= (1<<6); // Peripheral to Memory // 4. Enable the circular mode (CIRC) pRXStream->CR |= 1<<8; // 5. Enable the Memory Increment (MINC) pRXStream->CR |= 1<<10; // 6. Set the Peripheral data size (PSIZE) pRXStream->CR &= ~(3<<11); // 00 : 8 Bit Data // 7. Set the Memory data size (MSIZE) pRXStream->CR &= ~(3<<13); // 00 : 8 Bit Data // 8. Set the Priority Level pRXStream->CR &= ~(3<<16); // PL = 0 } void DMA_Config (uint32_t srcAdd, uint32_t destAdd, uint16_t datasize) { DMA_Stream_TypeDef* pRXStream = DMA1_Stream5; //RX // 1. Set the data size in CNDTR Register pRXStream->NDTR = datasize; // 2. Set the peripheral address in PAR Register pRXStream->PAR = srcAdd; // 3. Set the Memory address in M0AR Register pRXStream->M0AR = destAdd; } void TE_error_callback(void) { while (1); //TODO: Implement callback } void FE_error_callback(void) { while (1); //TODO: Implement callback } void DME_error_callback(void) { while (1); //TODO: Implement callback } void IDLE_IRQ_callback(void) { while (1); //TODO: Implement callback }
stm32f446_it.c
#include "stm32f446xx.h" #include <stdint.h> #define is_it_HT() DMA1->HISR & ( 1 << 10) #define is_it_FT() DMA1->HISR & ( 1 << 11) #define is_it_TE() DMA1->HISR & ( 1 << 9) #define is_it_FE() DMA1->HISR & ( 1 << 6) #define is_it_DME() DMA1->HISR & ( 1 << 8) #define is_it_IDLE() USART2->SR & ( 1 << 4) extern void HT_Complete_callback(void); extern void FT_Complete_callback(void); extern void TE_error_callback(void); extern void FE_error_callback(void); extern void DME_error_callback(void); extern void IDLE_IRQ_callback(void); void USART2_IRQHandler(void) { if(is_it_IDLE()) { USART2->SR &= ~( 1 << 4); // clear IDLE bit } else { ; } } void DMA1_Stream5_IRQHandler(void) { if( is_it_HT() ) { DMA1->HIFCR |= ( 1 << 10); HT_Complete_callback(); }else if(is_it_FT() ) { DMA1->HIFCR |= ( 1 << 11); FT_Complete_callback(); }else if ( is_it_TE() ) { DMA1->HIFCR |= ( 1 << 9); TE_error_callback(); }else if (is_it_FE() ) { DMA1->HIFCR |= ( 1 << 6); FE_error_callback(); }else if( is_it_DME() ) { DMA1->HIFCR |= ( 1 << 8); DME_error_callback(); }else{ ; } }
调试时初始化流程正常,能进入主循环,但一旦通过PC发送字节,就会进入DMA传输错误中断处理函数。
解答
1. 使能顺序问题
你当前的使能顺序是错误的,正确的UART DMA接收使能顺序应该是:
- 先配置UART基础参数(波特率、数据位、停止位等)
- 使能UART外设(
UE位,CR1的bit13) - 使能UART的DMA接收(
DMAR位,CR3的bit6) - 最后使能DMA流
错误顺序会导致UART在DMA未准备就绪时就触发传输请求,直接引发DMA错误中断。
2. 其他关键错误修正
(1) 波特率配置错误
STM32F446RE的APB1默认时钟为45MHz,115200波特率对应的BRR值计算如下:BRR = APB1_CLK / (16 * BAUD_RATE) = 45000000/(16*115200) ≈ 24.414
整数部分为24(0x18),小数部分0.414*16≈6.624取6(0x6),因此BRR应设置为0x186,而非代码中的0x8B。波特率不匹配会导致接收帧错误,进而触发DMA传输错误。
(2) DMA标志清除不彻底
在DMA_init()中,仅执行DMA1->HIFCR = 0x00无法完全清除Stream5的残留中断标志,需显式清除所有相关标志:
DMA1->HIFCR |= (1 << 6) | (1 << 8) | (1 << 9) | (1 << 10) | (1 << 11);
(3) UART CR1清除后的配置遗漏
代码中执行pUART2->CR1 = 0x00会清除所有UART配置,需补全基础参数:
pUART2->CR1 &= ~(1 << 12); // M=0,8位数据位 pUART2->CR2 &= ~(3 << 12); // STOP=00,1位停止位
(4) IDLE中断标志清除方式错误
USART的IDLE位不能直接通过写SR寄存器清除,正确流程是先读SR寄存器,再读DR寄存器:
void USART2_IRQHandler(void) { if(is_it_IDLE()) { volatile uint32_t dummy; dummy = USART2->SR; // 先读SR dummy = USART2->DR; // 再读DR,清除IDLE标志 } }
修正后的UART配置函数示例
void UART2_config (void) { USART_TypeDef* pUART2 = USART2; DMA_Stream_TypeDef* pRXStream = DMA1_Stream5; //RX pUART2->CR1 = 0x00; // Clear ALL pUART2->CR2 = 0x00; pUART2->CR3 = 0x00; // 配置数据位、停止位:8位数据,1位停止 pUART2->CR1 &= ~(1 << 12); pUART2->CR2 &= ~(3 << 12); // 配置波特率 pUART2->BRR = 0x186; // 45MHz APB1时钟下115200波特率 // 使能RX和IDLE中断 pUART2->CR1 |= (1 << 2) | (1 << 4); NVIC_EnableIRQ(USART2_IRQn); // 1. 先使能UART外设 pUART2->CR1 |= (1 << 13); // 2. 使能UART DMA接收 pUART2->CR3 |= (1 << 6); // 3. 最后使能DMA流 pRXStream->CR &= ~(1<<0); while((pRXStream->CR & (1<<0))); pRXStream->CR |= 1<<0; }
总结
- 调整使能顺序为
UART参数配置 >> UART EN >> DMAR EN >> DMA Stream EN - 修正波特率BRR值
- 正确清除DMA和UART的中断标志
- 补全UART基础配置项
以上修改可解决DMA传输错误中断的问题。
内容的提问来源于stack exchange,提问作者Rupesh kadam

