你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】串口工作在 DMA 模式下有时接收异常

[复制链接]
STMCU小助手 发布时间:2022-2-14 21:13
前言
客户反馈在使用 STM32F205 的串口工作在 DMA 模式时,有时能够接收数据,有时完全没有数据,但如果换成中断模式来接
收又能 100%正常收到数据。
一 复现现象
问题背景
与客户沟通,客户使用的是 STM32F2 标准库 V1.1.0,串口波特率为 1.408Mbps,不经过串口 RS232,直接连接主 CPU 和从 MCU(STM32F205)的串口发送和接收引脚,如下图所示:

L~F9~}F}TMRGAGZ(8)`)LX8.png


尝试重现问题
由于客户使用的是主从架构,实验采用两块 STM3220G-EVAL 评估板来重现现象。一块用来不间断发送串口数据,另一块采用串口 DMA 进行接收,直接通过杜邦线连接串口 PIN 脚并共地,不使用评估板上的 RS232 收发器。接收端使用STM32F2xx_StdPeriph_Examples\ USART\USART_TwoBoards 的示例代码。代码片段如下:
  1. int main(void)
  2. {
  3. ...
  4. USART_Config();
  5. ...
  6. while (1)
  7. {
  8. /* Clear Buffers */
  9. Fill_Buffer(RxBuffer, TXBUFFERSIZE);
  10. Fill_Buffer(CmdBuffer, 2);

  11. DMA_DeInit(USARTx_RX_DMA_STREAM);
  12. DMA_InitStructure.DMA_Channel = USARTx_RX_DMA_CHANNEL;
  13. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  14. /************* USART will receive the the transaction data ****************/
  15. /* Transaction data (length defined by CmdBuffer[1] variable) */
  16. DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)RxBuffer;
  17. DMA_InitStructure.DMA_BufferSize =10;// (uint16_t)CmdBuffer[1];
  18. DMA_InitStructure.DMA_Mode =DMA_Mode_Normal;//DMA_Mode_Circular;
  19. DMA_Init(USARTx_RX_DMA_STREAM, &DMA_InitStructure);
  20. NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn;
  21. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  22. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  23. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  24. NVIC_Init(&NVIC_InitStructure);
  25. /* Enable DMA Stream Transfer Complete interrupt */
  26. DMA_ITConfig(USARTx_RX_DMA_STREAM, DMA_IT_TE|DMA_IT_DME|DMA_IT_FE, ENABLE);
  27. /* Enable the DMA Stream */
  28. DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);
  29. /* Enable the USART Rx DMA requests */
  30. USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);
  31. // USART_Cmd(USARTx, ENABLE);
  32. // while(SET ==USART_GetFlagStatus(USARTx,USART_FLAG_ORE))
  33. // {
  34. // Tmp =USART_ReceiveData(USARTx);
  35. // }
  36. while ((DMA_GetFlagStatus(USARTx_RX_DMA_STREAM, USARTx_RX_DMA_FLAG_TCIF) ==
  37. RESET)
  38. {
  39. }
  40. /* Clear all DMA Streams flags */
  41. DMA_ClearFlag(USARTx_RX_DMA_STREAM, USARTx_RX_DMA_FLAG_HTIF |
  42. USARTx_RX_DMA_FLAG_TCIF);

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

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

  47. //handle the RxBuffer data...
  48. //...
  49. }
  50. }
复制代码
USART_Config()函数如下:
  1. static void USART_Config(void)
  2. {
  3. USART_InitTypeDef USART_InitStructure;
  4. GPIO_InitTypeDef GPIO_InitStructure;
  5. /* Peripheral Clock Enable -------------------------------------------------*/
  6. /* Enable GPIO clock */
  7. RCC_AHB1PeriphClockCmd(USARTx_TX_GPIO_CLK | USARTx_RX_GPIO_CLK, ENABLE);
  8. /* Enable USART clock */
  9. USARTx_CLK_INIT(USARTx_CLK, ENABLE);

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

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

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

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

  23. GPIO_InitStructure.GPIO_Pin = USARTx_RX_PIN;
  24. GPIO_Init(USARTx_RX_GPIO_PORT, &GPIO_InitStructure);
  25. /* USARTx configuration ----------------------------------------------------*/
  26. /* Enable the USART OverSampling by 8 */
  27. USART_OverSampling8Cmd(USARTx, ENABLE);

  28. USART_InitStructure.USART_BaudRate = 1408000;//3750000;
  29. USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  30. USART_InitStructure.USART_StopBits = USART_StopBits_1;
  31. /* When using Parity the word length must be configured to 9 bits */
  32. USART_InitStructure.USART_Parity = USART_Parity_No;
  33. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  34. USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  35. USART_Init(USARTx, &USART_InitStructure);
  36. /* Configure DMA controller to manage USART TX and RX DMA request ----------*/
  37. DMA_InitStructure.DMA_PeripheralBaseAddr = USARTx_DR_ADDRESS;
  38. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  39. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  40. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  41. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  42. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  43. DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
  44. DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
  45. DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  46. DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  47. DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  48. /* Here only the unchanged parameters of the DMA initialization structure are
  49. configured. During the program operation, the DMA will be configured with
  50. different parameters according to the operation phase */

  51. /* Enable USART */
  52. USART_Cmd(USARTx, ENABLE);
  53. }
复制代码

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

二 程序分析

由上述代码可知,程序是先在 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 了,也能解释客户提到的有时正常有时不正常的现象了。

三 问题处理
处理有两种方法,第一种方法是在使能 DMA 后,及时将数据寄存器 DR 中的数据清除掉,如下代码所示:
  1. ...
  2. /* Enable the DMA Stream */
  3. DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);
  4. /* Enable the USART Rx DMA requests */
  5. USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);
  6. while(SET ==USART_GetFlagStatus(USARTx,USART_FLAG_ORE))
  7. {
  8. Tmp =USART_ReceiveData(USARTx);
  9. }
  10. ...
复制代码


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

YNFNU51MS8FI%{Z{XOKKZRA.png

第一种方法类似于一种纠错措施,下面介绍另一种推荐的方法,如下代码所示:
  1. ...
  2. /* Enable the DMA Stream */
  3. DMA_Cmd(USARTx_RX_DMA_STREAM, ENABLE);
  4. /* Enable the USART Rx DMA requests */
  5. USART_DMACmd(USARTx, USART_DMAReq_Rx , ENABLE);
  6. USART_Cmd(USARTx, ENABLE);
  7. ...
复制代码

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

四 结论

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

收藏 评论0 发布时间:2022-2-14 21:13

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版