嵌入式软件之分层设计
嵌入式软件分层设计思想
1. 设计目的
解决业务逻辑与硬件实现的强耦合问题,提高代码的可移植性、扩展性和可维护性,采用分层架构设计。核心思想是将“业务(做什么)”与“硬件操作(怎么做)”分离。
2. 架构总览
系统通常分为三层,自上而下分别为:
- 应用层 (Application Layer)
- 中间件/业务抽象层 (Middleware / Abstraction Layer)
- 驱动层 (BSP / Driver Layer)
- 数据流向:硬件 -> 驱动层 -> (回调通知) -> 中间件层 -> (事件/数据) -> 应用层。
- 控制流向:应用层 -> (API调用) -> 中间件层 -> (驱动接口) -> 驱动层 -> 硬件。
3. 各层详解
3.1 应用层 (Application Layer)
-
定位:系统的“大脑”,负责具体的业务逻辑、状态机流转和用户交互(如:UI界面的响应变化、蜂鸣器鸣叫等)。
-
职责:
- 状态管理:维护系统状态(状态机,如:空闲、工作、设置模式)。
- 业务逻辑:根据输入事件决定系统行为(如:收到“报警”事件 -> 触发蜂鸣器逻辑)。
- UI 交互:决定屏幕显示内容。
-
原则:
- 不直接访问硬件:严禁直接操作寄存器或调用底层驱动函数(如
HAL_GPIO_WritePin,这是HAL库对硬件直接使用,应用层永远不会调用如HAL库函数)。 - 事件驱动:通过轮询或阻塞方式获取“业务事件”来驱动程序运行。
- 不直接访问硬件:严禁直接操作寄存器或调用底层驱动函数(如
-
代码特征:
// 伪代码示例:二段式状态机 void App_Main_Loop() { Event_t evt; while (1) { // -状态管理-:输入处理 (负责状态切换) while (Get_Event(&evt)) { switch (current_state) { case STATE_IDLE: if (evt.type == EVENT_START) current_state = STATE_WORK; break; case STATE_WORK: if (evt.type == EVENT_STOP) current_state = STATE_IDLE; break; } } // 第二阶段:状态执行 (负责持续性任务,包含-业务逻辑- -UI 交互-等) switch (current_state) { case STATE_IDLE: // IDLE状态业务 break; case STATE_WORK: // STATE_WORK状态业务 break; } } }
3.2 中间件/业务抽象层 (Middleware Layer)
-
定位:负责承上启下。
-
职责:
- 向下(适配):实现驱动层的回调接口,接收原始数据,处理数据将其转化为事件。
- 向上(抽象):提供统一的业务接口(如 控制流:
Get_Event, 数据流:Get_Sensor_Value)。 - 数据清洗:将物理信号(如“引脚低电平”)转换为业务含义(如“按键按下”);将原始字节流(如“串口收到0x01”)转换为业务指令(如“开启设备”)。
-
关键机制:
- 事件队列(可选):缓冲输入事件,解耦中断上下文与主循环上下文。
- 数据结构抽象:定义与硬件无关的数据结构(如
struct SensorData),屏蔽底层数据格式差异。
-
代码特征:
// [中间件层] 接收驱动回调,转换为事件 void Driver_Callback_Impl(void *data) { //回调函数 Event_t evt; evt.type = Parse_Data_To_Event(data); // 数据清洗:将原始数据转为业务事件 Event_Queue_Push(evt); // 推入队列 } // [中间件层] 提供给应用层的接口 bool Get_Event(Event_t *evt) { return Event_Queue_Pop(evt); }- 另附:事件队列 (Ring Buffer)
为了解耦 中断 和 主循环 ,通常使用环形队列。
#define QUEUE_SIZE 10 typedef struct { Event_t buffer[QUEUE_SIZE]; volatile uint8_t head; // 写入位置 volatile uint8_t tail; // 读取位置 } EventQueue_t; static EventQueue_t queue; // 入队 (通常在中断/回调中调用 - 生产者) void Event_Queue_Push(Event_t evt) { uint8_t next = (queue.head + 1) % QUEUE_SIZE; if (next != queue.tail) { // 防止溢出覆盖 queue.buffer[queue.head] = evt; queue.head = next; } // else: 队列满,丢弃事件或报错 } // 出队 (在主循环调用 - 消费者) bool Event_Queue_Pop(Event_t *evt) { if (queue.head == queue.tail) return false; // 队列为空 *evt = queue.buffer[queue.tail]; queue.tail = (queue.tail + 1) % QUEUE_SIZE; return true; }
- 另附:事件队列 (Ring Buffer)
3.3 驱动层 (BSP / Driver Layer)
- 定位:系统的“手脚”,负责直接操作硬件。
- 职责:
- 硬件控制:初始化外设(GPIO, UART, I2C等,在STM32使用CubeMX时,这写通常由CubeMX完成。但硬件模块初始化仍是需要用户完成的),提供基础读写接口。
- 机制提供者:定义回调函数类型,并在中断或轮询中调用回调,通知上层。
- 原则:
- 无业务逻辑:驱动层只管“收发数据”,不管“数据是什么意思”。例如,串口驱动只负责把字节存入缓冲区,不负责解析字符串命令。
- 独立性:驱动文件应可直接复用到其他项目,不包含任何特定项目的头文件。
- 代码特征:
// [驱动层] 只负责产生原始数据/信号,不包含业务逻辑 void Hardware_IRQ_Handler(void) { // 1. 读取硬件状态 uint8_t raw_data = Read_Register(); // 2. 调用回调通知上层 (如果有注册) if (g_Callback_Func) { g_Callback_Func(raw_data); } // 注意:这里不进行 if(raw_data == 0x01) { Open_Door(); } 这样的业务判断 }
4. 核心交互机制
4.1 回调机制 (Callback)
为了让驱动层能通知中间件层,通常使用回调机制。
-
方式一:函数指针注册(推荐)
适用于需要灵活配置或多实例的场景。- 驱动层:定义函数指针类型,提供注册函数
Driver_Register_Callback。 - 中间件层:定义具体的处理函数,并在初始化时注册给驱动。
// [驱动层] 通用定义 typedef void (*EventHandler_t)(void *data); void Driver_Register_Callback(Device_Handle *dev, EventHandler_t cb); // [中间件层] 注册 void Middleware_Init() { Driver_Register_Callback(&my_device, Middleware_Process_Func); } - 驱动层:定义函数指针类型,提供注册函数
-
方式二:弱函数 (Weak Function)
适用于全局唯一的硬件资源,代码实现简单。- 驱动层:定义
__weak void HAL_Driver_Callback(void) {},并在中断中调用。 - 中间件层:重写该函数
void HAL_Driver_Callback(void) { ... },编译器会自动链接此版本。
- 驱动层:定义
4.2 面向对象的驱动设计
对于同类硬件(如多个按键、多个传感器),建议使用结构体句柄(Handle)来管理对象,实现“类”的概念。
-
设计思想:
- 定义一个结构体
Device_Handle,包含该设备的属性(ID、GPIO端口)和操作方法(函数指针)。 - 驱动函数只针对
Handle操作,而不是针对具体硬件。
// ========================================== // [驱动层] 通用驱动实现 (Driver Layer) // ========================================== // 定义通用设备句柄 typedef struct Device_Handle { uint8_t id; // 设备ID void *user_data; // 用户私有数据 (如 GPIO 端口号) // 虚函数:底层操作接口,由中间件层实现并注入 // 驱动层只管调用,不知道具体怎么读 IO uint8_t (*ReadState)(struct Device_Handle *handle); // 回调函数:事件触发时通知中间件层 void (*EventCallback)(struct Device_Handle *handle, uint8_t event); } Device_Handle; // 驱动核心逻辑 (抽象的 Tick 函数) // 这里的逻辑是通用的:只要 ReadState 返回有效,就触发回调 void Device_Ticks(Device_Handle *handle) { // 纯逻辑判断,不涉及具体硬件读写 if (handle->ReadState(handle) == 1) { // 假设 1 为激活状态 if (handle->EventCallback) { handle->EventCallback(handle, 0x01); // 触发通用事件 } } } // ========================================== // [中间件层] 具体实现与绑定 (Middleware Layer) // ========================================== // 1. 实现底层读写接口 (适配层) // 这里才真正调用 HAL 库 uint8_t My_Read_GPIO_Impl(Device_Handle *handle) { // 从 user_data 中取出 GPIO 端口信息 GPIO_TypeDef *port = (GPIO_TypeDef*)handle->user_data; return HAL_GPIO_ReadPin(port, GPIO_PIN_0); } // 2. 实现事件回调 (业务层) // 这里将驱动层的通用事件转换为业务事件 void My_Event_Handler_Impl(Device_Handle *handle, uint8_t event) { Event_t evt; evt.id = handle->id; evt.type = EVENT_BUTTON_CLICK; // 转换业务含义 Event_Queue_Push(evt); // 推入队列 } // 3. 初始化与绑定 void Middleware_Init() { static Device_Handle my_dev; my_dev.id = 1; my_dev.user_data = GPIOA; // 传入具体硬件参数 my_dev.ReadState = My_Read_GPIO_Impl; // 绑定读写实现 my_dev.EventCallback = My_Event_Handler_Impl; // 绑定事件回调 // 将句柄注册到驱动层的链表或数组中 Device_Register(&my_dev); } - 定义一个结构体
5. 总结
分层设计的核心在于依赖倒置和接口隔离。
- 应用层依赖中间件层提供的业务接口。
- 中间件层依赖驱动层提供的硬件接口,并通过回调响应硬件中断。
- 驱动层不依赖任何上层逻辑,保持纯净。
通过这种方式,当更换硬件平台时,只需重写驱动层;当变更业务逻辑时,只需修改应用层。

浙公网安备 33010602011771号