嵌入式架构分层设计低耦合高内聚项目落地实战工程 - 第2节

2. 嵌入式系统基础回顾

做嵌入式开发这些年,我见过太多项目从一个人写死循环开始,最后变成一团乱麻。说白了,架构就是给代码画个边界,让每个人知道自己该待在哪。

今天咱们把嵌入式软件架构的三种主流形态——裸机、RTOS、Linux——放在一起看看。你想想看,这三种架构本质上都是在解决同一个问题:怎么把硬件操作和业务逻辑分开。

裸机架构:最原始,也最直接

裸机架构就是没有操作系统,一个while(1)跑到底。我刚开始做项目时就是这么干的,一个main函数里塞了传感器读取、数据处理、显示刷新……后来加需求加到自己都看不懂。

裸机架构的核心特征:

  • 所有代码在同一个循环中顺序执行
  • 中断服务程序处理紧急事件
  • 没有任务调度,全靠开发者手动安排时序
  • 资源占用极低,适合MCU资源受限的场景

裸机架构的分层其实很薄,通常就两层:硬件抽象层(HAL)和应用层。HAL封装寄存器操作,应用层直接调用HAL接口。嗯,这里要注意,裸机架构最怕的就是中断嵌套和临界区保护,我曾经在一个电机控制项目里因为中断优先级没配好,导致电机在高速运行时突然抖动——排查了整整两天。

RTOS架构:给任务装上调度器

当项目里同时要处理按键、显示、通信、控制时,裸机就有点力不从心了。RTOS(实时操作系统)引入了任务、信号量、消息队列这些概念,说白了就是把CPU时间切成片,分给不同的任务。

特性 裸机 RTOS
任务管理 手动轮询 优先级调度
资源隔离 弱 强(通过互斥量)
实时性 依赖中断 可预测
代码复杂度 低 中
典型场景 简单传感器 工业控制、无人机

我个人习惯在RTOS项目里至少分三层:硬件抽象层(HAL)、操作系统抽象层(OSAL)、应用层(APP)。OSAL这层特别重要,它把RTOS的API再包一层,这样以后换RTOS时,应用层代码基本不用动。

避坑指南:我曾经在一个项目里直接用了FreeRTOS的原生API,后来客户要求换成RT-Thread,结果应用层代码改了一大半。从那以后,我所有RTOS项目都加一层OSAL。

Linux架构:复杂系统的终极选择

当硬件资源足够(通常需要MMU、大容量RAM/Flash),且需要网络、文件系统、多进程时,Linux就登场了。Linux的分层更清晰:内核空间和用户空间完全隔离,驱动在内核态,应用在用户态。

Linux嵌入式架构通常分四层:

  1. 硬件层:芯片、外设、总线
  2. 内核层:驱动、文件系统、网络协议栈
  3. 系统服务层:udev、dbus、systemd
  4. 应用层:业务逻辑、UI、通信协议

你想想看,Linux的优势在于生态——驱动不用自己写,协议栈现成的,调试工具链成熟。但代价是启动时间、实时性、资源占用都比RTOS大。

注意:Linux不适合硬实时场景。如果要求响应时间在微秒级,还是得用RTOS或者裸机+中断。我见过有人硬要在Linux上做电机控制,结果因为调度延迟导致电机过冲——这不是Linux的错,是选型没想清楚。

经典三层架构:HAL、OSAL、APP

不管用裸机、RTOS还是Linux,我个人认为最实用的分层方案就是三层架构。它不复杂,但足够应对大多数嵌入式项目。

嵌入式三层架构示意图 应用层(APP) 业务逻辑 · 状态机 · 协议解析 · 用户交互 操作系统抽象层(OSAL) 任务管理 · 同步互斥 · 内存管理 · 时间服务 硬件抽象层(HAL) GPIO · UART · SPI · I2C · ADC · Timer 依赖方向:APP → OSAL → HAL,上层只能调用相邻下层接口

HAL层:把硬件差异关进笼子里

HAL(Hardware Abstraction Layer)是离硬件最近的一层。它的职责就一个:把芯片厂商提供的寄存器操作封装成统一的接口。

举个例子,同样是点亮一个LED,STM32和GD32的GPIO寄存器地址不一样。但有了HAL,应用层调用的都是HAL_GPIO_WritePin(),底层差异被屏蔽了。

// HAL层接口示例
typedef struct {
    void (*init)(void);
    void (*write)(uint8_t pin, uint8_t level);
    uint8_t (*read)(uint8_t pin);
} GPIO_Driver_t;

// 具体芯片实现
static void stm32_gpio_init(void) {
    // STM32特有的寄存器配置
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    GPIOA->MODER |= (1 << 10); // PA5输出
}

static void gd32_gpio_init(void) {
    // GD32特有的寄存器配置
    RCU->AHBEN |= RCU_AHBEN_GPIOAEN;
    GPIOA->CTL |= (1 << 10); // PA5输出
}

// HAL层统一接口
const GPIO_Driver_t GPIO_Driver = {
    .init = stm32_gpio_init,  // 换芯片时改这里
    .write = gpio_write_pin,
    .read = gpio_read_pin
};

我的经验:HAL层不要做任何业务判断,只做纯粹的硬件操作。我曾经见过有人在HAL层里加延时、做状态判断,结果换芯片时全乱套了。

OSAL层:让应用不依赖具体OS

OSAL(Operating System Abstraction Layer)是三层架构里最容易被忽略、但最值钱的一层。它的作用是把RTOS或Linux的系统调用封装成一套通用接口。

你想想看,如果应用层直接调用xTaskCreate()(FreeRTOS)或pthread_create()(Linux),那换OS时得改多少代码?

功能 FreeRTOS RT-Thread OSAL统一接口
创建任务 xTaskCreate rt_thread_create OSAL_TaskCreate
发送信号量 xSemaphoreGive rt_sem_release OSAL_SemGive
获取消息 xQueueReceive rt_mq_recv OSAL_MsgRecv
延时 vTaskDelay rt_thread_mdelay OSAL_DelayMs
// OSAL层接口示例
// 应用层只调用OSAL接口,不直接调用RTOS API
void app_task(void *param) {
    while(1) {
        // 等待传感器数据
        OSAL_SemTake(sensor_sem, OSAL_WAIT_FOREVER);
        
        // 处理数据
        process_sensor_data();
        
        // 发送结果
        OSAL_MsgSend(result_queue, &data, sizeof(data));
        
        // 周期性执行
        OSAL_DelayMs(100);
    }
}

注意:OSAL层不要做得太厚。只封装常用的系统服务(任务、信号量、消息队列、定时器、内存管理),不要试图把RTOS的所有特性都包一层。我曾经见过一个OSAL层封装了200多个接口,结果维护成本比直接调RTOS还高。

APP层:专注业务逻辑

APP层是离用户最近的一层。它的职责就是实现业务逻辑——按键怎么响应、数据怎么处理、界面怎么显示。APP层不应该知道底层用的是STM32还是GD32,也不应该知道跑的是FreeRTOS还是Linux。

说白了,APP层只调用OSAL和HAL的接口,不直接操作寄存器,不直接调用RTOS API。这样做的最大好处是:

  • 可移植性:换芯片、换OS,APP层代码不用改
  • 可测试性:可以写单元测试,Mock掉底层接口
  • 可维护性:新人接手时,只看APP层就能理解业务

三层架构的核心原则:

  • 上层只能调用相邻下层,不能跨层调用
  • 同层之间通过接口通信,不直接依赖
  • 每一层只做自己的事,不越界