STM32CubeMX在FreeRTOS下使用串口进行数据收发(不定长度)

本文介绍如何在STM32CubeMX中结合FreeRTOS实现串口的不定长数据收发功能。通过使用定时器记录数据,每5ms处理一次,配合标志位和缓冲区,确保数据的正确接收和处理。实验结果验证了方案的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32CubeMX->FreeRTOS+USART接收不定长数据

由于本人做的一个项目功能相对复杂,要求使用操作系统,且项目工程中有很多需要串口操作的外设,所以需要对串口设计不定长的收发功能,裸机跑惯了的孩子就是比较野,一天瞎吉尔弄,现在是不是GG,这里先批评一下自己。为了解决这一问题并做个笔记,就有了这篇博客。

实现思路:
​ 使用串口、定时器记录接收到的数据,每5ms进入一次处理函数(一条数据处理完毕)
​ 使用一个计数位、一个标志位、一个暂存buff
​ 标志位置1则认为数据处理完成,将数据打印出来
使用硬件功能:串口3及中断,定时器7及中断
使用操作系统元素:一个互斥量
使能RTOS后会RTOS会强制调用系统时钟,所以在使能了操作系统之后需要将系统时钟换为定时器TIM1

操作步骤

  1. 启动STM32CubeMX并对芯片选型
    主要设置

    1. 串口3及中断使能,115200,8,None,1
    2. 定时器7及中断使能,预分频9000-1,自动重载50-1(时钟树APB1、2为90MHz)
    3. SYS时钟源设为定时器1
    4. 使能FreeRTOS,创建1个串口任务、创建1个互斥量
    5. 使能两个GPIO输出驱动LED
      以上都清楚的大佬转到代码功能实现继续
  2. 使能RCC时钟源为外部时钟


3. 设置SYS调试方式为串口,时钟源改为定时器1


4. 设置时钟树,是APB1、2为90MHz,或者自行设定,在后面定时器工作会用到

5. 使能串口3,并启用串口3中断,串口3设置为115200,8Bit,None,1(不知道什么意思的就没有必要看下去了)

在这里插入图片描述
6. 使能定时器7,并使能中断,定时器预分频系数与之前的90MHz相关,需要通过预分频系数和自动重载值将定时时间设为5ms【200Hz】,不会算的看我定时器博客(这里我用了一个不常用的定时器,用哪个都可以,只要注意好定时器隶属哪根时钟线就行)

在这里插入图片描述
7. 使能FreeRTOS并创建一个串口任务和一个二值量(二值信号量用于串口数据接收完成后进入串口任务)

在这里插入图片描述
在这里插入图片描述
8. 设置两个IO输出作为一个可视化依据

在这里插入图片描述
9. 项目命名、选择使用的程序编辑软件我的是MDK_ARM V5、分离.c.h文件,点击右上角GENERATE CODE生成工程
在这里插入图片描述

  1. 打开项目,先编译没有错误之后再进行修改

