1. 课程导论与分层架构全景:为什么需要分层?

各位同学好,我是这门课的主讲。今天咱们聊聊嵌入式软件分层这件事。

先问大家一个问题:你写过的嵌入式代码,最怕什么?

我个人最怕的是——改一个LED闪烁频率,结果把串口通信搞崩了。听起来离谱?我在项目中遇到过不止一次。裸机开发时代,这种牵一发动全身的噩梦,几乎是家常便饭。

1.1 从裸机开发到分层架构的演进

咱们先看看裸机开发长什么样。说白了,就是一个 main.c 里塞满所有东西:

// 典型的裸机代码(别这么写)
void main(void) {
    init_clock();
    init_gpio();
    init_uart();
    init_timer();
    
    while(1) {
        // 读传感器
        uint8_t data = read_sensor();
        // 处理数据
        uint16_t value = data * 10 + offset;
        // 显示到LCD
        lcd_display(value);
        // 同时还要处理按键
        if(button_pressed()) {
            // 改显示模式
            display_mode++;
        }
        delay_ms(100);
    }
}

这种写法有什么问题?我总结了几点:

  • 耦合严重:传感器、LCD、按键全搅在一起
  • 移植困难:换个MCU,整个文件重写
  • 调试痛苦:出问题不知道是硬件还是逻辑的锅
  • 团队协作难:两个人改同一个文件,合并时想哭

我记得刚入行那会儿,带我的老工程师看我写的代码,叹了口气说:“你这代码,就像一碗浆糊。”当时不服气,后来自己踩了坑才明白——裸机开发在小项目里还能凑合,一旦项目超过5000行,维护成本指数级上升。

避坑指南:我曾经接手过一个裸机项目,代码量2万行,全部在一个.c文件里。改一个定时器参数,花了三天才把所有依赖理清楚。从那以后,我坚决推行分层设计。

1.2 经典三层架构概览:HAL、DRV、APP

那怎么解决这个问题?分层。嵌入式领域最经典的分层方式就是三层架构:

层级 全称 职责 典型内容
APP Application Layer 业务逻辑、状态机、协议解析 用户需求实现、算法、流程控制
DRV Driver Layer 硬件抽象、寄存器操作、中断处理 GPIO、UART、SPI、I2C驱动
HAL Hardware Abstraction Layer 统一接口、平台无关、资源管理 设备句柄、读写接口、配置结构体

你想想看,这三层的关系就像一家公司:

  • APP 是老板,只管“做什么”
  • HAL 是部门经理,负责“怎么安排”
  • DRV 是一线员工,负责“具体干”

老板不需要知道员工用什么工具,员工也不需要关心老板的战略规划。各司其职,互不干扰。

1.3 驱动分离的核心思想

驱动分离,说白了就是让硬件驱动和上层逻辑彻底解耦。我习惯用一个比喻:

驱动就像插座,上层应用就像电器。 你换个插座(换MCU),电器插上去照样用。前提是——插座接口要标准化。

驱动分离的核心原则就三条:

  1. 接口统一:所有驱动暴露相同的API,比如 drv_open()、drv_read()、drv_write()
  2. 数据隐藏:上层不直接操作寄存器,只通过句柄或指针访问
  3. 配置独立:硬件参数(引脚、时钟、波特率)放在单独配置文件里
核心要点:驱动分离不是让代码变多,而是让代码变“干净”。每层只关心自己的事,出了问题也只在那一层找。

来看一个实际例子。假设你要从STM32换到GD32,传统做法是:

// 传统做法:到处改
// stm32_uart.c
USART1->DR = data;

// 改成
// gd32_uart.c
USART0->DATA = data;

如果用了驱动分离:

// 上层代码完全不用动
int ret = uart_write(handle, data, len);

// 底层只需要实现这个接口
// stm32版本
int uart_write(void *handle, uint8_t *data, uint32_t len) {
    // STM32寄存器操作
}

// gd32版本
int uart_write(void *handle, uint8_t *data, uint32_t len) {
    // GD32寄存器操作
}

嗯,这里要注意:驱动分离不是银弹。小项目(比如一个传感器+一个LED)强行分层反而增加复杂度。我建议项目超过3个外设,或者需要跨平台移植时,再上分层架构。

1.4 分层架构全景图

下面这张图,是我自己画的分层架构全景。你可以看到数据是怎么从硬件一路流到应用的:

嵌入式软件分层架构全景图 APP 应用层 业务逻辑 · 状态机 · 协议解析 · 用户需求实现 HAL 硬件抽象层 统一接口 · 平台无关 · 资源管理 · 设备句柄 DRV 驱动层 寄存器操作 · 中断处理 · 时序控制 · 硬件配置 硬件层(MCU · 外设 · 传感器) 数据流向:硬件 → 驱动 → 抽象 → 应用 接口调用:应用 → 抽象 → 驱动 → 硬件

这张图你看懂了吗?数据从硬件往上走,命令从应用往下传。每一层都只和相邻层打交道,绝不越级。

我的经验:刚开始做分层时,最容易犯的错误是“跨层调用”。比如APP直接调用DRV的寄存器操作函数。记住:APP只能调HAL,HAL只能调DRV,DRV只能操作硬件。这条规则,我建议你贴在工位上。

1.5 为什么我坚持用分层架构

说了这么多,总结一下分层架构的好处:

  • 可移植性:换MCU只改DRV层,APP和HAL基本不动
  • 可测试性:可以单独测试每一层,不用等硬件就绪
  • 可维护性:出问题定位快,改代码影响范围小
  • 团队协作:不同人负责不同层,并行开发不冲突

我记得有一次,客户临时要求换MCU型号。因为用了分层架构,我只花了两天就完成了驱动移植,APP层一行代码没改。客户都惊了,问我们是不是早就知道要换芯片。其实哪有什么预知,不过是架构设计得好罢了。

好了,这一章的内容就到这里。分层架构是后面所有章节的基础,理解透了,后面的驱动分离、接口设计、测试策略才能学得扎实。