打印
[STM32F2]

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

[复制链接]
816|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
前言
在使用 STM32F205 的串口工作在 DMA 模式时,有时能够接收数据,有时完全没有数据,但如果换成中断模式来接
收又能 100%正常收到数据  

使用特权

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


使用特权

评论回复
板凳
盗铃何须掩耳|  楼主 | 2021-11-8 10:14 | 只看该作者
尝试重现问题
由于客户使用的是主从架构,实验采用两块 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 的串口都能正常。


使用特权

评论回复
地板
盗铃何须掩耳|  楼主 | 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 了, 也能解释客户提到的有时正常有时不正常的
现象了。  

使用特权

评论回复
5
盗铃何须掩耳|  楼主 | 2021-11-8 10:17 | 只看该作者
三 问题处理
处理有两种方法,第一种方法是在使能 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,最后使能串口,这样就能避免类似问题出现了


使用特权

评论回复
6
wowu| | 2021-12-4 09:51 | 只看该作者
非常不错的解决办法

使用特权

评论回复
7
renzheshengui| | 2021-12-4 09:54 | 只看该作者
感觉dma很容易出问题啊

使用特权

评论回复
8
wakayi| | 2021-12-4 09:55 | 只看该作者
普通的和dma能提高多少效率啊

使用特权

评论回复
9
tpgf| | 2021-12-4 09:57 | 只看该作者
使能的顺序有要求吗

使用特权

评论回复
10
xiaoqizi| | 2021-12-4 09:59 | 只看该作者
这是一种手动的清除方式吗

使用特权

评论回复
11
木木guainv| | 2021-12-4 10:01 | 只看该作者
这个芯片是不是使用的比较少啊

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

48

主题

376

帖子

0

粉丝