IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> FreeRTOS之01-简介、移植及系统配置 -> 正文阅读

[嵌入式]FreeRTOS之01-简介、移植及系统配置

0 引言

在本科的时候,从51开始入嵌入式开发的坑,慢慢的接触到了K60、STM32等单片机,但是在开发时并没有操作系统的概念,直接在裸机上编写程序(程序由一个死循环和若干个中断服务程序构成,平时CPU执行while循环中的代码,出现中断事件时,跳转到服务程序进行处理)。但随着所开发的应用系统变的复杂之后,裸机程序难以阅读和维护。

研究生时接触到了RTOS,在开发时可以将一个复杂的应用分割成多个小的任务,每个任务完成一部分工作,且都可以写成死循环的形式。一个处理器核心在某一时刻运行一个任务,RTOS根据任务的优先级,通过任务调度器进行任务与任务之间切换。由于每次对一个任务的执行时间很短,给人造成多个任务在一个时刻同时运行的错觉。若合理地划分任务且调度方法优良,则可使个任务看起来是并行执行的,减少了CPU的空闲时间,提高了CPU的利用率。

FreeRTOS是我接触和学习的第一种RTOS(后面项目中使用的是Rtthread,读研那会儿刚好比较火,而且可以通过IDE图形化界面管理工程、中文资料…),当时是19年研一刚入学,虽然写的程序还在,但已经没有什么印象了,只记得写过。。。最近准备毕业论文,没有像研一研二那样参与实验室项目(其实已经很久没写程序了,手痒痒~~),为了让自己过的更充实,满足自己的Soul Demand,加上明年工作需要使用FreeRTOS,所以决定学习FreeRTOS源码及其应用开发,并针对学习过程中遇到的问题进行总结和记录,方便日后的温故。。。。

都说“万事开头难”,今天就把头开了。。继续保命写毕设论文,后面慢慢整理。。

所选开发环境及硬件配置

  • 德飞莱开发板。芯片为stm32f103zet6;LED2、LED3的引脚为PE5、PB5,低电平亮;WK_UP、KEY1、KEY2、KEY3的引脚为PA0、PE4、PE3、PE2,除WK_UP外其余按键按下为低电平;USART1 TX和RX引脚分别为PA9、PA10;外部晶振为8MHZ和32.768KHZ。
  • 软件:STM32CUBMX、KEIL、串口调试助手(sscom33.exe)

1 FreeRTOS简介

RTOS全称是Real Time Operating System,不是指某一个确定的系统,而是指一类系统,比如UCOs、FreeRTOS、RTX、RT-Thread等。其中,FreeRTOS是一个免费的、可裁剪的小型RTOS系统,其特点包括:

  • 内核支持抢占式,合作式和时间片调度;
  • 提供了一个用于低功耗的Tickless模式;
  • 系统的组件在创建时可以选择动态或者静态的RAM,比如任务、消息队列、信号量、软件定时器等
  • FreeRTOS-MPU支持Corex-M系列中的MPU单元,如STM32F767;
  • 系统简单、小巧、易用,通常情况下内核占用4k-9k字节的空间
  • 高可移植性,代码主要C语言编写;
  • 支持实时任务和协程(co-routines也有称为合作式、协同程序,本教程均成为协程);
  • 任务与任务、任务与中断之间可以使用任务通知、消息队列、二值信号量、数值型信号量、递归互斥信号量和互斥信号量进行通信和同步,此外还有事件组(或者事件标志);
  • 具有优先级继承特性的互斥信号量;
  • 高效的软件定时器;
  • 强大的跟踪执行功能;
  • 堆栈溢出检测功能;
  • 任务数量和任务优先级不限。

FreeRTOS的官网:https://www.freertos.org/

2 FreeRTOS移植(两种方式)

