2、模块目录结构:Zephyr模块的标准目录布局、关键文件详解
好,咱们接着聊模块。上一章我讲了什么是模块,以及为什么需要它。这一章,咱们直接动手——看看一个标准的Zephyr模块,它的目录到底长什么样。
说白了,模块就是一个有规矩的文件夹。你随便扔一堆代码进去,Zephyr不认识。但如果你按照它的“规矩”来摆,它就能自动找到你的代码、编译进去、还能在menuconfig里配置。这个“规矩”,就是我今天要讲的目录结构和三个关键文件。
2.1 标准目录布局:一个模块的“骨架”
我在项目中见过很多新手,上来就写代码,结果模块怎么都加载不进去。后来一看,目录结构不对。嗯,这里要注意,Zephyr对目录结构是有要求的,虽然不是强制,但大家都遵循这个约定。
一个典型的Zephyr模块,长这样:
my_driver_module/
├── CMakeLists.txt # 告诉构建系统:怎么编译我
├── Kconfig # 告诉配置系统:我有哪些开关
├── zephyr/
│ └── module.yml # 告诉Zephyr:我是谁,我在哪
├── drivers/
│ ├── CMakeLists.txt
│ └── my_driver/
│ ├── CMakeLists.txt
│ └── my_driver.c
├── include/
│ └── my_driver.h
├── dts/
│ └── bindings/
│ └── my-device.yaml
├── samples/
│ └── hello_my_driver/
│ ├── CMakeLists.txt
│ ├── prj.conf
│ └── src/
│ └── main.c
└── tests/
└── my_driver_test/
├── CMakeLists.txt
├── prj.conf
└── src/
└── main.c
你可能会问:“这么多文件夹,每个都要吗?”
我的回答是:不是。但上面这几个,是“最佳实践”。
咱们拆开来看:
- CMakeLists.txt:根目录的构建文件。它告诉Zephyr,我这个模块有哪些源文件、依赖哪些库。
- Kconfig:配置菜单。你可以在menuconfig里看到它定义的选项。
- zephyr/module.yml:模块的“身份证”。Zephyr通过它来发现你这个模块。
- drivers/:放驱动代码。如果你写的是驱动,就放这里。
- include/:头文件。对外暴露的API放这里。
- dts/bindings/:设备树绑定。如果你的驱动需要设备树支持,这里放yaml文件。
- samples/:示例代码。方便别人快速上手。
- tests/:测试代码。我建议每个模块都写测试,别偷懒。
README.md,写清楚这个模块是干什么的、怎么用。别小看这个文件,半年后你自己回来看代码,都得靠它回忆。
2.2 关键文件一:CMakeLists.txt —— 构建的“指挥中心”
这个文件,说白了就是告诉CMake:“嘿,我的源文件在这,帮我编译一下。” 但Zephyr的CMakeLists.txt,比普通的要复杂一点点。
一个最简的模块级CMakeLists.txt,长这样:
# 根目录的 CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
# 添加子目录,让CMake去drivers里找
add_subdirectory(drivers)
# 如果你有额外的库,可以这样加
# zephyr_library()
# zephyr_library_sources(src/my_lib.c)
而drivers目录下的CMakeLists.txt,通常是:
# drivers/CMakeLists.txt
zephyr_library()
zephyr_library_sources(
my_driver/my_driver.c
)
zephyr_library_include_directories(../include)
这里有几个关键点:
- zephyr_library():这是Zephyr提供的宏,用来创建一个Zephyr风格的库。它会自动处理链接和依赖。
- zephyr_library_sources():把源文件加进去。
- zephyr_library_include_directories():指定头文件路径。
zephyr_library_include_directories(),结果头文件死活找不到。编译报错“file not found”,我查了半天,最后发现是路径没加进去。所以,头文件路径一定要显式声明,别指望CMake自动帮你找。
2.3 关键文件二:Kconfig —— 配置的“开关面板”
Kconfig文件,就是你在menuconfig里看到的那些选项。它定义了你的模块有哪些配置开关。
一个典型的Kconfig文件:
# Kconfig
menuconfig MY_DRIVER
bool "My Driver Support"
default n
help
Enable support for my custom driver.
if MY_DRIVER
config MY_DRIVER_SPEED
int "Driver speed in Hz"
default 1000000
range 1000 10000000
help
Set the operating speed of the driver.
config MY_DRIVER_DEBUG
bool "Enable debug output"
default n
help
Enable verbose debug messages.
endif # MY_DRIVER
这里要注意几个语法:
- menuconfig:创建一个可展开的菜单项。用户选中它,才能看到里面的子选项。
- bool:布尔类型,要么y要么n。
- int:整数类型,可以设范围。
- default n:默认关闭。我建议所有模块默认都关掉,用户需要时再开。
- help:帮助信息。写清楚这个选项是干什么的。
模块名_功能名 的格式。比如 MY_DRIVER_SPEED,这样在menuconfig里搜索时,一眼就能看出来是哪个模块的。
2.4 关键文件三:zephyr/module.yml —— 模块的“身份证”
这个文件,是Zephyr 2.6之后引入的。它的作用,就是告诉Zephyr构建系统:“我是一个模块,我的代码在这里,我的Kconfig在那里。”
一个标准的module.yml:
# zephyr/module.yml
name: my_driver
build:
cmake: .
kconfig: Kconfig
字段解释:
- name:模块的名字。要唯一,不要跟其他模块重名。
- build.cmake:CMakeLists.txt所在的目录。点号表示当前目录。
- build.kconfig:Kconfig文件的路径。相对于模块根目录。
你可能会问:“就这么简单?”
对,就这么简单。但别小看它,没有这个文件,Zephyr根本不会理你的模块。
kconfig: Kconfig 指向主文件,然后在主文件里用 source "subdir/Kconfig" 引入子文件。这样结构更清晰。
2.5 实战:如何让Zephyr找到你的模块
好,文件都写好了。怎么让Zephyr知道你的模块存在呢?
有两种方式:
- 环境变量方式:设置
ZEPHYR_EXTRA_MODULES环境变量,指向你的模块路径。 - West方式:在west.yml里添加你的模块。
我个人推荐第二种,因为更规范。west.yml里这样写:
manifest:
projects:
- name: my_driver
path: modules/my_driver
url: https://github.com/yourname/my_driver.git
revision: main
然后运行 west update,Zephyr就会自动拉取并识别你的模块。
2.6 总结:一个模块的“最小可行”结构
如果你现在就想动手试试,最少需要哪些文件?
| 文件 | 是否必须 | 作用 |
|---|---|---|
| zephyr/module.yml | 是 | 告诉Zephyr:我是模块 |
| CMakeLists.txt | 是 | 告诉构建系统:怎么编译 |
| Kconfig | 推荐 | 提供配置选项 |
| 源文件 | 是 | 你的代码 |
| 头文件 | 推荐 | 对外接口 |
嗯,这一章的内容就到这里。下一章,我会带你手把手创建一个完整的模块,从零开始,一步步跑起来。到时候你就知道,这些文件到底是怎么配合工作的。
记住:目录结构是骨架,三个关键文件是灵魂。骨架歪了,灵魂再强也跑不起来。