FreeRTOS

FreeRTOS与我们熟悉的Linux是两种常见的操作系统内核,虽然它们都是用来管理系统资源并提供应用运行程序的环境,但它们的设计目标、应用场景以及功能特定都有很大的区别。

1. FreeRTOS的特性

  • FreeRTOS设计用于嵌入式系统,特别是资源有限的微控制器环境(MCU),强调实时性,即需要在确定的时间内响应和处理任务,非常适合用于时间敏感的应用,FreeRTOS通常运行在简单的硬件上,没有MMU(内存管理单元)等高级的硬件特性。

  • FreeRTOS中没有虚拟内存的概念,一般使用的是静态内存分配或简单的动态内存分配

  • FreeRTOS提供了优先级抢占式调度,支持任务优先级、时间片轮转等机制。
  • FreeRTOS没有完整的文件系统,适合运行在不需要文件存储的设备上,但是可以通过扩展支持一些简单的文件系统
  • FreeRTOS内核非常小,非常适合资源受限的设备

  • FreeRTOS的特性是

    • 具有抢占式或者合作式的实时操作系统内核
    • 功能可裁剪,最小占用10kB左右rom空间,0.5kB ram空间
    • 灵活的任务优先级分配
    • 具有低功耗模式
    • 有互斥锁、信号量、消息队列等功能
    • 运行过程可追踪
    • 支持中断嵌套

2. FreeRTOS的使用

在FreeRTOS中,任务(Task)是通过API来进程创建和管理的,任务相当于线程的概念,而不是像Linux中的进程,FreeRTOS中没有进程的概念,所有的任务共享一个内存地址空间。

任务创建的基本步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "FreeRTOS.h"
#include "task.h"

// 定义任务函数
void vTaskFunction(void *pvParameters)
{
// 任务的主体
for( ;; )
{
// 执行任务
}
}

int main(void)
{
// 创建任务
xTaskCreate(
vTaskFunction, // 任务函数
"TaskName", // 任务的名字,用于调试
1000, // 堆栈大小(以字节为单位)
NULL, // 传递给任务函数的参数
1, // 优先级
NULL // 用于存储任务句柄(如果需要管理任务)
);

// 启动调度器,开始调度任务
vTaskStartScheduler();

// 如果调度器启动失败,通常是因为内存不足
for( ;; );
}

一个四足机器人的控制示例:使用遥控器进行步态控制,用FreeRTOS进行任务调度管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"

// 定义队列用于传输步态选择
QueueHandle_t xGaitQueue;

// 步态定义
typedef enum {
GAIT_WALK,
GAIT_RUN,
GAIT_JUMP
} GaitType;

// DEBUS 通信处理任务
void vDebusCommTask(void *pvParameters) {
GaitType currentGait;

for (;;) {
// 假设从 DEBUS 通信接口读取遥控器的输入
int remoteInput = debusReceiveCommand();

// 根据遥控器输入选择步态
switch (remoteInput) {
case REMOTE_WALK_COMMAND:
currentGait = GAIT_WALK;
break;
case REMOTE_RUN_COMMAND:
currentGait = GAIT_RUN;
break;
case REMOTE_JUMP_COMMAND:
currentGait = GAIT_JUMP;
break;
default:
// 其他情况不处理
continue;
}

// 将选择的步态发送到步态队列中
xQueueSend(xGaitQueue, &currentGait, portMAX_DELAY);
}
}

// 步态控制任务
void vGaitControlTask(void *pvParameters) {
GaitType selectedGait;

for (;;) {
// 从队列中获取当前选择的步态
if (xQueueReceive(xGaitQueue, &selectedGait, portMAX_DELAY) == pdTRUE) {
switch (selectedGait) {
case GAIT_WALK:
// 执行步行步态
executeWalkingGait();
break;
case GAIT_RUN:
// 执行奔跑步态
executeRunningGait();
break;
case GAIT_JUMP:
// 执行跳跃步态
executeJumpingGait();
break;
default:
break;
}
}
}
}

int main(void) {
// 创建步态选择队列
xGaitQueue = xQueueCreate(5, sizeof(GaitType));

// 创建通信任务
xTaskCreate(vDebusCommTask, "DEBUSComm", 1000, NULL, 2, NULL);

// 创建步态控制任务
xTaskCreate(vGaitControlTask, "GaitControl", 1000, NULL, 2, NULL);

// 启动调度器
vTaskStartScheduler();

// 应该不会到这里
for (;;);
}

3. FreeRTOS的任务调度

FreeRTOS中的任务调度是基于优先级和时间片轮转的内核调度机制,其核心思想是通过判断任务的优先级、任务状态和系统时钟来确定哪个任务应该运行,调度器的具体实现涉及到几个重要的机制和数据结构。

任务态

每个任务在FreeRTOS中都有不同的状态:

  • 运行态(Running)
  • 就绪态(Ready)
  • 阻塞态(Blocked)
  • 挂起态(Suspended)

调度优先级

FreeRTOS才有优先级抢占式调度,每个任务在创建时都被分配一个优先级,优先级越高,任务越有可能被调度器调度执行,当有更高优先级的任务进行就绪状态的时候,调度器会立即停止当前的任务,将CPU切换给更高优先级的任务。

  • 任务优先级比较:调度器会不断检查所有就绪态任务的优先级,选择优先级最高的任务执行。

  • 抢占调度:当一个更高优先级的任务进入就绪态时(比如从阻塞态变为就绪态),调度器会中断当前正在运行的低优先级任务,并将控制权交给高优先级任务。

时间片轮转

在同一优先级下,如果有多个任务处于就绪状态,FreeRTOS 会通过时间片轮转机制来实现公平调度:

  • 每个任务在其时间片到期时,会让出 CPU 控制权,调度器会选择同优先级下的下一个任务执行。
  • 时间片轮转的间隔由系统时钟中断决定,即每次时钟中断会触发一次任务切换。

任务上下文切换

任务切换是调度器的核心操作,它需要保存当前任务的上下文(寄存器、栈指针等),并恢复下一个任务的上下文。

  • 当调度器决定切换到新的任务时,会首先保存当前任务的 CPU 上下文,包括程序计数器(PC)、栈指针(SP)和通用寄存器。
  • 然后,调度器会恢复下一个任务的上下文,将其状态重新加载到 CPU 寄存器中,并从其被中断的位置继续执行。

调度器的工作流程

大致流程如下:

  1. 启动调度器时,系统会进入调度循环,检查所有任务的状态。
  2. 调度器选择优先级最高且处于就绪态的任务运行。
  3. 当时钟中断发生或有更高优先级的任务就绪时,调度器执行任务切换。
  4. 如果多个相同优先级的任务就绪,调度器使用时间片轮转机制切换它们。
  5. 任务完成或进入阻塞态,调度器选择下一个任务运行。