1. 嵌入式软件架构概述:分层架构思想、嵌入式软件分层模型、为什么需要中间件层

各位同学,咱们今天聊聊嵌入式软件架构。说实话,我见过太多工程师一上来就写代码,写到后面发现改不动、测不了、复用更是天方夜谭。为什么会这样?说白了,就是缺少一个清晰的架构分层。

我个人习惯,在开始任何一个嵌入式项目之前,先画一张分层图。哪怕只有三层,也得画。这就像盖房子,你得先有结构,再往里填砖。今天这章,我们就来把这张图讲透。

1.1 分层架构思想:把复杂问题拆开

分层架构不是什么新鲜概念。计算机网络的OSI七层模型,操作系统的内核态/用户态,都是分层思想。嵌入式系统也一样——把软件按职责切分成若干层,每层只干自己的事,层与层之间通过接口通信。

这样做的好处很明显:

  • 隔离变化:硬件换了,只改底层;业务逻辑变了,只改上层。
  • 便于测试:每一层可以单独验证,不用等所有硬件就绪。
  • 提升复用:写好的中间件,换个项目还能用,不用重写。

我在项目中遇到过最典型的反面案例:一个同事把所有代码写在一个文件里,GPIO初始化、PID算法、WiFi通信、UI显示全混在一起。后来要换MCU,整个项目几乎重写。嗯,这就是不分层的代价。

1.2 嵌入式软件分层模型

嵌入式软件通常分为四层。我画了一张图,你一看就明白:

嵌入式软件分层架构模型 应用层 业务逻辑、用户界面、控制策略 中间件层 协议栈、文件系统、算法库、任务调度 OSAL层 操作系统抽象层:任务、信号量、队列、时间管理 HAL层 硬件抽象层:GPIO、UART、I²C、SPI、定时器 依赖方向:上层依赖下层

从上往下看:

层级 职责 典型内容
应用层 实现产品业务逻辑 控制算法、UI界面、数据采集策略
中间件层 提供通用功能模块 TCP/IP协议栈、FATFS文件系统、任务调度器
OSAL层 屏蔽不同OS的差异 任务创建、信号量、消息队列、时间管理
HAL层 封装MCU外设寄存器 GPIO读写、UART收发、I²C时序

1.3 为什么需要中间件层?

你可能会问:有了HAL和OSAL,为什么还要单独搞一个中间件层?

我举个例子。你做一个智能家居网关,需要WiFi通信、MQTT协议、JSON解析、OTA升级。这些功能跟你的业务逻辑(比如“温度超过30度开空调”)没有直接关系,但它们又是系统不可或缺的部分。

如果你把这些代码直接写在应用层,应用层会变得臃肿不堪。如果你把它们塞进OSAL层,那OSAL就不再是“操作系统抽象”,而变成了一个大杂烩。

中间件层的作用,就是给这些“通用但非OS、非硬件”的功能一个家。

我的经验:中间件层是嵌入式项目复用率最高的部分。我手上有几个中间件模块——环形缓冲区、软件定时器、简易状态机——已经用了七八个项目,每次移植只需要改OSAL接口,核心代码一行不动。

1.4 中间件层的典型职责

中间件层到底管哪些事?我列一个清单:

  • 通信协议栈:TCP/IP、MQTT、CoAP、HTTP Client
  • 数据管理:文件系统(FATFS/LittleFS)、KV存储、环形缓冲区
  • 算法库:PID控制、FFT、CRC校验、加密算法
  • 任务调度:协程调度器、时间片轮转、事件驱动框架
  • 设备抽象:传感器驱动框架、显示驱动框架
  • 诊断与维护:日志系统、Shell命令行、看门狗管理

你看,这些功能有一个共同点:它们不直接操作硬件寄存器,也不依赖特定操作系统的API。它们通过OSAL层获取任务、信号量、时间等服务,通过HAL层间接访问硬件。

避坑指南:我曾经在一个项目里,把WiFi驱动直接写在了中间件层。结果换了一款WiFi芯片,整个中间件层都要改。正确的做法是:WiFi驱动放在HAL层,中间件层只放TCP/IP协议栈。驱动换了,协议栈不受影响。

1.5 一个简单的分层示例

光说不练假把式。我们看一段代码,感受一下分层的好处。

假设我们要通过UART发送一条日志。不分层的写法:

// 不分层:直接操作寄存器
void log_send(const char *msg) {
    while (*msg) {
        while (!(UART0->SR & (1 << 7)));  // 等待发送就绪
        UART0->DR = *msg++;                 // 写数据寄存器
    }
}

分层的写法:

// HAL层:封装UART发送
void hal_uart_send(uint8_t data) {
    while (!(UART0->SR & (1 << 7)));
    UART0->DR = data;
}

// OSAL层:提供互斥保护
void osal_mutex_take(mutex_t *m);
void osal_mutex_give(mutex_t *m);

// 中间件层:日志模块
void log_send(const char *msg) {
    osal_mutex_take(&log_mutex);
    while (*msg) {
        hal_uart_send(*msg++);
    }
    osal_mutex_give(&log_mutex);
}

// 应用层:调用日志
void app_task(void *arg) {
    log_send("System started.");
}

看出区别了吗?

  • 如果换MCU,只需要改hal_uart_send,日志模块不用动。
  • 如果换RTOS,只需要改osal_mutex_take/give,日志模块不用动。
  • 如果日志要加时间戳,在中间件层改,应用层不用动。

这就是分层的力量。

1.6 本章小结

嵌入式软件分层不是理论空谈,它是被无数项目验证过的工程实践。HAL层管硬件差异,OSAL层管操作系统差异,中间件层管通用功能,应用层管业务逻辑。每一层各司其职,层间通过接口解耦。

中间件层尤其重要——它是项目复用率的保障,也是架构灵活性的体现。你想想看,如果每个项目都要重写TCP协议栈、重写文件系统、重写日志模块,那得浪费多少时间?