USART DMA雙緩沖給PC發送數據和接收PC數據


用DMA雙緩沖給PC發送串口數據 和 接收PC串口數據。

理解雙緩沖概念:就是利用兩個數組輪流導出或導進數據。

比如定義兩個緩沖區數組usart_buffer0[USART_NUM] 和 usart_buffer1[USART_NUM],數組大小USART_NUM要設置一樣。

 

給PC發送數據時:

DMA先從usart_buffer0(先假設從usart_buffer0)緩沖區拿數據發給PC,usart_buffer0發完后,DMA再從usart_buffer1中拿數據發給PC,usart_buffer1發完后,DMA再次回到usart_buffer0拿數據發給PC,循環操作。

注意:DMA在對usart_buffer1拿數據的同時,CPU可以更新usart_buffer0中的數據,同理,DMA在對usart_buffer0拿數據的同時,CPU也可以更新usart_buffer1中的數據

 接收數據也是同樣的道理。

 

先看看幾個問題:

 

(1)、DMA先從哪個地址拿數據發給PC?

    答:按照正常思路,覺得是先打印Memory0中的數據,而按照下面程序的配置,實測,PC先打印的數據是Memory1,這地方讓我有點迷糊,有點像“后進先出”。所以后面傳數組地址時,稍微注意一下。

                             

 

 

 

 

(2)、怎么知道DMA當前在對哪個緩沖區拿數據?

    答:可通過DMA_GetCurrentMemoryTarget(DMA_Stream_TypeDef* DMAy_Streamx);函數獲取信息,

    這里使用寄存器if (DMA1_Stream5->CR&(1<<19) ) 來判斷,也就是檢測CR寄存器中的第bit19位。

(3)、當DMA發送完usart_buffer0后,會不會進入中斷,還是等usart_buffer1發送完后進中斷?

    答:每當發送完一個緩沖就會進入一次中斷。

(4)、配置DMA緩沖區大小時,是配置USART_NUM還是 2*USART_NUM?  

     答: 配置大小是 USART_NUM

(5)、DMA切換緩沖區需要每次配置嗎?

    答:只需配置一次,軟件會自動切換。

先是頭文件

#ifndef __DEBUG_USART_H
#define    __DEBUG_USART_H

#include "stm32f4xx.h"
#include <stdio.h>

#define NEW_BOARD 1
#define OLD_BOARD 0


#if NEW_BOARD  //USART2 DMA1 µÚ6¸öÊý¾ÝÁ÷ µÚ4¸öͨµÀ
#define USART_ADDRESS 0x40004404
#define DEBUG_USART                             USART2
#define DEBUG_USART_CLK                         RCC_APB1Periph_USART2
#define DEBUG_USART_BAUDRATE                    115200

#define DEBUG_USART_RX_GPIO_PORT                GPIOD
#define DEBUG_USART_RX_GPIO_CLK                 RCC_AHB1Periph_GPIOD
#define DEBUG_USART_RX_PIN                      GPIO_Pin_6
#define DEBUG_USART_RX_AF                       GPIO_AF_USART2
#define DEBUG_USART_RX_SOURCE                   GPIO_PinSource6

#define DEBUG_USART_TX_GPIO_PORT                GPIOD
#define DEBUG_USART_TX_GPIO_CLK                 RCC_AHB1Periph_GPIOD
#define DEBUG_USART_TX_PIN                      GPIO_Pin_5
#define DEBUG_USART_TX_AF                       GPIO_AF_USART2
#define DEBUG_USART_TX_SOURCE                   GPIO_PinSource5

#define RCC_AHB1Periph_DMAx                                          RCC_AHB1Periph_DMA1
#define DMAx_Streamx                                                          DMA1_Stream6
#define Rx_DMAx_Streamx                                                  DMA1_Stream5
#define DMA_ALL_IT_FLAG                                                     DMA_IT_FEIF6|DMA_IT_DMEIF6|DMA_IT_TEIF6|DMA_IT_HTIF6|DMA_IT_TCIF6
#define DMA_Channel_x                                                         DMA_Channel_4
#define DMAx_Streamx_IRQn                                                 DMA1_Stream6_IRQn
#define DMAx_Streamx_IRQHandler                                    DMA1_Stream6_IRQHandler
#define DMA_IT_TCIFx                                                        DMA_IT_TCIF6
#endif

#if OLD_BOARD //USART1 DMA2  µÚ7¸öÊý¾ÝÁ÷ µÚ4¸öͨµÀ
#define USART_ADDRESS 0x40011004
#define DEBUG_USART                             USART1
#define DEBUG_USART_CLK                         RCC_APB2Periph_USART1
#define DEBUG_USART_BAUDRATE                    115200

