串口工作在 DMA 模式下有时接收异常 前言 客户反馈在使用 STM32F205 的串口工作在 DMA 模式时, 有时能够接收数据, 有时完全没有数据, 但如果换成中断模式来接 收又能 100% 正常收到数据 一复现现象 问题背景 与客户沟通, 客户使用的是 STM32F2 标准库 V1.1.0, 串口波特率为 1.408Mbps, 不经过串口 RS232, 直接连接主 CPU 和从 MCU(STM32F205) 的串口发送和接收引脚, 如下图所示 : Tx STM32F205 Rx CPU 尝试重现问题 图 1 由于客户使用的是主从架构, 实验采用两块 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 的串口都能正常 二程序分析 由上述代码可知, 程序是先在 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 中的数据清除掉, 如下代码所示 : /* 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 标志 : 第一种方法类似于一种纠错措施, 下面介绍另一种推荐的方法, 如下代码所示 : /* 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 及时转走 这个 是推荐的解决方法 四结论 标准库中的示例代码一般来说只供参考, 对于大部分情况来说都是能正常工作的, 但偶尔也会出现不适用的情况, 此时更需要我们针对问题进行思考分析, 进一步找到原因才能解决问题 对于串口使用 DMA 来接收的情况, 这里建议一定要先使能 DMA, 最后使能串口, 这样就能避免类似问题出现了
重要通知 - 请仔细阅读 意法半导体公司及其子公司 ( ST ) 保留随时对 ST 产品和 / 或本文档进行变更 更正 增强 修改和改进的权利, 恕不另行通知 买方在订货之前应获取关于 ST 产品的最新信息 ST 产品的销售依照订单确认时的相关 ST 销售条款 买方自行负责对 ST 产品的选择和使用, ST 概不承担与应用协助或买方产品设计相关的任何责任 ST 不对任何知识产权进行任何明示或默示的授权或许可 转售的 ST 产品如有不同于此处提供的信息的规定, 将导致 ST 针对该产品授予的任何保证失效 ST 和 ST 徽标是 ST 的商标 所有其他产品或服务名称均为其各自所有者的财产 本文档中的信息取代本文档所有早期版本中提供的信息 2015 STMicroelectronics - 保留所有权利