2、分层架构核心思想:从单体架构到分层架构的演进,经典三层模型(HAL、OSAL、App)介绍

好,咱们今天聊聊分层架构。说实话,我最早入行那会儿,写的嵌入式代码基本都是“一锅端”。一个 main.c 文件,里面 GPIO 初始化、定时器配置、业务逻辑全揉在一起。那时候项目小,芯片资源也紧,觉得这样挺直接。直到有一次,客户要求把产品从 STM32 平台换到 GD32 上……

嗯,那真是一场噩梦。所有硬件相关的代码散落在各个业务逻辑里,我花了整整两周才把移植搞定,还引入了一堆新 bug。从那以后,我彻底成了分层架构的忠实拥趸。

2.1 为什么必须分层?——从“一锅端”到“分而治之”

单体架构,说白了就是所有代码混在一起。硬件操作、系统调度、业务逻辑,全在一个大循环里。你想想看,这种代码有什么问题?

  • 移植成本高:换个 MCU,所有代码都要翻一遍,找哪些地方用了寄存器操作。
  • 测试困难:想单独测业务逻辑?得先把硬件搭好,还得祈祷别烧板子。
  • 团队协作乱:A 工程师改了个 GPIO 定义,B 工程师的业务逻辑莫名其妙就崩了。
  • 复用性差:这个项目的代码,下个项目基本没法直接用,得重写。

分层架构的核心思想,就是“关注点分离”。每一层只关心自己的事,层与层之间通过标准接口通信。这样做的好处,我在项目中体会太深了。

分层架构的三大好处:

  1. 可移植性:换硬件平台,只改最底层,上层纹丝不动。
  2. 可测试性:可以 mock 掉底层,单独验证业务逻辑。
  3. 可维护性:每层代码职责清晰,新人上手快,改 bug 定位准。

2.2 经典三层模型:HAL、OSAL、App

嵌入式领域最经典的分层模型,就是这三层:硬件抽象层(HAL)、操作系统抽象层(OSAL)、应用层(App)。我习惯叫它“三明治结构”。

嵌入式软件三层架构模型 应用层(App) 业务逻辑、状态机、协议栈、用户界面 不依赖具体硬件和操作系统 操作系统抽象层(OSAL) 任务管理、信号量、消息队列、时间管理 屏蔽 RTOS 差异(FreeRTOS / RT-Thread / uCOS) 硬件抽象层(HAL) GPIO、UART、I2C、SPI、ADC、定时器 封装 MCU 寄存器操作,提供统一接口 业务代码 零硬件依赖 系统服务 统一 API 硬件操作 平台相关

2.2.1 硬件抽象层(HAL)

HAL 是最底层,直接跟 MCU 的寄存器打交道。它的任务就是封装所有硬件操作,向上层提供一套统一的、平台无关的接口。

我个人习惯,HAL 层的接口命名都带 HAL_ 前缀,比如 HAL_GPIO_WritePin()、HAL_UART_Transmit()。这样一看就知道是硬件操作,方便代码审查。

我的经验:HAL 层不要做任何业务判断。它就是个“翻译官”,把上层指令翻译成寄存器操作。我曾经见过有人在 HAL 层里加了个超时重试逻辑,结果换芯片后定时器频率变了,整个逻辑全乱套。

来看一个简单的 GPIO 控制接口定义:

/* hal_gpio.h - 硬件抽象层 GPIO 接口 */
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>

/* GPIO 端口枚举,平台无关 */
typedef enum {
    HAL_GPIO_PORT_A,
    HAL_GPIO_PORT_B,
    HAL_GPIO_PORT_C,
    HAL_GPIO_PORT_D
} hal_gpio_port_t;

/* GPIO 引脚号 */
typedef uint8_t hal_gpio_pin_t;

/* GPIO 电平状态 */
typedef enum {
    HAL_GPIO_LOW  = 0,
    HAL_GPIO_HIGH = 1
} hal_gpio_state_t;

/* 初始化指定引脚为输出模式 */
void HAL_GPIO_InitOutput(hal_gpio_port_t port, hal_gpio_pin_t pin);

/* 设置引脚电平 */
void HAL_GPIO_WritePin(hal_gpio_port_t port, hal_gpio_pin_t pin, hal_gpio_state_t state);

/* 读取引脚电平 */
hal_gpio_state_t HAL_GPIO_ReadPin(hal_gpio_port_t port, hal_gpio_pin_t pin);

#endif /* HAL_GPIO_H */

你看,这个头文件里没有任何 STM32 或 GD32 的影子。换平台时,只需要重新实现这个 .c 文件就行。上层代码完全不用动。

