细说STM32单片机FreeRTOS基础知识及用法

目录

一、FreeRTOS基础

1、初识FreeRTOS

2、FreeRTOS的特点和许可方式

3、FreeRTOS的一些概念和术语

(1)实时性

(2)任务

(3)移植

4、为什么要使用RTOS

二、CubeMX项目配置

1、SYS组件

2、RCC组件   

3、GPIO

4、FreeRTOS

5、Code Generator

6、设置独立的HAL基础时钟源

7、NVIC

三、软件设计

1、含FreeRTOS的项目的文件组成 

2、主程序 

3、freertos.c

四、运行与调试


        FreeRTOS是一个完全免费和开源的嵌入式实时操作系统,已被作为一个中间件集成到STM32 MCU固件库中。在STM32Cube开发方式中,用户可以很方便地使用FreeRTOS。

一、FreeRTOS基础

1、初识FreeRTOS

        FreeRTOS是一个完全免费和开源的嵌入式实时操作系统(Real-time Operating System,RTOS)。户很方便地在STM32Cube开发方式中使用FreeRTOS。本例将介绍FreeRTOS的特点和主要功能,通过一个简单的示例介绍FreeRTOS的文件组成,并介绍FreeRTOS的基本编程使用方法。

2、FreeRTOS的特点和许可方式

        FreeRTOS是一个技术上非常完善和成功的RTOS系统,具有如下标准功能。

  • 抢占式(pre-emptive)或合作式(co-operative)任务调度方式
  • 非常灵活的优先级管理
  • 灵活、快速而轻量化的任务通知(task notification)机制。
  • 队列(queue [kjuː])功能。
  • 二值信号量(binary semaphore)。
  • 计数信号量(counting semaphore)。
  • 互斥量(mutex [m'juteks] )。
  • 递归互斥量(recursive mutex)。
  • 软件定时器(software timer)。
  • 事件组(event group)。
  • 时间节拍钩子函数(tick hook function)。
  • 空闲时钩子函数(idle hook function)。
  • 栈溢出检查(stack overflow checking)。
  • 踪迹记录(trace recording)。
  • 任务运行时间统计收集(task run-time statics gathering)。
  • 完整的中断嵌套模型(对某些架构有用)。
  • 用于低功耗的无节拍(tick-less)特性。

        除了技术上的优势,FreeRTOS的开源免费许可协议也为用户扫除了使用FreeRTOS的障碍。FreeRTOS不涉及其他任何知识产权(Intellectual Property,IP)问题,因此用户可以完全免费地使用FreeRTOS,即使用于商业性项目,也无须公开自己的源代码,无须支付任何费用。当然,如果用户想获得额外的技术支持,那么可以付费升级为商业版本。FreeRTOS还有两个衍生的商业版本:

  • OpenRTOS是一个基于FreeRTOS内核的商业许可版本;  
  • SafeRTOS是另一个基于FreeRTOS内核的商业许可版本;

3、FreeRTOS的一些概念和术语

(1)实时性

        RTOS一般应用于对实时性有要求的嵌入式系统。实时性指任务的完成时间是确定的。日常使用的Windows、iOS、Android等是非实时操作系统,非实时操作系统对任务完成时间没有严格要求

        FreeRTOS是一个实时操作系统,特别适用于基于MCU的实时嵌入式应用。这种应用通常包括硬实时(hard real-time)和软实时(soft real-time)

        软实时,指任务运行要求有一个截止时间,但即便超过这个截止时间,也不会使系统变得毫无用处。例如,对敲按键的反应不够及时,可能使系统显得响应慢一点,但系统不至于无法使用。

        硬实时,指任务运行要求有一个截止时间,如果超过了这个截止时间,可能导致整个系统的功能失效。例如,轿车的安全气囊控制系统,如果在出现撞击时响应缓慢,就可能导致严重

的后果。

        FreeRTOS是一个实时操作系统,基于FreeRTOS开发的嵌入式系统可以满足硬实时要求。

(2)任务

        操作系统的主要功能就是实现多任务管理,而FreeRTOS是一个支持多任务的实时操作系统。FreeRTOS将任务称为线程(thread),但其还有一个常用的名称“任务”(task)。嵌入式操作系统中的任务与高级语言(如C++、Python)中的线程很相似。例如,任务或线程间通信与同步都使用信号量、互斥量等技术。

        一般的MCU是单核的,处理器在任何时刻只能执行一个任务的代码。FreeRTOS的多任务功能是通过其内核中的任务调度器来实现的,FreeRTOS支持基于任务优先级的抢占式任务调度算法,因而能满足硬实时的要求。

(3)移植

        FreeRTOS中有少部分与硬件密切相关的源代码,需要针对不同架构的MCU进行一些改写。这个过程称为移植。一套移植的FreeRTOS源代码称为一个接口(port)。

        针对某种MCU的移植,一般是由MCU厂家或FreeRTOS官方网站提供的,用户如果对FreeRTOS的底层代码和目标MCU非常熟悉,也可以自己进行移植。初学者或一般的使用者最好使用官方已经移植好的版本,以保证正确性,减少重复工作量。

        在CubeMX中,安装某个系列STM32 MCU的固件库时,就已经有移植好的FreeRTOS源代码。例如,对于STM32F4系列,其STM32CubeF4固件库就包含针对STM32F4移植好的FreeRTOS源代码,用户只需知道如何使用即可。

4、为什么要使用RTOS

        初学单片机编程时,一般都是从裸机编程开始,一般的嵌入式系统,如果功能要求不是太复杂或者程序设计比较精良,裸机系统也能很好地实现功能。但是如果功能要求比较复杂,需要分解为多个任务才能实现,就必须使用RTOS,或者对实时性要求比较高时,也必须使用RTOS。

        此外,使用RTOS并且将功能分解为多个任务,可以使程序功能模块化,程序结构更简单,便于维护和扩展,也便于团队协作开发,提高开发效率。熟悉了FreeRTOS的使用后,用户会发现使用FreeRTOS开发嵌入式系统,功能更强,使用更方便,在应用开发中,会习惯于使FreeRTOS。

二、CubeMX项目配置

        在CubeMX中,安装的MCU固件库中已经有FreeRTOS,在CubeMX组件面板的Middleware组里有FreeRTOS,可以像配置MCU的外设一样配置FreeRTOS。在本例中,通过一个非常简单的示例讲解使用FreeRTOS的基本方法,并且剖析FreeRTOS的文件组成和一些关键概念。

        本例使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。 创建一个STM32F407ZG项目。

1、SYS组件

        设置Debug接口为Serial Wire;    

2、RCC组件   

        设置HSE为Crystal/Ceramic Resonator;在时钟树上,设置HSE为25MHz,选择HSE作为主锁相环(Main PLL)的时钟源,设置HCLK为168MHz。需要根据开发板电路上实际的晶振频率设置HSE频率。

3、GPIO

        使用开发板上的LED1和LED2,它们连接的引脚分别是PA6和PA4,将这两个引脚都设置为GPIO_Output,推挽输出,无上拉或下拉。默认设置为高电平(默认LED熄灭)。

4、FreeRTOS

        在组件面板的Middleware组里有FreeRTOS,启用FreeRTOS并进行模式和参数设置,在模式设置(Mode)部分只有一个参数Interface,其右侧的下拉列表框里有3个选项。

  • Disable,表示不使用FreeRTOS。
  • CMSIS_V1,启用FreeRTOS,并且使用接口CMSIS_V1。
  • CMSIS_V2,启用FreeRTOS,并且使用接口CMSIS_V2。

        这里的接口是ARM公司定义的CMSIS-RTOS接口,有V1和V2两个版本。对于新的设计,应该使用CMSIS_V2版本。

        FreeRTOS的配置部分有8个页面,配置的内容比较多:

  • Configure parameters,参数配置。配置FreeRTOS的多组参数,如图1-3所示,这些参数对应于文件FreeRTOSConfig.h中的一些宏定义。
  • Include parameters,包含参数。配置FreeRTOS的包含参数,是一些函数的条件编译设置,这些包含参数对应于文件FreeRTOSConfig.h中的一些宏定义。
  • Advanced settings,高级设置。一些高级参数设置。
  • Tasks and Queues,任务和队列。任务和队列的管理,包括创建、删除和编辑等操作。
  • Timers and Semaphores,定时器和信号量。管理软件定时器,二值信号量和计数信号量。
  •  Mutexes,互斥量。管理互斥量和递归互斥量。
  • FreeRTOS Heap Usage,FreeRTOS堆空间使用情况统计。
  • User Constants,用户常数。用户自定义常数的设置。

 

        这几个页面的设置涉及FreeRTOS中的一些主要功能的使用,例如任务、信号量、互斥量等。在本示例项目中,保持FreeRTOS的所有参数设置为默认值。

5、Code Generator

        要选择为外设初始化生成.h/.c文件对。

6、设置独立的HAL基础时钟源

        在使用FreeRTOS时,应该使用一个独立的定时器作为HAL基础时钟源,而不是使用SysTick定时器在不使用FreeRTOS的时候,这个TimebaseSource默认是SysTick。在使用FreeRTOS时,FreeRTOS会将SysTick用作基础时钟,所以需要设置一个定时器作为HAL的基础时钟。

        将TIM6作为HAL基础时钟。所以,HAL的基础时钟是定时器TIM6,FreeRTOS的基础时钟是SysTick定时器。这两个定时器的定时周期都是1ms,但是各自的作用不同。 

7、NVIC

        启用了FreeRTOS并完成这些设置后,NVIC的自动设置会有较多修改:

  • 优先级分组策略,设置为4位全部用于抢占优先级,所以抢占优先级的设置范围是0到15。
  • TIM6中断的抢占优先级,设置为最高的0级,根据需要修改
  • System tick timer(SysTick定时器)和Pendable requestfor system service(可挂起的系统服务请求)中断的抢占优先级,设置为最低的15,且都不能修改。
  • 相对于未开启FreeRTOS的项目,中断列表中增加了一列Uses FreeRTOS functions,一些中断被勾选了这一项,TIM6的中断则没有勾选这一项。

三、软件设计

1、含FreeRTOS的项目的文件组成 

        完成设置后,在CubeMX中生成CubeIDE项目代码。在CubeIDE中打开项目,其文件目录树如图所示:

        项目浏览器中新增了与FreeRTOS相关的程序文件,包括可修改的用户程序文件和不可修改的FreeRTOS源程序文件。用户可修改的文件分布在\Inc和\Src目录下,包括以下几个文件。

 

  • \Core\Inc目录下的文件FreeRTOSConfig.h,是FreeRTOS的配置文件,包含很多宏定义,这些宏定义大多与CubeMX中FreeRTOS的可视化设置参数对应。
  • \Core\Src目录下的文件stm32f4xx_hal_timebase_tim.c,是设置HAL基础时钟的文件。当设置了TIM6作为HAL基础时钟源,这个文件里的代码就是对TIM6的一些设置,使基础时钟的中断周期为1ms。TIM6替代了原来SysTick定时器在HAL中的作用,而SysTick定时器由FreeRTOS使用
  • \Core\Src目录下的文件freertos.c,是CubeMX生成的FreeRTOS初始化文件,主要在这个文件里创建任务,编写用户功能代码
  • FreeRTOS的源程序文件在目录\Middlewares\Third_PartylFreeRTOS\Source下,已经针对所选择的MCU型号做好了底层代码移植,所以,这个目录下的FreeRTOS源代码不需要用户做任何修改。

2、主程序 

        在main()函数中,加入用户功能代码,增加注释段,以说明本示例程序的设计目的。主程序的代码如下:

/* USER CODE BEGIN 0 */
/**
 * 构建项目后,将其下载到开发板上并运行测试,会发现LED1闪烁,而LED2只是点亮。
 * 这说明执行了任务函数StartDefaultTask(),但没有执行main()函数中最后的while()死循环里的代码。
 * 因为在main()函数中,执行osKernelStart()函数时,FreeRTOS就接管了CPU的控制权,所以执行不到while()循环中的代码。
 * 
 */
/* USER CODE END 0 */
 /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);	//点亮PA4=LED2

  /**
  * osKernelInitialize():		CMSIS-RTOS函数,初始化FreeRTOS的调度器
  * MX_FREERTOS_Init():	    FreeRTOS对象初始化函数,在freertos.c中实现
  * osKernelStart():			CMSIS-RTOS函数,启动FreeRTOS的任务调度器
  */
  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();

  /* Call init function for freertos objects (in cmsis_os2.c) */
  MX_FREERTOS_Init();

  /* Start scheduler */
  osKernelStart();

        main()函数的开头部分依然是调用函数HAL_Init()进行HAL初始化。函数HAL_Init()内部会调用函数HAL_InitTick()对HAL基础时钟进行初始化。默认情况下,HAL基础时钟源使用Cortex-M4F内核的SysTick定时器,调用文件stm32f4xx_hal.c中的弱函数HAL_InitTick()。当基础时钟源设置为使用定时器TIM6后,CubeMX生成代码时创建了一个文件stm32f4xx_hal_timebase_tim.c,在这个文件中重新实现了函数HAL_InitTick(),对TIM6进行初始化,使用TIM6生成HAL的嘀嗒时钟信号。

        已配置外设的初始化只有MX_GPIO_Init(),是对LED1和LED2连接的两个引脚PA6和PA4进行GPIO初始化。系统和外设初始化完成后,调用函数osKernelInitialize()进行FreeRTOS调度器的初始化。

        函数MX_FREERTOS_Init()是FreeRTOS中创建对象的初始化函数,这个函数在文件freertos .c中实现。其主要功能是创建用户定义的任务、信号量、队列等在FreeRTOS中用到的对象。

        之后调用了函数osKernelStart(),这是CMSIS-RTOS标准接口函数,其内部调用FreeRTOS的函数vTaskStartScheduler(),功能是启动FreeRTOS内核的任务调度器。执行这个函数后,FreeRTOS就接管了系统的控制权,处理器循环执行FreeRTOS中各个任务的代码,FreeRTOS进行任务调度与其他功能管理。处理器不会再执行osKernelStart()之后的代码行,也就是不会执行main()函数最后的while()循环里的代码。程序下载运行测试时,会发现复位后LED2亮了,但是LED2不会闪烁。

3、freertos.c

        本示例只有一个任务,任务函数是StartDefaultTask()。CubeMX生成了这个函数的基本框架,可以看到这个函数的主体就是一个死循环。本示例中,希望在此任务里让LED1闪烁,在函数StartDefaultTask()里添加用户代码,完成的函数代码如下:

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
  for(;;)
  {
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);	//LED1输出翻转
	  osDelay(500);												//延时500个tick
  }
  /* USER CODE END StartDefaultTask */
}

        在for循环中,使用的延时函数是osDelay(),这是CMSIS-RTOS标准接口函数,内部调用FreeRTOS的延时函数vTaskDelay(),延时单位是时钟节拍(tick)。FreeRTOS的基础时钟产生的嘀嗒信号的周期是1ms,所以一个节拍就是1ms。

        在FreeRTOS的任务管理中,延时函数vTaskDelay()是非常重要的。任务函数的主体一般就是一个死循环,任务函数在执行延时函数vTaskDelay()时,会交出CPU的使用权,由FreeRTOS进行任务调度,使其他任务可以获得CPU的使用权,否则,高优先级任务将总是占用CPU,其他任务就无法执行。

四、运行与调试

        构建项目后,将其下载到开发板上并运行测试,会发现LED1闪烁,而LED2只是点亮。这说明执行了任务函数StartDefaultTask(),但没有执行main()函数中最后的while()死循环里的代码。因为在main()函数中,执行osKernelStart()函数时,FreeRTOS就接管了CPU的控制权,所以执行不到while()循环中的代码。