4、自定义组件入门:什么是自定义组件、组件与模块的区别、何时需要创建自定义组件。
好,咱们进入第四章。前面几章我们把 Zephyr 的构建系统、模块机制都捋了一遍。现在该聊聊一个很实际的问题了——自定义组件。
说实话,我第一次接触 Zephyr 的时候,也被“组件”和“模块”这两个词搞得有点晕。它们到底是不是一回事?什么时候该用哪个?今天我就把这块掰开揉碎了讲清楚。
4.1 什么是自定义组件?
先给个最直白的定义:自定义组件,就是你自己写的、能在 Zephyr 构建系统里被“看见”的代码单元。
它不是一个玄乎的概念。说白了,你写了一个驱动、一个库、或者一个协议栈的实现,然后通过 Kconfig 和 CMakeLists.txt 把它注册到 Zephyr 的构建体系里。这样,别人用 west build 的时候,就能通过 CONFIG_XXX=y 把它编译进去。
我个人的习惯是:只要你的代码需要被多个项目复用,就应该做成组件。别把代码到处复制粘贴,那是给自己挖坑。
核心特征:
- 有自己的
Kconfig文件,定义配置选项 - 有自己的
CMakeLists.txt,描述如何编译 - 可以被
CONFIG_XXX开关控制 - 通常放在
zephyr/modules或modules目录下
举个例子。我之前做过一个温湿度传感器驱动,SHT30。如果只是临时用一次,我可能直接在 app 里写死。但后来发现好几个项目都要用,我就把它抽成了一个组件。结构大概是这样:
modules/
└── sht30/
├── Kconfig
├── CMakeLists.txt
├── sht30.c
└── sht30.h
你看,就这么简单。但有了这个结构,任何项目只要在 prj.conf 里加一行 CONFIG_SHT30=y,就能直接用。
4.2 组件与模块的区别
这个问题我当年也困惑过。Zephyr 文档里既有“模块(Module)”,又有“组件(Component)”,到底差在哪?
嗯,这里要注意:模块是组织单位,组件是功能单位。
我画个表格帮你理解:
| 维度 | 模块(Module) | 组件(Component) |
|---|---|---|
| 定义 | 一个独立的代码仓库或目录 | 一个可配置的功能单元 |
| 粒度 | 较大,可以包含多个组件 | 较小,通常对应一个驱动或库 |
| 配置方式 | 通过 west.yml 引入 |
通过 Kconfig 开关控制 |
| 典型例子 | Zephyr 官方模块、第三方 SDK | 某个 I2C 驱动、算法库 |
| 复用范围 | 跨项目、跨组织 | 跨模块、跨文件 |
你想想看,一个模块可以包含多个组件。比如你从 GitHub 上拉了一个 zephyr-module-ai,这个模块里可能包含了神经网络推理组件、图像预处理组件、模型加载组件。模块是“容器”,组件是“零件”。
我记得有一次,团队里有人把整个传感器驱动库做成了一个模块,里面放了十几个驱动。结果每次编译都要拉一堆没用的代码。后来我建议他把每个驱动拆成独立的组件,用 Kconfig 控制。这样,项目里用几个传感器就开几个组件,清爽多了。
我的建议:
如果你只是写一个驱动或一个库,做成组件就够了。只有当你需要管理一组相关的组件,或者需要从外部仓库引入代码时,才考虑做成模块。
4.3 何时需要创建自定义组件?
这个问题其实没有标准答案。但根据我的经验,下面这几种情况,你应该考虑创建自定义组件:
4.3.1 代码需要跨项目复用
这是最直接的理由。你写了一个 CRC 校验库,项目 A 用了,项目 B 也要用。别复制粘贴,做成组件。以后修 bug 只需要改一处。
4.3.2 硬件外设驱动
Zephyr 官方驱动库虽然丰富,但总有一些小众芯片没有支持。比如我之前用过一个国产的温湿度传感器,官方没有驱动。我就自己写了一个组件,放在 modules/sensors 下。后来同事的项目也用到了,直接 west add 就拉过去了。
4.3.3 算法或协议栈
比如你实现了一个轻量级的 MQTT 解析器,或者一个 PID 控制算法。这些逻辑相对独立,做成组件后,可以方便地在不同项目间共享。
4.3.4 需要隐藏实现细节
有时候,你写的代码不想让所有人都看到源码。做成组件后,可以只提供头文件和库文件。Zephyr 的模块机制支持预编译库,这点很实用。
避坑指南:
我曾经犯过一个错误:把业务逻辑也做成了组件。比如一个“设备配网流程”,里面包含了 UI 交互、网络扫描、状态机。结果这个组件跟具体项目耦合太紧,根本没法复用。最后又拆回去了。
所以,组件应该只封装通用功能,不要包含业务逻辑。业务逻辑应该放在应用层。
4.3.5 什么时候不该用组件?
反过来,也有不适合用组件的情况:
- 一次性代码:只在当前项目用一次,以后大概率不会再用。别折腾了,直接写在 app 里。
- 高度耦合的代码:如果你的代码跟项目里的其他部分纠缠不清,强行拆成组件反而会增加复杂度。
- 过于简单的代码:比如就一个头文件,几个宏定义。没必要为了“组件化”而组件化。
4.4 一个简单的实战例子
光说不练假把式。我带你走一遍创建自定义组件的流程。假设我们要写一个简单的“软件定时器”组件,用来替代硬件定时器做延时。
第一步,创建目录结构:
modules/
└── soft_timer/
├── Kconfig
├── CMakeLists.txt
├── soft_timer.c
└── soft_timer.h
第二步,写 Kconfig:
menuconfig SOFT_TIMER
bool "Software Timer Support"
default n
help
Enable software timer implementation.
This provides a lightweight timer using system tick.
第三步,写 CMakeLists.txt:
zephyr_library()
zephyr_library_sources(soft_timer.c)
zephyr_library_include_directories(.)
第四步,写代码。这里就不展开了,你懂的。
第五步,在项目里启用:
# prj.conf
CONFIG_SOFT_TIMER=y
搞定。就这么简单。
小技巧:
如果你想让组件在 west build 时自动被扫描到,记得在 west.yml 里注册一下。或者直接把组件放在 zephyr/modules 目录下,Zephyr 会自动发现。
4.5 总结
好了,这一章的内容就这些。我们来捋一下重点:
- 自定义组件就是你自己写的、可配置的代码单元
- 组件 vs 模块:组件是功能单元,模块是组织容器。一个模块可以包含多个组件
- 什么时候用:代码需要复用、是硬件驱动、是通用算法、需要隐藏实现
- 什么时候别用:一次性代码、高度耦合、过于简单
下一章,我会带你深入组件的内部结构,看看 Kconfig 和 CMakeLists.txt 到底怎么写才规范。到时候我会分享一些我踩过的坑,保证让你少走弯路。
公众号:蓝海资料掘金营,微信deep3321