FreeRTOS#

嵌入式操作系统类型#

  • 分时操作系统(裸机开发),例如 windows;

  • 实时操作系统,例如 FreeRTOS;FreeRTOS 需要 4k-9k 的内核大小

优势 (为什么选择 FreeRTOS)#

  1. 免费开源

  2. 时间片调度,实时性好

系统代码风格 (Hal 库和 os 库)#

在定义变量时,把变量的类型作为前缀,方便用户根据变量的名字就可以知道变量的类型。

例如 char 类型的变量前缀是 c,short 类型的变量前缀是 s,long 类型的变量前缀是 l,数据结构、结构体和任务句柄等的前缀是 x。

如果是无符号型前缀加 u,如果是指针前缀再加 p。

在定义函数时,命名方式一般如下:前缀表示返回值类型,后跟函数所在的文件名和函数功能。

例 1:xTaskCreate(...),即表示返回值是一个数据结构,(如果是 pdPass, 其宏定义为 1,表示创建任务成功),在 Task.c 中定义,作用是创建任务。

例 2:vTaskPrioritySet(...),返回值是 void,在 Task.c 中定义,作用是设置优先级。

对于宏定义,前常有小写字母作为前缀,表示在哪个头文件中进行定义。

例:portMAX_DELAY,位于 portable.h 中,ConfigUSE_PREEMPTION,位于 FreeRTOSConfig.h

备注

FreeRTOS 的系统配置文件是 FreeRTOSConfig.h,可在里面放我们的配置,不配置会有一些默认。

时钟基准#

FreeRTOS 系统采用了 Systick(滴答定时器),用于任务的时间片调度,因此为了防止时间的冲突我们常需要在 CubeMx 配置时选择一个定时器作为我们 Hal 库的时钟(之后会演示给大家)。

我们把时间片的长短称为 tick,一般 FreeRTOS 的默认配置为 1000,即 1 秒内有 1000 个 tick,每个 tick 的时长为 1ms。由此我们可以来改变系统的实 时性的强度,当然若是数值太低,时间片配置为了 1s 的长度,那么就不配称之为实时操作系统了;同理如果这个配置的数值太高,时间片配置成了 1us 这种,那么 cpu 一直就用来切换任务了,没时间去执行任务了,因此 1000 是一个合理的值。

任务#

任务是竞争资源的最小单元(cpu),在任何时刻只有一个任务得到运行(即单 cpu),FreeRTOS 调度器决定运行哪个任务。调度器会不断启动,停止每个任务,从宏观上看,所有的任务都在同时进行。

任务的 4 种状态#

  • Running 运行态 CPU 的使用权正被这个任务占用。

  • Ready 就绪态 由于同优先级或更高优先级正在运行而处于排队的任务。

  • Blocked 阻塞态 等待信号,事件标志组等的状态(阻塞延时)

  • Suspended 挂起态 通过 vTaskSuspend() 挂起,挂起后的任务将不再被执行,只有调用 xTaskResume() 才可恢复。

调度器#

FreeRTOS 中支持 3 种调度方式:合作式调度,抢占式调度和时间片调度。

  1. 合作式调度。当任务需要运行时,被添加到等待队列,任务运行直到完成,然后由调度器选择下一个任务。调度简单,占用资源少,但实时性不好,一般都不会使用。

  2. 抢占式调度。是一种多任务的系统结构,高优先级的任务可以抢占低优先级任务的 CPU 使用权。

  3. 时间片调度。针对同优先级任务,调度算法为同优先级任务分配了一个专门的列表,用于记录当前就绪的任务,并且为每个任务分配一个时间片(ticks)。

任务优先级与切换#

FreeRTOS 的最高优先级是通过 FreeRTOSConfig.h 中的 configMAX_PRIORITIES 进行配置的,实际可用范围是 0-(configMAX_PRIORITIES-1),空闲任务的优先级是 0(即最小),最大不超过 32-1,一般来说高优先级任务必须是阻塞式的。