#define DEBUG_USART_RX_GPIO_PORT                GPIOA
#define DEBUG_USART_RX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
#define DEBUG_USART_RX_PIN                      GPIO_Pin_10
#define DEBUG_USART_RX_AF                       GPIO_AF_USART1
#define DEBUG_USART_RX_SOURCE                   GPIO_PinSource10

#define DEBUG_USART_TX_GPIO_PORT                GPIOA
#define DEBUG_USART_TX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
#define DEBUG_USART_TX_PIN                      GPIO_Pin_9
#define DEBUG_USART_TX_AF                       GPIO_AF_USART1
#define DEBUG_USART_TX_SOURCE                   GPIO_PinSource9


#define RCC_AHB1Periph_DMAx                                            RCC_AHB1Periph_DMA2
#define DMAx_Streamx                                                        DMA2_Stream7
#define DMA_ALL_IT_FLAG                                                    DMA_IT_FEIF7|DMA_IT_DMEIF7|DMA_IT_TEIF7|DMA_IT_HTIF7|DMA_IT_TCIF7
#define DMA_Channel_x                                                        DMA_Channel_4
#define DMAx_Streamx_IRQn                                                DMA2_Stream7_IRQn
#define DMAx_Streamx_IRQHandler                                    DMA2_Stream7_IRQHandler
#define DMA_IT_TCIFx                                                        DMA_IT_TCIF7

#endif


#define __DEBUG     //¿ªÆô´®¿Úµ÷ÊÔ

#ifdef  __DEBUG
#define DEBUG(format,...)    printf("File:"__FILE__",Line:%03d:"format"\n",__LINE__,##__VA_ARGS__) 
#else

#define DEBUG(format,...)

#endif


void Debug_USART_Config(void);
void USART_Tx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num);
void USART_Rx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num);



#endif /* __USART1_H */
View Code

 

 

串口IO配置:

void Debug_USART_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
        
  RCC_AHB1PeriphClockCmd( DEBUG_USART_RX_GPIO_CLK|DEBUG_USART_TX_GPIO_CLK, ENABLE);

#if NEW_BOARD
  /* 使能 UART 時鍾 */
  RCC_APB1PeriphClockCmd(DEBUG_USART_CLK, ENABLE);  //使用的是USART2
#else
    RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);//使用的USART1
#endif
  
  /* 連接 PXx 到 USARTx_Tx*/
  GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,DEBUG_USART_RX_SOURCE, DEBUG_USART_RX_AF);

  /*  連接 PXx 到 USARTx__Rx*/
  GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,DEBUG_USART_TX_SOURCE,DEBUG_USART_TX_AF);

  /* 配置Tx引腳為復用功能  */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN  ;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  /* 配置Rx引腳為復用功能 */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
  GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
            
  /* 配置串DEBUG_USART 模式 */
  USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  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(DEBUG_USART, &USART_InitStructure); 
  USART_Cmd(DEBUG_USART, ENABLE);
}
View Code

 串口printf函數重定義

///重定向c庫函數printf到串口DEBUG_USART,重定向后可使用printf函數
int fputc(int ch, FILE *f)
{
        /* 發送一個字節數據到串口DEBUG_USART */
        USART_SendData(DEBUG_USART, (uint8_t) ch);
        
        /* 等待發送完畢 */
        while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET);        
    
        return (ch);
}

///重定向c庫函數scanf到串口DEBUG_USART,重寫向后可使用scanf、getchar等函數
int fgetc(FILE *f)
{
        /* 等待串口輸入數據 */
        while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_RXNE) == RESET);

        return (int)USART_ReceiveData(DEBUG_USART);
}
View Code

 

存儲器到外設的DMA 發送配置:

 

u8 tx_flag = 0;
u8 rx_flag = 0;
u8 usart_DMA_complete_tx = 0;
u8 usart_DMA_complete_rx = 0;