2.1 官网源码移植【传统方法】

  • 步骤1 源码下载,并解压。

    • 在FreeRTOS官网下载界面,下载最新版(其实之所以下载最新版,是因为带有example),当前时间为 2021/12/02。
      在这里插入图片描述
    • 解压下载后的文件,得到文件夹为FreeRTOSv202111.00`,重点在于上面那两个文件夹:FreeRTOS和FreeRTOS-Plus。
      在这里插入图片描述
      FreeRTOS文件夹中,Demo文件夹里面是FreeRTOS的相关例,License文件夹里面就是相关的许可信息,Source文件夹为FreeRTOS源码。其中,Source文件夹中的portable文件夹中有FreeRTOS与不同的具体硬件平台(编译环境、MCU)之间连接相关的文件,portable文件夹内容见下图。Keil文件夹中只有一个文件: See-also-the-RVDS-directory.txt,意思是参考RVDS文件夹里面的文件。 在这里插入图片描述
      FreeRTOS-Plus文件夹中的源码其实并不是FreeRTOS系统的源码,是在这个FreeRTOS系统上另外增加的一些功能代码,比如CLI、FAT、TCP、Trace等。前期学习,没有必要看。
  • 步骤2 通过STM32cubeMX新建并配置裸机工程,用于后面的移植。

    • 配置下载方式、时钟;
      因为Systick要用作FreeRTOS的时基定时,所以此处时钟源选择TIM7。
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    • 配置LED、KEY和USART,主要用于后面测试;
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      注意:因为FreeRTOS源码port.c中定义了PendSV_HandlerSVC_Handler函数,所以这里不能生成,否则会报错,而且增加移植难度。。。而且我也试了,如果没有取消勾选,后面通过删除stm32f1xx_it.c文件中自动生成的PendSV_HandlerSVC_Handler函数,虽然没有报错了,但是当调用vTaskStartScheduler();函数开启任务调度,程序会直接进入HardFault_Handler中,然后就是“你懂得”。。。死循环。。。

    • 生成裸机工程。
      在这里插入图片描述
      在这里插入图片描述

  • 步骤3 在前面生成的裸机工程中正式移植FreeRTOS源码。

    • 在基础工程中,新建文件夹Middlewares\Third_Party\FreeRTOS,将步骤1解压出来的源码文件夹FreeRTOSv202111.00\FreeRTOS\Source中的文件拷贝到其中。
      在这里插入图片描述
      文件夹Source中删除.gitmodules文件;文件夹portable中只保留KeilMemMangRVDS三个文件夹。
      在这里插入图片描述
    • 向KEIL工程,新建分组Middlewares/FreeRTOS并添加文件,然后添加头文件路径。其中,heap_4.c文件在MemMang文件夹中,为五种内存管理方法中的一种。
      在这里插入图片描述
      在这里插入图片描述
      编译工程,会提示打不开FreeRTOSConfig.h文件,就是没有这个文件,需要手动新建该文件。本文为从步骤1解压出来的示例文件夹FreeRTOSv202111.00\FreeRTOS\Demo\CORTEX_STM32F103_Keil中复制而来,并放到KEIL工程文件夹template\Middlewares\Third_Party\FreeRTOS\include下。(这也是我没有用FreeRTOS LTS 202012.03的原因)
      再次编译,我遇到了两个错误提示:Undefined symbol xTaskGetSchedulerState (referred from stm32f1xx_it.o).Undefined symbol xTaskGetCurrentTaskHandle (referred from stream_buffer.o).。解决方法为:在FreeRTOSConfig.h中添加宏定义#define INCLUDE_xTaskGetSchedulerState 1#define INCLUDE_xTaskGetCurrentTaskHandle 1两个宏定义。 此外,在FreeRTOSConfig.h中增加下面程序段,并将代码#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000)修改为#define configCPU_CLOCK_HZ ( ( unsigned long ) SystemCoreClock ),其他部分默认不变。
      /* Ensure definitions are only used by the compiler, and not by the assembler. */
      #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
        #include <stdint.h>
        extern uint32_t SystemCoreClock;
      #endif
      /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
      standard names. */
      #define xPortPendSVHandler PendSV_Handler
      #define vPortSVCHandler SVC_Handler
      

    最后,在stm32f1xx_it.h文件中找到void SysTick_Handler(void)函数,修改为如下。

    void SysTick_Handler(void)
    {
      /* USER CODE BEGIN SysTick_IRQn 0 */
        if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
        {
            xPortSysTickHandler();	
        }
        HAL_IncTick();
      /* USER CODE END SysTick_IRQn 0 */
    
      /* USER CODE BEGIN SysTick_IRQn 1 */
    
      /* USER CODE END SysTick_IRQn 1 */
    }
    

    此时,编译将0 Error(s), 0 Warning(s),如果还有其他问题请留言或自行百度解决。。。

  • 步骤4 添加延时相关的文件。
    在KEIL工程文件夹中新建SYSTEM\delay文件夹,文件夹中添加delay.cdelay.h文件,然后再工程中添加头文件路径。
    在这里插入图片描述
    在这里插入图片描述
    delay.h

    #ifndef __DELAY_H
    #define __DELAY_H 			   
    #include "main.h"
    
    void delay_init(void);
    void delay_ms(uint32_t nms);
    void delay_us(uint32_t nus);
    void delay_xms(uint32_t nms);
    #endif
    

    delay.c

    #include "delay.h"
    
    static uint32_t fac_us=0;							//us延时倍乘数			   
    static uint16_t fac_ms=0;							//ms延时倍乘数
     
    //这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!
    //本工程在stm32cubmx中,SYSTICK的时钟频率设置为和AHB的频率相等,所以可以不用设置。
    void delay_init()
    {
    	uint32_t reload;
    	HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK,本工程可以不要
    	fac_us=SystemCoreClock/1000000;
    	reload=SystemCoreClock/1000000;				//每秒钟的计数次数 单位为M  
    	reload*=1000000/configTICK_RATE_HZ;			//根据configTICK_RATE_HZ设定溢出时间
    																					//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右	
    	fac_ms=1000/configTICK_RATE_HZ;				//代表OS可以延时的最少单位	   
    
    	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
    	SysTick->LOAD=reload; 						//每1/configTICK_RATE_HZ秒中断一次	
    	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    
    }								    
    
    
    //延时nus
    //nus:要延时的us数.	
    //nus:0~204522252(最大值即2^32/fac_us@fac_us=168)	    								   
    void delay_us(uint32_t nus)
    {		
    	uint32_t ticks;
    	uint32_t told,tnow,tcnt=0;
    	uint32_t reload=SysTick->LOAD;				//LOAD的值	    	 
    	ticks=nus*fac_us; 						//需要的节拍数 
    	told=SysTick->VAL;        				//刚进入时的计数器值
    	while(1)
    	{
    		tnow=SysTick->VAL;	
    		if(tnow!=told)
    		{	    
    			if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
    			else tcnt+=reload-tnow+told;	    
    			told=tnow;
    			if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.
    		}  
    	};										    
    }  
    //延时nms
    //nms:要延时的ms数
    //nms:0~65535
    void delay_ms(uint32_t nms)
    {	
    	if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    	{		
    		if(nms>=fac_ms)						//延时的时间大于OS的最少时间周期 
    		{ 
       			vTaskDelay(nms/fac_ms);	 		//FreeRTOS延时
    		}
    		nms%=fac_ms;						// RTOS无法提供这么小的延时了,采用普通方式延时    
    	}
    	delay_us((uint32_t)(nms*1000));				//普通方式延时
    }
    
    //延时nms,不会引起任务调度
    //nms:要延时的ms数
    void delay_xms(uint32_t nms)
    {
    	uint32_t i;
    	for(i=0;i<nms;i++) delay_us(1000);
    }
    
  • 步骤5 增加测试程序验证工程。约定: 添加的所有个人代码均在/* USER CODE xxx BEGIN xxx*/和/* USER CODE xxx END xxx*/之间。

  • main.h

    /* USER CODE BEGIN Includes */
    #include "stdio.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    
    #include "delay.h"
    #include "gpio.h"
    #include "usart.h"
    /* USER CODE END Includes */
    
  • gpio.h

    /* USER CODE BEGIN Private defines */
    #define LED2(n)		    (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET))
    #define LED2_Toggle() (HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5)) //LED0输出电平翻转
    #define LED3(n)		    (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET))
    #define LED3_Toggle() (HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5)) //LED1输出电平翻转
    
    #define KEY1()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4) //KEY1按键
    #define KEY2()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3) //KEY2按键
    #define KEY3()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2) //KEY3按键
    #define WK_UP()       HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) //WKUP按键
    /* USER CODE END Private defines */
    
  • usart.h

    /* USER CODE BEGIN Prototypes */
    #define EN_USART1_RX 	 1 //使能(1)/禁止(0)串口1接收中断	
    #define RXBUFFERSIZE   1 //缓存大小
    extern uint8_t aRxBuffer[RXBUFFERSIZE];//接收缓存区
    /* USER CODE END Prototypes */
    
  • usart.c

    /* USER CODE BEGIN 1 */
    #if EN_USART1_RX==1
    	void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    	{
    		if(huart->Instance==USART1)
    		{
    				HAL_UART_Transmit(&huart1,(uint8_t*)aRxBuffer,1,1000);	//发送接收到的数据
    				while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);		//等待发送结束
    			
    				HAL_UART_Receive_IT(&huart1,(uint8_t *)aRxBuffer, RXBUFFERSIZE);//重新开启中断并设置RxXferCount为1
    		}
    		
    	}
    #endif
    #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((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    		USART1->DR=(uint8_t)ch;      
    		return ch;
    	}
    #endif 
    /* USER CODE END 1 */
    
  • main.c

    /* USER CODE BEGIN 0 */
    // START任务优先级、任务堆栈大小、任务句柄、任务函数
    #define START_TASK_PRIO		1
    #define START_STK_SIZE 		128  
    TaskHandle_t StartTask_Handler;
    void start_task(void *pvParameters);
    
    // LED2任务优先级、任务堆栈大小、任务句柄、任务函数
    #define LED2_TASK_PRIO		2	
    #define LED2_STK_SIZE 		50  
    TaskHandle_t LED2Task_Handler;
    void led2_task(void *pvParameters);
    
    // LED3任务优先级、任务堆栈大小、任务句柄、任务函数
    #define LED3_TASK_PRIO		3	
    #define LED3_STK_SIZE 		50  
    TaskHandle_t LED3Task_Handler;
    void led3_task(void *pvParameters);
    
    // FLOAT任务优先级、任务堆栈大小、任务句柄、任务函数
    #define FLOAT_TASK_PRIO		4
    #define FLOAT_STK_SIZE 		128
    TaskHandle_t FLOATTask_Handler;
    void float_task(void *pvParameters);
    /* USER CODE END 0 */
    

    main函数

    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    	delay_init();
    #if EN_USART1_RX==1
    	HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE);	
    #endif
    	xTaskCreate((TaskFunction_t )start_task,            //任务函数
    							(const char*    )"start_task",          //任务名称
    							(uint16_t       )START_STK_SIZE,        //任务堆栈大小
    							(void*          )NULL,                  //传递给任务函数的参数
    							(UBaseType_t    )START_TASK_PRIO,       //任务优先级
    							(TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    	vTaskStartScheduler();          //开启任务调度
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
    //  while (1)
    //  {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    			
    //  }
      /* USER CODE END 3 */
    }
    
    /* USER CODE BEGIN 4 */
    //开始任务任务函数
    void start_task(void *pvParameters)
    {
        taskENTER_CRITICAL();           //进入临界区
    
        xTaskCreate((TaskFunction_t )led2_task,     	
                    (const char*    )"led1_task",   	
                    (uint16_t       )LED2_STK_SIZE, 
                    (void*          )NULL,				
                    (UBaseType_t    )LED2_TASK_PRIO,	
                    (TaskHandle_t*  )&LED2Task_Handler);   
    
        xTaskCreate((TaskFunction_t )led3_task,     
                    (const char*    )"led3_task",   
                    (uint16_t       )LED3_STK_SIZE, 
                    (void*          )NULL,
                    (UBaseType_t    )LED3_TASK_PRIO,
                    (TaskHandle_t*  )&LED3Task_Handler); 
        xTaskCreate((TaskFunction_t )float_task,     
                    (const char*    )"float_task",   
                    (uint16_t       )FLOAT_STK_SIZE, 
                    (void*          )NULL,
                    (UBaseType_t    )FLOAT_TASK_PRIO,
                    (TaskHandle_t*  )&FLOATTask_Handler); 
    								
    	  vTaskDelete(StartTask_Handler);   //删除开始任务,不加这一句程序会进入prvTaskExitError函数中,然后一直死循环
        taskEXIT_CRITICAL();            //退出临界区							
    }
    
    void led2_task(void *pvParameters)
    {
        while(1)
        {
            LED2_Toggle();
            delay_ms(500);
        }
    }   
    
    void led3_task(void *pvParameters)
    {
        while(1)
        {
            LED3(0);
            delay_ms(200);
            LED3(1);
            delay_ms(800);
        }
    }
    
    void float_task(void *pvParameters)
    {
    	static double float_num=0.00;
    	while(1)
    	{
    		float_num+=0.01f;
    		printf("float_num的值为: %.4f\r\n",float_num);
        delay_ms(1000);
    	}
    }
    /* USER CODE END 4 */
    

    本程序包括了设计四个任务:start_task、 led2_task、led3_task和float_task,这四个任务的任务功能如下:start_task用来创建其他三个任务;led2_task和led3_task分别控制LED2和LED3闪烁,闪烁时间不同;float_task使用printf定时向串口助手上传数据。此外,还通过串口中断将收到数据上传上位机。
    对应工程名template

2.2 STM32cubMX软件直接创建带FreeRTOS的工程

  • 首先,同样的配置下载方式、时钟;
    因为Systick要用作FreeRTOS的时基定时,所以此处时钟源选择TIM7。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 选择FreeRtos版本,有CMSIS_V1和CMSIS_V2两个版本,两者区别详细见官网介绍链接

    所谓CMSIS,全称为Cortex Microcontroller Software Interface Standard,即基于CORTEX内核微处理的软件接口标准。
    RTOSV1主要支持基于CortexM0/M0+/M3/M4/M7内核的芯片,而RTOSv2组件是基于RTOSv1的扩展,除了支持全系列的CortexM内核芯片外,还支持Cortex-A5/A7/A9内核的芯片,支持动态对象创建,支持多核系统配置,向下兼容RTOS v1组件。因为RTOS v2是对RTOSv1的扩展且与之兼容,所以一般选RTOSv2没问题。当然,如果你的芯片是CortexM核的芯片,选择RTOSv1组件也合适。

    因为STM32F103ZET6为M3内核的单片机,所以我选用CMSIS_V1版本,其他配置默认。
    在这里插入图片描述

  • 配置LED、KEY和USART,主要用于后面测试;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 生成工程。
    在这里插入图片描述
    在这里插入图片描述
    经过以上配置生成的工程是不会报错的。

  • 2.1 官网源码移植(从0开始)中的步骤4相同,添加延时相关的文件。

  • 继续完善工程,增加测试程序验证工程。约定: 添加的所有个人代码均在/* USER CODE xxx BEGIN xxx*/和/* USER CODE xxx END xxx*/之间。

    • main.h
    /* USER CODE BEGIN Includes */
    #include "stdio.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    
    #include "delay.h"
    #include "gpio.h"
    #include "usart.h"
    /* USER CODE END Includes */
    
    • gpio.h
    /* USER CODE BEGIN Private defines */
    #define LED2(n)		    (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET))
    #define LED2_Toggle() (HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5)) //LED0输出电平翻转
    #define LED3(n)		    (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET))
    #define LED3_Toggle() (HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5)) //LED1输出电平翻转
    
    #define KEY1()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4) //KEY1按键
    #define KEY2()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3) //KEY2按键
    #define KEY3()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2) //KEY3按键
    #define WK_UP()       HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) //WKUP按键
    /* USER CODE END Private defines */
    
    • usart.h
    /* USER CODE BEGIN Prototypes */
    #define EN_USART1_RX 	 1 //使能(1)/禁止(0)串口1接收中断	
    #define RXBUFFERSIZE   1 //缓存大小
    extern uint8_t aRxBuffer[RXBUFFERSIZE];//接收缓存区
    
    /* USER CODE END Prototypes */
    
    • usart.c
    /* USER CODE BEGIN 1 */
    #if EN_USART1_RX==1
    	void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    	{
    		if(huart->Instance==USART1)
    		{
    				HAL_UART_Transmit(&huart1,(uint8_t*)aRxBuffer,1,1000);	//发送接收到的数据
    				while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);		//等待发送结束
    			
    				HAL_UART_Receive_IT(&huart1,(uint8_t *)aRxBuffer, RXBUFFERSIZE);//重新开启中断并设置RxXferCount为1
    		}
    		
    	}
    #endif
    #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((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    		USART1->DR=(uint8_t)ch;      
    		return ch;
    	}
    #endif 
    /* USER CODE END 1 */
    
    • main.c
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    	delay_init();
    #if EN_USART1_RX==1
    	HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, RXBUFFERSIZE);	
    #endif
      /* USER CODE END 2 */
    
      /* Call init function for freertos objects (in freertos.c) */
      MX_FREERTOS_Init();
      /* Start scheduler */
      osKernelStart();
    
      /* We should never get here as control is now taken by the scheduler */
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
    //  while (1)
    //  {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    //  }
      /* USER CODE END 3 */
    }
    
    • freertos.c
    /* USER CODE BEGIN Variables */
    // LED2任务优先级、任务堆栈大小、任务句柄、任务函数
    #define LED2_TASK_PRIO		2	
    #define LED2_STK_SIZE 		50  
    TaskHandle_t LED2Task_Handler;
    void led2_task(void *pvParameters);
    
    // LED3任务优先级、任务堆栈大小、任务句柄、任务函数
    #define LED3_TASK_PRIO		3	
    #define LED3_STK_SIZE 		50  
    TaskHandle_t LED3Task_Handler;
    void led3_task(void *pvParameters);
    
    // FLOAT任务优先级、任务堆栈大小、任务句柄、任务函数
    #define FLOAT_TASK_PRIO		4
    #define FLOAT_STK_SIZE 		128
    TaskHandle_t FLOATTask_Handler;
    void float_task(void *pvParameters);
    /* USER CODE END Variables */
    
    /* USER CODE BEGIN RTOS_THREADS */
    /* add threads, ... */
    xTaskCreate((TaskFunction_t )led2_task,     	
                  (const char*    )"led1_task",   	
                  (uint16_t       )LED2_STK_SIZE, 
                  (void*          )NULL,				
                  (UBaseType_t    )LED2_TASK_PRIO,	
                  (TaskHandle_t*  )&LED2Task_Handler);   
    
      xTaskCreate((TaskFunction_t )led3_task,     
                  (const char*    )"led3_task",   
                  (uint16_t       )LED3_STK_SIZE, 
                  (void*          )NULL,
                  (UBaseType_t    )LED3_TASK_PRIO,
                  (TaskHandle_t*  )&LED3Task_Handler); 
      xTaskCreate((TaskFunction_t )float_task,     
                  (const char*    )"float_task",   
                  (uint16_t       )FLOAT_STK_SIZE, 
                  (void*          )NULL,
                  (UBaseType_t    )FLOAT_TASK_PRIO,
                  (TaskHandle_t*  )&FLOATTask_Handler); 
    /* USER CODE END RTOS_THREADS */
    
    /* USER CODE BEGIN Application */
    void led2_task(void *pvParameters)
    {
        while(1)
        {
            LED2_Toggle();
            delay_ms(500);
        }
    }   
    
    void led3_task(void *pvParameters)
    {
        while(1)
        {
            LED3(0);
            delay_ms(200);
            LED3(1);
            delay_ms(800);
        }
    }
    
    void float_task(void *pvParameters)
    {
    	static double float_num=0.00;
    	while(1)
    	{
    		float_num+=0.01f;
    		printf("float_num的值为: %.4f\r\n",float_num);
        delay_ms(1000);
    	}
    }
    /* USER CODE END Application */
    

本程序与2.1 官网源码移植(从0开始)程序相同。
对应工程名template1

3 FreeRTOS系统配置

3.1 FreeRTOSConfig.h文件说明

FreeRTOS的系统配置文件为FreeRTOSConfig.h,在此配置文件中包含了许多INCLUDE_config的开头宏定义,可以完成FreeRTOS的裁剪和配置。
FreeRTOS官方对FreeRTOSConfig.h的文档的说明:http://www.freertos.org/a00110.html

3.1.1 INCLUDE_开始的宏

使用"INCLUDE_"开头的宏用来表示使能或除能FreeRTOS中相应的API函数,作用就是用来配置FreeRTOS中的可选API函数的。其实本质就是条件编译,不需要的功能就不用编译,这样就可以根据实际需求来减少系统占用的ROM和RAM大小,根据自己所使用的MCU来调整系统消耗,降低成本。

  • INCLUDE_xSemaphoreGetMutexHolder
    如果要使用函数xQueueGetMutexHolder()的话宏INCLUDE_xSemaphoreGetMutexHolder必须定义为1。
  • INCLUDE_xTaskAbortDelay
    如果要使用函数xTaskAbortDelay()的话将宏INCLUDE_xTaskAbortDelay定义为1。
  • INCLUDE_vTaskDelay
    如果要使用函数vTaskDelay()的话需要将宏 INCLUDE_vTaskDelay定义为1。
  • INCLUDE_vTaskDelayUntil
    如果要使用函数vTaskDelayUntil()的话需要将宏INCLUDE_vTaskDelayUntil定义为1。
  • INCLUDE_vTaskDelete
    如果要使用函数vTaskDelete()的话需要将宏INCLUDE_vTaskDelete定义为1。
  • INCLUDE_xTaskGetCurrentTaskHandle
    如果要使用函数 xTaskGetCurentTaskHandle() 的话需要将宏INCLUDE_xTaskGetCurrentTaskHandle定义为1。
  • INCLUDE_XTaskGetHandle
    如果要使用函数xTaskGetHandle()的话需要将宏INCLUDE_xTaskGetHandle定义为1。
  • INCLUDE_xTaskGetldleTaskHandle
    如果要使用函数 xTaskGetldleTaskHandle()的话需要将宏INCLUDE_xTaskGetidleTaskHandle定义为1。
  • INCLUDE_xTaskGetSchedulerState
    如果要使用函数xTaskGetSchedulerState()的话需要将宏INCLUDE_xTaskGetSchedulerState定义为1。
  • INCLUDE_uxTaskGetStackHighWaterMark*
    如果要使用函数 uxTaskGetStackHighWaterMark()的话需要将宏INCLUDE uxTaskGetStackHighWaterMark定义为1。
  • INCLUDE_uxTaskPriorityGet
    如果要使用函数uxTaskPriorityGet)的话需要将宏INCLUDE_uxTaskPriorityGet定义为1。
  • INCLUDE_vTaskPrioritySet
    如果要使用函数vTaskPriorityset0的话需要将宏INCLUDE_vTaskPrioritySet定义为1。
  • INCLUDE_xTaskResumeFromlSR
    如果要使用函数xTaskResumeFromlSR()的话需要将宏INCLUDE_xTaskResumeFromlSR和INCLUDE_vTaskSuspend都定义为1。
  • INCLUDE_eTaskGetState
    如果要使用函数eTaskGetState()的话需要将宏INCLUDE_eTaskGetState定义为1。
  • INCLUDE_vTaskSuspend
    如果要使用函数vTaskSuspend()、vTaskResume()、prvTaskIsTaskSuspended()、xTaskResumeFromlSRO的话宏INCLUDE_vTaskSuspend要定义为1;
    如果要使用函数xTaskResumeFromISR()的话宏INCLUDE_xTaskResumeFromISR和INCLUDE_vTaskSuspend都必须定义为1。
  • INCLUDE_xTimerPendFunctionCall
    如果要使用函数xTimerPendFunctionCall()和xTimerPendFunctionCallFromlSR()的话宏INCLUDE_xTimerPendFunctionCall和configUSE_TIMERS都必须定义为1。

3.1.2 config开始的宏

config开始的宏和INCLUDE_开始的宏一样,都是用来完成 FreeRTOS 的配置和裁剪的。

  • configAPPLICATION_ALLOCATED_HEAP
    默认情况下,FreeRTOS 的堆内存是由编译器来分配的,将宏configAPPLICATION_ALLOCATED_HEAP定义为1的话堆内存可以由用户自行设置,堆内存在heap_1.c、 heap_2.c、 heap-3.c、heap-4.c和heap-5.c中有定义,具体在哪个文件取决于用户的选择哪种内存管理方式。本文选用heap 4.c,heap4.c中有下面定义。
    在这里插入图片描述
    可以看出,当宏configAPPLICATION_ALLOCATED_HEAP定义为1的话,需要用户自行堆内存ucHeap,否则的话就是编译器来分配的。
  • configASSERT
    断言,类似于C标准库中的assert()函数,调试代码的时候可以检查传入的参数是否合理。如果传递给 configASSERT()的参数为零,则会触发断言。使用断言的话会导致开销加大,一般在调试阶段使用
    注意: configASSERT()函数需要用户自行去定义,可以是显示到LCD上的函数,也可以是通过串口打印出来的函数。比如下面代码,当参数x错误时通过串口打印出发生错误的文件名和错误所在的行号:
    #define vAssertCalled(char,int)  printf("Error:%s,%d\r\n",char,int)
    #define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
    
    由于内置宏__LINE__是整数型的而不是字符串型,采用宏STR和和宏VAL来辅助完成这个转化。宏STR用来把整形行号替换掉__LINE__,宏VAL用来把这个整形行号字符串化。忽略宏STR和VAL中的任何一个,只能得到字符串”LINE”。经过上述转化后,vAssertCalled只需要接收一个字符串形式的参数。
    #define STR(x)  VAL(x)  
    #define VAL(x)  #x 
    #define vAssertCalled(char)  printf("Error:%s\r\n",char)
    #define configASSERT(x) ((x)?(void) 0 :vAssertCalled(__FILE__ ":" STR(__LINE__) " " #x"\n" ))
    
  • configCHECK_FOR_STACK_OVERFLOW
    设置堆栈溢出检测,每个任务都有一个任务堆栈,如果使用函数xTaskCreate()创建一个任务时,这个任务的堆栈是自动从FreeRTOS的堆(ucHeap)中分配的,堆栈的大小是由参数usStackDepth的决定。如果使用函数xTaskCreateStatic()创建任务时,任务堆栈是由用户设置的,参数pxStackBuffer为任务堆栈,一般是一个数组。
    堆栈溢出是导致应用程序不稳定的主要因素, FreeRTOS提供了两种可选的机制来帮助检测和调试堆栈溢出,两种机制都要设置宏configCHECK_FOR_STACK_OVERFLOW。如果使能了堆栈检测功能的话,即宏configCHECK_FOR_STACK_OVERFLOW不为0,那么用户必须提供一个钩子函数(回调函数),当内核检测到堆栈溢出以后就会调用这个钩子函数,此钩子函数原型如下:
    void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName );  
    
    参数xTask和pcTaskName为堆栈溢出任务的句柄和名字。注意,如果溢出非常严重,这两个参数信息也可能是错误的!!在这种情况下,可以直接检查pxCurrentTCb变量。有些处理器可能在堆栈溢出的时候生成一个fault中断来提示这种错误,另外,建议在调试的时候使用堆栈溢出检测,因为它会增加上下文切换的开销。
    configCHECK_FOR_STACK_OVERFLOW=1,使用堆栈溢出检测方法1。

相关参考

1、基于CubeMx配置RTOS和GUI时的两个小问题
2、FreeRTOS 之三 全配置项详解、裁剪(FreeRTOSConfig.h)
That’s all.Check it.

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-12-03 13:11:47  更:2021-12-03 13:11:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/9 1:03:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码