1. 课程导论与整体架构:为什么需要软件分层?低功耗设计的核心思想是什么?协议解析的通用方法论。

各位同学好,我是你们这门课的老周。做嵌入式十几年了,踩过的坑比写过的代码还多。今天咱们开篇,不讲虚的,直接聊三个核心问题:软件为什么要分层?低功耗到底在低什么?协议解析有没有套路?

这三个问题,说白了就是嵌入式开发的“内功心法”。你代码写得再花哨,底层逻辑不通,项目迟早要翻车。我见过太多工程师,上来就怼业务逻辑,结果后期维护改到想砸键盘。嗯,咱们今天就把这些底层逻辑理清楚。

1.1 为什么需要软件分层?—— 从“一锅粥”到“分灶吃饭”

先问大家一个问题:你写单片机程序,是不是经常一个 main.c 里塞了上千行?GPIO 初始化、定时器配置、协议解析、业务逻辑全揉在一起?

我以前也这么干。直到有一次,一个智能家居项目,客户突然要换 WiFi 模块。好家伙,我那个 main.c 里,WiFi 驱动和业务逻辑缠得跟麻花似的。改一个地方,崩三个功能。加班三天,最后发现是 SPI 时序被业务代码里的一个延时给冲了。

从那以后,我彻底明白了:不分层,就是给自己挖坑。

软件分层的核心价值:

  • 解耦:硬件变了,只改驱动层;协议变了,只改协议层;业务变了,只改应用层。互不干扰。
  • 复用:写好的 I2C 驱动,下个项目直接 copy。不用重写,不用调试。
  • 可测试:每一层都能单独测。驱动层用逻辑分析仪看波形,协议层用串口打 log,应用层直接模拟输入。定位问题快得多。
  • 可移植:从 STM32 换到 GD32,或者从裸机换到 RTOS,只要接口不变,上层代码几乎不用动。

那具体怎么分?我习惯分成四层,你想想看,是不是这个理:

层级 职责 典型内容 依赖关系
应用层 业务逻辑、状态机、用户交互 按键处理、数据显示、报警逻辑 依赖协议层、硬件抽象层
协议层 数据封装、解析、校验、组包 Modbus、CANOpen、自定义协议 依赖硬件抽象层
硬件抽象层 (HAL) 封装 MCU 外设操作,提供统一接口 GPIO_WritePin、I2C_Read、UART_Send 依赖驱动层
驱动层 直接操作寄存器,与硬件时序相关 SPI 时序、定时器中断、DMA 配置 依赖 MCU 手册

你看,每一层只干自己的事。驱动层只管把寄存器的值写对,HAL 层只管把驱动封装成好用的函数,协议层只管把字节流解析成结构体,应用层只管拿着结构体做决策。各司其职,出了问题也容易定位。

我曾经在一个项目中,把 HAL 层的接口定义成 int HAL_UART_Read(uint8_t *buf, uint16_t len)。不管底层是 STM32 的 USART 还是 NXP 的 LPUART,上层代码完全不用改。换芯片?只改驱动层和 HAL 层的实现,协议层和应用层直接编译通过。这就是分层的好处。

1.2 低功耗设计的核心思想 —— “省着用,不如不用”

低功耗设计,很多新手第一反应是“把主频降下来”。其实这只是最基础的一步。真正的低功耗设计,核心思想就一句话:让系统在不需要干活的时候,彻底休息。

你想想看,单片机跑在 72MHz 的时候,电流可能 50mA。但你让它进入 STOP 模式,电流能降到 10uA。差了 5000 倍!所以关键不是“跑慢点”,而是“别跑”。

低功耗设计的三个层次:

  1. 架构级:选择低功耗 MCU,合理规划电源域,能不供电的模块就关掉。
  2. 软件级:事件驱动,没事就睡。用中断唤醒,而不是轮询。
  3. 硬件级:外围器件选型,上拉电阻大小,甚至 PCB 漏电流都要考虑。

我重点说说软件级。很多工程师写代码,喜欢在主循环里放一个 while(1),然后不断查询标志位。这其实是最耗电的做法。CPU 一直在跑,一直在取指令、执行指令,电流下不来。

正确的做法是:事件驱动 + 睡眠。

  • 没事干的时候,调用 __WFI() 或者进入 STOP 模式。
  • 有事件来了(比如按键按下、定时器溢出、数据接收),用中断唤醒 CPU。
  • 处理完事件,继续睡。

我曾经优化过一个温湿度采集器。原来代码是每 100ms 查询一次传感器,平均电流 2.3mA。改成中断唤醒 + 每 10 秒采集一次后,平均电流降到了 45uA。电池寿命从 3 个月延长到了 2 年。客户高兴坏了。