void USART_Tx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num)
{
    NVIC_InitTypeDef   NVIC_InitStructure;
    DMA_InitTypeDef  DMA_InitStructure;
    
   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMAx,ENABLE);//DMA時鍾使能 
    
    DMA_DeInit(DMAx_Streamx);
    while (DMA_GetCmdStatus(DMAx_Streamx) != DISABLE){}//等待DMAx_Streamx可配置 
        
    DMA_ClearITPendingBit(DMAx_Streamx,DMA_ALL_IT_FLAG);//清空DMAx_Streamx上所有中斷標志

  /* 配置 DMA Stream */
  DMA_InitStructure.DMA_Channel = DMA_Channel_x;  //通道設置
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_ADDRESS;//外設地址為
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer0;//DMA 存儲器memory0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存儲器到外設模式
  DMA_InitStructure.DMA_BufferSize = num;//數據傳輸量 ,注意這個大小
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存儲器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設數據長度:8位 注意數據寬度是8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;//存儲器數據長度:8位 注意數據寬度是8位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循環模式 ,雙緩沖區只能是循環模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高優先級
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式        
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外設突發單次傳輸
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存儲器突發單次傳輸
  DMA_Init(DMAx_Streamx, &DMA_InitStructure);//初始化DMA Stream

  DMA_DoubleBufferModeConfig(DMAx_Streamx,(uint32_t)buffer1,DMA_Memory_1);//配置Memory1地址,指向buffer1
  DMA_DoubleBufferModeCmd(DMAx_Streamx,ENABLE);//雙緩沖模式開啟
    
  DMA_ITConfig(DMAx_Streamx,DMA_IT_TC,ENABLE);//開啟傳輸完成中斷
    
  USART_DMACmd(DEBUG_USART,USART_DMAReq_Tx,ENABLE);//開啟串口對DMA發送請求
    
  DMA_Cmd(DMAx_Streamx,DISABLE); //為了驗證,DMA到底是先從哪個緩沖區拿數據,先不着急開啟
    
  NVIC_InitStructure.NVIC_IRQChannel = DMAx_Streamx_IRQn; //對應的中斷號
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//搶占優先級0
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//子優先級0
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
  NVIC_Init(&NVIC_InitStructure);//配置
}

void DMAx_Streamx_IRQHandler(void)
{
    if(DMA_GetITStatus(DMAx_Streamx,DMA_IT_TCIFx)==SET)//DMA傳輸完成標志
    { 
        DMA_ClearITPendingBit(DMAx_Streamx,DMA_IT_TCIFx);//清DMA傳輸完成標准
//        DMA_Cmd(DMAx_Streamx,DISABLE);
        if(DMAx_Streamx->CR&(1<<19)) 
        {
            tx_flag=2;     //說明DMA當前在Memory1中,通過判斷該標志,CPU可以更新Memory0中的數據
        }
        else                            
        {
            tx_flag=1;    //說明DMA當前在Memory0中,通過判斷該標志,CPU可以更新Memory1中的數據
        }
        usart_DMA_complete_tx++;
    }
}

 外設到存儲器DMA接收配置:

//USART2_RX 使用的是DMA1 ,第5個數據流,第四個通道
void USART_Rx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num)
{
    NVIC_InitTypeDef   NVIC_InitStructure;
    DMA_InitTypeDef  DMA_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMAx,ENABLE);//DMA時鍾使能 
    
    DMA_DeInit(DMA1_Stream5);
    while (DMA_GetCmdStatus(DMA1_Stream5) != DISABLE){}//等待DMA1_Stream5可配置 
        
    DMA_ClearITPendingBit(DMA1_Stream5,DMA_IT_FEIF5|DMA_IT_DMEIF5|DMA_IT_TEIF5|DMA_IT_HTIF5|DMA_IT_TCIF5);//清空DMA1_Stream5上所有中斷標志

  /* 配置 DMA Stream */
  DMA_InitStructure.DMA_Channel = DMA_Channel_x;  //通道
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_ADDRESS;//外設地址為
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer0;//DMA 存儲器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_BufferSize = num;//數據傳輸量 
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存儲器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外設數據長度:8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;//存儲器數據長度:8位 
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循環模式 
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高優先級
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式        
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外設突發單次傳輸
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存儲器突發單次傳輸
  DMA_Init(DMA1_Stream5, &DMA_InitStructure);//初始化DMA Stream
        
    DMA_DoubleBufferModeConfig(DMA1_Stream5,(uint32_t)buffer1,DMA_Memory_1);//配置DMA_Memory_1地址 
  DMA_DoubleBufferModeCmd(DMA1_Stream5,ENABLE);//雙緩沖模式開啟
    
  DMA_ITConfig(DMA1_Stream5,DMA_IT_TC,ENABLE);//開啟傳輸完成中斷
    
    USART_DMACmd(DEBUG_USART,USART_DMAReq_Rx,ENABLE);//開啟DMA接收請求
    
    DMA_Cmd(DMA1_Stream5,DISABLE);
    
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn; 
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//搶占優先級
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//子優先級
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中斷通道
  NVIC_Init(&NVIC_InitStructure);//配置
    
}

