[FreeRTOS+STM32CubeMX] 04 USART串口的DMA接收

我们接着上小节,在上一小节中,我们利用USART的中断系统实现了串口的接收功能,但是在收数据的时候,只能一字节一字节传输,或者由我们设置的字节数顺序传输,如果设置了传送数字,就不能传送字符串,实在有些不方便(比如,我要发送123456给单片机,它智能一个字节一个字节的从队列读出来再依次传给我),除此之外,每次传输数据,都要占用CPU,在任务量多且数据量大的时候,CPU负荷将会大大增加,那么本小节就来解决以上讲到的问题:如何利用USART串口通信实现字符串、数字的直接传输且不占用CPU。

首先先要引入一下DMA数据通道的概念,简单来说,DMA就是单片机上的一个不占用CPU的对外连接的通道,可以连接各种外设,在传输数据的过程中,不用预先存储在单片机的寄存器,而是直接通过DMA通道传输。这样一来,数据的传输不会占用CPU内存,实现大量数据的传输,DMA在工程中的应用十分广泛。

一、CubeMX配置

接下来利用Cubemx进行配置:

重新新建一个工程,配置RCC和SYS,因为要用到Free RTOS,所以不选用SYSTICL,这里我们使用的是TIM1

使能USART1为异步通信:

打开NVIC中断,设置DMA

添加两个DMA,分别为发送DMA通道和接收DMA通道,优先级设置为very high

确保三个中断都打开

配置FREERTOS,添加一个任务,命名为USART_Rx(这里没有发送任务,以接收任务为例)

再创建一个队列,用来存放DMA传输的数据,注意:这里的队列我们设置为指针模式,即队列传输时传的是一个地址,传输的数据我们要放到指针当中。

Queue Size设置为5(看具体需求,设置为1也没问题,当然越多传输的越快)

Item Size 我们设置为void * ,即设为一个指针

这个Interface设为V2 V1 都可以,V2 具有更大的API函数,这里以V2 为例

配置时钟为72MHz

命名并将IDE改为MDK

注意:如果使用的Interface是V2 ,下面的Package要改为1.8.5的版本

生成Keil5 代码

二、代码程序

先看到我们的freertos.c文件,由目的出发,我们想要上位机通过DMA传送数据给单片机,而DMA又是通过传送数据到队列中进行数据的传输,那么我们在任务中就应该从队列中取数据,并对数据进行处理(我这里对数据做了一个回显操作),接下来看代码部分:

