第一章:无OS环境概述

嵌入式系统简介

嵌入式系统,说白了就是「藏在设备里的计算机」。你家里的微波炉、智能手表、汽车ECU,甚至你桌上的路由器——里面都跑着嵌入式系统。我做了十几年嵌入式开发,最深的感受是:它不像PC那样有键盘鼠标,也不像手机那样有触摸屏,很多时候连个显示屏都没有。

嵌入式系统的核心特点是什么?我总结了几点:

  • 资源受限:RAM可能只有几KB到几MB,Flash也就几十KB到几MB。你想想看,PC上随便一个浏览器就吃掉几百MB内存,这在嵌入式世界里简直是天文数字。
  • 实时性要求:很多任务必须在规定时间内完成。比如安全气囊的触发,晚了1毫秒可能就是生与死的区别。
  • 功耗敏感:电池供电的设备,每毫安时都要精打细算。我做过一个物联网传感器项目,电池要用三年,代码里每一行都要考虑功耗。
  • 可靠性优先:设备一旦部署,可能几年甚至十几年不重启。你敢想象家里的空调隔三差五死机吗?

嗯,这里要注意:嵌入式系统并不等于单片机。虽然单片机是嵌入式的主力,但ARM Cortex-A系列跑Linux也算嵌入式。我们这门课聚焦的是「无操作系统」的场景,也就是裸机编程。

无操作系统与RTOS的区别

很多初学者会问:到底什么时候用裸机,什么时候上RTOS?我个人的经验是——看任务的复杂度和实时性要求。

先说说两者的本质区别:

对比项 裸机(无OS) RTOS(实时操作系统)
任务调度 主循环+中断,手动管理 基于优先级抢占式调度
资源开销 极低,几KB RAM即可 需要几KB到几十KB RAM
实时性 取决于中断响应和主循环周期 确定性高,可预测
开发难度 简单任务容易,复杂任务痛苦 学习曲线陡峭,但扩展性好
典型场景 传感器采集、简单控制、LED驱动 多任务通信、复杂协议栈、音视频处理

为什么会这样?我举个例子你就明白了。假设你要做一个温度采集器,每秒读一次传感器,然后通过串口打印。裸机方案就是一个while循环,里面调用延时函数,简单粗暴。但如果你要做无人机飞控——同时要处理GPS数据、IMU姿态解算、电机PWM控制、遥控器信号——裸机就捉襟见肘了。这时候RTOS的线程管理、信号量、消息队列就派上用场了。

我的建议:如果任务数不超过3-5个,且任务间交互简单,裸机完全够用。别为了用RTOS而用RTOS,那会增加不必要的复杂度。

裸机编程的特点与挑战

裸机编程,说白了就是「一个人管所有事」。没有操作系统帮你调度任务,没有驱动层帮你管理硬件,一切都要自己动手。我刚开始做裸机开发时,觉得这有什么难的?不就是写个死循环吗?后来才发现,坑多着呢。

先说说裸机编程的特点:

  • 完全控制权:CPU是你的,内存是你的,外设也是你的。想怎么用就怎么用,没有系统调用开销。
  • 代码量小:一个完整的裸机程序可能只有几百行C代码,加上启动文件也就一两千行。
  • 响应速度快:中断来了直接处理,没有任务切换的开销。我测过,STM32F103的中断响应时间可以做到几十个时钟周期。
  • 调试困难:没有printf?没有断点?那就用LED闪烁来调试吧。说实话,我早期很多bug都是靠LED闪烁定位的。

但裸机编程的挑战也不小:

我曾经踩过的坑:有一次做多路ADC采集,用了延时函数做定时采样。结果延时期间中断来了,打断了采样时序,数据全乱了。后来改用定时器中断触发采样才解决。裸机编程里,时序问题是最容易出bug的地方。

具体来说,裸机编程有这几个核心挑战:

  1. 任务调度全靠自己:没有RTOS帮你分时复用CPU,你得手动设计主循环的结构。是轮询?是状态机?还是中断驱动?每种方案都有适用场景。
  2. 资源管理要小心:全局变量满天飞?小心被中断和主循环同时访问。我习惯的做法是:中断里只设置标志位,主循环里处理具体逻辑。这样可以避免很多竞态问题。
  3. 代码可维护性差:项目大了,一个main函数几千行,改一个功能可能影响一片。这时候状态机设计模式就很重要了。
  4. 移植性差:裸机代码通常和硬件耦合很紧。换个MCU,启动文件、外设驱动、中断向量表全要重写。

这里分享一个我常用的裸机程序框架:

// 典型的裸机主循环结构
int main(void) {
    // 1. 硬件初始化
    SystemClock_Config();    // 配置系统时钟
    GPIO_Init();             // 初始化GPIO
    UART_Init();             // 初始化串口
    Timer_Init();            // 初始化定时器
    
    // 2. 软件初始化
    App_Init();              // 应用层初始化
    
    // 3. 主循环
    while(1) {
        // 轮询各个任务
        Task_SensorRead();   // 读取传感器
        Task_DataProcess();  // 数据处理
        Task_Communication();// 通信处理
        Task_Display();      // 显示更新
        
        // 可选的低功耗模式
        // __WFI();           // 等待中断
    }
}

小技巧:主循环里不要用硬延时(比如HAL_Delay),那会阻塞整个系统。改用定时器+标志位的方式,或者用状态机来管理时间片。我习惯用一个1ms的定时器中断,累加一个全局变量作为系统滴答,各个任务根据这个滴答来判断是否该执行。

最后说一句:裸机编程虽然「原始」,但它是理解计算机系统底层运作的最好方式。你想想看,当你亲手写出一个从零启动、没有操作系统、却能稳定运行几年的嵌入式系统时,那种成就感是RTOS给不了的。这门课,我会带你一步步掌握这些技能。