FreeRTOS例程4-串口DMA收发不定长数据

基础知识点

DMA

DMA(Direct Memory Access),即直接内存存储,在一些数据的传输中,如串口、SPI等,采用DMA方式,传输过程不需要CPU参与,可用让CPU有更多的时间处理其他的事情。

STM32F4的DMA通道选择如下:

接下来的程序思路如下:
在这里插入图片描述

编程要点

DMA发送

串口DMA发送配置

由于是发送不定长的数据,先不需要配置发送的长度,在每次的发送时,再配置。

//=======================================
//串口DMA发送配置
//=======================================
void dma_uart_tx_init()
{
	DMA_InitTypeDef  DMA_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
	
	DMA_DeInit(Uart_Tx_DMAStream);//使用----->DMA2_Stream7
	while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}//等待DMA可配置 

	/* 配置 DMA Stream */
	DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道选择
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //目的:DMA外设地址
	DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)SendBuff;              //源:DMA存储器0地址
	DMA_InitStructure.DMA_DIR                = DMA_DIR_MemoryToPeripheral; //方向:存储器到外设模式
	//DMA_InitStructure.DMA_BufferSize       = BUF_SIZE;                   //长度:数据传输量(先不配置)
	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_MemoryDataSize_Byte;    //存储器数据长度:8位
	DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;            //使用普通模式 
	DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;        //DMA优先级:中等优先级
	DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 
	DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小
	DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存储器单次传输
	DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外设单次传输
	DMA_Init(Uart_Tx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
	
	//中断配置
	DMA_ITConfig(Uart_Tx_DMAStream,DMA_IT_TC,ENABLE);  //配置DMA发送完成后产生中断
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;//
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7;//抢占优先级8
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;		//子优先级0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
	USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA发送
	DMA_Cmd (Uart_Tx_DMAStream,DISABLE);//先不要使能DMA!           
}

DMA发送完成中断

DMA发送完成后,触发DMA发送完成中断,这里可用释放自定义的DMA发送完成信号量,表明下次的DMA传输可用进行。

//=======================================
//DMA发送完成中断服务程序
//=======================================
void DMA2_Stream7_IRQHandler(void)
{
	BaseType_t xHigherPriorityTaskWoken;
	//printf("ooooooo\r\n");
    if(DMA_GetITStatus(Uart_Tx_DMAStream,DMA_IT_TCIF7)!= RESET) //检查DMA传输完成中断 DMA_IT_TCIF7
    {
        DMA_ClearITPendingBit(Uart_Tx_DMAStream,DMA_IT_TCIF7); 
		//printf("dma tx ok\r\n");
		if(uartDMATCSemaphore!=NULL)
		{
			//释放二值信号量
			xSemaphoreGiveFromISR(uartDMATCSemaphore,&xHigherPriorityTaskWoken);	//释放DMA传输完成二值信号量
		}
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
    }
}
 

DMA发送函数接口

//=======================================
//串口DMA发送函数
//======================================= 
void uart_DMA_send(u8 *str,u16 ndtr)
{
	u8 i;
	u8 *p=str;
	
	while(xSemaphoreTake(uartDMATCSemaphore,2)!=pdTRUE);//获取信号量,等待DMA发送可用
	
	DMA_Cmd(Uart_Tx_DMAStream, DISABLE);                      //关闭DMA传输 
	while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}	//确保DMA可以被设置  
	DMA_SetCurrDataCounter(Uart_Tx_DMAStream,ndtr);          //数据传输量 
	for(i=0;i<ndtr;i++)
	{
		SendBuff[i]=*p++;
	}
	DMA_Cmd(Uart_Tx_DMAStream, ENABLE);                      //开启DMA传输 
}

DMA接收

串口DMA接收配置

需要配置一个接收地址和一个接收长度,用于DMA接收数据的暂存。

//=======================================
//串口DMA接收配置
//=======================================
void dma_uart_rx_init()
{
	DMA_InitTypeDef  DMA_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
	
	DMA_DeInit(Uart_Rx_DMAStream);//使用----->DMA2_Stream5
	while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}//等待DMA可配置 

	/* 配置 DMA Stream */
	DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道选择
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //源:DMA外设地址
	DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)ReceiveBuff;           //目的:DMA存储器0地址
	DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralToMemory; //方向:外设到存储器模式
	DMA_InitStructure.DMA_BufferSize         = BUF_SIZE;                   //长度:数据传输量
	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_MemoryDataSize_Byte;    //存储器数据长度:8位
	DMA_InitStructure.DMA_Mode               = DMA_Mode_Normal;            //使用普通模式 
	DMA_InitStructure.DMA_Priority           = DMA_Priority_Medium;        //DMA优先级:中等优先级
	DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 
	DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小
	DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存储器单次传输
	DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外设单次传输
	DMA_Init(Uart_Rx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
	
	USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收
	DMA_Cmd (Uart_Rx_DMAStream,ENABLE);//使能          
}

串口空闲中断

串口空闲中断的作用与上一篇FreeRTOS例程3-串口中断接收不定长的数据与二值信号量的使用介绍的一样,都是在发送完一串字符后被触发,这次由于使用了DMA接收,所以接收的数据在DMA缓冲区,且接收的数据长度可用根DMA接收通道的总长度与剩余长度的差值来计算,将接收的数据复制出来使用即可,同时释放自定义的串口空闲信号量,以便其它任务可用及时获取串口接收到的数据。

