[STM32F2] STM32F205串口工作在 DMA 模式下有时接收异常

[复制链接]
1130|10
 楼主| 盗铃何须掩耳 发表于 2021-11-8 09:50 | 显示全部楼层 |阅读模式
前言
在使用 STM32F205 的串口工作在 DMA 模式时,有时能够接收数据,有时完全没有数据,但如果换成中断模式来接
收又能 100%正常收到数据  

 楼主| 盗铃何须掩耳 发表于 2021-11-8 10:13 | 显示全部楼层
问题背景
使用的是 STM32F2 标准库 V1.1.0,串口波特率为 1.408Mbps,不经过串口 RS232,直接连接主 CPU 和
从 MCU(STM32F205)的串口发送和接收引脚,如下图所示:  

26111618887a2ac439.png
 楼主| 盗铃何须掩耳 发表于 2021-11-8 10:14 | 显示全部楼层
尝试重现问题
由于客户使用的是主从架构,实验采用两块 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 的串口都能正常。


 楼主| 盗铃何须掩耳 发表于 2021-11-8 10:16 | 显示全部楼层
二 程序分析
由上述代码可知,程序是先在 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 了, 也能解释客户提到的有时正常有时不正常的
现象了。  

 楼主| 盗铃何须掩耳 发表于 2021-11-8 10:17 | 显示全部楼层
三 问题处理
处理有两种方法,第一种方法是在使能 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 标志:
8971461888895aad5a.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,最后使能串口,这样就能避免类似问题出现了


wowu 发表于 2021-12-4 09:51 | 显示全部楼层
非常不错的解决办法
renzheshengui 发表于 2021-12-4 09:54 | 显示全部楼层
感觉dma很容易出问题啊
wakayi 发表于 2021-12-4 09:55 | 显示全部楼层
普通的和dma能提高多少效率啊
tpgf 发表于 2021-12-4 09:57 | 显示全部楼层
使能的顺序有要求吗
xiaoqizi 发表于 2021-12-4 09:59 | 显示全部楼层
这是一种手动的清除方式吗
木木guainv 发表于 2021-12-4 10:01 | 显示全部楼层
这个芯片是不是使用的比较少啊
您需要登录后才可以回帖 登录 | 注册

本版积分规则

50

主题

385

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部