大家好,我是杂烩君。最近后台好多朋友催更呀~
有人说 “能不能聊聊 STM32、GD32 这些常用单片机的代码分层设计?写项目时总乱糟糟的”!
这不就安排上了~ 今天咱们就聚焦 “通用嵌入式软件架构分层” 这个实用技巧,聊聊怎么把项目代码分层得明明白白的、维护起来不头疼~
1. Arch-Platform-Target三层抽象
嵌入式系统中的 Arch-Platform-Target 三层抽象是一种常用的软件架构设计模式,用于提高代码的可移植性和可维护性。
1.1 Arch层(架构支持层)
Arch架构支持层是最底层,与硬件直接相关。它包含了针对特定处理器架构的代码,例如ARM、MIPS等。这一层通常包括中断处理、上下文切换、内存管理单元(MMU)配置、缓存控制等。Arch层为上层提供了统一的硬件抽象接口。
主要职责:
1.2 Platform层(平台抽象层)
Platform平台抽象层位于Arch层之上,Target层之下。通过Arch层提供的接口来访问硬件,同时为Target层提供统一的平台服务接口。
主要职责:
- 硬件抽象和驱动封装提供统一的硬件访问接口屏蔽底层硬件差异
1.3 Target层(目标应用层)
Target目标应用层属于上层,实现具体的业务逻辑和功能。Target层通过Platform层提供的服务来访问硬件,因此当硬件平台改变时,只需要修改Platform层和Arch层,
而Target层的代码可以保持相对不变。
2. 实际项目常见分层思想
Arch-Platform-Target是个核心分层思想,实际项目中,可能还包含OSAL(系统抽象层)、Services(基础组件服务)等模块。
嵌入式小型项目(单一 RTOS、少量外设):Platform 下可能直接包含OSAL、Services,目录简单、上手快。如:
嵌入式中大型项目(可能换 RTOS/芯片):OSAL 、Services独立于 Platform,同属于中间层。Platform 专注板级与外设封装,OSAL 专注 RTOS API 抽象,Services专注于各种基础组件及中间件的管理。如:
进一步放大细分:
Arch:启动、异常、时基。
BSP/Platform:板级时钟、PinMux、外设驱动抽象。
OSAL:任务/同步/队列/内存适配。
Services/Middleware:log、cli/shell、kv/存储、文件系统、网络协议栈、OTA、安全等。
Target/App:业务域。
可选:HAL(MCU 厂商层)与 Driver Framework(如 device tree/board cfg)单独放,防止业务碰到寄存器。
3. STM32+RTOS项目的分层设计案例
下面结合 STM32 + RTOS 给出一个体现 Arch-Platform-Target 的职责分离的目录规划和代码示例:
3.1 工程目录设计
stm32_project/
├── arch/ # CPU/架构相关
│ └── arm/cortex-m0/
│ ├── startup_gcc.s # 启动与向量表
│ ├── system_stm32f0xx.c # 时钟/系统初始化
│ └── arch_port.c # SysTick、临界段封装
├── platform/ # 平台/Board 支持
│ └── stm32f072/
│ ├── bsp_clock.c
│ ├── bsp_gpio.c
│ ├── bsp_uart.c
│ └── platform_init.c # 统一平台初始化入口
├── osal/ # OS 抽象层(屏蔽不同 RTOS)
│ ├── osal.h # 统一任务/互斥/队列接口
│ ├── osal_freertos.c # FreeRTOS 适配实现
│ └── osal_port.h # 基础类型、错误码
├── services/ # 常用系统组件
│ ├── log/
│ └── kv/
├── external/ # 第三方库(协议栈/文件系统/安全等)
│ ├── lwip/
│ ├── mbedtls/
│ └── littlefs/
├── target/ # 业务/应用
│ └── app/
│ ├── main.c # 任务创建、启动调度
│ └── app_led.c # 具体业务
├── freertos/ # FreeRTOS 内核与移植
│ ├── CMSIS/ # 官方 CMSIS 头文件
│ ├── portable/GCC/ARM_CM0/ # FreeRTOS Cortex-M0 移植层
│ └── FreeRTOSConfig.h
└── drivers/ # MCU HAL 库
└── stm32f0xx_hal/
分层约束:
- Arch 仅处理与核心架构相关的启动、时钟、异常向量、SysTick 驱动,不直接操作业务外设。Platform 负责芯片外设封装(GPIO、UART、I2C 等)和板级资源命名,向 Target 暴露统一 API。Target 只依赖 Platform 提供的接口做业务,不直接引用 HAL/寄存器。
3.2 关键代码示例
Arch 层:SysTick 驱动 FreeRTOS 时基
arch/arm/cortex-m0/arch_port.c
Arch 层只负责把内核时钟和中断接好,具体任务调度逻辑由 FreeRTOS 内核完成。更换 RTOS 时,Arch 层需要少量调整。
Platform 层:封装 LED
platform/stm32f072_nucleo/bsp_gpio.c
Platform 层对外暴露 platform_led_*等统一接口,Target 层不感知 HAL 细节。
Target 层:创建任务并调用平台接口
target/app/app_led.c
target/app/main.c
Target 层只依赖 Platform 的初始化与业务 API,后续如果换成 GD32 或更换板载外设,仅需改动 Platform 与 Arch,不影响业务代码。
OSAL 层:统一 RTOS 抽象
osal/osal.h
osal/osal_freertos.c(适配 FreeRTOS)
#include"osal.h"
#include"FreeRTOS.h"
#include"task.h"
#include"queue.h"
#include"semphr.h"
intosal_thread_create(osal_thread_t *t, constchar *name,
osal_thread_entry_t entry, void *arg,
uint16_t stack_words, uint8_t priority)
{
if (xTaskCreate(entry, name, stack_words, arg, priority, (TaskHandle_t *)t) != pdPASS)
return OSAL_ERR_FAIL;
return OSAL_OK;
}
voidosal_thread_delay_ms(uint32_t ms)
{
vTaskDelay(pdMS_TO_TICKS(ms));
}
voidosal_start_scheduler(void)
{
vTaskStartScheduler();
}
intosal_mutex_create(osal_mutex_t *m)
{
*m = xSemaphoreCreateMutex();
return *m ? OSAL_OK : OSAL_ERR_FAIL;
}
intosal_mutex_lock(osal_mutex_t m, uint32_t timeout_ms)
{
return xSemaphoreTake((SemaphoreHandle_t)m, pdMS_TO_TICKS(timeout_ms)) == pdTRUE ? OSAL_OK : OSAL_ERR_TIMEOUT;
}
voidosal_mutex_unlock(osal_mutex_t m)
{
xSemaphoreGive((SemaphoreHandle_t)m);
}
intosal_queue_create(osal_queue_t *q, uint16_t item_size, uint16_t len)
{
*q = xQueueCreate(len, item_size);
return *q ? OSAL_OK : OSAL_ERR_FAIL;
}
intosal_queue_send(osal_queue_t q, constvoid *item, uint32_t timeout_ms)
{
return xQueueSend(q, item, pdMS_TO_TICKS(timeout_ms)) == pdTRUE ? OSAL_OK : OSAL_ERR_TIMEOUT;
}
intosal_queue_recv(osal_queue_t q, void *item, uint32_t timeout_ms)
{
return xQueueReceive(q, item, pdMS_TO_TICKS(timeout_ms)) == pdTRUE ? OSAL_OK : OSAL_ERR_TIMEOUT;
}
OSAL 封装线程、延时、互斥、队列,并对外暴露统一的错误码(在 osal_port.h 中定义 OSAL_OK/OSAL_ERR_FAIL/OSAL_ERR_TIMEOUT)。若切换到 RT-Thread 或 Zephyr,仅需新增对应 osal_xxx.c。
切换 RTOS的要点:
-
- 新增适配文件:
osal/osal_xxx.c
-
- ,实现与
osal.h
-
- 一致的 API。调整启动与时基:
Arch
-
- 层改为调用新RTOS的启动入口,并按新RTOS要求设置 SysTick/中断优先级;移除
vTaskStartScheduler
-
- 相关逻辑。配置与链接:替换 RTOS 源码与配置文件(如移除 FreeRTOS 源,加入新RTOS源),在构建脚本中切换编译宏(例如
-DUSE_RTTHREAD
- )。检查栈/优先级语义:如果不同RTOS优先级数值方向不同,适配时需在 OSAL 内部转换,保证业务侧传入的“逻辑优先级”保持一致。队列/超时语义:确认阻塞超时单位(ms 或 tick),在 OSAL 内部做统一换算,避免业务层被不同 RTOS 语义影响。
4. 总结
嵌入式软件中,合理的分层设计可以提升可移植、可维护、可测试性。可以用 Arch/Platform/OSAL/Services/Target 分层隔离硬件差异、RTOS 差异与业务逻辑。
分层带来的实际收益:
- 可移植:更换 MCU(F0 -> F4 或 GD32)时,Target 基本不变,只需更新 Arch/Platform。可维护:外设封装集中在 Platform,避免业务层散落 HAL 调用。可测试:Platform API 可在仿真或 PC Mock 中替换实现,便于单元测试业务逻辑。
4.1 QA
-
- Q:OSAL 是必须的吗?
-
- A:若项目只用一种 RTOS,可以不加 OSAL;但预期会换 RTOS(如 FreeRTOS ⇄ RT-Thread),建议在早期就放 OSAL,后续只需替换适配文件,业务无需改动。
-
- Q:Platform 层和驱动 HAL 的区别?
-
- A:HAL 是芯片厂的寄存器封装;Platform 层在 HAL 之上做二次封装并用统一命名(如 platform_led_toggle),对 Target 暴露一致接口,避免业务代码直接依赖 HAL 细节。
如果觉得有帮助,欢迎点赞、在看、转发三连。
819