摘要:本文是 Zephyr BSP 系列第 33 篇,聚焦 CMake 构建系统。讲解
zephyr_library()等构建宏、Devicetree/Kconfig/CMake 三角关系、SoC 与 Driver 的 CMake 层级差异,以及"Kconfig 开了但代码没编译"等常见坑的排查思路,并串联第 20~32 篇形成完整 BSP 闭环。关键词:Zephyr;CMake;BSP;构建系统;Kconfig
CMake / Build System:Zephyr BSP 最后是怎么"编译起来"的
到第 31 篇,你已经把 Company SoC 的 Kconfig 配置体系建立起来了。
现在进入一个非常关键、也很容易混淆的部分:
Devicetree 告诉 Zephyr「硬件是什么」,Kconfig 告诉 Zephyr「启用什么」,CMake 则负责告诉编译系统「到底编译哪些代码、去哪里找这些代码、怎么链接成最终 firmware」。
所以可以把前面的几篇串起来:
Zephyr BSP
│
┌──────────────┼──────────────┐
│ │ │
Devicetree Kconfig CMake
│ │ │
描述硬件结构 决定功能配置 决定构建过程
│ │ │
└──────────────┼──────────────┘
↓
C / C++ Source
↓
Compiler
↓
Linker
↓
zephyr.elf / bin
1. 为什么 BSP 需要 CMake?
假设你的 Company SoC 有:
Company SoC
├── UART
├── GPIO
├── SPI
├── I2C
├── TIMER
├── CLOCK
└── IRQ
对应 Zephyr source:
soc/company/xyz/
├── CMakeLists.txt
├── Kconfig
├── Kconfig.defconfig
├── soc.c
├── clock.c
└── startup.c
drivers/serial/
├── uart_company.c
drivers/gpio/
├── gpio_company.c
drivers/spi/
├── spi_company.c
问题来了:
Zephyr 怎么知道这些 .c 文件应该被编译?
答案就是:
CMake
例如:
zephyr_library()
zephyr_library_sources(
soc.c
clock.c
startup.c
)
这实际上是在告诉 Zephyr:
把这些 source 加入当前 build。
2. Zephyr 的 CMake 不是普通 CMake
这是非常重要的一点。
你当然可以看到:
add_library(...)
add_executable(...)
target_sources(...)
target_include_directories(...)
但 Zephyr BSP 通常大量使用自己的 CMake helper:
zephyr_library()
zephyr_library_sources()
zephyr_library_sources_ifdef()
zephyr_include_directories()
例如:
zephyr_library_sources(
uart_company.c
uart_company_dma.c
)
或者:
zephyr_library_sources_ifdef(
CONFIG_UART_COMPANY_DMA
uart_company_dma.c
)
后者非常重要。
它把:
Kconfig
↓
CONFIG_UART_COMPANY_DMA
↓
CMake
↓
uart_company_dma.c
连接起来。
3. Kconfig 和 CMake 到底是什么关系?
假设:
Kconfig
定义:
CONFIG_COMPANY_UART
CONFIG_COMPANY_UART_DMA
CONFIG_COMPANY_SPI
用户:
CONFIG_COMPANY_UART=y
CONFIG_COMPANY_UART_DMA=y
CONFIG_COMPANY_SPI=n
那么 CMake 可以:
zephyr_library()
zephyr_library_sources_ifdef(
CONFIG_COMPANY_UART
uart_company.c
)
zephyr_library_sources_ifdef(
CONFIG_COMPANY_UART_DMA
uart_company_dma.c
)
zephyr_library_sources_ifdef(
CONFIG_COMPANY_SPI
spi_company.c
)
最终:
编译
├── uart_company.c
├── uart_company_dma.c
└── spi_company.c ❌
所以:
Kconfig 决定「有没有这个功能」,CMake 决定「这个功能对应哪些 source 被编译」。
4. 最小 Company SoC CMakeLists.txt
假设你的 SoC:
soc/company/mychip/
结构:
soc/company/mychip/
├── CMakeLists.txt
├── Kconfig
├── Kconfig.defconfig
├── soc.c
├── clock.c
└── irq.c
最简单:
zephyr_library()
zephyr_library_sources(
soc.c
clock.c
irq.c
)
意思就是:
创建 Zephyr library
│
├── soc.c
├── clock.c
└── irq.c
5. 如果代码依赖 CONFIG 呢?
例如:
CONFIG_SOC_COMPANY_MYCHIP=y
CONFIG_COMPANY_CLOCK=y
那么:
zephyr_library()
zephyr_library_sources(
soc.c
)
zephyr_library_sources_ifdef(
CONFIG_COMPANY_CLOCK
clock.c
)
如果:
CONFIG_COMPANY_CLOCK=y
那么:
clock.c
加入 build。
如果:
CONFIG_COMPANY_CLOCK=n
则:
clock.c
不会被编译。
6. Driver 的 CMake 又在哪里?
这里是 BSP 初学者非常容易迷糊的地方。
你可能会认为:
soc/company/mychip/
CMakeLists.txt
会自动编译:
drivers/serial/uart_company.c
不会。
SoC CMake 和 Driver CMake 是不同层级。
例如:
└── mychip/
└── CMakeLists.txt
drivers/
└── serial/
└── CMakeLists.txt
Driver 自己负责:
zephyr_library()
zephyr_library_sources_ifdef(
CONFIG_UART_COMPANY
uart_company.c
)
所以最终:
SoC CMake
│
├── soc.c
├── clock.c
└── irq.c
│
↓
Driver CMake
│
├── uart_company.c
├── gpio_company.c
└── spi_company.c
7. CMake 是怎么被 Zephyr 找到的?
这是理解 Zephyr build system 的关键。
用户执行:
west build -b company_board app
大致发生:
这张时序图把 west build 的完整调用链串起来了:west 先调用 CMake,CMake 依次加载 Board → SoC → Driver → Application 各层 CMakeLists.txt,汇总所有源文件并解析 Kconfig 生成 .config;随后 CMake 生成 build.ninja 交给 Ninja,Ninja 驱动 Compiler 把 .c 编译成 .o,最后由 Linker 链接成 zephyr.elf。每个环节的关键产物(.config、build.ninja、.o、.elf)都在图中标注出来了。
所以:
west
本身不是编译器。
更准确地说:
west
↓
CMake
↓
Ninja
↓
arm-none-eabi-gcc
例如:
west build -b nucleo_f303re samples/hello_world
背后会产生:
build/
├── CMakeCache.txt
├── build.ninja
├── zephyr/
│ ├── zephyr.elf
│ ├── zephyr.bin
│ └── include/
└── ...
8. build.ninja 是什么?
这是非常值得你打开看的文件。
执行:
west build -b your_board samples/hello_world
之后:
less build/build.ninja
你会发现里面最终出现大量类似:
arm-none-eabi-gcc
以及:
soc.c
clock.c
irq.c
uart_company.c
也就是说:
CMakeLists.txt
↓
CMake
↓
build.ninja
↓
Ninja
↓
gcc
所以你以后调 BSP 编译问题时:
build.ninja 是一个非常重要的「最终证据」。
9. CMake 如何找到 Board?
假设:
boards/company/myboard/
├── CMakeLists.txt
├── Kconfig.board
├── Kconfig.defconfig
├── myboard.dts
└── myboard.yaml
Board 可以有自己的:
CMakeLists.txt
例如:
board_runner_args(...)
或者添加 board-specific source。
但是很多 board 不需要大量 CMake。
因为 Zephyr 的 board / SoC / architecture 层已经有对应的构建逻辑。
10. Board → SoC → Architecture 的关系
现在把前面 20~32 篇真正串起来:
west build -b company_board
│
↓
Board
│
↓
SoC
│
↓
Architecture
│
↓
Zephyr Kernel
同时:
Board
│
└── DTS
│
├── UART
├── GPIO
├── SPI
└── I2C
以及:
Kconfig
│
├── SoC configuration
├── Driver configuration
└── Kernel configuration
最终全部汇合:
Board
│
┌───────┼────────┐
↓ ↓ ↓
DTS Kconfig CMake
│ │ │
│ │ └── Source files
│ │
│ └── CONFIG_*
│
└── Hardware description
│
↓
Compiler
│
↓
Linker
│
↓
zephyr.elf
11. zephyr_library() 到底是什么?
这个宏非常值得理解。
例如:
zephyr_library()
你可以把它粗略理解为:
创建一个属于 Zephyr 构建系统管理的 library target。
然后:
zephyr_library_sources(
soc.c
clock.c
)
把 source 放进去。
最终这些 .c:
soc.c
clock.c
先被编译成:
soc.c.o
clock.c.o
然后进入 library,例如:
libzephyr.a
或者相关 Zephyr archive。
最后 linker:
ld
把各种 object/library 合并:
libc.a
application objects
startup objects
│
↓
zephyr.elf
12. 为什么 Zephyr 喜欢 library?
因为 Zephyr 是一个非常模块化的系统。
例如:
Application
│
├── Kernel
├── UART
├── GPIO
├── SPI
├── I2C
├── Networking
└── Filesystem
每个 subsystem / driver 可以独立管理自己的 source。
最终 linker 再把它们组合起来。
这使得:
CONFIG_UART=n
时:
UART driver
甚至可以完全不进入最终 firmware。
13. 一个完整 Company UART 的例子
现在把:
28 UART
29 GPIO/SPI/I2C/Timer
30 Board
31 Kconfig
32 CMake
真正串起来。
假设:
drivers/serial/
├── CMakeLists.txt
└── uart_company.c
Kconfig:
config UART_COMPANY
bool "Company UART driver"
depends on SERIAL
CMake:
zephyr_library()
zephyr_library_sources_ifdef(
CONFIG_UART_COMPANY
uart_company.c
)
Devicetree:
&uart0 {
compatible = "company,my-uart";
status = "okay";
};
Driver:
#define DT_DRV_COMPAT company_my_uart
static int company_uart_init(const struct device *dev)
{
/* UART hardware initialization */
return 0;
}
最终:
DTS
↓
uart0 enabled
Kconfig
↓
CONFIG_UART_COMPANY=y
CMake
↓
uart_company.c included
Compiler
↓
uart_company.c.o
Linker
↓
zephyr.elf
这就是一个完整 Zephyr driver 从描述到 firmware 的生命周期。
14. 最容易踩的坑:Kconfig 开了,但代码没编译
例如:
CONFIG_UART_COMPANY=y
你以为:
uart_company.c
肯定会编译。
其实不一定。
如果 CMake 没有:
zephyr_library_sources_ifdef(
CONFIG_UART_COMPANY
uart_company.c
)
那么:
Kconfig
↓
CONFIG_UART_COMPANY=y
虽然存在,
但:
uart_company.c
可能根本没有进入 build。
结果可能出现:
undefined reference
例如:
undefined reference to `__device_dts_ord_...`
15. 反过来也一样
CMake 可以加入:
zephyr_library_sources(
uart_company.c
)
但是 Kconfig 没有正确配置:
CONFIG_UART_COMPANY
那么 source 虽然被编译:
uart_company.c.o
但是里面可能:
# if defined(CONFIG_UART_COMPANY)
...
# endif
没有有效代码。
所以:
Kconfig 和 CMake 必须配套。
16. 如何检查一个 .c 到底有没有被编译?
这是 BSP 调试中非常实用的技能。
先:
west build -t pristine
west build -b your_board your_app
然后:
grep -R "uart_company.c" build/
或者:
grep -R "uart_company.c.o" build/
如果出现:
build.ninja
中的编译规则,说明 CMake 已经把它加入 build。
也可以:
ninja -C build -v
看完整编译命令。
17. 一个非常实用的调试思路
以后看到:
undefined reference
不要马上修改 C 代码。
先沿着这条链检查:
1. Kconfig
↓
2. CONFIG_xxx=y ?
↓
3. CMake
↓
4. source 是否加入 build?
↓
5. .o 是否生成?
↓
6. symbol 是否存在?
↓
7. linker 是否把它链接进去?
例如:
grep CONFIG_UART build/zephyr/.config
然后:
grep uart_company build/build.ninja
然后:
find build -name '\*uart_company\*'
最后:
arm-none-eabi-nm build/zephyr/zephyr.elf | grep company_uart
17.1 实战排查:CONFIG_UART_COMPANY=y 但 undefined reference
下面用一个完整例子,把第 17 节的思路走一遍。
场景:你在 prj.conf 里设置了:
CONFIG_UART_COMPANY=y
编译时却报:
undefined reference to `company_uart_init`
或:
undefined reference to `__device_dts_ord_...`
第一步:确认 Kconfig 真的生效
grep CONFIG_UART_COMPANY build/zephyr/.config
输出:
CONFIG_UART_COMPANY=y
说明 Kconfig 配置没问题,CONFIG_UART_COMPANY 确实被打开了。
第二步:搜索 build.ninja,确认 CMake 是否把源文件加入构建
grep uart_company build/build.ninja
如果输出为空,说明 CMake 没有把 uart_company.c 加入构建——问题出在 CMakeLists.txt。
如果输出类似:
build zephyr/drivers/serial/CMakeFiles/drivers__serial.dir/uart_company.c.obj: C_COMPILER__drivers__serial_ ...
说明 CMake 已经把它加入构建,问题可能出在编译或链接阶段。
第三步:确认 .o 文件是否生成
find build -name '*uart_company*'
输出:
build/zephyr/drivers/serial/CMakeFiles/drivers__serial.dir/uart_company.c.obj
如果这个文件存在,说明编译阶段没问题。
第四步:用 nm 检查符号是否存在于最终 ELF
arm-none-eabi-nm build/zephyr/zephyr.elf | grep company_uart
如果输出为空,说明 链接阶段没有把该符号链接进去。
第五步:定位根因——检查 CMakeLists.txt
打开:
drivers/serial/CMakeLists.txt
发现内容:
zephyr_library()
zephyr_library_sources(
uart_company.c
)
这里没有用 zephyr_library_sources_ifdef(),而是无条件编译。但更常见的问题是:
zephyr_library()
zephyr_library_sources_ifdef(
CONFIG_UART_COMPANY
uart_company.c
)
先核对 Kconfig 里定义的符号名:
config UART_COMPANY
而 CMake 里引用的是:
CONFIG_UART_COMPANY
注意:Zephyr 的 Kconfig 符号会自动加上 CONFIG_ 前缀,所以 config UART_COMPANY 对应的宏正是 CONFIG_UART_COMPANY,这一处写法是对的。
真正的问题往往出在符号名本身不一致:例如 Kconfig 里定义的是 COMPANY_UART,而 CMake 里却写成了 UART_COMPANY。
第六步:修复 CMakeLists.txt
假设 Kconfig 里定义的是:
config COMPANY_UART
bool "Company UART driver"
depends on SERIAL
那么 CMakeLists.txt 应该改为:
zephyr_library()
zephyr_library_sources_ifdef(
CONFIG_COMPANY_UART
uart_company.c
)
完整 diff:
--- a/drivers/serial/CMakeLists.txt
+++ b/drivers/serial/CMakeLists.txt
@@ -1,5 +1,5 @@
zephyr_library()
zephyr_library_sources_ifdef(
- CONFIG_UART_COMPANY
+ CONFIG_COMPANY_UART
uart_company.c
)
第七步:重新编译验证
west build -t pristine
west build -b your_board your_app
再次检查:
grep CONFIG_COMPANY_UART build/zephyr/.config
输出:
CONFIG_COMPANY_UART=y
然后:
grep uart_company build/build.ninja
确认源文件已加入构建。
最后:
arm-none-eabi-nm build/zephyr/zephyr.elf | grep company_uart
这次能看到符号了:
00000000 T company_uart_init
17.2 实战排查:Kconfig 已开启但 CMake 未加入源文件
下面再走一个更常见的场景:Kconfig 配置完全正确,但 CMakeLists.txt 里漏写了 zephyr_library_sources_ifdef() 条目,导致源文件根本没进构建。
场景:你在 prj.conf 里设置了:
CONFIG_UART_COMPANY=y
编译时却报:
undefined reference to `company_uart_init`
第一步:确认 Kconfig 真的生效
grep CONFIG_UART_COMPANY build/zephyr/.config
输出:
CONFIG_UART_COMPANY=y
Kconfig 没问题,功能确实被启用了。
第二步:搜索 build.ninja,确认 CMake 是否把源文件加入构建
grep uart_company build/build.ninja
输出为空。
这说明 uart_company.c 根本没有被 CMake 加入构建。问题很可能出在 drivers/serial/CMakeLists.txt。
第三步:确认 .o 文件是否存在
find build -name '*uart_company*'
输出为空,进一步证实:这个源文件从未被编译。
第四步:用 nm 检查最终 ELF 里的符号
arm-none-eabi-nm build/zephyr/zephyr.elf | grep company_uart
输出为空,说明链接器里根本没有这个符号,所以才会报 undefined reference。
第五步:定位根因——检查 CMakeLists.txt
打开:
drivers/serial/CMakeLists.txt
发现内容:
zephyr_library()
zephyr_library_sources(
uart_company_common.c
)
问题就在这里:CMakeLists.txt 里只有无条件编译的 uart_company_common.c,缺少 zephyr_library_sources_ifdef(CONFIG_UART_COMPANY uart_company.c) 这条条件编译条目。所以即使 Kconfig 里 CONFIG_UART_COMPANY=y,uart_company.c 也不会被加入构建。
第六步:修复 CMakeLists.txt
修复前:
zephyr_library()
zephyr_library_sources(
uart_company_common.c
)
修复后:
zephyr_library()
zephyr_library_sources(
uart_company_common.c
)
zephyr_library_sources_ifdef(
CONFIG_UART_COMPANY
uart_company.c
)
完整 diff:
--- a/drivers/serial/CMakeLists.txt
+++ b/drivers/serial/CMakeLists.txt
@@ -1,5 +1,9 @@
zephyr_library()
zephyr_library_sources(
uart_company_common.c
)
+
+zephyr_library_sources_ifdef(
+ CONFIG_UART_COMPANY
+ uart_company.c
+)
第七步:重新编译验证
west build -t pristine
west build -b your_board your_app
再次检查 build.ninja:
grep uart_company build/build.ninja
这次能看到编译规则了:
build zephyr/drivers/serial/CMakeFiles/drivers__serial.dir/uart_company.c.obj: C_COMPILER ...
再确认 .o 文件:
find build -name '*uart_company*'
输出:
build/zephyr/drivers/serial/CMakeFiles/drivers__serial.dir/uart_company.c.obj
最后用 nm 确认符号:
arm-none-eabi-nm build/zephyr/zephyr.elf | grep company_uart
输出:
00000000 T company_uart_init
undefined reference 消失,链接成功。
17.3 实战排查:Devicetree 节点已启用但驱动未初始化
前面两个例子都聚焦在「Kconfig / CMake」这条链上。但还有一种非常隐蔽的情况:Kconfig 开了、CMake 也把源文件编译进去了、链接也成功了,可驱动就是没有被初始化——设备树节点明明 status = "okay",init 函数却从未被调用。
这类问题要沿着 DTS 节点状态 → binding 匹配 → driver 注册宏 → init 函数调用 四步排查。
场景:你在 myboard.dts 里启用了 UART 节点:
&uart0 {
compatible = "company,my-uart";
status = "okay";
};
编译、链接都成功,但运行时 company_uart_init 从未被调用,串口完全没反应。
第一步:确认 dts 节点状态
先确认设备树节点确实被启用,且 status 没有被覆盖:
grep -A3 "uart0" build/zephyr/zephyr.dts
预期输出:
uart0: serial@40001000 {
compatible = "company,my-uart";
status = "okay";
reg = <0x40001000 0x400>;
};
如果这里 status = "disabled" 或节点根本没出现,说明问题出在设备树,而不是驱动代码。
第二步:确认 binding 匹配
Zephyr 通过 compatible 字符串查找 binding 文件。检查 binding 是否存在:
find dts/bindings -name "*company*my-uart*"
预期输出:
dts/bindings/serial/company,my-uart.yaml
再确认 binding 里的 compatible 与 dts 节点一致:
grep compatible dts/bindings/serial/company,my-uart.yaml
预期输出:
compatible: "company,my-uart"
如果 binding 文件不存在,或 compatible 拼写不一致,驱动就无法被实例化。
第三步:确认 driver 注册宏
打开驱动源文件,检查 DT_DRV_COMPAT 是否与 binding 的 compatible 对应:
grep -n "DT_DRV_COMPAT" drivers/serial/uart_company.c
预期输出:
#define DT_DRV_COMPAT company_my_uart
注意:DT_DRV_COMPAT 里的写法是把 compatible 字符串 "company,my-uart" 中的逗号和连字符替换为下划线,即 company_my_uart。
再确认注册宏存在:
grep -nE "DEVICE_DT_INST_DEFINE|DEVICE_DT_INST_INIT" drivers/serial/uart_company.c
预期输出:
DEVICE_DT_INST_DEFINE(0, company_uart_init, NULL, NULL, NULL, POST_KERNEL,
CONFIG_UART_COMPANY_INIT_PRIORITY, &company_uart_driver_api);
如果 DT_DRV_COMPAT 写错,或缺少 DEVICE_DT_INST_DEFINE,驱动就不会被注册。
第四步:确认 init 函数是否被调用
用 nm 在最终 ELF 里查找设备实例符号:
arm-none-eabi-nm build/zephyr/zephyr.elf | grep -E "device_state|__device_dts_ord"
预期输出(能看到 uart0 对应的设备实例):
00000000 D __device_dts_ord_0
00000000 T company_uart_init
如果 company_uart_init 存在但 __device_dts_ord_0 不存在,说明驱动代码编译进去了,但没有对应的设备树实例——问题出在 DTS 节点或 binding 匹配。
第五步:定位根因——检查 binding 的 compatible 拼写
打开 binding 文件:
cat dts/bindings/serial/company,my-uart.yaml
发现内容:
compatible: "company,my-uart"
而 DTS 节点里写的是:
compatible = "company,my-uart";
看起来一致?再仔细看,DTS 节点里实际写的是:
compatible = "company,my_uart";
注意:binding 里是 company,my-uart(连字符),DTS 里却写成了 company,my_uart(下划线)。Zephyr 的 binding 匹配是严格字符串匹配,一个字符不一致就匹配不上,驱动自然不会被实例化。
第六步:修复 dts 节点
修复前:
&uart0 {
compatible = "company,my_uart";
status = "okay";
};
修复后:
&uart0 {
compatible = "company,my-uart";
status = "okay";
};
完整 diff:
--- a/boards/company/myboard/myboard.dts
+++ b/boards/company/myboard/myboard.dts
@@ -1,5 +1,5 @@
&uart0 {
- compatible = "company,my_uart";
+ compatible = "company,my-uart";
status = "okay";
};
第七步:重新编译验证
west build -t pristine
west build -b myboard your_app
再次确认设备树节点:
grep -A3 "uart0" build/zephyr/zephyr.dts
这次能看到:
uart0: serial@40001000 {
compatible = "company,my-uart";
status = "okay";
reg = <0x40001000 0x400>;
};
再检查设备实例符号:
arm-none-eabi-nm build/zephyr/zephyr.elf | grep "__device_dts_ord_0"
这次能看到:
00000000 D __device_dts_ord_0
company_uart_init 会被 Zephyr 内核在启动阶段自动调用,串口恢复正常。
总结:这个例子的根因是 DTS 节点里的 compatible 与 binding 文件不一致(下划线 vs 连字符),导致驱动无法匹配到设备树实例。排查时按「DTS 节点状态 → binding 匹配 → driver 注册宏 → init 函数调用」四步走,就能快速定位是设备树、binding、注册宏还是初始化顺序的问题。
总结:这个例子的根因是 CMakeLists.txt 中缺少 zephyr_library_sources_ifdef() 条目。Kconfig 配置正确,但 CMake 没有把对应的 .c 文件加入构建。排查时按「.config → build.ninja → .o → nm」这条链逐步确认,就能快速定位是 Kconfig、CMake、编译还是链接的问题。
总结:这个例子的根因是 Kconfig 符号名与 CMake 里引用的符号名不一致。排查时按「.config → build.ninja → .o → nm」这条链逐步确认,就能快速定位是 Kconfig、CMake、编译还是链接的问题。
这样就能定位:
Kconfig 问题
or
CMake 问题
or
Compile 问题
or
Link 问题
18. CMake + Kconfig + Devicetree 三角关系
这是本篇最重要的模型:
┌─────────────┐
│ Devicetree │
│ “硬件是什么” │
└──────┬──────┘
│
↓
device instance
│
│
┌──────────────┐ │ ┌──────────────┐
│ Kconfig │───────────┼──────────→│ CMake │
│ “启用什么” │ │ │ “编译什么” │
└──────────────┘ │ └──────┬───────┘
│ │
↓ ↓
Driver code Source files
│ │
└────────┬─────────┘
↓
Compiler
↓
Linker
↓
zephyr.elf
你可以记成一句话:
DTS 决定「实例」,Kconfig 决定「功能」,CMake 决定「代码」,Linker 决定「最终映像」。
19. Company BSP 推荐目录结构
到这里,你的 Company SoC BSP 可以逐渐形成:
zephyr/
│
├── arch/
│ └── ...
│
├── soc/
│ └── company/
│ └── mychip/
│ ├── CMakeLists.txt
│ ├── Kconfig
│ ├── Kconfig.defconfig
│ ├── soc.c
│ ├── clock.c
│ ├── reset.c
│ └── irq.c
│
├── boards/
│ └── company/
│ └── myboard/
│ ├── CMakeLists.txt
│ ├── Kconfig.board
│ ├── Kconfig.defconfig
│ ├── myboard.dts
│ └── myboard.yaml
│
├── dts/
│ └── bindings/
│ └── serial/
│ └── company,my-uart.yaml
│
├── drivers/
│ ├── serial/
│ │ ├── CMakeLists.txt
│ │ ├── Kconfig
│ │ └── uart_company.c
│ │
│ ├── gpio/
│ │ ├── CMakeLists.txt
│ │ └── gpio_company.c
│ │
│ └── spi/
│ ├── CMakeLists.txt
│ └── spi_company.c
│
└── ...
20. 最终完整构建链
现在把 20~32 篇压缩成一张图:
这张图把整条构建链串起来了:west build 命令先经过 Board → SoC → Architecture 三层,确定目标平台;随后 Devicetree 提供硬件描述、Kconfig 提供功能配置,两者共同汇入 CMake,由 CMake 决定到底编译哪些 .c 源文件。源文件经 Compiler 编译成 .o 目标文件与 .a 静态库,再由 Linker 链接成最终的 zephyr.elf,并进一步生成 zephyr.bin 和 zephyr.hex 烧录镜像。整条链路中,CMake 是承上启下的枢纽——它把硬件描述、功能配置和源码选择三者统一起来。
21. 这一篇你真正需要掌握的 6 个东西
① CMakeLists.txt
负责描述 build。
② zephyr_library()
创建 Zephyr 管理的 library。
③ zephyr_library_sources()
加入 source:
zephyr_library_sources(foo.c)
④ zephyr_library_sources_ifdef()
让 Kconfig 控制 source:
zephyr_library_sources_ifdef(
CONFIG_FOO
foo.c
)
⑤ build.ninja
CMake 最终生成的实际构建规则。
⑥ zephyr.elf
所有代码经过:
CMake
↓
Compiler
↓
Linker
之后得到的最终 ELF。
22. 你现在已经到了一个很重要的阶段
前面的:
20 SoC BSP
21 SoC Port Skeleton
22 CPU Architecture
23 Startup
24 Interrupt Controller
25 Clock / Reset
26 Devicetree
27 Binding
28 UART Driver
29 GPIO/SPI/I2C/Timer
30 Board
31 Kconfig
32 CMake
实际上已经组成一个完整的 Company SoC BSP 闭环:
┌──────────────┐
│ Company SoC │
└──────┬───────┘
│
┌────────────┼────────────┐
↓ ↓ ↓
Architecture SoC Board
│ │ │
↓ ↓ ↓
Startup Clock Devicetree
IRQ Reset Binding
│ │ │
└────────────┼────────────┘
↓
Drivers
↓
Kconfig
↓
CMake
↓
Compiler
↓
Linker
↓
Firmware

355

被折叠的 条评论
为什么被折叠?



