1 前言 客户反馈在使用STM32F205的串口工作在DMA模式时,有时能够接收数据,有时完全没有数据,但如果换成中断模式来接收又能100%正常收到数据。 2 复现现象2.1 问题背景与客户沟通,客户使用的是STM32F2标准库V1.1.0,串口波特率为1.408Mbps,不经过串口RS232,直接连接主CPU和从MCU(STM32F205)的串口发送和接收引脚,如下图所示:
图1
2.2 尝试重现问题由于客户使用的是主从架构,实验采用两块STM3220G-EVAL评估板来重现现象。一块用来不间断发送串口数据,另一块采用串口DMA进行接收,直接通过杜邦线连接串口PIN脚并共地,不使用评估板上的RS232收发器。接收端使用STM32F2xx_StdPeriph_Examples USARTUSART_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... //... }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
按如上代码,有如下现象:
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
第一种方法类似于一种纠错措施,下面介绍另一种推荐的方法,如下代码所示:
//.../* 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,最后使能串口,这样就能避免类似问题出现了。 嵌入式学习交流群:561213221
|