代码功能实现

  1. 首先在usart.c文件对串口3进行printf函数重定义,使串口3可用printf函数输出

    内容加入到/* USER CODE BEGIN 0 */框架内

    /* USER CODE BEGIN 0 */
    /********************************************************************************/
    #include "stdio.h"
    //加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
    //#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)	
    #if 1
    #pragma import(__use_no_semihosting)             
    //标准库需要的支持函数                 
    struct __FILE 
    { 
    	int handle; 
    }; 
    
    FILE __stdout;       
    //定义_sys_exit()以避免使用半主机模式    
    void _sys_exit(int x) 
    { 
    	x = x; 
    } 
    //重定义fputc函数 
    int fputc(int ch, FILE *f)
    { 	
    	while((USART3->SR&0X40)==0);//循环发送,直到发送完毕   
    	USART3->DR = (uint8_t) ch;      
    	return ch;
    }
    #endif 
    /********************************************************************************/
    /* USER CODE END 0 */
    
  2. 定义串口使用的变量

    主要包括串口数据存储BUFF,串口数据长度计数BUFF,串口接收成功标志位(此处借鉴正点原子的串口处理方法)

    在main.c文件下的定义/* USER CODE BEGIN PV */框架内定义

    /* USER CODE BEGIN PV */
    /********************************************************************************/
    uint8_t RxBuffer[MAX_REC_LENGTH] = {0};		//串口数据存储BUFF		长度2048
    uint8_t RxFlag = 0;							//串口接收完成标志符
    uint16_t RxCounter = 0;						//串口长度计数
    uint8_t RxTemp[REC_LENGTH] = {0};			//串口数据接收暂存BUFF	长度1
    
    extern osSemaphoreId myBinarySem01Handle;	//操作系统定义的互斥量
    /********************************************************************************/
    /* USER CODE END PV */
    

    并在usart.h文件下的/* USER CODE BEGIN Private defines */框架下定义长度和链接串口用变量

    /* USER CODE BEGIN Private defines */
    /********************************************************************************/
    #define REC_LENGTH 1
    #define MAX_REC_LENGTH 2048
    
    extern uint8_t RxBuffer[MAX_REC_LENGTH];
    extern uint8_t RxFlag;
    extern uint16_t RxCounter;
    extern uint8_t RxTemp[REC_LENGTH];
    /********************************************************************************/	 
    /* USER CODE END Private defines */
    
  3. 串口中断回调函数,在main.c的/* USER CODE BEGIN 4 */框架下添加串口回调函数

    /********************************************************************************/
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口3接收完成回调函数
    {
    	if(huart->Instance == USART3)
    	{
    		__HAL_TIM_SET_COUNTER(&htim7,0);							//清除定时器7计数值	
    		if(0 == RxCounter)											//如果是首字符(每帧数据开头)则开启定时器
    		{
    			__HAL_TIM_CLEAR_FLAG(&htim7, TIM_FLAG_UPDATE);			//清除中断标志位
    			HAL_TIM_Base_Start_IT(&htim7);							//开启基本定时器
    		}
    		RxBuffer[RxCounter] = RxTemp[0];							//缓存数据放入接收数组
    		RxCounter++;												//计数器加1
    		HAL_UART_Receive_IT(&huart3,(uint8_t *)RxTemp, REC_LENGTH);	//重新使能中断
    	}
    }
    /********************************************************************************/
    
  4. 定时器周期回调函数,由于在设置中系统时钟使用了定时器1,所以在main.c文件存在

    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    

    在函数 /* USER CODE BEGIN Callback 1 */框架下添加定时器7的回调函数

      /* USER CODE BEGIN Callback 1 */
    	/********************************************************************************/
    	if(htim->Instance == TIM7)
    	{
    		HAL_GPIO_TogglePin(DS1_GPIO_Port, DS1_Pin);	//LED反转一次
    		RxFlag = 1;																	//接收标志位置1
    		HAL_TIM_Base_Stop_IT(&htim7);								//关闭定时器
    		osSemaphoreRelease(myBinarySem01Handle);		//释放二值信号量,进入串口任务
    	}
    	/********************************************************************************/
      /* USER CODE END Callback 1 */
    
  5. 对freeRTOS添加任务

    首先在freertos.c文件的/* USER CODE BEGIN Includes */ 框架下添加串口使用的头文件

    /* USER CODE BEGIN Includes */     
    /********************************************************************************/
    #include "usart.h"
    #include "stdio.h"
    /********************************************************************************/
    /* USER CODE END Includes */
    
  6. 在freertos主任务中启动串口中断以及添加LED闪烁任务

    /* USER CODE END Header_StartDefaultTask */
    void StartDefaultTask(void const * argument)
    {
      /* USER CODE BEGIN StartDefaultTask */
    	HAL_UART_Receive_IT(&huart3,(uint8_t *)RxTemp, REC_LENGTH);
      /* Infinite loop */
      for(;;)
      {
    	HAL_GPIO_TogglePin(DS0_GPIO_Port,DS0_Pin);
        osDelay(200);
      }
      /* USER CODE END StartDefaultTask */
    }
    
  7. 在freertos串口子任务中添加串口数据打印功能

    /* USER CODE END Header_Start_U3_Task */
    void Start_U3_Task(void const * argument)
    {
      /* USER CODE BEGIN Start_U3_Task */
      /* Infinite loop */
      for(;;)
      {
    	/********************************************************************************/  
    	osSemaphoreWait(myBinarySem01Handle,100);						//等待二值信号量
    	if(RxFlag == 1)													//数据接收完成
    	{
    		for(int i = 0; i<RxCounter; i++)							//打印接收数组存储的内容
    			printf("%c",RxBuffer[i]);	
             printf("\r\n");											//打印完成换行
    		RxFlag = 0;													//接收标志清零
    		RxCounter = 0;												//接收计数清零
    		memset(RxBuffer ,0, MAX_REC_LENGTH);						//清空接收数组
    	}
    	/********************************************************************************/
        osDelay(1);
      }
      /* USER CODE END Start_U3_Task */
    }
    

实验结果

编译下载程序,打开串口调试助手,用串口转USB模块将串口3接入电脑,使用串口调试助手测试
在这里插入图片描述
上图为测试结果,发送ASWaterbenben后回复相同内容,每帧数据用换行符隔开,至此,实验成功!

DS0灯200ms反转一次,每次接收到一帧完整数据DS1灯就会反转一次。这个效果难以展示,你们自己试试就好!

如果本博客对您有帮助,希望点赞关注走一波,今后会看心情更新!哈哈哈哈哈哈哈哈哈

<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生成的代码正确集成FreeRTOS和DMA设置。 最后,生成相关问题,可能涉及更详细的配置步骤、调试方法或性能优化,比如如何调整任务优先级、处理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. **DMA与FreeRTOS的线程安全**:在DMA中断中调用FreeRTOS API时,需使用`FromISR`后缀的函数(如`xQueueSendFromISR`)。 2. **缓冲区管理**:建议使用双缓冲区策略,避免数据处理过程中DMA覆盖未处理的数据。 3. **优先级冲突**:确保DMA中断优先级低于FreeRTOS的调度器中断优先级(通过CubeMX的NVIC配置调整)。 ---
评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值