1、架构思维启蒙:为什么需要分层?从面包板到工业级代码的进化之路。
大家好,我是你们的嵌入式架构课讲师。
今天咱们聊点实在的。很多刚入行的朋友问我:“为什么我的代码跑着跑着就崩了?”或者“明明功能都实现了,老板却说我代码写得像坨屎?”
嗯,这背后其实就一个问题——你还没建立起架构思维。
说白了,写MCU代码和盖房子是一个道理。你见过谁家盖楼直接往地上堆砖头的?没有吧。但很多嵌入式工程师,就是这么干的。
1.1 面包板时代的“野蛮生长”
我记得刚入行那会儿,带我的老工程师扔给我一块STM32开发板,说:“把LED点亮,然后做个呼吸灯效果。”
我当时怎么写的?
void main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
while(1)
{
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
HAL_Delay(500);
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
HAL_Delay(500);
}
}
你看,所有代码都堆在main函数里。功能简单的时候,这没问题。但项目一复杂,就完蛋了。
我在一个项目中遇到过这种情况:一个同事写了3000行的main函数,里面塞了按键扫描、LCD显示、传感器读取、电机控制……
后来要加个新功能,他找了半天不知道从哪里下手。改了一行代码,结果LED不亮了。为什么?因为全局变量被意外修改了。
1.2 为什么需要分层?
你想想看,一个复杂的嵌入式系统,通常包含哪些东西?
- 硬件层:GPIO、定时器、UART、SPI、I2C……
- 驱动层:操作这些外设的代码
- 中间件层:RTOS、文件系统、协议栈
- 应用层:业务逻辑、用户交互
如果不分层,这些代码会混在一起。就像把衣服、袜子、鞋子全塞进一个洗衣机里洗——最后拿出来,什么颜色都串了。
分层的核心目的就三个:
- 解耦:硬件变了,应用代码不用改。我换了个MCU,驱动层重写,上层逻辑直接复用。
- 可读性:别人看你的代码,一眼就知道哪部分是干什么的。
- 可维护性:修bug、加功能,只改对应层,不会牵一发动全身。
1.3 从“面包板”到“工业级”的进化
咱们来看看,一个典型的工业级嵌入式软件,是怎么分层的。
| 层级 | 职责 | 典型文件 |
|---|---|---|
| 应用层 | 业务逻辑、状态机、用户交互 | app_main.c, app_task.c |
| 中间件层 | RTOS、文件系统、通信协议 | FreeRTOS, lwIP, FatFS |
| 硬件抽象层(HAL) | 封装MCU外设操作,提供统一接口 | hal_gpio.c, hal_uart.c |
| 驱动层 | 直接操作寄存器,最底层代码 | stm32f4xx_gpio.c |
你看,每一层都有明确的边界。上层调用下层,下层为上层服务。但注意——上层不能越级调用下层。
举个例子:应用层想读一个传感器数据。正确的做法是:
- 应用层调用中间件层的传感器驱动接口
- 传感器驱动调用HAL层的I2C接口
- HAL层调用驱动层的寄存器操作
如果应用层直接去操作I2C寄存器,那分层就形同虚设了。
1.4 一个简单的分层示例
咱们用LED闪烁这个例子,看看分层怎么写。
驱动层(driver_led.c):
#include "stm32f4xx.h"
void LED_Init(void)
{
RCC->AHB1ENR |= (1 << 3); // 使能GPIOD时钟
GPIOD->MODER |= (1 << 24); // 设置PD12为输出
}
void LED_On(void)
{
GPIOD->BSRR = (1 << 12);
}
void LED_Off(void)
{
GPIOD->BSRR = (1 << 28);
}
硬件抽象层(hal_led.c):
#include "hal_led.h"
#include "driver_led.h"
void HAL_LED_Init(void)
{
LED_Init();
}
void HAL_LED_SetState(uint8_t state)
{
if(state)
LED_On();
else
LED_Off();
}
应用层(app_main.c):
#include "hal_led.h"
void App_Task(void)
{
HAL_LED_Init();
while(1)
{
HAL_LED_SetState(1);
DelayMs(500);
HAL_LED_SetState(0);
DelayMs(500);
}
}
你看,应用层根本不知道底层用的是哪个MCU,也不知道LED接在哪个GPIO上。它只知道调用HAL_LED_SetState()。
如果哪天要换MCU,只需要重写驱动层和HAL层,应用层代码一行都不用改。
1.5 什么时候可以不分层?
说实话,也不是所有项目都需要严格分层。
- 一次性原型验证:功能验证完就扔了,不分层也没关系
- 代码量小于500行:一个人维护,逻辑简单,不分层也能跑
- 硬件永远不会变:比如一个固定型号的遥控器,量产10年不改
但话说回来,养成好习惯比什么都重要。我见过太多人,一开始图省事不分层,结果项目越做越大,最后代码根本没法维护,只能推倒重来。
嗯,这一章咱们先聊到这儿。下一章,我会带大家手把手搭建一个真正的分层架构,从文件结构到接口定义,一步步来。
记住:架构思维,是区分“码农”和“工程师”的分水岭。
公众号:蓝海资料掘金营,微信deep3321