STM32基礎分析——USART的DMA模式


有關USART的DMA傳輸模式,其基本的概念和配置,網上有很多博客和教程都有,這里不再贅述,只是記錄一下比較容易忽視而造成調試不通的問題。

1. 串口發送和接收分屬兩個DMA通道

一般方式操作串口時,讀寫數據都是只操作DR(數據寄存器),雖然它是由兩個寄存器組成的,一個給發送用(TDR),一個給接收用(RDR),但是用戶只能操作DR寄存。而DMA模式下,串口發送和接收分屬兩個DMA通道,需要單獨配置。

分別配置的代碼如下:

static void USART1_Tx_DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;  			// 配置DMA1_Channel4中斷
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    NVIC_Init(&NVIC_InitStructure); 
    
    DMA_DeInit(USART_TX_DMA_CHANNEL);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                  // 開啟DMA時鍾
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;        // 設置DMA源地址:串口數據寄存器地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)sendbuff;          // 內存地址(要傳輸的變量的指針)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;                  // 方向:從內存到外設
    DMA_InitStructure.DMA_BufferSize = CMD_NUM;                         // 傳輸大小	
    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一次模式	
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;               // 優先級:中 
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                        // 禁止內存到內存的傳輸
    DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);		            // 配置DMA通道DMA1_Channel4
   
    DMA_ITConfig(USART_TX_DMA_CHANNEL,DMA_IT_TC,ENABLE); 
    DMA_Cmd (USART_TX_DMA_CHANNEL,DISABLE);                              // 關閉DMA
}


static void USART1_Rx_DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
  //注意,接收沒使用接收DMA中斷
//    NVIC_InitTypeDef NVIC_InitStructure;
//    
//    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;  
//    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
//    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;  
//    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
//    NVIC_Init(&NVIC_InitStructure); 
    
    DMA_DeInit(USART_RX_DMA_CHANNEL);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                  // 開啟DMA時鍾
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;        // 設置DMA源地址:串口數據寄存器地址*/
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_cmd;            // 內存地址(要傳輸的變量的指針)
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                  // 方向:外設到內存
    DMA_InitStructure.DMA_BufferSize = CMD_NUM;                         // 傳輸大小	
    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一次模式	
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;               // 優先級:中 
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                        // 禁止內存到內存的傳輸
    DMA_Init(USART_RX_DMA_CHANNEL, &DMA_InitStructure);		            // 配置DMA通道DMA1_Channel5
   
//    DMA_ITConfig(USART_RX_DMA_CHANNEL,DMA_IT_TC,ENABLE); 
    DMA_Cmd (USART_RX_DMA_CHANNEL,ENABLE);                              // 使能DMA
}

注意:在串口的基本配置當中要打開DMA傳輸模式,函數如下:

    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);                    // 開啟串口發送DMA
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);                    // 開啟串口接收DMA  

2. 間隔單次傳輸

將DMA傳輸模式設置為Normal(一次傳輸),傳輸完成需要再次傳輸時,需要再次向DMA通道的傳輸數量寄存器(CNDTR)寫入要傳輸的字節數。但是,在寫入前,需要關閉DMA,寫完CNDTR后再打開。

2.1 串口DMA發送

我的設計方法是在初始化的時候,默認先關閉發送DMA,在需要串口發送數據時,先配置CNDTR,再打開DMA,發送完成后進入中斷函數,再關閉DMA。

void DMA1_Channel4_IRQHandler(void)  
{  
    DMA_ClearFlag(DMA1_FLAG_TC4);  
    DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE);  
} 
  
*********
//代碼片段
    DMA_SetCurrDataCounter(DMA1_Channel4,(uint16_t)CMD_NUM);      // 關於DMA單次傳輸,這條非常重要
    DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);

2.2 串口DMA接收

設計方法是:不啟用DMA接收通道中斷,而使用串口傳輸中斷,在串口中斷函數中對DMA處理。注意,一般串口中斷我們采用的是接收中斷USART_IT_RXNE,接收一次即中斷一次。在DMA模式下要使用空閑中斷USART_IT_IDLE,空閑中斷是在檢測到接收數據后,在數據總線上的一個字節時間內,如果沒有接收到新的數據,則觸發空閑中斷,它是在串口的RXNE位被置位之后才開始檢測。簡單理解是,連續的一串數據發送完成之后,才觸發空閑中斷。

串口的CR1寄存器的IDLE位被硬件置1,必須采用軟件將IDLE位清零才能避免反復進入空閑中斷。具體的做法是先讀取狀態寄存器USART_SR,再讀取數據寄存器USART_DR,完成后自動清除。需要注意的是,不能采用庫函數USART_ClearFlag()或者USART_ClearItPending()來清除IDEL標志,因為這兩個函數接收的中斷標志位僅包括:

  • USART_FLAG_CTS: CTS Change flag (not available for UART4 and UART5).

  • USART_FLAG_LBD: LIN Break detection flag.

  • USART_FLAG_TC: Transmission Complete flag.

  • USART_FLAG_RXNE: Receive data register not empty flag.

同理,關閉DMA后,重置傳輸字節數,再開啟DMA(因為串口一直要監測接收數據)。串口中斷函數基礎代碼如下:

void USART1_IRQHandler(void)                             
{
    uint32_t temp = 0;
    
    if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET) 
    {
//      temp = USART_GetITStatus(USART1,USART_IT_IDLE);       // 在判斷時已經讀取過一次
        temp = USART_ReceiveData(USART1);                     // 必須添加這條語句
        DMA_Cmd(USART_RX_DMA_CHANNEL,DISABLE);
        DMA_SetCurrDataCounter(DMA1_Channel5,(uint16_t)CMD_NUM);
        DMA_Cmd(USART_RX_DMA_CHANNEL,ENABLE);
    }
}

3. 疑問

實際上這里面還有一些隱含方式方法,感興趣的可以嘗試一下,歡迎分享。

  • 現在采用的是串口中斷來處理接收問題,是否可以采用DMA接收中斷來處理數據接收?就如同DMA發送中斷來處理發送數據一樣。

4. 參考文獻

  1. 《STM32F10X參考手冊》
  2. 《32位基於ARM微控制器STM32F101xx與STM32F103xx 固件函數庫》
  3. STM32的串口空閑中斷
  4. STM32的串口采用DMA方式接收數據測試
  5. STM32使用串口IDLE中斷的兩種接收不定長數據的方式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM