1、课程导论:为什么需要分层架构?从“点灯大师”到“架构师”的思维转变

刚入行做单片机开发那会儿,我觉得自己挺牛的。

拿到一块新板子,第一件事就是找 GPIO 寄存器手册,对着地址写 0x01,灯亮了,成就感爆棚。那时候我觉得,所谓嵌入式开发,不就是操作寄存器嘛。

后来项目越做越大,代码从几百行膨胀到几万行。我发现自己越来越不敢改代码——改一个地方,崩三个地方。调试一个 bug 要翻遍整个 main.c,那滋味,真不好受。

为什么会这样?

说白了,就是代码没有层次。所有逻辑揉成一团,硬件操作和业务逻辑混在一起,谁也离不开谁。

从“点灯”到“架构”,差在哪?

先看两段代码,你感受一下差距。

新手写法:

// main.c - 点灯
void delay(volatile uint32_t count) {
    while(count--);
}

int main(void) {
    // 直接操作寄存器
    *(volatile uint32_t *)0x40021018 |= (1 << 3);  // RCC_APB2ENR
    *(volatile uint32_t *)0x40010C00 |= (1 << 20); // CRL
    *(volatile uint32_t *)0x40010C0C |= (1 << 20); // BSRR

    while(1) {
        *(volatile uint32_t *)0x40010C0C |= (1 << 20);
        delay(500000);
        *(volatile uint32_t *)0x40010C10 |= (1 << 20);
        delay(500000);
    }
}

分层写法:

// gpio_driver.c - 驱动层
void GPIO_SetPin(GPIO_TypeDef *GPIOx, uint16_t pin, uint8_t state) {
    if(state) GPIOx->BSRR = pin;
    else      GPIOx->BRR  = pin;
}

// led_service.c - 服务层
void LED_Toggle(void) {
    static uint8_t on = 0;
    on = !on;
    GPIO_SetPin(GPIOB, GPIO_PIN_12, on);
}

// app_main.c - 应用层
int main(void) {
    GPIO_Init(GPIOB, GPIO_PIN_12, GPIO_MODE_OUT_PP);
    while(1) {
        LED_Toggle();
        HAL_Delay(500);
    }
}

看出区别了吗?

第一种写法,硬件换了,整个 main.c 重写。第二种写法,换硬件只需要改驱动层,应用层代码纹丝不动。

这就是分层架构的价值——隔离变化。

核心观点:分层不是炫技,是为了让代码“可维护、可移植、可测试”。你写的每一行代码,都应该问自己一句:如果明天换芯片,这段代码还能用吗?

分层架构到底分几层?

我个人习惯把单片机软件分成四层。不多不少,刚刚好。

层次 职责 典型内容 依赖关系
应用层 业务逻辑、状态机、用户交互 任务调度、协议解析、UI 逻辑 只调用服务层接口
服务层 功能模块封装、策略组合 LED 管理、传感器数据融合、通信协议 调用驱动层抽象接口
驱动抽象层 硬件无关的驱动接口定义 GPIO、UART、I2C、SPI 抽象结构体 由具体驱动实现
硬件驱动层 直接操作寄存器、HAL 库封装 寄存器读写、中断服务、DMA 配置 依赖芯片手册

你想想看,这四层像什么?

像一座楼。地基是硬件驱动层,承重墙是驱动抽象层,房间装修是服务层,住的人用的是应用层。你不能把水管直接埋在地基里,也不能把家具挂在承重墙上——每一层各司其职。

我的经验:刚开始做分层时,容易犯一个错——过度抽象。明明只用一个 GPIO 口,非要搞个虚函数表。没必要。分层要适度,小项目两层就够了,大项目才需要四层。记住:架构是为项目服务的,不是为架构师服务的。

分层架构的核心逻辑

我用一张图把分层架构的核心逻辑画出来。你看完就明白了。

单片机软件分层架构核心逻辑 应用层 业务逻辑 · 状态机 · 用户交互 服务层 功能封装 · 策略组合 · 数据融合 驱动抽象层 硬件无关接口 · 结构体定义 · 回调注册 硬件驱动层 寄存器操作 · HAL 封装 · 中断服务 依赖方向(上层调用下层) 隔离变化 换芯片只改 最底层 上层代码不动 核心原则:上层依赖抽象,不依赖具体实现 每一层只通过接口与相邻层通信,禁止跨层调用

这张图你要记住。整个课程的核心,就是围绕这四层展开的。

为什么你必须要学分层?

我遇到过不少工程师,觉得分层是“大公司才用的东西”,小项目没必要。嗯,我以前也这么想。

直到有一次,我接了一个项目,客户要求先做 STM32 版本验证功能,量产时换成国产芯片。我当时没分层,所有代码都写在 main.c 里。换芯片那天,我加班到凌晨三点,改了两百多处寄存器操作。改完还不敢测试,因为不知道哪里会崩。

那次之后,我痛定思痛。所有新项目,不管大小,先搭分层骨架。哪怕只点一个灯,也要把驱动抽象出来。

避坑指南:我曾经犯过一个错——在应用层直接调用 HAL_Delay()。后来换 RTOS 时,所有延时都要改成 osDelay(),改了几十个文件。正确的做法是:在服务层封装一个 Sys_Delay(uint32_t ms),应用层只调用这个接口。换系统时,只改这一个函数。

分层架构带来的三个好处

  1. 可移植:换芯片、换编译器、换 RTOS,只需要改最下面一层。我有个项目从 STM32F1 移植到 GD32F1,只花了半天——因为驱动抽象层把差异全挡住了。
  2. 可测试:没有分层时,测试一个业务逻辑必须烧录到板子上。分层后,你可以 mock 掉驱动层,在 PC 上跑单元测试。我团队现在 CI 流程里跑 200 多个测试用例,全是分层带来的。
  3. 可协作:两个人同时开发一个项目,一个人写驱动,一个人写应用。只要接口定好了,各干各的,互不干扰。效率翻倍。

这门课你会学到什么?

这门课不讲虚的。30 章内容,每一章都有代码、有案例、有坑。

  • 你会学会如何设计驱动抽象层——从 GPIO 到 UART,从 I2C 到 SPI,每个外设都给出抽象接口模板。
  • 你会学会如何封装服务层——LED 管理、按键扫描、传感器驱动、通信协议,全部模块化。
  • 你会学会如何组织应用层——状态机、任务调度、事件驱动,让业务逻辑清晰可读。
  • 你还会学会调试技巧——如何用断言、日志、回调机制快速定位分层中的问题。

说白了,这门课就是帮你从“点灯大师”变成“架构师”。

不是让你背概念,是让你真正写出能拿出手的、能交付的、能维护的嵌入式代码。

最后说一句:架构不是设计出来的,是重构出来的。不要指望第一次就设计出完美的分层。先搭个骨架,写起来,发现问题再调整。重要的是——开始做。