void DMA1_Stream5_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_Stream5,DMA_IT_TCIF5)==SET)//DMA傳輸完成標志
    { 
        DMA_ClearITPendingBit(DMA1_Stream5,DMA_IT_TCIF5);//清DMA傳輸完成標准
//        DMA_Cmd(DMAx_Streamx,DISABLE);
        if(DMA1_Stream5->CR&(1<<19))
        {
            rx_flag=2;   //說明DMA當前在Memory1中,主函數中通過判斷該標志,將Memory0中的數據拷貝到temp數組中
        }
        else                           
        {
            rx_flag=1;    //說明DMA當前在Memory0中,主函數通過判斷該標志,將Memory1中的數據拷貝到temp數組中
        }
        
        if(usart_DMA_complete_rx ==0)
        {
            printf("Please input 5 datas again>>\r\n");//當前面第一次輸入的5個數據收到之后,再次提示輸入5個數據,這樣就給兩個緩沖buffer都填沖了數據
        }
        usart_DMA_complete_rx++;
    }
}

主函數:

#define USART_NUM 5
u8 usartx_buffer0[USART_NUM] = {0x00,0x11,0x22,0x33,0x44};
u8 usartx_buffer1[USART_NUM] = {0x55,0x66,0x77,0x88,0x99};
extern u8 tx_flag;
extern u8 rx_flag;
extern u8 usart_DMA_complete_tx;
extern u8 usart_DMA_complete_rx;

int
main(void) {   Debug_USART_Config(); //串口IO配置 KEY_LMR_Init();//按鍵配置 u8 temp[10] = {0}; u8 i = 0; printf("USART DMA DoubleBuffer Tx/Rx Test\r\n"); printf("Press the Left key,STM send data to PC!\r\n"); printf("Press the Right key,STM receive data from PC !\r\n"); while(1) { if(KEYL==0) { Delay_ms(1000); //消抖,保證按下一次按鍵只進入一次 USART_Tx_DMA_Init(usartx_buffer1,usartx_buffer0,USART_NUM);//注意這里先usartx_buffer1->Memory0,后是usartx_buffer0->Memory1,實測是先打印出來的是Memory1中的數據, DMA_Cmd(DMAx_Streamx,ENABLE); //這樣打印順序00 ,11,22,33,44,55,66,77,88,99。 設置緩沖大小是USART_NUM,而不是2*USART_NUM } if(usart_DMA_complete_tx ==2)//當發完一輪數據,即10個數據后,關掉DMA中斷,要不然串口會一直不停的循環打印 { DMA_Cmd(DMAx_Streamx,DISABLE); usart_DMA_complete_tx = 0; printf("Tx Complete!!!\r\n"); } if(KEYR==0) //右鍵有按下時,PC可以給ST發送數據,數據量為USART_NUM { Delay_ms(1000); printf("Please input 5 datas>>\r\n");//提示輸入5個數據 USART_Rx_DMA_Init(usartx_buffer1,usartx_buffer0,USART_NUM);//注意這里先usartx_buffer1->Memory0,后是usartx_buffer0->Memory1,這樣PC先發一組5個數據,是先保存在usartx_buffer0中 DMA_Cmd(DMA1_Stream5,ENABLE); } if(rx_flag==1) { memcpy(temp,usartx_buffer0,sizeof(u8)*USART_NUM); //將Memory1中的數據拷貝到temp中的前5位當中 } if(rx_flag==2) { memcpy((u8*)(temp+USART_NUM),usartx_buffer1,sizeof(u8)*USART_NUM);//將Memory0中的數據拷貝到temp中的后5位當中 } if(usart_DMA_complete_rx ==2) //接收到了10個數據,關掉DMA,按一次按鍵只收一輪數據 { DMA_Cmd(DMA1_Stream5,DISABLE); usart_DMA_complete_rx = 0; printf("Rx Complete!!!\r\n"); printf("Press the middle key to print Rx_Buff!!\r\n"); } if(KEYM == 0)//當接收完一輪數據后,按中鍵將接收到的數據進行打印出來對比 { Delay_ms(1000); for(i=0; i<10; i++) { printf("0x%-2x\r\n",temp[i]); //左對齊打印 } printf("Print Rx_buff Complete!!!\r\n"); memset(temp,0,sizeof(u8) * USART_NUM * 2); } } }

 


免責聲明!

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



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