void task2(void *argument)
{
  /* USER CODE BEGIN task2 */
	__HAL_UART_ENABLE_IT(&huart1 ,UART_IT_IDLE);
	//开启空闲中断,当然你也可以用上一节的HAL_UART_Receive_IT(&huart1,&存放地址,1)由于这里我们要用DMA传输,没有用到地址,所以不用该函数,直接开启中断
	
	HAL_UART_Receive_DMA(&huart1 ,uart_rx_data_t[uart_buff_ctrl].buffer,UART_BUFFER_SIZE);//我们用DMA传输,用这个receive函数,存储在结构体中
	
	UART_RX_TypeDef *RecvUartData;//定义一个结构体指针,用来存储接收的数据的.buffer和.size
	
  /* Infinite loop */
  for(;;)
  {
	  
	  if(xQueueReceive(uart_queueHandle,&RecvUartData,portMAX_DELAY )== pdTRUE){
		  //队列中存入了数据
		  //将数据做了一个串口回显,当然你用作其它处理也可以
		  HAL_UART_Transmit(&huart1,RecvUartData->buffer,RecvUartData->size,1000);
		  
	  }
    osDelay(1);
  }

先看到for函数,我们肯定是先编写代码,然后看需要用到什么变量,再依次去定义它;

在for循环中,我写了一个if语句,如果队列中有数据了,就把数据回显到上位机;

用到了xQueueReceive函数,uart_queueHandle是创建的队列的名称,在文件的上方有cubemx帮我们定义

RecvUartData是我们存数据的地址,这里我们需要定义一下,由于USART在传输数据时需要知道数据的字节数,所以指针RecvUartData中应该包含两个数据1.buffer具体的数据;2.size数据的长度,于是这个指针应该是一个结构体,所以我们定义一个结构体,这个结构体中应该包含两个数据:

typedef struct {
	uint8_t buffer[UART_BUFFER_SIZE];	
	uint16_t size;
	
}UART_RX_TypeDef;

在文件的最上方定义一个结构体,我们命名为 UART_RX_TypeDef;可以看到结构体中有两个参数,一个是buffer数组,数组的个数为UART_BUFFER_SIZE个,这里我们需要定义一下UART_BUFFER_SIZE,也就是说,buffer数组共有256个数据位供我们存放,也就是buffer的数据空间大小;我们再定义了 一个UART_BUFFER_QUANTITY为5,这与我们的队列长度Item Size一致,Item Size改变时,这个也要修改;

有了结构体,我们再去定义RecvUartData;UART_RX_TypeDef *RecvUartData;

HAL_UART_Transmit串口发送函数,RecvUartData->buffer结构体中传输的数据;RecvUartData->size结构体中的size数据的长度;

目的已经明确了,那现在怎么让数据通过DMA传输到队列里呢?接着往下,打开main.c文件

如果我们打开了串口的中断,当上位机发送数据过来时,触发中断,如果这个时候我们再中断函数中执行DMA存入队列的操作,即可达到目的;

所以我们定义一个函数void USART1_DMAHandler(void),一旦中断发生,执行该函数。

接下来看该函数怎么实现传输队列的操作:

void USART1_DMAHandler(void){
	//创建一个DMA函数,一旦串口产生中断,则执行该函数,
	//所以该函数要写在USART1_IRQHandler函数当中,一旦上位机发送信号触发,执行USART1_IRQHandler函数,即可执行到该函数
	
	if(__HAL_UART_GET_FLAG(&huart1 ,UART_FLAG_IDLE)!=RESET){
		//判断是否发生空闲中断,此时既然能进入该函数,一定已经发生了空闲中断
		
		UART_RX_TypeDef *Data;//再定义一个这个结构体,包含的东西与uart_rx_data_t一致,我们要将uart_rx_data_t中的数据传到Data指针当中再一个一个传输
		
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清楚空闲中断标志,方便下次判断是否空闲中断
		HAL_UART_DMAStop(&huart1 );//停止DMA传输,关闭DMA
		
		uart_rx_data_t[uart_buff_ctrl].size = UART_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
		//从DMA的结构体中找到这个数据长度(单片机接收的数据);注意这里的__HAL_DMA_GET_COUNTER函数计算的是未发送的数据长度,所以要用总长度UART_BUFFER_SIZE减一下
		//实际上算出来的这个长度,不用管它具体是多少,减完之后肯定大于.size里实际传输的长度
		//uart_rx_data_t[uart_buff_ctrl].size的意思是uart_rx_data_t这个结构体中的第uart_buff_ctrl个数组中的size项的值
		
		Data = &uart_rx_data_t[uart_buff_ctrl];
		//第一次传输时,uart_buff_ctrl = 0,传的是结构体中的第一个数组的意思,之后每次接收时uart_buff_ctrl都自加,分别传到不同的结构体数组当中,传到第五个时,uart_buff_ctrl又要重新取0,达到环形
		//将这个Data指针地址发送至队列,让队列存一个地址
		
		xQueueSendFromISR(uart_queueHandle,&Data,NULL);
		
		uart_buff_ctrl++;//自加
		
		uart_buff_ctrl %= UART_BUFFER_QUANTITY; //再取余,让它计到5时重新归零,当然这里也可以直接使用if语句
		
		HAL_UART_Receive_DMA(&huart1 , uart_rx_data_t[uart_buff_ctrl].buffer,UART_BUFFER_SIZE);//重新开启DMA传输,传输buffer。这个函数在任务当中也要用到		
		
		
		
	}

}

创建一个DMA函数,一旦串口产生中断,则执行该函数,所以该函数要写在USART1_IRQHandler函数当中,一旦上位机发送信号触发,执行USART1_IRQHandler函数,即可执行到该函数;

if语句判断是否发生空闲中断,此时既然能进入该函数,一定已经发生了空闲中断;

UART_RX_TypeDef *Data;再定义一个这个结构体,包含的东西与uart_rx_data_t一致,我们要将uart_rx_data_t中的数据传到Data指针当中再一个一个传输。注意:刚刚结构体是在freertos.c中定义的,不是一个全局结构体,这里我们要在main.c中重新定义,操作与前面一样

清楚空闲中断标志,方便下次判断是否空闲中断;停止DMA传输,关闭DMA;

再定义一个结构体变量,uart_rx_data_t[];这是一个数组,里面共有UART_BUFFER_QUANTITY位,即5位,与我们的队列长度一致。

uart_rx_data_t 是一个数组,包含 UART_BUFFER_QUANTITY 个 UART_RX_TypeDef 结构体,每个结构体可以独立地存储一组 UART 接收数据

uint8_t uart_buff_ctrl = 0;定义一个 uart_buff_ctrl ,由于我们的结构体有5个,每次传输时只用得到一个,要想区分它们,需要有一个标志,我们用 uart_buff_ctrl作为标志,起始位为0,每传输一次 uart_buff_ctrl加一;

uart_rx_data_t[uart_buff_ctrl].size结构体中的size大小:

从DMA的结构体中找到这个数据长度(单片机接收的数据);注意这里的__HAL_DMA_GET_COUNTER函数计算的是未发送的数据长度,所以要用总长度UART_BUFFER_SIZE减一下;实际上算出来的这个长度,不用管它具体是多少,减完之后肯定大于.size里实际传输的长度;uart_rx_data_t[uart_buff_ctrl].size的意思是uart_rx_data_t这个结构体中的第uart_buff_ctrl个数组中的size项的值

Data = &uart_rx_data_t[uart_buff_ctrl];
 第一次传输时,uart_buff_ctrl = 0,传的是结构体中的第一个数组的意思,之后每次接收时uart_buff_ctrl都自加,分别传到不同的结构体数组当中,传到第五个时,uart_buff_ctrl又要重新取0,达到环形;将这个Data指针地址发送至队列,让队列存一个地址

xQueueSendFromISR;将Data发送至队列;uart_buff_ctrl自加再取余,让它计到5时重新归零,当然这里也可以直接使用if语句;

HAL_UART_Receive_DMA();最后重启DMA传输;传输buffer。这个函数在任务当中也要用到,用作DMA开始传输的标志;

注意:在main.c中用到了很多其它文件的结构体如下所示,需要进行全局声明一下,否则Keil认不到这些结构体和函数;

xQueueSendFromISR();这个函数在queue.h中,用的时候要加一下头文件

USART1_DMAHandler()编写完毕,现在我们要去串口中断函数中加上该函数:

找到it.c文件,找到USART1_IRQHandler函数,该函数即中断执行函数,我们将我们自己写的函数写进去

注意:我们自己写的这个函数也是一个外来函数,也要在头部声明一下

接下来回到我们的任务;

我们需要开启中断(虽然外面已经配置过了开启USART中断,但是里面还需要写中断开始函数):

__HAL_UART_ENABLE_IT(&huart1 ,UART_IT_IDLE);

当然你也可以用上一节的HAL_UART_Receive_IT(&huart1,&存放地址,1) 这个函数

但是由于这里我们要用DMA传输,没有直接传输到地址,所以不用该函数;所以直接开启中断

HAL_UART_Receive_DMA();开启DMA传输,这个我们在刚刚写的函数最后也用到了,关闭了DMA还要再打开它,这里我们用在任务的开始,表示开始DMA的传输;

至此,代码编写完毕。

三、实际调试

我们将代码载入单片机,看看实际调试效果:

可以看到,我们发送123,单片机接收123,发送”我是发送“,单片机也能收到中文”我是发送“,发送英文字符串”woshifasong“,单片机也能收到;如果我们一次性发送又带中文又带数字又带英文的消息,单片机也能收到,并且一次性全部回显。

这是由于我们DMA传输的是一个指针地址,我们将数据全部放到了指针里面,队列传输时,传的也是地址,该地址中不仅包含了数据,还有数据的字节长度,所以在单片机回显的时候,可以直接引用指针中的数据buffer,也可以直接引用指针中的长度Size,完成DMA的传输。

以上就是利用USART串口通信基于DMA的接收数据功能,关于DMA的发送数据功能与接收功能大差不差,读者可以自行模仿修改。其实,实现代码一次性传输的是指针的作用,而DMA在这里只是起到了一个跳过CPU直接传输的作用。在串口通信时,也可以使用指针的方法,使数据完成一次性传输。

以上就是本小节的全部内容,如有错误,敬请雅正!

参考文献:

STM32HAL库CubeMx FreeRTOS 消息队列 DMA串口空闲中断接收消息

<think>好的,用户的问题是关于在STM32 CubeMX中配置FreeRTOS使用DMA传输的。首先,我需要回忆一下STM32开发的相关知识,特别是CubeMX的配置步骤、FreeRTOS的集成以及DMA的使用方法。 用户提到了引用[1]中的内容,其中提到了在CubeMX中打开FreeRTOS并保持默认配置,新手一般不需要改动。这可能涉及到FreeRTOS的初始配置步骤,但用户现在需要结合DMA进行数据传输,比如串口的空闲中断和DMA接收。所以,我需要将这三个部分结合起来:CubeMX配置、FreeRTOS任务创建和DMA的设置。 首先,配置CubeMX的基础部分,比如时钟树、项目设置等。然后启用FreeRTOS,确保在Middleware中选择正确的版本,并创建任务。这里可能需要设置任务堆栈大小、优先级等参数,但根据引用[1]的提示,新手可以保持默认,但需要确认是否需要调整以适应DMA传输的需求。 接下来是配置DMA,比如配置串口DMA通道。用户可能使用的是UART,需要启用DMA传输,并设置空闲中断。这部分需要配置NVIC,开启空闲中断,并在代码中编写中断处理函数,这可能涉及到CubeMX生成的HAL库函数的使用,比如HAL_UARTEx_ReceiveToIdle_DMA。 然后,结合FreeRTOS的消息队列,确保DMA传输完成后的数据处理可以在任务间通信。需要创建消息队列,并在DMA完成中断或回调函数中发送消息到队列,任务则接收并处理这些数据。 需要注意的是,HAL库的DMA传输函数可能会在传输完成时调用回调函数,而使用FreeRTOS时,中断服务例程中需要使用FreeRTOS的API,如xQueueSendFromISR,并处理任务切换。 可能存在一些常见问题,比如DMA缓冲区溢出、任务优先级设置不当导致数据丢失,或者中断配置错误导致无法触发。需要提醒用户检查这些点,并确保CubeMX生成的代码正确集成FreeRTOSDMA设置。 最后,生成相关问题,可能涉及更详细的配置步骤、调试方法或性能优化,比如如何调整任务优先级、处理DMA中断冲突,或者使用其他外设的DMA配置。</think>### 如何在STM32 CubeMX中配置FreeRTOS使用DMA传输 #### **步骤1:基础工程配置** 1. 打开STM32CubeMX,选择目标MCU型号(如STM32F4/F7/H7系列)。 2. 配置系统时钟(RCC)和调试接口(SYS),确保时钟树设置正确。 3. 在`Middleware`分类下启用`FREERTOS`,选择CMSIS_V1或CMSIS_V2模式(建议默认CMSIS_V1)[^1]。 #### **步骤2:FreeRTOS任务与队列配置** 1. 在FreeRTOS配置界面(`Config Parameters`)中,调整以下参数: - `TOTAL_HEAP_SIZE`:根据DMA缓冲区大小和应用需求调整堆空间(例如4096字节)。 - 在`Tasks and Queues`标签页创建任务,设置任务优先级和堆栈大小(例如默认任务优先级为`osPriorityNormal`)。 2. 创建消息队列(用于DMA数据传输): ```c osMessageQueueId_t dmaQueue; dmaQueue = osMessageQueueNew(10, sizeof(DMA_Data), NULL); //队列容量10,数据块大小为自定义结构体 ``` #### **步骤3:DMA串口配置(以UART空闲中断为例)** 1. 启用USART/UART外设,选择`Asynchronous`模式。 2. 打开DMA设置标签页: - 添加`RX`方向的DMA请求(如`UART1_RX`),配置为循环模式(Circular)或普通模式(Normal)。 - 设置数据宽度(通常为`Byte`),优先级根据需求调整。 3. 启用空闲中断(Idle Interrupt): - 在NVIC设置中勾选`USARTx global interrupt`和`USARTx idle interrupt`。 #### **步骤4:生成代码与关键函数添加** 1. 生成代码后,在`main.c`中补充以下内容: - **DMA启动**:在任务中调用HAL库函数启动DMA接收: ```c HAL_UARTEx_ReceiveToIdle_DMA(&huart1, buffer, BUFFER_SIZE); ``` - **空闲中断回调**:在`stm32xx_it.c`中实现空闲中断处理,并通过队列传递数据: ```c void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLE_FLAG(&huart1); uint16_t len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx); osMessageQueuePut(dmaQueue, &len, 0, 0); //将接收长度发送到队列 } HAL_UART_IRQHandler(&huart1); } ``` - **任务处理**:在FreeRTOS任务中读取队列并处理数据: ```c void DataProcessTask(void *argument) { uint16_t dataLen; while (1) { if (osMessageQueueGet(dmaQueue, &dataLen, NULL, osWaitForever) == osOK) { //处理buffer[0]到buffer[dataLen-1]的数据 } } } ``` #### **关键注意事项** 1. **DMAFreeRTOS的线程安全**:在DMA中断中调用FreeRTOS API时,需使用`FromISR`后缀的函数(如`xQueueSendFromISR`)。 2. **缓冲区管理**:建议使用双缓冲区策略,避免数据处理过程中DMA覆盖未处理的数据。 3. **优先级冲突**:确保DMA中断优先级低于FreeRTOS的调度器中断优先级(通过CubeMX的NVIC配置调整)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值