//=======================================
//串口1空闲中断服务程序,用于DMA接收
//=======================================
void USART1_IRQHandler(void)                	
{
	uint8_t data;//接收数据暂存变量
	BaseType_t xHigherPriorityTaskWoken;
	
	if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)//空闲中断
	{
		data = USART1->SR;
		data = USART1->DR;
		
		DMA_Cmd(Uart_Rx_DMAStream,DISABLE);//关闭DMA接收
		while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}	//确保DMA可以被设置 
		rx_cnt = BUF_SIZE - DMA_GetCurrDataCounter(Uart_Rx_DMAStream);//得到真正接收数据个数  
		DMA_SetCurrDataCounter(Uart_Rx_DMAStream,BUF_SIZE);//重新设置接收数据个数    
	    //printf("rx_cnt:%d\r\n",rx_cnt);
		memcpy(rxbuf,ReceiveBuff,rx_cnt);//先复制出来,防止下次的数据来了之后将其覆盖
	    DMA_ClearFlag(Uart_Rx_DMAStream,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//这里的各种标志还没搞懂
		DMA_Cmd(Uart_Rx_DMAStream,ENABLE); //开启DMA接收
			
		if(uartRxIDLESemaphore!=NULL)
		{
			//printf("nnnnnnn\r\n");
			//释放二值信号量
			xSemaphoreGiveFromISR(uartRxIDLESemaphore,&xHigherPriorityTaskWoken);//释放串口空闲中断二值信号量
		}
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
	}
} 

串口配置与测试任务

串口配置

基础的GPIO配置,以及串口空闲中断配置,并调用上面的串口DMA发送与接收配置。

//=======================================
//串口配置
//=======================================
void uart_init(u32 bound)
{
	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟

	//串口1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1

	//USART1端口配置
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;             //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	      //速度50MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;            //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;             //上拉
	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10

	//USART1 初始化设置
	USART_InitStructure.USART_BaudRate            = bound;                         //波特率设置
	USART_InitStructure.USART_WordLength          = USART_WordLength_8b;           //字长为8位数据格式
	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(USART1, &USART_InitStructure); //初始化串口1

    //DMA Config
    dma_uart_tx_init();//串口DMA发送配置
	dma_uart_rx_init();//串口DMA接收配置
	
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口空闲中断
	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;       //串口1中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6; //抢占优先级8
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;		//子优先级0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
	USART_Cmd(USART1, ENABLE);  //使能串口1 
}

测试任务

创建DMA发送完成信号量和串口空闲信号量,并先释放DMA发送完成信号量,用于第一次DMA发送时获取信号量。然后测试两条DMA发送不定长字符串,最后测试DMA接收不定长字符串。

//打印任务函数(测试任务)
void print_task(void *pvParameters)
{
	//创建二值信号量
	uartDMATCSemaphore = xSemaphoreCreateBinary();
	uartRxIDLESemaphore = xSemaphoreCreateBinary();
	xSemaphoreGive(uartDMATCSemaphore);
	
	u8 str1[]="ma nong ai xue xi\r\n";
	uart_DMA_send(str1,sizeof(str1));
	
	u8 str2[]="xxpcb.github.io\r\n";
	uart_DMA_send(str2,sizeof(str2));
	
	BaseType_t err = pdFALSE;
	while(1)
	{
		err=xSemaphoreTake(uartRxIDLESemaphore,5);	//获取信号量
		if(err==pdTRUE)							//获取信号量成功
		{  
			uart_DMA_send("receive:",sizeof("receive:"));
			uart_DMA_send(rxbuf,rx_cnt);
			uart_DMA_send("\r\n",sizeof("\r\n"));
			
			rx_cnt=0;
		}
	}
}

实验结果

通过串口助手,可以先接收到DMA发送的两个字符串(第一条hello是测试串口的,不是DMA发的),然后通过串口调试助手发送两次nice to meet you,测试DMA接收。

hello
ma nong ai xue xi
xxpcb.github.io
receive:nice to meet you
receive:nice to meet you

完整工程代码已保存至GitHub:https://github.com/xxpcb/FreeRTOS-STM32F407-examples

觉得文章有用,可以关注我哟~

stm32cubemx是一个用于生成STM32微控制器初始化代码的工具,而FreeRTOS是一个实时操作系统,串口DMA(直接存储器访问)是一种在数据传输时减少CPU负荷和提高效率的方式。当使用STM32CubeMX结合FreeRTOS来接收不定数据时,可以按照以下步骤进行操作。 首先,在CubeMX中配置串口DMA的初始化参数,使其支持不定数据接收。可以选择需要的串口和对应的DMA通道,配置串口的波特率、数据位和停止位等参数。然后,配置DMA以支持循环传输和变化度的数据接收。 其次,生成初始化代码并在FreeRTOS中集成。CubeMX可以生成针对FreeRTOS的初始化代码,将配置好的串口DMA初始化函数添加到FreeRTOS任务中,并在任务中实现数据接收的逻辑。可以利用FreeRTOS提供的任务管理和事件控制功能,让串口DMA在后台接收数据,同时不影响其他任务的运行。 最后,根据实际需求处理不定数据。在串口DMA接收到数据后,根据数据度进行相应的处理,可以通过消息队列或者信号量将数据传递给其他任务进行后续处理,也可以在接收完成后发送一个事件信号通知其他任务进行处理。需要根据具体需求来设计数据处理的流程,确保数据能够被准确地接收和处理。 总之,在使用STM32CubeMX和FreeRTOS进行串口DMA接收不定数据时,需要合理配置串口DMA参数,并在FreeRTOS任务中实现数据接收和处理的逻辑,以实现高效、稳定地数据传输和处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值