嵌入式软件分层

软件分层的核心目的是降低开发者的心智负担。当我们面对一个复杂系统时,不需要同时思考从硬件寄存器到业务逻辑的所有细节,而是聚焦于当前层的职责,信任下层提供的抽象。

分层带来的价值:

  • 可替换性:更换硬件平台时,只需修改驱动层
  • 可复用性:协议层代码可以在不同项目间复用
  • 可测试性:每层可以独立进行单元测试
  • 协作开发:不同开发者可以并行开发不同层

常见的软件分层

AUTOSAR

AUTOSAR(汽车开放系统架构)是汽车电子领域的工业标准,提供了完整的分层规范:

┌────────────────────────────────────┐
│  Application Layer (SWC)           │  应用软件组件
├────────────────────────────────────┤
│  Runtime Environment (RTE)         │  运行时环境
├────────────────────────────────────┤
│  Services Layer (BSW)              │  基础软件服务层
├────────────────────────────────────┤
│  ECU Abstraction Layer             │  ECU 抽象层
├────────────────────────────────────┤
│  MCAL                              │  微控制器抽象层
├────────────────────────────────────┤
│  Hardware                          │  硬件
└────────────────────────────────────┘

AUTOSAR 的优势在于标准化程度高,有完整的规范文档和工具链支持。但对于中小型嵌入式项目来说过于复杂。

OSI 七层模型

OSI 模型是网络通信的经典参照,专注于数据在网络中的传输:

┌──────────────────┐
│  应用层          │  HTTP, FTP, MQTT
├──────────────────┤
│  表示层          │  数据格式转换、加密
├──────────────────┤
│  会话层          │  连接管理
├──────────────────┤
│  传输层          │  TCP, UDP
├──────────────────┤
│  网络层          │  IP 路由
├──────────────────┤
│  数据链路层      │  帧封装、MAC
├──────────────────┤
│  物理层          │  比特流传输
└──────────────────┘

OSI 模型的价值在于清晰定义了每层的职责边界,但它只覆盖"通信"这一个维度,不涉及业务逻辑和系统服务。

面向业务数据流的六层模型

结合 AUTOSAR 的分层思想和 OSI 的通信模型,针对中小型嵌入式项目提出简化的六层架构。核心关注点是业务数据如何从硬件一层层流向应用

分层

┌─────────────────────────────────────────────────────────────────┐
│ L6 应用层   │ 状态机 │ 业务逻辑 │ UI 交互                       │
├─────────────────────────────────────────────────────────────────┤
│ L5 服务层   │ 日志 │ 配置存储 │ 定时任务 │ Shell               │
├─────────────────────────────────────────────────────────────────┤
│ L4 协议层   │ Modbus │ 私有协议 │ Protobuf │ JSON              │
├─────────────────────────────────────────────────────────────────┤
│ L3 传输层   │ TCP/UDP │ 可靠传输 │ 分包重组                     │
├─────────────────────────────────────────────────────────────────┤
│ L2 链路层   │ UART帧 │ CAN帧 │ 以太网帧                         │
├─────────────────────────────────────────────────────────────────┤
│ L1 驱动层   │ HAL │ 外设驱动 │ DMA │ 中断                        │
└─────────────────────────────────────────────────────────────────┘

各层详解

L1 驱动层 (Driver/HAL)

职责:屏蔽硬件差异,提供统一的外设操作接口

向上提供:字节级别的读写能力

经典参照 项目 特点
STM32 HAL ST 官方库,接口统一,易于移植
CMSIS-Driver ARM 标准驱动接口规范
RT-Thread Device 统一设备框架 rt_device_*
ESP-IDF Driver 乐鑫官方驱动层

L2 链路层 (Link)

职责:在字节流上建立帧结构,处理帧定界、校验、转义

从下层获取:连续的字节流 向上提供:完整的、经过校验的数据帧

经典参照 协议 帧格式 适用场景
HDLC 标志位 + 转义 + CRC 通用串口通信
CAN 帧 硬件级帧结构 CAN 总线通信

L3 传输层 (Transport)

职责:提供可靠传输、分包重组、流量控制

从下层获取:单个数据帧(可能丢失、乱序) 向上提供:可靠的、有序的数据流

