1 前言
客戶反饋在使用STM32F205的串口工作在DMA模式時,有時能夠接收數據,有時完全沒有數據,但如果換成中斷模式來接收又能100%正常收到數據。
2 復現現象
2.1 問題背景
與客戶溝通,客戶使用的是STM32F2標準庫V1.1.0,串口波特率爲1.408Mbps,不經過串口RS232,直接連接主CPU和從MCU(STM32F205)的串口發送和接收引腳,如下圖所示:
2.2 嘗試重現問題
由於客戶使用的是主從架構,實驗採用兩塊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的串口都能正常。
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標誌:
第一種方法類似於一種糾錯措施,下面介紹另一種推薦的方法,如下代碼所示:
//...
/* 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,最後使能串口,這樣就能避免類似問題出現了。
本文轉自:http://www.stmcu.org/module/forum/thread-606799-1-1.html