3. 嵌入式RTOS基础

在进入 S32K 的具体 RTOS 工程之前,必须先建立一套统一的“词汇表”。很多初学者在移植 FreeRTOS 时感到吃力,并不是因为 API 难记,而是因为对“任务”“调度器”“内核对象”“实时性”这些概念的理解仍停留在裸机思维。本节把前后台系统与 RTOS 放在同一张图上对比,并逐一厘清核心术语,为后续在 S32K 上创建任务、配置优先级、使用队列与信号量打下基础。

一、前后台系统:裸机程序的基本形态

前后台系统(Foreground/Background System)是最常见的裸机结构。后台是一个永不退出的主循环,前台是中断服务程序(ISR)。主循环轮询处理各种事件,中断则负责响应紧急的硬件请求。

int main(void)
{
    Board_Init();
    for (;;) {
        Task_Sample();      /* 后台:轮询采样 */
        Task_Display();     /* 后台:刷新显示 */
        Task_Comm();        /* 后台:处理通信 */
    }
}

void ADC_IRQHandler(void)   /* 前台:中断响应 */
{
    g_adcValue = ADC_Read();
}

它的优点是结构简单、无额外内存开销、无需上下文切换。但缺点同样明显:所有后台任务的实时性取决于最慢的那个任务,一旦某个任务耗时过长,其他任务的响应就会被推迟;中断与主循环之间共享变量时,必须用临界区或原子操作保护,否则容易出现数据竞争。

二、RTOS:用任务与调度器重构程序

RTOS(Real-Time Operating System)把“一个大循环”拆成多个独立的任务(Task),每个任务拥有自己的栈和优先级,由调度器(Scheduler)决定何时运行哪个任务。任务在等待事件时主动阻塞,CPU 让给其他就绪任务,从而获得更好的实时性和可维护性。

下图对比了前后台系统与 RTOS 的执行结构,并标出任务、调度器、内核对象与实时性指标之间的关系。

前后台系统 前台:中断服务程序 ISR 紧急响应,抢占主循环 后台:主循环 for(;;) 采样 显示 通信 顺序执行,实时性受最慢任务制约 实时性指标 • 响应时间 = 中断延迟 + 处理时间 • 抖动大,取决于循环周期 • 无优先级抢占,紧急任务被拖延 RTOS 系统 调度器 Scheduler 按优先级抢占,决定任务切换 任务 Task(独立栈 + 优先级) Task_High Task_Mid Task_Low 阻塞时让出 CPU,就绪即被调度 内核对象 Kernel Objects • 队列 Queue:任务间传数据 • 信号量 Semaphore:同步与互斥 • 互斥量 Mutex:带优先级继承 演进

三、核心术语逐条厘清

  • 任务(Task):一个可独立调度的执行单元,拥有自己的栈空间、优先级和状态(就绪、运行、阻塞、挂起)。在 FreeRTOS 中由 xTaskCreate() 创建。
  • 调度器(Scheduler):内核的核心,负责在就绪任务中选择优先级最高的任务运行。S32K 上常用抢占式调度,高优先级任务就绪时立即抢占低优先级任务。
  • 上下文切换(Context Switch):保存当前任务的寄存器现场,恢复下一个任务的现场。切换本身有开销,通常几微秒量级。
  • 内核对象(Kernel Object):由内核管理的通信与同步资源,包括队列、信号量、互斥量、事件组、任务通知等。
  • 临界区(Critical Section):访问共享资源时关闭中断或调度器,保证操作的原子性。应尽量短小。

四、实时性指标:硬实时与软实时

实时性并不等于“快”,而是“在确定的时间内完成响应”。常用指标包括:

指标 含义 S32K 工程中的关注点
中断延迟 从中断发生到 ISR 第一条指令执行的时间 NVIC 优先级配置、中断嵌套
响应时间 从事件发生到任务处理完成的总时间 任务优先级、阻塞时间
抖动(Jitter) 响应时间的波动范围 调度确定性、临界区长度
截止时间 任务必须完成的最晚时刻 硬实时任务不可错过
CPU 利用率 任务占用 CPU 的时间比例 建议留 20%~30% 余量

硬实时:错过截止时间会导致系统失效,如电机换相、安全气囊触发。软实时:偶尔超时只影响体验,如界面刷新、日志上传。S32K 的 RTOS 工程通常混合两者,用优先级区分。

五、典型场景:从裸机到 RTOS 的改造

假设一个 S32K144 节点需要同时完成:1 kHz 电流环控制、100 Hz CAN 报文发送、1 Hz 温度采集。裸机方案把三者塞进主循环,电流环的周期会被 CAN 发送阻塞,导致控制抖动。改用 RTOS 后:

/* 电流环:最高优先级,周期 1 ms */
xTaskCreate(Task_CurrentLoop, "Curr", 256, NULL, 5, NULL);

/* CAN 发送:中优先级,等待队列数据 */
xTaskCreate(Task_CanSend, "Can", 512, NULL, 3, NULL);

/* 温度采集:低优先级,1 s 一次 */
xTaskCreate(Task_TempSample, "Temp", 256, NULL, 1, NULL);

vTaskStartScheduler();   /* 启动调度器,永不返回 */

电流环任务用 vTaskDelayUntil() 获得严格周期,CAN 任务阻塞在队列上,温度任务最低优先级。这样电流环的抖动从裸机时的数百微秒降到几十微秒以内。

六、常见误区与复盘

误区一:任务越多越好。每个任务都要占用独立栈(S32K 上通常 128~512 字),任务过多会耗尽 RAM。应按功能合并,而不是按函数拆分。

误区二:在中断里调用阻塞 API。ISR 中只能使用 ...FromISR() 结尾的接口,否则会破坏内核状态。

误区三:优先级设置随意。优先级应依据截止时间分配,而不是“感觉重要”。优先级反转时要用互斥量的优先级继承机制。

误区四:临界区过长。关中断时间过长会直接恶化中断延迟,违背实时性初衷。

理解这些术语之后,后续在 S32K 上配置 FreeRTOS、划分任务、选择内核对象时,就能从“能跑”走向“可预测地跑”。实时系统的价值不在于功能多,而在于行为确定。