注意:低功耗设计不是一味地睡。你要考虑唤醒时间、唤醒后的处理效率、以及外围器件的功耗。比如,有些传感器上电后需要稳定时间,你如果频繁唤醒,功耗反而更高。我曾经踩过这个坑,一个气压传感器每次上电要 10ms 稳定,我每 100ms 唤醒一次,结果功耗比一直开着还大。后来改成每 1 分钟采集一次,才真正省电。

1.3 协议解析的通用方法论 —— “从比特流到结构体”

协议解析,说白了就是把一串不知道啥意思的字节,变成程序能理解的结构体。不管是 Modbus、CAN、还是自定义协议,套路都差不多。

我总结了一个“五步法”,你照着做,基本不会出错:

  1. 定帧:怎么知道一帧数据开始了?怎么知道结束了?常见的有:固定帧头帧尾、长度字段、超时判断。
  2. 校验:数据在传输过程中有没有出错?CRC、累加和、奇偶校验。
  3. 解析:把字节按照协议格式,拆成各个字段。注意大小端、对齐、位域。
  4. 组包:发送的时候,把结构体变成字节流。注意填充、转义。
  5. 状态机:用一个有限状态机来管理接收过程。避免阻塞,支持断帧重传。

举个例子,一个最简单的自定义协议:帧头 0xAA 0x55,长度 1 字节,数据 N 字节,校验 1 字节(累加和)。

// 接收状态机
typedef enum {
    STATE_IDLE,
    STATE_HEADER1,
    STATE_HEADER2,
    STATE_LENGTH,
    STATE_DATA,
    STATE_CHECK
} rx_state_t;

rx_state_t state = STATE_IDLE;
uint8_t rx_buf[256];
uint8_t rx_len;
uint8_t rx_index;
uint8_t rx_check;

void uart_rx_isr(uint8_t byte) {
    switch (state) {
        case STATE_IDLE:
            if (byte == 0xAA) state = STATE_HEADER1;
            break;
        case STATE_HEADER1:
            if (byte == 0x55) state = STATE_HEADER2;
            else state = STATE_IDLE; // 重新同步
            break;
        case STATE_HEADER2:
            rx_len = byte;
            rx_index = 0;
            rx_check = 0xAA + 0x55 + rx_len;
            state = STATE_DATA;
            break;
        case STATE_DATA:
            rx_buf[rx_index++] = byte;
            rx_check += byte;
            if (rx_index >= rx_len) state = STATE_CHECK;
            break;
        case STATE_CHECK:
            if (byte == (uint8_t)(~rx_check + 1)) { // 补码校验
                // 一帧完整数据接收完成,处理 rx_buf
                process_frame(rx_buf, rx_len);
            }
            state = STATE_IDLE;
            break;
        default:
            state = STATE_IDLE;
            break;
    }
}

你看,这个状态机很清晰。每个字节进来,只做一件事。不会阻塞,不会丢帧。而且支持断帧重传——如果中间收到一个错误的字节,状态机会自动回到 IDLE,等待下一个帧头。

协议解析的通用原则:

  • 不要阻塞:解析过程必须在中断或任务中完成,不能等待。
  • 不要假设:永远假设数据可能出错、可能丢失、可能乱序。
  • 不要硬编码:协议参数(帧头、长度、校验方式)尽量用宏定义或配置文件。

好了,咱们把今天的内容用一张图总结一下。这张图就是整个课程的知识骨架,后面每一章都会围绕它展开。

课程知识体系总览 软件分层架构 应用层 协议层 硬件抽象层 (HAL) 驱动层 低功耗设计 架构级:选型与电源域 软件级:事件驱动+睡眠 硬件级:外围与漏电流 协议解析方法论 定帧 校验 解析 组包 状态机 课程核心逻辑 1. 软件分层是基础架构,保证代码可维护、可移植。 2. 低功耗设计是目标,贯穿硬件选型到软件实现。 3. 协议解析是手段,用状态机+五步法处理所有通信。 三者结合,才能做出稳定、省电、易维护的嵌入式产品。

这张图把咱们今天讲的三个核心内容串起来了。软件分层是骨架,低功耗是灵魂,协议解析是手段。后面 29 章,咱们会一个一个细节地抠。从 GPIO 怎么配置最省电,到 Modbus 协议怎么移植,再到 RTOS 下的低功耗调度,我都会把我在项目中踩过的坑、总结的经验,毫无保留地分享给你们。

好,今天就到这儿。记住这三个核心思想,后面的路就好走了。