1. 课程导论与分层架构全景:为什么需要分层?
各位同学好,我是你们这趟实战之旅的向导。咱们这门课叫《STM32分层架构思想在HAL库项目中的模块解耦落地驱动开发实战》,名字挺长,但核心就一件事:怎么把代码写得漂亮、写得稳、写得容易改。
第一节课,咱们先不急着敲键盘。先聊聊一个根本问题——为什么需要分层?
1.1 从裸机开发说起:那段“一把梭”的日子
我记得刚入行那会儿,做单片机开发,基本都是裸机。什么叫裸机?就是一个 main 函数里,初始化外设、轮询按键、处理逻辑、刷新显示,全塞在一起。代码结构大概长这样:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_I2C1_Init();
while (1)
{
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_RESET)
{
// 按键处理
HAL_Delay(20);
// 读取传感器
HAL_I2C_Master_Receive(...);
// 计算数据
// 串口发送
HAL_UART_Transmit(...);
// 刷新屏幕
}
}
}
你看,逻辑上好像也没错。但项目一复杂,问题就来了。
我曾经接手过一个项目,前任工程师把整个温控系统写在一个文件里,足足 3000 行。我想改个传感器驱动,结果发现它和按键消抖、PID 计算、OLED 刷新全耦合在一起。改一行,崩一片。那段时间我每天都在“改 bug 引入新 bug”的循环里打转。
说白了,裸机开发最大的痛点就是耦合度高、复用性差、调试困难。你想想看,一个项目里,硬件换了、外设变了、需求改了,代码就得重写大半。这哪行?
1.2 分层架构的演进:从“一锅炖”到“分盘装”
那怎么解决?其实思路很简单——把不同职责的代码分开。
就像做饭,你不会把洗菜、切菜、炒菜全在一个锅里完成吧?你会分步骤、分工具。代码也一样。
分层架构的演进,大致经历了三个阶段:
| 阶段 | 特点 | 典型问题 |
|---|---|---|
| 裸机直写 | 所有代码在 main.c,函数间直接调用 | 耦合高、难复用、调试痛苦 |
| 模块化拆分 | 按功能拆成 .c/.h 文件,如 led.c、uart.c | 模块间仍存在直接依赖,换硬件要改多处 |
| 分层架构 | 按抽象层级划分,下层为上层提供服务 | 接口清晰、可替换、可测试 |
我个人习惯把分层架构理解成“依赖倒置”——上层依赖下层的接口,而不是具体实现。这样,底层换了,上层不用动。
1.3 三层架构的职责划分:HAL层、中间层、应用层
咱们这门课采用经典的三层架构。我画了张图,你看一眼就明白了:
这张图就是咱们整个课程的骨架。咱们来逐一拆解:
1.3.1 HAL层:硬件的“翻译官”
HAL 层,全称 Hardware Abstraction Layer,硬件抽象层。说白了,它就是 ST 官方帮我们封装好的外设驱动库。
你不需要关心 USART 的波特率寄存器怎么配、I2C 的时序怎么调。你只需要调用:
HAL_UART_Transmit(&huart1, data, len, timeout);
HAL_I2C_Master_Receive(&hi2c1, dev_addr, buf, len, timeout);
HAL 层的职责很纯粹:屏蔽硬件差异,提供统一 API。换芯片?只要 STM32 系列,HAL 接口基本不变。
一个小建议:尽量不要在 HAL 层之外直接操作寄存器。我见过有人为了“性能”在应用层直接写 GPIO->ODR,结果换芯片时改到崩溃。HAL 层就是你的防火墙。
1.3.2 中间层:承上启下的“桥梁”
中间层,也叫 Middleware Layer。它是整个架构里最灵活、也最考验设计能力的一层。
它的职责包括:
- 协议封装:比如把 HAL 的 UART 收发包装成 Modbus 协议栈
- 数据管理:比如环形缓冲区、FIFO、参数存储
- 模块抽象:比如定义一个“传感器接口”,底下可以接 DS18B20、DHT11 或模拟输入
- 系统服务:比如 FreeRTOS 的任务管理、信号量、消息队列
举个例子,你写了一个按键驱动模块:
// 中间层:按键模块
typedef struct {
uint8_t pin;
GPIO_TypeDef *port;
uint32_t last_press_time;
uint8_t state;
} Key_t;
uint8_t Key_GetState(Key_t *key); // 返回按下/释放/长按
应用层调用 Key_GetState() 时,根本不知道底层是 GPIO 中断还是轮询。这就是解耦。
1.3.3 应用层:业务逻辑的“舞台”
应用层是离用户最近的一层。它只关心“做什么”,不关心“怎么做”。
比如一个智能温控器:
- 应用层:读取温度 → 判断是否超限 → 控制继电器 → 更新屏幕
- 中间层:提供
TempSensor_Read()、Relay_Control()、LCD_Display() - HAL层:提供
HAL_I2C_Master_Receive()、HAL_GPIO_WritePin()
你看,应用层只调用中间层的接口,中间层再调用 HAL 层。每一层各司其职。
1.4 三层之间的交互关系:单向依赖,接口隔离
这里有个关键原则:依赖方向必须从上到下,不能反向。
- 应用层 → 调用 → 中间层
- 中间层 → 调用 → HAL层
- HAL层 → 不依赖任何上层
如果中间层需要通知应用层(比如数据接收完成),怎么办?用回调函数或事件标志,而不是直接调用应用层的函数。
我曾经在一个项目中,看到中间层直接调用了应用层的显示函数。结果后来要换屏幕,应用层改了接口,中间层也得跟着改,连锁反应。记住:下层永远不知道上层的存在。
为了更直观,我画了另一张交互图:
1.5 为什么这套架构能落地?
你可能会问:道理我都懂,但实际项目中真的能这么规整吗?
我的回答是:能,而且必须这么做。
咱们这门课后面的 29 章,每一章都会带着你在 STM32 HAL 库项目里,一步步把这三层架构落地。从 GPIO 驱动、UART 协议栈、I2C 传感器接入,到 FreeRTOS 任务管理、参数存储、状态机设计,全部按这个架构来。
到课程结束时,你会拥有一个可复用、可移植、可测试的嵌入式项目框架。换芯片?换传感器?加功能?改需求?都不怕。
一句话总结这节课:分层架构不是理论空谈,它是解决嵌入式项目“耦合地狱”的实战利器。从裸机到分层,不是炫技,是生存需要。