完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
[tr]串口发送TXPA9DMA2_Stream7通道4正常模式[/tr]
PA9,PA10复用输出 注意点 串口接受数据dma方式不能开启串口接受中断 Dma双缓冲默认开启循环模式 Dma开启时需要确保相对应的标志位清零 仅在使能指针递增模式时允许突发模式 如果禁止数据流时仍有某些数据存留在FIFO 中, DMA 控制器会将剩余的数据继续传输到目标(即使已经有效禁止了数据流)。 以后再更USART串口空闲中断接受不定长数据 USART.h文件 #ifndef __MYUSART_H_ #define __MYUSART_H_ #include "stm32f4xx.h" #include #include #include #include "LED.h" /* 最大超时时间 */ #define TIMEOUT_MAX 10000 /* 串口发送和接收数据寄存器地址 */ #define USART1_DR_BASE (USART1_BASE + 0X04) /* 串口波特率配置 */ #define USART1_BaudRate 115200 /* 串口发送使用的GPIO时钟 */ #define USART1_TX_GPIO_CLK RCC_AHB1Periph_GPIOA /* 串口发送使用的GPIO */ #define USART1_TX_GPIO GPIOA /* 串口发送使用的引脚 */ #define USART1_TX_Pin GPIO_Pin_9 /* 串口发送复用配置的源 */ #define USART1_TX_PinSource GPIO_PinSource9 /* 串口接收使用的GPIO时钟 */ #define USART1_RX_GPIO_CLK RCC_AHB1Periph_GPIOA /* 串口接收使用的GPIO */ #define USART1_RX_GPIO GPIOA /* 串口接收使用的引脚 */ #define USART1_RX_Pin GPIO_Pin_10 /* 串口接收复用配置的源 */ #define USART1_RX_PinSource GPIO_PinSource10 /*串口发送和接收使用DMA的时钟 */ #define USART1_DMA_CLK RCC_AHB1Periph_DMA2 /* 串口发送使用的DMA流 */ #define USART1_TX_DMAStream DMA2_Stream7 /* 串口发送使用的通道 */ #define USART1_TX_DMA_Channel DMA_Channel_4 /* 串口发送DMA中断 */ #define USART_TX_DMA_IRQn DMA2_Stream7_IRQn /* 串口接收使用的DMA流 */ #define USART1_RX_DMAStream DMA2_Stream5 /* 串口接收使用的通道 */ #define USART1_RX_DMA_Channel DMA_Channel_4 /* 串口接收DMA中断 */ #define USART_RX_DMA_IRQn DMA2_Stream5_IRQn /* 完成 / 不完成 */ #define COMPLETE 1 #define UNCOMPLETE 0 /* 发送缓冲区大小 */ #define SENDBUFF_SIZE 128 /* 发送缓冲区 */ extern uint8_t USART1_SendBuff[SENDBUFF_SIZE]; /* 发送标志 初始化发送完成 即USART1_TX_FLAG = COMPLETE */ extern uint8_t USART1_TX_FLAG; /* 接收缓冲区大小 */ #define RECEIVEBUFF_SIZE 64 /* 接收缓冲区0 */ extern uint8_t USART1_ReceiveBuff0[RECEIVEBUFF_SIZE]; /* 接收缓冲区1 */ extern uint8_t USART1_ReceiveBuff1[RECEIVEBUFF_SIZE]; void USART1_Init(void); void USART1_printf(char *format, ...); #endif // __MYUSART_H_ USART.C文件 #include "USART.h" /* 发送缓冲区 */ uint8_t USART1_SendBuff[SENDBUFF_SIZE] = {0}; /* 发送标志 初始化发送完成 即USART1_TX_FLAG = COMPLETE */ uint8_t USART1_TX_FLAG = COMPLETE; /* 接收缓冲区0 */ uint8_t USART1_ReceiveBuff0[RECEIVEBUFF_SIZE] = {0}; /* 接收缓冲区1 */ uint8_t USART1_ReceiveBuff1[RECEIVEBUFF_SIZE] = {0}; /** * @brief 配置串口1 USART1 DMA发送和接收 并开启DMA传输完成中断 串口发送和接收DMA FIFO模式 阈值4个字(16个字节) 突发模式16个节拍的1次突发 * @retval None */ void USART1_Init(void) { __IO uint16_t Timeout = TIMEOUT_MAX; //配置超时等待时间 /* 初始化结构体 */ GPIO_InitTypeDef GPIO_InitStructure; //GPIO初始化结构体 USART_InitTypeDef USART_InitStructure; //USART初始化结构体 DMA_InitTypeDef DMA_InitStructure; //DMA初始化结构体 NVIC_InitTypeDef NVIC_InitStructure; //中断初始化结构体 /*************** 配置GPIO ***************/ /* 打开串口发送GPIO的时钟 */ RCC_AHB1PeriphClockCmd(USART1_TX_GPIO_CLK|USART1_RX_GPIO_CLK, ENABLE); /* 设置串口发送的引脚为复用 */ //设置复用推挽输出 GPIO_InitStructure.GPIO_Pin = USART1_TX_Pin; //设置为复用模式 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //设置为推挽模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //输出速度为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //上拉 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //根据初始化结构体配置 GPIO_Init(USART1_TX_GPIO, &GPIO_InitStructure); /* 打开串口接收GPIO的时钟 */ RCC_AHB1PeriphClockCmd(USART1_RX_GPIO_CLK, ENABLE); /* 设置串口接收的引脚为复用 */ //设置复用推挽输出 GPIO_InitStructure.GPIO_Pin = USART1_RX_Pin; //设置为复用模式 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //设置为推挽模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //输出速度为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //浮空 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //根据初始化结构体配置 GPIO_Init(USART1_RX_GPIO, &GPIO_InitStructure); /* 复用串口发送和接收引脚 */ GPIO_PinAFConfig(USART1_TX_GPIO, USART1_TX_PinSource, GPIO_AF_USART1); GPIO_PinAFConfig(USART1_RX_GPIO, USART1_RX_PinSource, GPIO_AF_USART1); /*************** 配置串口 ***************/ /* 开启串口1时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); /* 配置串口1 波特率115200 停止位1位 无校验位 使能发送和接收 */ //设置波特率115200 USART_InitStructure.USART_BaudRate = USART1_BaudRate; //设置字长为8位 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //设置停止位1位 USART_InitStructure.USART_StopBits = USART_StopBits_1; //无校验 USART_InitStructure.USART_Parity = USART_Parity_No; //开启接收和发送 USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; //硬件流控制:不使用硬件流 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //配置USART1 USART_Init(USART1, &USART_InitStructure); /* 开启DMA发送和接收 */ USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); /*************** 配置串口发送DMA ***************/ /* 打开DMA时钟 */ RCC_AHB1PeriphClockCmd(USART1_DMA_CLK, ENABLE); /* 复位DMA 在启用DMA流之前,请检查它是否已禁用。 请注意,当同一个流被多次使用时,此步骤非常有用: 启用,然后禁用,然后重新启用... 在这种情况下,DMA流禁用只有在进行中的数据传输结束时才有效。 在确认启用位之前不可能重新配置它已经被硬件清除了。 如果流只使用一次,则此步骤可能被绕过。 ***个人理解 假如之前使用该DMA的这个流则此时DMA是启用状态 现在我们复用该流,但是该DMA是启用状态,只有当它数据传输结束的时候我们才能真正的禁用它 所以我们得检测它是不是在启用状态,如果是启用状态则等待它数据传输完成。 在他禁用有效之后也就是数据传输完成之后我们再配置该DMA的流*/ DMA_DeInit(USART1_TX_DMAStream); /* 检测确保DMA数据流复位完成 也就是一直是启用状态 如果复位完成应该是DISABLE */ while(DMA_GetCmdStatus(USART1_TX_DMAStream) == ENABLE); /* 串口发送DMA配置 */ //通道选择 DMA_InitStructure.DMA_Channel = USART1_TX_DMA_Channel; //外设基地址 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART1_DR_BASE; //存储器地址 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)USART1_SendBuff; //模式选择 存储器到外设 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //需要传输的数据数目 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; //外设地址是否自增 这里选择不自增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //存储器地址是否自增 这里选择自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //设置外设数据宽度 1个字节 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //设置存储器数据宽度 1个字节 DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //一次传输模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //循环传输模式 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //设置优先级高 DMA_InitStructure.DMA_Priority = DMA_Priority_High; //启用FIFO模式 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //外设单次模式 不使用突发模式 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //存储器突发模式 16个字节一次突发 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC16; /* 根据初始化结构体配置DMA */ DMA_Init(USART1_TX_DMAStream, &DMA_InitStructure); /*************** 配置串口接收DMA ***************/ /* 打开DMA时钟 */ RCC_AHB1PeriphClockCmd(USART1_DMA_CLK, ENABLE); /* 复位DMA 在启用DMA流之前,请检查它是否已禁用。 请注意,当同一个流被多次使用时,此步骤非常有用: 启用,然后禁用,然后重新启用... 在这种情况下,DMA流禁用只有在进行中的数据传输结束时才有效。 在确认启用位之前不可能重新配置它已经被硬件清除了。 如果流只使用一次,则此步骤可能被绕过。 ***个人理解 假如之前使用该DMA的这个流则此时DMA是启用状态 现在我们复用该流,但是该DMA是启用状态,只有当它数据传输结束的时候我们才能真正的禁用它 所以我们得检测它是不是在启用状态,如果是启用状态则等待它数据传输完成。 在他禁用有效之后也就是数据传输完成之后我们再配置该DMA的流*/ DMA_DeInit(USART1_RX_DMAStream); /* 检测确保DMA数据流复位完成 也就是一直是启用状态 如果复位完成应该是DISABLE */ while(DMA_GetCmdStatus(USART1_RX_DMAStream) == ENABLE); /* 串口发送DMA配置 */ //通道选择 DMA_InitStructure.DMA_Channel = USART1_RX_DMA_Channel; //外设基地址 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART1_DR_BASE; //存储器地址 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)USART1_ReceiveBuff0; //模式选择 外设到存储器 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //需要传输的数据数目 DMA_InitStructure.DMA_BufferSize = RECEIVEBUFF_SIZE; //外设地址是否自增 这里选择不自增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //存储器地址是否自增 这里选择自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //设置外设数据宽度 1个字节 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //设置存储器数据宽度 1个字节 DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte; //一次传输模式 //DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //循环传输模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //设置优先级高 DMA_InitStructure.DMA_Priority = DMA_Priority_High; //启用FIFO模式 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //外设单次模式 不使用突发模式 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //存储器突发模式 16个字节一次突发 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC16; /* 串口接收DMA双缓冲模式 */ DMA_DoubleBufferModeConfig(USART1_RX_DMAStream, (uint32_t)USART1_ReceiveBuff1, DMA_Memory_0); DMA_DoubleBufferModeCmd(USART1_RX_DMAStream, ENABLE); /* 根据初始化结构体配置DMA */ DMA_Init(USART1_RX_DMAStream, &DMA_InitStructure); /*************** 配置串口发送DMA中断 ***************/ /* 设置串口发送DMA传输完成中断 */ /* 指定要启用或禁用的IRQ通道。 此参数可以是@ref IRQn_类型的枚举器枚举 (对于完整的STM32设备IRQ通道列表,请参考stm32f4xx.h文件)*/ NVIC_InitStructure.NVIC_IRQChannel = USART_TX_DMA_IRQn; /* 指定IRQ通道的抢占优先级 在NVIC的IRQChannel中指定。此参数可以是值 如表@ref MISC_NVIC_Priority_table所述,介于0和15之间较低的优先级值表示优先级较高*/ //设置抢占优先级 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; /* 指定指定IRQ通道的子优先级级别在NVIC信道中。 此参数可以是值 如表@ref MISC_NVIC_Priority_table所述,介于0和15之间 较低的优先级值表示优先级较高*/ //设置子优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; /*指定是否在NVIC的IRQChannel中定义IRQ通道将启用或禁用。 此参数可以设置为启用或禁用*/ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* 根据指定的初始化NVIC外围设备 *NVIC_InitStruct中的参数。*/ //根据自定义的中断初始化结构体初始化中断 NVIC_Init(&NVIC_InitStructure); /* 打开串口发送DMA传输完成中断 */ DMA_ITConfig(USART1_TX_DMAStream, DMA_IT_TC, ENABLE); /*************** 配置串口接收DMA中断 ***************/ /* 设置串口接收DMA传输完成中断 */ /* 指定要启用或禁用的IRQ通道。 此参数可以是@ref IRQn_类型的枚举器枚举 (对于完整的STM32设备IRQ通道列表,请参考stm32f4xx.h文件)*/ NVIC_InitStructure.NVIC_IRQChannel = USART_RX_DMA_IRQn; /* 指定IRQ通道的抢占优先级 在NVIC的IRQChannel中指定。此参数可以是值 如表@ref MISC_NVIC_Priority_table所述,介于0和15之间较低的优先级值表示优先级较高*/ //设置抢占优先级 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; /* 指定指定IRQ通道的子优先级级别在NVIC信道中。 此参数可以是值 如表@ref MISC_NVIC_Priority_table所述,介于0和15之间 较低的优先级值表示优先级较高*/ //设置子优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; /*指定是否在NVIC的IRQChannel中定义IRQ通道将启用或禁用。 此参数可以设置为启用或禁用*/ NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* 根据指定的初始化NVIC外围设备 *NVIC_InitStruct中的参数。*/ //根据自定义的中断初始化结构体初始化中断 NVIC_Init(&NVIC_InitStructure); /* 打开串口接收DMA传输完成中断 */ DMA_ITConfig(USART1_RX_DMAStream, DMA_IT_TC, ENABLE); /*************** 使能DMA和串口 ***************/ /* 使能串口接收DMA */ DMA_Cmd(USART1_RX_DMAStream, ENABLE); /* 检查DMA流是否已有效启用。 如果存在配置参数错误,传输未启动(即 配置了错误的FIFO阈值…)*/ //检测DMA数据流是否有效并带有超时检测功能 Timeout = TIMEOUT_MAX; while(DMA_GetCmdStatus(USART1_RX_DMAStream) == DISABLE && Timeout-- > 0); //超时就让程序运行下面循环:红色LED灯闪烁 if(Timeout == 0) { while(1) { LED_R = ~LED_R; Delay_ms(500); } } /* 使能串口 */ USART_Cmd(USART1, ENABLE); } /** * @brief 串口发送 DMA方式 * @param length: 数组长度 * @retval 无 */ static void USART1_DMA_Send(uint16_t length) { /* 等待上一次发送完成 如果USART1_TX_FLAG为COMPLETE则上一次发送完成 */ while(USART1_TX_FLAG != COMPLETE) { } /* 设置这次的发送标志未完成 */ USART1_TX_FLAG = UNCOMPLETE; /* 设置需要发送的数组地址 也可以用寄存器操作DMA2_Stream7->M0AR = (uint32_t)data;*/ //DMA_MemoryTargetConfig(DMA2_Stream7, (uint32_t)data, DMA_Memory_0); /* 设置需要发送的字节长度 也可以用寄存器操作DMA2_Stream7->NDTR = (uint16_t)length;*/ DMA_SetCurrDataCounter(USART1_TX_DMAStream, length); /* 使能串口发送DMA */ DMA_Cmd(USART1_TX_DMAStream, ENABLE); } /** * @brief 串口发送仿printf DMA方式 * @param format: 需要发送的数据 * @retval 无 */ void USART1_printf(char *format, ...) { //VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件:#include va_list arg_ptr; //实例化可变长参数列表 va_start(arg_ptr, format); //初始化可变参数列表,设置format为可变长列表的起始点(第一个元素) //SENDBUFF_SIZE + 1 假如不加1的话最大只能发SENDBUFF_SIZE - 1个字节 因为vsnprintf()函数会再拼接好字符串之后自动加上' |