2.2.2 操作系统抽象层(OSAL)

OSAL 这一层,很多人容易忽略。觉得“我用 FreeRTOS 就用 FreeRTOS 的 API 呗,干嘛还要包一层?”

我一开始也这么想。直到有一次,项目中期客户要求把 RTOS 从 FreeRTOS 换成 RT-Thread,因为后者有更好的网络协议栈支持。结果呢?所有任务创建、信号量、队列的调用全要改,光改这些就花了一周。

OSAL 的作用,就是封装 RTOS 的系统服务,提供统一的接口。常见的封装对象包括:

系统服务 FreeRTOS API OSAL 统一接口
任务创建 xTaskCreate() OSAL_TaskCreate()
信号量 xSemaphoreCreateBinary() OSAL_SemCreate()
消息队列 xQueueCreate() OSAL_QueueCreate()
延时 vTaskDelay() OSAL_DelayMs()

注意:OSAL 层不要引入任何业务逻辑。它只做“系统调用翻译”。我曾经见过有人在 OSAL 层里加了个任务优先级自动调整的算法,结果换 RTOS 后调度策略不同,系统直接跑飞了。

2.2.3 应用层(App)

应用层是咱们写业务逻辑的地方。状态机、通信协议、数据处理、用户界面,全在这层。它的核心约束是:不直接调用任何 HAL 或 RTOS 的原生 API。

应用层只通过 HAL 和 OSAL 的接口来间接操作硬件和系统服务。这样做的好处,说白了就是“业务代码一次编写,到处编译”。

举个例子,一个简单的 LED 闪烁任务:

/* app_led_task.c - 应用层 LED 控制任务 */
#include "osal.h"
#include "hal_gpio.h"

#define LED_PORT    HAL_GPIO_PORT_B
#define LED_PIN     0
#define BLINK_MS    500

void App_LedTask(void *param)
{
    /* 初始化 LED 引脚(通过 HAL) */
    HAL_GPIO_InitOutput(LED_PORT, LED_PIN);

    while (1)
    {
        HAL_GPIO_WritePin(LED_PORT, LED_PIN, HAL_GPIO_HIGH);
        OSAL_DelayMs(BLINK_MS);

        HAL_GPIO_WritePin(LED_PORT, LED_PIN, HAL_GPIO_LOW);
        OSAL_DelayMs(BLINK_MS);
    }
}

你看,这个文件里没有 HAL_GPIO_WriteBit(ST 风格),也没有 vTaskDelay(FreeRTOS 风格)。它只用了咱们自己定义的 HAL 和 OSAL 接口。换平台?重新编译 HAL 和 OSAL 的 .c 文件就行,App 层代码一个字都不用改。

2.3 分层架构的依赖规则

这里有个关键点,我称之为“单向依赖原则”:

  • App → OSAL → HAL:上层依赖下层,下层绝不能反向依赖上层。
  • 同层之间:尽量通过接口通信,不要直接调用同层其他模块的内部函数。
  • 跨层调用:禁止 App 直接调 HAL,也禁止 HAL 调 App。

我曾经在一个项目里发现,有人为了“省事”,在 HAL 的 UART 中断里直接调了 App 层的协议解析函数。结果换芯片后,UART 中断频率变了,协议解析的时序全乱,查了三天才找到原因。

避坑指南:如果你发现代码里出现了 #include "app_xxx.h" 在 HAL 层的 .c 文件里,那一定是架构设计出了问题。赶紧重构,别等到移植时再后悔。

2.4 什么时候可以“打破”分层?

嗯,我知道有人会问:是不是所有场景都必须严格分层?

我的回答是:规则是死的,项目是活的。在以下场景,可以适当放宽:

  1. 资源极度受限的 MCU(比如 1KB RAM):分层会带来额外的函数调用开销和代码体积。这时候可以适当合并层,但要做好注释,标明“此处为架构妥协”。
  2. 性能敏感的中断服务函数:中断里如果走 HAL → OSAL → App 的完整路径,延迟可能不可接受。可以在 HAL 层提供“快速通道”回调注册机制,但必须用文档严格约束。
  3. 原型验证阶段:快速出 Demo 时,可以先写“平铺代码”。但一定要在进入正式开发前完成分层重构,否则技术债会越滚越大。

说白了,分层架构是工具,不是教条。但作为架构师,你得清楚:每一次打破规则,都是在借未来的债。我个人习惯,在项目启动时就定好分层目录结构,并在代码评审时重点检查依赖方向。养成习惯后,团队协作效率会明显提升。

好,这一章的核心内容就这些。记住:HAL 管硬件、OSAL 管系统、App 管业务。各司其职,移植无忧。