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翻转,用示波器看波形,就能直观看到任务是否按时执行。我每次调实时系统都这么干,比看日志靠谱多了。
好了,这一章就到这里。下一章咱们聊任务调度算法——怎么让多个实时任务和谐共处,不打架。到时候我会分享一个我踩过的优先级反转的坑,保证让你印象深刻。