4. SDK 架构设计:分层模型、API 接口定义、文件组织
做 SDK 这事儿,我踩过不少坑。早年给一个嵌入式平台写打标控制 SDK,上来就把所有功能揉在一个大文件里,结果后面每个新需求都得改核心代码,测试组天天哀嚎。后来我学乖了——架构先行。先定好分层,再定接口,最后规划文件怎么摆。这节课我把这套方法拆给你看。
4.1 分层模型:把职责拆清楚
我的习惯是分三层:用户层、核心层、硬件抽象层。每层只管自己的事,层与层之间通过定义好的接口通信。
| 层次 | 职责 | 依赖关系 |
|---|---|---|
| 用户层 (User Layer) | 提供面向业务场景的 API,比如「开始打标」「停止打标」「设置参数」 | 只调用核心层接口 |
| 核心层 (Core Layer) | 管理打标策略、状态机、任务队列,不关心硬件具体怎么操作 | 依赖硬件抽象层接口 |
| 硬件抽象层 (HAL) | 封装硬件寄存器读写、中断处理、DMA 传输,对上层暴露统一结构体 | 无依赖(或被板级支持包替换) |
你想想看,如果哪天换了一颗 MCU,只需要重写 HAL 层就行,上面两层几乎不用动。这个设计帮我省了至少两次大规模返工——有一次客户临时要求从 STM32 切到 GD32,我一天就搞定移植。
4.2 API 接口定义:名字和参数要一眼懂
接口设计是 SDK 的“脸面”。我给自己定了几条铁律:
- 命名前缀统一:比如
mark_开头,所有函数一眼就知道属于打标模块。 - 动词 + 名词:
mark_start、mark_stop、mark_set_speed,别搞奇奇怪怪的缩写。 - 参数有方向标记:输入参数用
const,输出参数用指针加_out后缀。 - 错误码枚举清晰:统一返回
mark_err_t,包含MARK_OK、MARK_ERR_BUSY、MARK_ERR_TIMEOUT等。
举个头文件片段:
// mark_api.h
typedef enum {
MARK_OK = 0,
MARK_ERR_INVALID_PARAM = -1,
MARK_ERR_HARDWARE_FAIL = -2,
MARK_ERR_TIMEOUT = -3
} mark_err_t;
typedef struct {
uint32_t power; // 激光功率,单位 mW
uint16_t speed; // 打标速度,单位 mm/s
uint8_t repeat_cnt; // 重复次数
} mark_config_t;
mark_err_t mark_init(void);
mark_err_t mark_start(const mark_config_t *config);
mark_err_t mark_stop(void);
mark_err_t mark_get_status(uint8_t *out_status);
我见过有人把参数类型混成 int 和 uint32_t 乱写,结果移植到 64 位平台直接崩。我个人习惯是明确宽度,能用 uint32_t 绝不用 int。
reserved 字段,万一将来要扩展,不用改 ABI。像 uint32_t reserved[4]; 就行。
4.3 文件组织:让新人也能快速找到需要的代码
文件结构搞好了,团队协作效率能翻倍。我常用的目录布局是这样的:
mark_sdk/
├── inc/ # 公共头文件
│ ├── mark_api.h # 用户层 API
│ ├── mark_core.h # 核心层内部接口
│ └── mark_hal.h # HAL 层定义
├── src/
│ ├── user/ # 用户层实现
│ │ └── mark_api.c
│ ├── core/ # 核心层
│ │ ├── mark_state_machine.c
│ │ └── mark_task_queue.c
│ └── hal/ # 硬件抽象层
│ ├── mark_hal_stm32.c
│ └── mark_hal_gd32.c
├── test/
│ ├── unit/ # 单元测试
│ └── integration/ # 集成测试
└── doc/
└── api_reference.md
每个 .c 文件对应一个 .h 头文件,尽量不写大而全的“万能头”。为什么?我曾经把 300 行的代码塞进一个文件,后来加功能时发现耦合严重,重构花了三周。所以现在严格规定:一个模块一个文件夹,模块内部尽量不要跨目录包含。
4.4 整体架构图
下面这张图概括了上面讲的所有内容。从用户应用到底层硬件,每层之间的箭头就是 API 调用方向。左侧的“异常处理”和“配置管理”是贯穿各层的辅助模块,我习惯把它们单独拿出来,避免核心逻辑被大量错误处理代码污染。
最后总结几句
分层模型让代码好改、好测;API 定义让调用者不用翻源码;文件组织让协作不乱。这三块做好了,SDK 的生命力就会很长——哪怕过了两三年,新来的同事也能几小时上手。嗯,今天就到这。记住:架构不是一次画出来的,而是在重构中打磨出来的。你自己动手做一个项目,回来再看这些原则,体会会更深。