RTOS 入门(1):什么是 RTOS ?试手 FreeRTOS
1. 通用操作系统(GPOS / General Purpose Operating System)
操作系统一般具有三种功能:
- Task scheduling 任务调度
- Resource management 资源管理
- Device drivers 设备驱动
为什么我们需要实时操作系统?
- 大多数通用操作系统在设计之初将人机交互放在最重要的位置,所以调度器会给任务划分优先级,这也就意味着有些运行时限会被忽略或者是延后。而因此所产生的微小延迟,对于人类来说是可以接收的。
- 除此之外,GPOS的调度器通常还是非强制性的,这就意味着我们无法知道某个任务确切的执行时刻和执行时间。这样的特性在一些系统中是致命的,比如汽车的刹车系统。
- 所以,在对于时间有严格要求的项目中,就需要实时操作系统(RTOS / Real-Time Operating System)的参与。RTOS采用的是基于优先级的抢占式调度方法,确保高优先级任务能够得到及时处理,严格遵守时限。
2. 无限循环结构(Super Loop)
- 在STM32中主函数中的
while(1)
循环以及Arduino代码中的loop
函数,都是采用了所谓的无限循环结构。 - 在一些比较业务比较简单的情况下,结合定时器以及中断,Super Loop 也可以实现理想的效果。但是它的限制在于你无法同时执行多个任务,以上图为例,如果 Task2 所需要的时间较长,而 Task3 又是你的屏幕刷新函数,那你的项目执行就会出现卡顿的情况。
- 如果你间隔小于1ms,那你最好的方式是通过中断来处理问题;但是如果你需要间隔小于几百ns,你需要主频非常高的CPU或者是FPGA。
- 实时操作系统与无限循环结构在嵌入式开发当中并没有高低之分,要根据具体的情况去选择。
3. 任务、线程、进程
Task | Thread | Process |
---|---|---|
任务是任何一项我们想在代码中完成的工作 | 线程是 CPU 使用其寄存器组和对堆栈的工作单元 | 进程是一个正在执行的计算机程序实例 |
- 一个进程会使用一个或多个线程完成工作
- 同一个进程之间的线程之间可以共享资源(堆、内存)
- 通常情况下,RTOS只能管理一个进程,而GPOS可以管理多个进程
- 在 FreeRTOS中 task 作为一种专有名词可以用来替代 thread 的
4. 适用实时操作系统的情况
- 首先,使用实时操作系统就意味着一定的开销,所以RTOS一般仅适用于时钟频率、内存较高的单片机
- 在此基础之上,如果你需要同时执行一些任务,并且需要严格遵守时限,那么RTOS是适用的。
5. FreeRTOS 实现 LED 灯闪烁
程序运行环境:ESP32 Arduino 环境
需要注意的是,ESP32 并不运行 Vanilla-FreeRTOS,为了支持双核芯片,在原生 FreeRTOS 基础上进行了修改,使其支持 SMP(对称多处理)。
但是基础的语法依旧是保持一致的,所以依赖 ESP32 程序库的 FreeRTOS 直接学习也不失为一个好的选择。
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
// Pins
static const int led_pin = 2;
// Our task: blink an LED
void toggleLED(void *parameter){
while(1){
digitalWrite(led_pin, HIGH);
vTaskDelay(500 / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void setup() {
// Configure pin
pinMode(led_pin, OUTPUT);
// Task to run forever
xTaskCreatePinnedToCore( // use xTaskCreate() in vanilla FreeRTOS
toggleLED, // Function to be called
"Toggle LED", // Name of task
1024, // Stack size (bytes in ESP32, words in FreeRTOS)
NULL, // Parameter to pass to function
1, // Task priority (0 to configMAX_PRIORTIES )
NULL, // Task handle
app_cpu); // Run on one core for demo purposes (ESP32 only)
// If this waa vailla FreeRTOS, you would want to call vTaskStartSchedule() in
// main after setting up your tasks
}
void loop() {
// put your main code here, to run repeatedly:
}
-
ESP32是一个双核开发板,但是我们的实验只使用它的单核模式。
-
vTaskDelay
函数是一个非阻塞式的延时函数,该函数指定的是延时多少 Tick,而不是ms。语句vTaskDelay(500 / portTICK_PERIOD_MS);
表示的才是50ms。但是RTOS中默认的Tick时长是1ms,所以vTaskDelay(500);
和vTaskDelay(500 / portTICK_PERIOD_MS);
两个语句都表示延时500ms。 -
xTaskCreatePinnedToCore
函数创建一个任务并且只在指定的 CPU 运行,这是 ESP32 独有的函数,与 vanilla FreeRTOS 中xTaskCreate
函数的作用是一致的。在 ESP32 中xTaskCreate
函数仍是有效的,但是调用它则会通知调度器用任意的CPU执行任务。 -
ESP32 中任务栈大小的单位是 bytes,任务栈最小是 768 bytes。而在 vanilla FreeRTOS 中任务栈大小的单位则是 words。
-
任务优先级从 0 开始,一直到 config 中定义的最大优先级,ESP32 默认的最大优先级是 24。
-
在 vanilla FreeRTOS 中,在设置完任务之后需要调用
vTaskStartSchedule
通知调度器开始工作。但是在Arduino框架之下,该函数在setup
函数之前已经被调用,无需重复调用。
参考链接: