用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 */
串口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); }
串口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); }
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); } } }