3. 实时控制系统概念:实时系统定义、硬实时与软实时、实时操作系统选型

好,咱们进入第三章。这一章聊的是实时控制系统的基础概念。说实话,很多初学者一听到「实时」两个字,就以为「快」就是实时。其实不是的。我见过不少项目,处理器跑得飞快,但该响应的时候没响应,结果系统崩了。嗯,这里头有门道。

3.1 什么是实时系统?

实时系统,说白了就是「在规定时间内必须完成指定任务」的系统。注意,关键不是「快」,而是「准时」。你想想看,一个理疗仪,设定好每100毫秒采集一次皮肤温度,如果第150毫秒才读到数据,那控制算法算出来的加热功率就是错的,轻则效果差,重则烫伤用户。

我个人习惯把实时系统比作「守时的人」。你约他三点开会,他三点零一分到,那就不算实时。哪怕他三点零一分到得再快,也是迟到。

实时系统的核心指标: 响应时间(Response Time)必须在截止时间(Deadline)之前完成。

我在项目中遇到过一种情况:一个嵌入式设备,CPU主频很高,但任务调度用的是轮询方式,结果高优先级的中断来了,还得等当前任务跑完才能处理。这就是典型的「快但不实时」。

3.2 硬实时 vs 软实时

这里有个重要的分水岭:硬实时和软实时。说白了,就是「错过截止时间会怎样」的问题。

硬实时(Hard Real-Time)

错过截止时间 = 系统崩溃或灾难性后果。比如心脏起搏器、飞机飞控、汽车安全气囊。理疗仪里的温度保护功能,也属于硬实时——如果温度超过安全阈值后100毫秒才关加热,皮肤已经烫伤了。

我曾经调试过一个理疗仪样机,温度传感器中断优先级设低了,结果被一个串口打印任务抢了CPU。温度超了,打印还在慢悠悠地输出日志。嗯,那次之后我学乖了:硬实时任务的中断优先级,必须最高。

软实时(Soft Real-Time)

错过截止时间 = 性能下降,但系统不会崩溃。比如视频播放偶尔卡顿、键盘输入偶尔延迟。理疗仪里的用户界面刷新,属于软实时——界面卡一下,用户顶多觉得体验不好,不会出安全问题。

特性 硬实时 软实时
错过截止时间后果 灾难性 性能下降
典型应用 安全控制、医疗设备 多媒体、人机交互
调度策略要求 确定性、可预测 尽力而为
理疗仪中的例子 温度保护、电流限制 LCD刷新、按键扫描

我的经验: 设计时先把任务按「硬/软」分类。硬实时任务用独立定时器中断触发,软实时任务放后台轮询。别混在一起,否则调试起来你会想哭。

3.3 实时操作系统选型

选RTOS(实时操作系统)这事儿,我踩过不少坑。市面上常见的RTOS有FreeRTOS、uC/OS-II、RT-Thread、VxWorks等。怎么选?我一般看三个维度。

维度一:确定性(Determinism)

RTOS的核心能力是「可预测」。比如中断响应时间、任务切换时间,必须是确定的,不能忽快忽慢。FreeRTOS在这方面做得不错,中断延迟通常在几十微秒级别。我测过某款国产RTOS,任务切换时间居然会随任务数量变化,这就很要命。

维度二:资源占用

理疗仪用的MCU通常是Cortex-M3/M4,Flash 128KB~512KB,RAM 32KB~128KB。RTOS内核不能太大。FreeRTOS最小配置只要4KB ROM、几百字节RAM。uC/OS-II稍大,但功能更全。RT-Thread功能丰富,但资源占用也高,适合资源充裕的芯片。

维度三:生态与支持

我建议优先选社区活跃、文档齐全的。FreeRTOS有亚马逊背书,资料满天飞。RT-Thread中文文档好,国内用的人多。uC/OS-II虽然经典,但商业授权贵,现在用得少了。

避坑指南: 我曾经选了一个小众RTOS,结果遇到一个调度bug,官方论坛没人回,最后自己啃源码才解决。从那以后,我选RTOS只选社区活跃的。别为了省几KB ROM,给自己挖坑。

选型对比表

RTOS 最小ROM 最小RAM 确定性 适合场景
FreeRTOS ~4KB ~200B 资源受限的MCU
uC/OS-II ~6KB ~1KB 需要丰富API的场景
RT-Thread ~8KB ~2KB 中高 功能复杂的嵌入式设备
VxWorks ~50KB ~10KB 极高 航空航天、工业控制

我个人习惯,做理疗仪这类医疗设备,首选FreeRTOS。原因很简单:确定性够用、资源占用低、社区大、遇到问题能快速找到答案。如果你做的是高端理疗仪,需要文件系统、网络协议栈,那RT-Thread更合适。

3.4 一个小例子:FreeRTOS任务创建

光说不练假把式。我贴一段实际项目中的代码,创建一个温度采集任务和一个加热控制任务。

// 温度采集任务,硬实时,优先级最高
void vTaskTempSensor(void *pvParameters) {
    for(;;) {
        // 读取ADC值,转换为温度
        uint32_t adc_val = ADC_Read(ADC_CH_TEMP);
        float temp = adc_val * 0.1f;  // 假设转换系数
        
        // 如果温度超过安全阈值,立即通知加热任务停止
        if(temp > TEMP_SAFE_MAX) {
            xTaskNotifyGive(xHandleHeatCtrl);
        }
        
        // 每100ms采集一次
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

// 加热控制任务,软实时,优先级中等
void vTaskHeatCtrl(void *pvParameters) {
    uint32_t ulNotifiedValue;
    for(;;) {
        // 等待温度采集任务的通知
        ulNotifiedValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        if(ulNotifiedValue > 0) {
            // 收到超温通知,立即关闭加热
            GPIO_WritePin(GPIO_HEAT, 0);
            // 记录日志(软实时,允许延迟)
            printf("Over temp! Heater off.\n");
        }
    }
}

你看,硬实时任务(温度采集)用定时器中断或高优先级任务保证准时执行。软实时任务(加热控制)可以等待通知,即使延迟几毫秒也没关系。这就是实时系统设计的基本思路。

小技巧: 调试时可以在每个任务入口加一个GPIO翻转,用示波器看波形,就能直观看到任务是否按时执行。我每次调实时系统都这么干,比看日志靠谱多了。

好了,这一章就到这里。下一章咱们聊任务调度算法——怎么让多个实时任务和谐共处,不打架。到时候我会分享一个我踩过的优先级反转的坑,保证让你印象深刻。