You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

总结

  1. 调整使能顺序为UART参数配置 >> UART EN >> DMAR EN >> DMA Stream EN
  2. 修正波特率BRR值
  3. 正确清除DMA和UART的中断标志
  4. 补全UART基础配置项

以上修改可解决DMA传输错误中断的问题。

内容的提问来源于stack exchange,提问作者Rupesh kadam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:44:52