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