串口工作在DMA模式下有时接收异常

1 前言

客户反馈在使用STM32F205的串口工作在DMA模式时,有时能够接收数据,有时完全没有数据,但如果换成中断模式来接收又能100%正常收到数据。

2 复现现象

2.1 问题背景

与客户沟通,客户使用的是STM32F2标准库V1.1.0,串口波特率为1.408Mbps,不经过串口RS232,直接连接主CPU和从MCU(STM32F205)的串口发送和接收引脚,如下图所示:

图1

图1

2.2 尝试重现问题

由于客户使用的是主从架构,实验采用两块STM3220G-EVAL评估板来重现现象。一块用来不间断发送串口数据,另一块采用串口DMA进行接收,直接通过杜邦线连接串口PIN脚并共地,不使用评估板上的RS232收发器。接收端使用STM32F2xx_StdPeriph_Examples\ USART\USART_TwoBoards的示例代码。代码片段如下:

int main(void)
{
  ...
  USART_Config();
  ...
  while (1)
  {
    /* Clear Buffers */
    Fill_Buffer(RxBuffer, TXBUFFERSIZE);
    Fill_Buffer(CmdBuffer, 2);

    DMA_DeInit(USARTx_RX_DMA_STREAM);
    DMA_InitStructure.DMA_Channel = USARTx_RX_DMA_CHANNEL;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    /************* USART will receive the the transaction data ****************/
    /* Transaction data (length defined by CmdBuffer[1] variable) */       
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RxBuffer;
    DMA_InitStructure.DMA_BufferSize =10;// (uint16_t)CmdBuffer[1];
    DMA_InitStructure.DMA_Mode =DMA_Mode_Normal;//DMA_Mode_Circular;
    DMA_Init(USARTx_RX_DMA_STREAM, &DMA_InitStructure);
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure); 
    /* Enable DMA Stream Transfer Complete interrupt */
    DMA_ITConfig(USARTx_RX_DMA_STREAM, DMA_IT_TE|DMA_IT_DME|DMA_IT_FE, ENABLE);

    /* Enable the DMA Stream */
    DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);
    /* Enable the USART Rx DMA requests */
    USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);

//  USART_Cmd(USARTx, ENABLE);
//  while(SET ==USART_GetFlagStatus(USARTx,USART_FLAG_ORE))
//  {
//    Tmp =USART_ReceiveData(USARTx);
//  }


    while ((DMA_GetFlagStatus(USARTx_RX_DMA_STREAM, USARTx_RX_DMA_FLAG_TCIF) ==     RESET)
    {
    }   
    /* Clear all DMA Streams flags */
    DMA_ClearFlag(USARTx_RX_DMA_STREAM, USARTx_RX_DMA_FLAG_HTIF | USARTx_RX_DMA_FLAG_TCIF);

    /* Disable the DMA Stream */
    DMA_Cmd(USARTx_RX_DMA_STREAM, DISABLE);

    /* Disable the USART Rx DMA requests */
    USART_DMACmd(USARTx, USART_DMAReq_Rx, DISABLE);

//handle the RxBuffer data...
    //...
  }
}

USART_Config()函数如下:

