有个关于为什么选择FreeRTOS的文章值得一看 ESP32 的IDF使用的是FreeRTOS,对该教程做一下学习笔记
参考FreeRTOS官网资料

0 关于FreeRTOS

  • 适用于多任务小型嵌入系统
  • 为裸机程序提供多任务功能的库

每一种编译器+处理器的组合都称为:FreeRTOS port
FreeRTOSConfig.h 包含各种关于FreeRTOS特性的定制化配置(应与应用代码放在统一路径)

1 任务管理 Task Management

调度算法:

  1. 抢占+时间片均衡(默认)

configUSE_PREEMPTION = 1
configUSE_TIME_SLICING = 1

  1. 相同优先级的依次执行,可被高优先级抢占
  2. ESP32的任务优先级_ESP32的任务优先级

  3. 抢占+时间片不均衡

configUSE_PREEMPTION = 1
configUSE_TIME_SLICING = 0

  1. 相同优先级不均分时间片,只有高优先级task退出执行状态才会进行相同优先级task调度调整。
  2. ESP32的任务优先级_c语言_02

  3. Co-operative
    只有当前执行task进入block态或者当前task调用taskYIELD(),才会发生任务切换。任务不能够抢占

configUSE_PREEMPTION = 0
configUSE_TIME_SLICING = anyValue

  1. 任务优先级失效,只有当前任务停止执行才会进行任务调度

ESP32的任务优先级_优先级_03

2 队列管理 Queue Management

队列是FIFO结构,可用于任务间、任务与中断间的通信机制
通信机制:通过一个或多个任务向队列数据,另一个或多个任务从队列数据,可以实现任务间的通信

  • 队列可以保存指定长度的自定义结构
  • 队列可以组成队列组,用于有特殊需求的场景
  • 将队列和任务调度结合,可以实现UART数据栈和TCP/IP协议栈的处理功能

3 软件定时器 Software Timer Management

软件定时器是为了在指定时间执行某个回调函数

定时器原理:

  • 对定时器的操作(启动、停止、重置)会通过定时器命令队列(timer command
    quene)向RTOS后台任务(daemon task 默认创建)传递命令
  • RTOS后台任务有两个功能:处理定时器命令、执行回调函数
  • 由于RTOS后台任务受configTIMER_TASK_PRIORITY的控制,存在两种简单情况:
    场景1

ESP32的任务优先级_ESP32的任务优先级_04

PS:定时器的起始时间是从加入定时器命令队列开始计算,也就是从上图的t2开始,而不是从t4开始计算

场景2:

ESP32的任务优先级_c语言_05

定时器属性:

  • 包括单词定时器和连续定时器
  • 支持创建、删除、启动、重置、修改定时时间
  • 有两种状态:静态 dormant(定时器未运行)、运行态 running(定时器工作中,达到定时时间后,会调用回调函数)参见下图:

4 中断管理 Interrupt Management

中断相较任务有更高的优先级(中断可以抢占任务,反之不行)

中断会对任务的执行时间、定时存在扰动,因此尽可能的减少中断服务程序(interrupt service routine)的执行时间,是很重要的。由此引申出:延迟中断执行(deferred interrupt processing)即将中断的处理挪到任务中来。

中断的上下文切换:中断处理中(任务A被中断,中断结束后应该继续执行任务A),如需在中断结束后执行任务B,那么就涉及任务的上下文切换。

延迟中断执行的实现:

0 中断处理前建立任务B,并监控信号量(semaphore)

1 中断处理中,发出信号线

2 中断处理后,任务B接收信号量,完成延迟的中断处理

3 继续执行任务A

ESP32的任务优先级_优先级_06

5 资源管理 Resource Management

多任务会带来数据损坏(data corruption)
比如:

  • 多任务对输出设备的使用
  • 对变量的读、修改、写回
  • 非原子操作
  • 程序的重入

都会引入数据被其他任务“篡改”的风险。

解决方法:互斥锁(mutual exclusion == mutex)技术。就是对存在线程风险的数据加上“锁”,进行保护。
引入互斥锁又会导致其他问题:优先级颠倒(priority inversion)死锁(deadlock or deadly embrace)递归互斥。当然也会有对应的解决方法

6 事件组 Event Groups

使用场景:
某个任务需要等待多个事件
多个任务等待同一事件或事件组合

7 任务通知 Task Notifications

任务之间的通信可以借助通信对象:队列、事件组、信号量等
任务通知 机制(受控于 configUSE_TASK_NOTIFICATIONS)给task增加两个属性:通知状态和通知值