这里介绍下任务切换,在 FreeRTOS 中中断优先级的分组是只有抢占优先级 0-15 ,而任务的时间片切换则是通过 PendsvSystick(滴答定时器)中断来实现切换,他们是最低中断优先级。

警告

任务优先级和中断优先级之间的联系和区别

实际上这两者是两个东西,没有任何联系。任何等级的中断都可以打断最高级的任务。注意以下不同:

  • 中断优先级是数字越小优先级越高,即 0 为中断最高优先级。

  • 任务优先级则是数字越大优先级越高,即 0 为任务最低优先级。

任务操作#

空闲任务#

空闲任务是在启动调度器时自动创建的。

作用:执行删除任务时,系统并不会立刻释放任务的内存空间,会将任务添加到结束列表中,真正系统资源回收在空闲任务中完成。可在空闲任务中实现低功耗功能。

任务创建#

任务删除#

任务挂起#

任务恢复#

延时函数#

相对延时和绝对延时#

相对延时:函数:vTaskDelay(x),从代码段运行到它开始延时,延时(准确来说是阻塞)x 个节拍(即 ticks)。

绝对延时:函数:vTaskDelayUntil(x),会把其他代码段的执行时间包含进去,补充阻塞时间,能够使得任务每隔 x 个 ticks 的时间执行一次。

原理:存在一个指针指向一个变量,变量记录了最后一次解除阻塞的时间,第一次使用要通过 xTaskGetTickCount() 来获取当前系统时钟,之后会在 vTaskDealyUntil() 中自动更新,常用于令任务按一定频率执行。(高优先级任务效果更好,因为低优先级任务会被抢占而受到一定影响),若同一个任务两次执行时间由于高优先级任务的调用而导致回来执行时已经超时,则直接会跳过此次阻塞延时。

临界段#

代码开始执行后不允许被中断打断,执行前关闭中断,执行后开启中断。

进入临界段前操作寄存器 basepri 来关闭小于等于宏定义 configLIBRARY_MAX_SYSCALL_INTERRURT_PRIORITY 中所定义的中断优先级,退出时会操作寄存器 basepri 来再次打开中断。

由于实现任务切换的是 PendSV 和滴答定时器中断,是最低的中断优先级,因此临界段代码在执行期间也不会被高优先级的任务打断。

常用任务中的临界段代码段如下(注意任务中和中断中的函数不能混用):

TaskENTER_CRITICAL();
/*临界段需要执行的代码*/
TaskEXIT_CRITICAL();

小技巧

  1. 临界段代码必须成对使用

  2. 常用于读取或修改全局变量(特别是通信)

  3. 常用于某些公共函数,而别是多个任务访问同一个函数时

  4. 临界段代码越短越好,影响系统的实时性

三种锁的概念#

调度锁:就是 RTOS 提供的调度器开关函数,处于调度锁之间的代码不会被高优先级任务打断,但和临界段不同的是它并没有关闭中断,中断仍然会正常执行。

中断锁:就是 RTOS 中提供的关于开关中断的函数,FreeRTOS 中没有专门的中断锁函数,可用临界段处理函数来代替。

任务锁:就是为了防止当前任务的执行被其他高优先级任务打断而提供的锁机制,FreeRTOS 没有专门的任务锁,可用调度锁或临界段来代替。(作者注:目前没感觉出和调度锁的区别)

FreeRTOS 常用启动流程#

方法一:在 main 函数中将硬件初始化,RTOS 系统初始化,同时创建所有任务,再启动 RTOS 调度器。

方法二:在 main 函数中将硬件初始化,RTOS 系统初始化,只创建一个启动任务,再启动 RTOS 调度器,之后在启动任务中创建各种应用任务,当所有任务创建完成后,启动任务再把自己删除。(注意启动任务要是最高优先级)

备注

CubeMx 中用的是第一种方法。