static void USART_Config(void)
{
  USART_InitTypeDef USART_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;

  /* Peripheral Clock Enable -------------------------------------------------*/
  /* Enable GPIO clock */
  RCC_AHB1PeriphClockCmd(USARTx_TX_GPIO_CLK | USARTx_RX_GPIO_CLK, ENABLE);

  /* Enable USART clock */
  USARTx_CLK_INIT(USARTx_CLK, ENABLE);

  /* Enable the DMA clock */
  RCC_AHB1PeriphClockCmd(USARTx_DMAx_CLK, ENABLE);

  /* USARTx GPIO configuration -----------------------------------------------*/ 
  /* Connect USART pins to AF7 */
  GPIO_PinAFConfig(USARTx_TX_GPIO_PORT, USARTx_TX_SOURCE, USARTx_TX_AF);
  GPIO_PinAFConfig(USARTx_RX_GPIO_PORT, USARTx_RX_SOURCE, USARTx_RX_AF);

  /* Configure USART Tx and Rx as alternate function push-pull */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

  GPIO_InitStructure.GPIO_Pin = USARTx_TX_PIN;
  GPIO_Init(USARTx_TX_GPIO_PORT, &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Pin = USARTx_RX_PIN;
  GPIO_Init(USARTx_RX_GPIO_PORT, &GPIO_InitStructure);

  /* USARTx configuration ----------------------------------------------------*/
  /* Enable the USART OverSampling by 8 */
  USART_OverSampling8Cmd(USARTx, ENABLE); 

  USART_InitStructure.USART_BaudRate = 1408000;//3750000;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  /* When using Parity the word length must be configured to 9 bits */
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  USART_Init(USARTx, &USART_InitStructure);

  /* Configure DMA controller to manage USART TX and RX DMA request ----------*/  
  DMA_InitStructure.DMA_PeripheralBaseAddr = USARTx_DR_ADDRESS;
  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_FIFOMode = DMA_FIFOMode_Enable;
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  /* Here only the unchanged parameters of the DMA initialization structure are
     configured. During the program operation, the DMA will be configured with 
     different parameters according to the operation phase */

  /* Enable USART */
  USART_Cmd(USARTx, ENABLE);
}

按如上代码,有如下现象:
1. 代码不做修改,若先启动接收端MCU再启动发送端MCU,接收端MCU的串口能正常接收。
2. 代码不做修改,若先启动发送端MCU再启动接收端MCU,接收端MCU的串口100%接收异常。
3. 修改发送端代码,改为发送端MCU串口每1秒间隔发送一次,则无论启动顺序如何,接收端MCU的串口都能正常。

3 程序分析

由上述代码可知,程序是先在USART_Config()函数函数内初始化串口并使能,然后再在接下来的main函数的while循环内初始化DMA并使能。这个是标准库内附带的示例代码,咋一看没什么问题,但仔细一想,针对用户的使用场景,这里就会产生一个问题:由于用户的主CPU有可能在从MCU启动之前就已经有可能启动,那么在这种情况下,在初始化完串口并使能后,到DMA使能之前这段时间内,若主CPU向从MCU发送串口数据,从MCU是否能正确接收?

从上述测试代码的结果2可以得出,若在串口初始化并使能后到DMA使能之前有数据来,MCU是不能接收的,经进一步调试,发现此时数据寄存器USART_DR存在一个数据,且在状态寄存器USART_SR中ORE值1,由此可知,串口的接收寄存器中已经接收到一个数据,但是后面的数据又来了,由于数据寄存器中的数据没有及时转移走(此时DMA还没有开启),从而导致后面的数据无法存入,所以产生了上溢错误(ORE),而一旦产生上溢错误后,就无法再触发DAM请求,及时之后再启动DMA也不行,无法触发DMA请求就无法将数据寄存器内的数据及时转移走,如此陷入死锁,这就是串口无法正常接收的原因。这时反观一下代码的结果3,这又将做如何解释?

仔细查看测试结果3,发现这个发送端每1秒间隔发送一次,那么就会存在这个一个概率,这个发送的时间点是否刚好在接收端MCU的串口初始化并使能和DMA使能之间还是之后,这个时间窗口非常关键,如果刚好在时间窗,那么串口接收就不正常,如果在这个时间窗之后,串口接收就能正常。由于测试代码采用的是1秒间隔,对于MCU来说这个是非常大的时间长度,还是很小概率能碰中这个时间窗的,因此,测试结果看起来是都能正常,实际严格来说,还是存在刚好碰中的可能。如果间隔时间缩短,那个碰中的几率就增大。由此看来,这也就能解释测试结果3了,也能解释客户提到的有时正常有时不正常的现象了。

4 问题处理

处理有两种方法,第一种方法是在使能DMA后,及时将数据寄存器DR中的数据清除掉,如下代码所示:

//...
/* Enable the DMA Stream */
    DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);
    /* Enable the USART Rx DMA requests */
    USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);

  while(SET ==USART_GetFlagStatus(USARTx,USART_FLAG_ORE))
  {
    Tmp =USART_ReceiveData(USARTx);
  }
  //...

这里是使用读DR的方法来清除的,从参考手册中也提到使用这种方法来清除ORE标志:

图2

图2

第一种方法类似于一种纠错措施,下面介绍另一种推荐的方法,如下代码所示:

//...
/* Enable the DMA Stream */
    DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);
    /* Enable the USART Rx DMA requests */
    USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);

  USART_Cmd(USARTx, ENABLE);
  //...

如上所示,可以先使能DMA再使能串口,这样就彻底不存在那个时间窗了,不管数据何时过来能能被DAM及时转走。这个是推荐的解决方法。

5 结论

标准库中的示例代码一般来说只供参考,对于大部分情况来说都是能正常工作的,但偶尔也会出现不适用的情况,此时更需要我们针对问题进行思考分析,进一步找到原因才能解决问题。对于串口使用DMA来接收的情况,这里建议一定要先使能DMA,最后使能串口,这样就能避免类似问题出现了。

本文转自:http://www.stmcu.org/module/forum/thread-606799-1-1.html

### STM32 DMA模块关闭后再重新开启时常见问题及解决方案 在STM32微控制器中,DMA(Direct Memory Access)是一种高效的数据传输机制,能够减少CPU的负担并提高系统的性能。然而,在实际应用中,当DMA被关闭后再次启用时可能会遇到一些常见的问题。以下是这些问题及其对应的解决方法: #### 1. **DMA通道冲突** 如果多个外设共享同一个DMA通道,则可能导致数据传输混乱或失败。这种情况下,即使某个外设的DMA已经关闭,另一个外设仍可能占用该通道。 - 解决方案: 确保不同外设使用的DMA请求不发生冲突。可以通过检查`RCC_APB1ENR`寄存器中的相关位来确认DMA通道的状态[^3]。另外,合理分配DMA资源,避免多路复用同一通道。 #### 2. **DMA标志未清零** 每次DMA传输完成后,硬件会设置相应的状态标志(如TCIF——Transfer Complete Interrupt Flag)。如果不及时清除这些标志,下次启动DMA时可能会误触发中断或其他异常行为。 - 解决方案: 在重启DMA之前,务必调用函数清理相关的标志位。例如: ```c __HAL_DMA_CLEAR_FLAG(&hdma_usart, DMA_FLAG_TC); ``` 此操作可防止残留标志干扰新一次的DMA工作流程[^2]。 #### 3. **缓冲区指针重置不当** 一旦DMA停止运行,其内部指向源地址和目标地址的指针并不会自动回到初始位置。这意味着如果没有手动调整这些指针的位置,下一轮传输将从上轮结束的地方继续而非预期起点。 - 解决方案: 每当准备重新激活DMA前,应显式设定新的起始地址以及长度参数。比如对于UART发送功能来说: ```c hdma_usart.Init.MemAddr = (uint32_t)dataBuffer; if(HAL_DMA_Init(&hdma_usart)!= HAL_OK){ Error_Handler(); } ``` #### 4. **串口中断优先级设置错误** 有时尽管DMA本身运作良好,但由于串口接收/发送完成后的处理逻辑存在问题也会间接影响整体表现。特别是当中断服务程序(ISRs)执行时间过长或者存在死循环等情况时尤为明显。 - 解决方案: 适当降低无关紧要的任务优先级,并优化ISR代码效率以缩短响应延迟。同时注意观察是否有其他高优先级事件抢占了当前任务所需的时间片[^4]。 #### 5. **电源管理引起的问题** 进入低功耗模式期间某些外设会被禁用甚至掉电,这其中包括部分DMA单元。这样即便后来恢复正常供电也无法立即恢复先前的工作条件。 - 解决方案: 设计应用程序框架时充分考虑节能需求与实时性之间的平衡关系。必要时候通过软件手段唤醒沉睡中的组件再行初始化[^1]。 --- ### 示例代码片段展示如何安全地停启DMA过程 下面给出一段简单的C语言伪代码演示上述提到的一些技巧的实际运用场景: ```c void Restart_DMA_Transmission(void){ // Step A: Stop existing transmission and clear flags. HAL_UART_DMAStop(&huart_instance); __HAL_DMA_CLEAR_FLAG(&hdma_usart,DMA_FLAG_TE|DMA_FLAG_HT|DMA_FLAG_TC); // Step B: Reset memory address pointer to start of buffer. hdma_usart.Instance->CMAR = (uint32_t)data_buffer_start; // Step C: Reconfigure any necessary parameters then restart the channel. if(HAL_DMA_Start_IT(&hdma_usart,(uint32_t)data_buffer_start,data_length)==HAL_OK){ HAL_UART_DMAPause(&huart_instance); HAL_UART_DMAResume(&huart_instance); } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值