2023年薪火培训电控第六讲 —— RTOS
什么是RTOS
RTOS 是实时操作系统(Real-Time Operating System)的缩写。它是一种专门用于实时任务处理的操作系统,用于管理和调度实时任务,并提供与硬件和外部设备的交互接口。
实时操作系统可以根据任务的时间要求和优先级,对任务进行调度和执行,以满足实时性的需求。它提供了任务管理、任务调度、中断处理、资源管理、通信机制等功能,使开发者能够方便地开发和管理实时应用程序。
操作系统
操作系统是一个控制程序,作为硬件和应用程序之间的桥梁,主要是和硬件打交道,负责协调分配计算资源和内存资源给不同的应用程序使用,并防止系统出现故障。面对来自不同应用程序的大量且互相竞争的资源请求,操作系统通过一个调度算法和内存管理算法尽可能把资源公平,有效率地分配给不同的程序。
实时???
实时性主要分为:硬实时和软实时
- 硬实时:要求任务在严格的时间限制内完成,并绝对不能错过截止时间。即使有一次时间违规,系统的正确性和可靠性也可能受到严重威胁。
- 软实时:任务有时间约束,但允许偶尔的时间违规。软实时任务的主要目标是在大部分情况下满足时间约束,但偶尔的延迟可能会被接受。
实时性的要求高主要是为了确保系统能够快速、准确地感知和响应各种情况,以提供安全性、稳定性和高效性能。
比如汽车的安全气囊系统,一旦检测到碰撞,系统必须在几毫秒的时间范围内完成气囊的充气,以在乘客撞击前提供必要的保护。任何延迟或错误可能导致安全气囊无法正常充气,从而无法起到保护作用,造成灾难性的后果。
和裸机开发的不同
裸机开发 | 基于RTOS的开发 |
---|---|
在没有操作系统支持的情况下直接编写代码来控制STM32。需要手动编写任务调度、同步和通信机制,以及处理中断和定时器等底层硬件操作。 | 更高级别的抽象和便利,使开发过程更加简化和高效。适用于需要处理多任务和并发操作的应用程序,同时提供了丰富的功能和组件来管理任务和资源。 |
现在市面上常见的RTOS有freeRTOS、RT-Thread、µC/OS等等,本节课主要介绍freeRTOS。
freeRTOS
相关背景:
FreeRTOS是一个热门的嵌入式设备用即时操作系统核心,它最初由Richard Barry于2003年左右开发,并由Barry的公司Real Time Engineers Ltd进行后续的开发和维护。2017年,该公司将FreeRTOS项目的管理权交给了亚马逊网络服务(Amazon Web Services,AWS)。Barry继续作为AWS团队的一员继续开发FreeRTOS。
优势
- FreeRTOS的设计小巧且简易,整个核心代码只有3到4个C文件,为了让代码容易阅读、移植和维护,大部分的代码都是以C语言编写,只有一些函数采用汇编语言编写。
- 开源,可免费使用
任务的配置和创建
CUBE MX的配置
- 根据自己的开发板选择型号
- 注意自己手上的是C6还是C8,可以给芯片打个光看一下
- RCC
- SYS,(这里有一点点不同,需要选择一个定时器作为时基)
以选TIM1
为例
- 在middleware中选择
FREERTOS
在下拉菜单中选择CMSIS_V1
- 配置时钟频率72MHz
- 在Project Manager中进行最后的配置(这部分和以前一样),生成代码,打开project
两种任务配置的方法
直接敲代码
void myTask(void *pvParameters){
for(;;){
/* 任务实现的代码 */
vTaskDelay(/*下一次执行的最短时间间隔*/);
/* 让任务进入阻塞状态 */
/* 也可以用osDelay()函数来实现 */
}
vTaskDelete( NULL );
// NULL表示删除当前的任务,也可通过传入其他任务句柄来删除其他任务
}
- Task永远不会返回(
return
),实现需要套在一个死循环内,如果循环结束了需要调用vTaskDelete()
进行删除
我们以翻转一个标志位为例为例:
int flag_a = 0;
/* 在程序前面定义 */
void flip_flag(void* parameter) {
int* p = parameter;
for (;;) {
*p = 1;
*p = 0;
vTaskDelay(1);
}
}
完成函数的定义之后,就可以创建任务
创建任务需要调用如下函数:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask);
参数 | 含义 |
---|---|
pvTaskCode |
任务函数指针 |
pcName |
任务名称,只在debug的时候有用,需要具有描述性。但这个参数不会被freeRTOS使用 |
usStackDepth |
栈深度 |
pvParameters |
参数指针,可以在传入任务后转换为需要的数据类型 |
uxPriority |
任务优先级 |
pxCreatedTask |
传出任务句柄的位置,用于在系统运行的时候改变任务的优先级或者删除 |
RTOS的标识符命名具有一定的规律,可以在下面这篇博客中进一步了解:
freeRTOS名称规范
在MX_FREERTOS_Init()
函数内创建任务:
xTaskCreate(flip_flag, "task_0", 128, &flag_a, 0, NULL);
(补充)使用Keil进行仿真
在使用之前,需要修改一下keil的配置
- 关闭代码优化
- 将优化级别改为
-O0
- 将优化级别改为
- 配置仿真参数
- 将debug页面左边的Use Stimulator和Limit Speed to Real Time选上
- 在下面的Dialog DLL中填入
DARMSTM.DLL
,Parameter中填入-pSTM32F103C6
(如果手上的板子是C8就填C8)
配置完成后,就可以进入debug模式
打开逻辑分析器后,将变量添加到里面,并将我们需要查看的标志位添加到逻辑分析器中
运行后即可看到标志位flag_a
的变化,说明任务可以正常执行。
同一个函数也可以用来创建多个任务,
int flag_b = 0;
xTaskCreate(flip_flag, "task_1", 128, &flag_b, 0, NULL);
两个任务都可以被执行
在CUBE MX中进行配置
CUBE MX同样为我们提供了比较简便的配置方式
任务需要填入的参数与之前的相同,优先级改为Normal
。
再次生成工程后,在freertos.c
可以找到cube自动生成的代码,我们在StartTask02()
的循环中写程序即可,内容与前面的flip_flag()
函数类似。
且在MX_FREERTOS_Init()
中,cube已经帮我们添加了创建任务的语句,只需要修改传入的参数
注意:在用cube重新生成工程之后,需要再次修改debug的配置
多任务调度
在实际开发的过程中,系统需要执行多个任务来维持自身的稳定,并对外界变化或指令进行相应。不同任务的执行时间和容忍的延迟各有不同,但我们使用的单片机是单核心的,在任意时刻只会有一个任务被执行,因此我们需要对任务进行合理的调度,来满足任务对于实时性的要求。
三种调度算法
- 先来先服务(FCFS)调度算法
按照任务到达的先后顺序进行调度,先到达的任务先执行。这种算法简单直观,但可能导致长任务优先的问题。
- 时间片轮转算法
将任务按照轮询顺序进行调度,每个任务执行一定的时间片(时间片轮转),然后切换到下一个任务。这种算法能够公平地分配CPU时间,但可能导致上下文切换开销较大。
可以试着将
flip_flag()
函数中的延迟注释掉,观察逻辑分析器的波形有什么变化 - 优先级调度算法
为每个任务分配优先级,并按照优先级进行调度。具有较高优先级的任务优先执行,但可能导致低优先级任务的饥饿问题。
可以通过下面的图了解一下
用Cubemx生成的FreeRTOS默认有七个优先级,这七个优先级定义在了cmsis_os.h
中。与中断优先级不同,任务的优先数越大,则优先级越高。
- 举个🌰
假设一个系统有三个任务
- 在分别进入准备状态时,三个任务都可以立即运行,此时能够满足硬实时的要求。
- 如果三个任务同时进入准备状态,则会按照优先级排队运行,此时假设前两个任务的运行时间大于第三个任务的容忍延迟,就会导致在第三个任务无法满足硬实时的要求。
- 其实只需要合理设置优先级,在三个任务同时进入准备时,也能够满足硬实时
- 举个🌰
任务状态
在RTOS中,一个任务有四种基本的工作状态:
- 运行态(Running)
- 阻塞态(Blocked):需要等待以下两种类型的事件才可恢复到就绪态。
- 时间相关事件:可以是延迟到期或者绝对时间到点。
- 同步事件:来自其他任务或者中断的事件。
- 挂起态(Suspended):对任务调度器不可见的状态,相当于从任务列表移出,但任务仍然存在,没有被删除。
- 就绪态(Ready):任务处于非运行状态,既没有阻塞也没有挂起,可以被运行,但尚未被运行。
状态量控制
阻塞态
-
时间相关阻塞,可通过
vTaskDelay()
或者vTaskDelayUntil()
函数是任务进入阻塞态 -
vTaskDelay()
参数 含义 xTicksToDelay
延迟的节拍(或称心跳)数 表示让任务在多少个心跳后会恢复到准备状态 -
vTaskDelayUntil()
参数 含义 pxPreviousWakeTime
上一次任务被唤醒的时刻 xTimeIncrement
到下一次唤醒的时间差 - 单位都是心跳时间
表示让任务在多少个心跳后恢复到准备状态,可以用来让任务周期性执行
心跳的频率为配置中的
TICK_RATE_HZ
挂起态:
-
只能通过
vTaskSuspend()
函数将任务挂起参数 含义 xTaskToSuspend
需要挂起任务的句柄指针,如果传入 NULL
则表示挂起当前任务 -
通过
vTaskResume()
函数将任务唤醒参数 含义 xTaskToResume
需要恢复任务的句柄指针
用官方文档的一张图总结
再加一点中断
我们可以添加一个定时器中断,在中断中反转一个新的标志位,查看标志位的变化和其他任务的行为
可以看到在正常任务被执行的过程中,定时器发生的中断也能够被处理
放大查看,可以发现在进入中断的时候,实际上也只有中断回调函数在执行
如果逻辑分析器看不到波形
- 检查一下keil的配置,代码优化是否为O0
- 在cube中配置定时器中断后,需要启用中断
延迟中断
如果在执行及时性要求非常高的任务时,发生了中断,我们又不希望中断处理耽误现有任务太多的时间,这个时候便需要进行延迟中断的操作
一般需要把中断处理程序的最小化,只执行与中断请求的最小处理相关的操作,例如清除中断标志位、保存关键状态、设状态位等,在完成原有任务后,再根据标志位来判定是否需要进一步处理中断。
如果中断触发不太频繁,也可以中断处理不使用的时候将处理任务挂起,中断发生的时候只将处理任务恢复,在处理完原任务之后,在通过调度器进入中断的后续处理程序,不过这种方法需要对任务的优先级进行合理设置。
作业
用freeRTOS和硬件包器件同时实现如下功能:
- 舵机控制
- 舵机位置初始化为90度;
- 通过蓝牙向单片机发送一个正整数,如果整数在0~180之间,让舵机调节至对应角度;如果大于180,则将舵机角度设置为90度
- 如果超过5秒没有收到新的位置数据,将舵机角度置为90度
- 呼吸灯控制
- 实现一个呼吸灯
- 呼吸灯受到一个按键控制,按键按下时,呼吸灯保持当前亮度不变;再次按下按键时,呼吸灯亮度继续变化