4. 嵌入式软件架构设计:前后台系统、RTOS与裸机选型
做嵌入式这么多年,我见过太多人在软件架构选型上栽跟头。说白了,选错了架构,后面调试、维护、加功能,每一步都是坑。今天咱们就把这几种架构掰开揉碎,讲清楚什么时候该用什么。
4.1 裸机编程:最直接的轮子
裸机,就是没有操作系统,没有调度器。你的代码直接跑在硬件上,一个 main 函数从头跑到尾。
核心特征:顺序执行、中断嵌套、全局变量满天飞。
我在早期做智能家居面板时,就用过纯裸机。功能简单:读按键、刷新屏幕、控制灯光。代码结构大概这样:
void main(void) {
system_init();
while(1) {
key_scan(); // 扫描按键
display_update(); // 刷新显示
led_control(); // 控制灯光
delay_ms(10); // 简单延时
}
}
这种写法,优点是简单、直观、资源占用极低。但缺点也很明显——实时性全靠程序员手算。你想想看,如果按键扫描里加了个耗时的 EEPROM 写入,屏幕刷新就得等,用户按了键没反应,体验极差。
| 对比项 | 裸机 | 前后台系统 | RTOS |
|---|---|---|---|
| 资源占用 | 极低(KB级RAM) | 低(几KB RAM) | 中等(10KB+ RAM) |
| 实时性 | 依赖主循环周期 | 中断响应快,任务响应慢 | 可预测,优先级抢占 |
| 任务数量 | 1~3个 | 3~8个 | 8个以上 |
| 调试难度 | 低 | 中等 | 较高(需理解调度机制) |
| 典型场景 | 玩具、简单传感器 | 家电控制面板 | 工业控制、无人机 |
4.2 前后台系统:中断驱动的艺术
前后台系统,也叫「中断驱动系统」。前台是中断服务程序(ISR),处理紧急事件;后台是主循环,处理非紧急任务。
我曾经做过一个温控器项目,用的就是前后台架构。温度传感器通过外部中断通知 MCU 读取数据,主循环里只做 PID 计算和屏幕刷新。效果不错,但有个坑——中断里不能做太多事。
避坑指南:我曾经在 ISR 里直接调用了 printf 打印调试信息,结果系统频繁死机。后来才发现,printf 是阻塞的,中断里一卡,整个系统就崩了。ISR 里只做标记,具体处理放主循环。
前后台系统的典型代码结构:
volatile uint8_t g_flag_adc_ready = 0;
void ADC_IRQHandler(void) {
g_flag_adc_ready = 1; // 只设标志,不做处理
}
void main(void) {
while(1) {
if(g_flag_adc_ready) {
g_flag_adc_ready = 0;
process_adc_data(); // 后台处理
}
update_display();
check_keypad();
}
}
这种架构,中断响应快,但后台任务之间没有优先级区分。如果同时有多个标志被置位,处理顺序完全取决于代码顺序。说白了,就是「谁先被检查,谁先被服务」。
4.3 RTOS:当复杂度超过临界点
当你的系统里有 5 个以上需要独立调度的任务时,就该上 RTOS 了。FreeRTOS 和 RT-Thread 是目前最主流的两个选择。
4.3.1 FreeRTOS:轻量级王者
FreeRTOS 我用了快十年。它的核心优势是极小、极稳定。一个最小系统只需要 4KB ROM 和 1KB RAM。任务调度、信号量、消息队列、软件定时器,该有的都有。
我记得有个项目,要在 STM32F103 上跑 8 个任务:传感器采集、数据解析、显示刷新、通信管理、日志记录、看门狗喂狗、按键处理、LED 指示。用裸机写,光任务切换逻辑就够写几百行。用 FreeRTOS,每个任务就是一个独立线程:
void task_sensor(void *pvParameters) {
while(1) {
read_sensor();
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms 采集一次
}
}
void task_display(void *pvParameters) {
while(1) {
update_screen();
vTaskDelay(pdMS_TO_TICKS(50)); // 50ms 刷新一次
}
}
int main(void) {
xTaskCreate(task_sensor, "Sensor", 128, NULL, 1, NULL);
xTaskCreate(task_display, "Display", 256, NULL, 2, NULL);
vTaskStartScheduler();
while(1); // 不会执行到这里
}
个人经验:任务栈大小别抠太死。我习惯给每个任务多分配 20% 的栈空间,防止递归调用或局部变量过大导致栈溢出。调试时可以用 uxTaskGetStackHighWaterMark() 查看实际使用量。
4.3.2 RT-Thread:国产之光
RT-Thread 比 FreeRTOS 重一些,但功能更丰富。它自带设备驱动框架、文件系统、网络协议栈,甚至还有 POSIX 接口。如果你做的是物联网网关或带屏幕的智能设备,RT-Thread 能省很多事。
我去年做的一个边缘计算节点,需要同时管理 WiFi、MQTT、本地存储和 Web 配置界面。用 FreeRTOS 的话,这些组件都得自己移植。用 RT-Thread,直接 menuconfig 勾选就行,开箱即用。
| 对比项 | FreeRTOS | RT-Thread |
|---|---|---|
| 最小 ROM | 4KB | 10KB |
| 最小 RAM | 1KB | 3KB |
| 调度方式 | 抢占式 + 协作式 | 抢占式 + 时间片轮转 |
| 组件生态 | 第三方移植 | 官方包管理器 |
| 学习曲线 | 平缓 | 中等 |
| 适合场景 | 资源受限、任务少 | 功能丰富、需要网络/文件系统 |
4.4 选型策略:到底该用哪个?
这个问题没有标准答案,但我总结了一套三步判断法:
- 数任务:如果功能逻辑可以拆成 3 个以内独立循环,裸机或前后台就够了。超过 5 个,上 RTOS。
- 看实时性:如果某个任务必须在 1ms 内响应(比如电机控制),RTOS 的抢占式调度是刚需。
- 算资源:MCU 的 RAM 小于 8KB、Flash 小于 64KB,优先考虑裸机或 FreeRTOS。资源充裕,RT-Thread 更省心。
我的建议:新手做项目,如果拿不准,先用裸机写个原型。跑通了,发现任务切换逻辑越来越复杂,再迁移到 RTOS。别一开始就上 RTOS,调度器本身也是 bug 源。
下面这张图,是我自己总结的选型决策流程,供你参考:
4.5 实际项目中的选型案例
我挑两个真实项目,说说当时是怎么选的。
案例一:智能门锁
- 任务:指纹识别、密码输入、蓝牙通信、电机驱动、低功耗管理
- MCU:STM32L051(8KB RAM, 64KB Flash)
- 选型:FreeRTOS
- 原因:任务数 5 个,低功耗模式下需要精确控制唤醒时机,FreeRTOS 的 tickless 模式正好合适。
案例二:便携式血氧仪
- 任务:传感器采集、数据处理、OLED 显示、按键响应
- MCU:GD32F103(20KB RAM, 128KB Flash)
- 选型:前后台系统
- 原因:任务只有 4 个,且采集和显示之间有明确的数据依赖关系,用标志位控制流程更简单。
避坑指南:我曾经在一个项目里,因为觉得 RTOS 很酷,硬给一个只需要 3 个任务的温控器上了 FreeRTOS。结果调试时发现,任务切换导致 ADC 采样时序抖动,反而比裸机更难调。后来换回前后台,问题立刻解决。记住:合适的才是最好的。
4.6 小结
嵌入式软件架构没有银弹。裸机简单直接,前后台灵活可控,RTOS 强大但复杂。选型时,先数任务、再看实时性、最后算资源。别为了用而用,也别因为怕学而不用。
嗯,这一章就到这里。记住我那句老话:架构选对了,后面全是顺风局;选错了,每一步都是逆风翻盘。