串口工作在DMA模式下有時接收異常

1 前言

客戶反饋在使用STM32F205的串口工作在DMA模式時,有時能夠接收數據,有時完全沒有數據,但如果換成中斷模式來接收又能100%正常收到數據。

2 復現現象

2.1 問題背景

與客戶溝通,客戶使用的是STM32F2標準庫V1.1.0,串口波特率爲1.408Mbps,不經過串口RS232,直接連接主CPU和從MCU(STM32F205)的串口發送和接收引腳,如下圖所示:

圖1

圖1

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標誌:

圖2

圖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,最後使能串口,這樣就能避免類似問題出現了。

本文轉自:http://www.stmcu.org/module/forum/thread-606799-1-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章