经典参照 协议 特点 适用场景
TCP 完整可靠传输 网络通信
Xmodem 停等协议 串口文件传输
CAN-TP (ISO 15765) CAN 分包传输 汽车诊断
自定义 ACK 机制 简单重传 资源受限场景
Modbus 寄存器读写 工业控制

L4 协议层 (Protocol)

职责:定义数据语义、命令格式、业务报文结构

从下层获取:可靠的字节流 向上提供:结构化的命令和数据

经典参照 协议 模式 适用场景
Protobuf 二进制序列化 高效数据交换
JSON 文本格式 调试友好
私有 TLV Tag-Length-Value 自定义协议

L5 服务层 (Service)

职责:提供系统级公共服务,与具体业务逻辑解耦

特点:服务层是"横向"的,为所有其他层提供支撑

典型服务 服务 经典实现 说明
日志 EasyLogger, RTT 调试和追踪
配置存储 NVS, LittleFS 参数持久化
定时任务 软件定时器 周期性任务调度
Shell letter-shell, finsh 命令行交互
OTA MCUboot 固件升级

L6 应用层 (Application)

职责:实现具体的业务逻辑

从下层获取:结构化的命令和数据 输出:业务动作、状态变更、响应数据

经典模式 模式 说明 适用场景
状态机 有限状态自动机 设备控制流程
事件驱动 消息队列 + 事件处理 异步响应场景
命令模式 命令表 + 处理函数 请求-响应场景

各层软件设计复用

实现层间灵活配置的关键在于选择合适的工具和框架。以下按配置灵活度分类整理。

L1 驱动层配置工具

工具 类型 说明
STM32CubeMX 图形化配置 生成 HAL 初始化代码,引脚/时钟/外设可视化配置
MCUXpresso Config 图形化配置 NXP 官方,类似 CubeMX

L2-L3 通信栈

工具/库 覆盖层级 配置方式 说明
lwIP L2-L4 lwipopts.h 宏配置 轻量 TCP/IP 栈,可深度裁剪
ringbuffer L2 代码抽象 环形缓冲区,驱动与链路层之间的数据缓存
kfifo (Linux) L2 代码抽象 内核无锁环形队列,可移植到裸机
lwrb L2 代码抽象 轻量级 ring buffer,支持 DMA

L4 协议层工具

工具 类型 配置方式 说明
nanopb Protobuf .proto → C 代码 嵌入式 Protobuf,编译时生成
cJSON / jsmn JSON 代码抽象 轻量 JSON 解析器
MessagePack 二进制序列化 代码抽象 比 JSON 更紧凑
FreeModbus Modbus 编译时配置 嵌入式 Modbus 从站

L5 服务层组件

组件 服务类型 配置方式 说明
EasyLogger 日志 宏配置 轻量,支持多后端
Segger RTT 日志 编译时 高速调试通道
LittleFS 文件系统 编译时配置 掉电安全,磨损均衡
SPIFFS 文件系统 编译时配置 SPI Flash 专用
NVS (ESP-IDF) 键值存储 运行时 参数持久化
letter-shell Shell 宏配置 轻量命令行
finsh (RT-Thread) Shell 运行时 支持 C 表达式

L6 应用层框架

框架 类型 配置方式 说明
QP/C 状态机 代码生成 (QM) 层次状态机,活动对象模式
stateMachine 状态机 代码抽象 轻量有限状态机,表驱动
libevent 事件管理 运行时 事件循环,回调驱动
FreeRTOS 事件组 事件管理 运行时 RTOS 内置事件标志
命令分发表 命令模式 代码抽象 cmd_id → handler 映射表

灵活度等级

每层的实现可以在不同灵活度之间选择,根据项目需求做取舍:

最灵活 ◄──────────────────────────────────────────► 最精简

动态配置         静态可配置        代码抽象          硬编码
(运行时切换)     (编译时选择)      (回调/函数指针)   (直接调用)
    │                │                 │               │
    ▼                ▼                 ▼               ▼
外部指令配置     menuconfig        接口结构体        内联代码

选择依据:

  • 资源受限 → 倾向硬编码,减少间接调用开销
  • 需要灵活 → 倾向动态配置,支持运行时调整
  • 团队协作 → 倾向代码抽象,接口清晰便于分工

发表评论