摘要:本文讲解 Zephyr Devicetree 的核心概念与实战用法。你将理解 Devicetree 如何描述 SoC 硬件、
.dtsi与.dts的分工、compatible如何连接驱动、reg/interrupts/clocks等关键属性,以及 Devicetree 如何最终生成 C 宏进入驱动代码。文章从最小 Company SoC 示例出发,串联 Kconfig、DTS、CMake 与 Driver Model,帮助你建立完整的 BSP 世界观。
Devicetree:把 Company SoC 描述给 Zephyr
到了这里,你已经把:
BOARD → SOC → ARCH → CMake → SoC code → Clock/Reset → IRQ这条链路基本打通了。
下一步非常关键:
Zephyr 怎么知道你的 Company SoC 里面到底有什么硬件?
答案就是:
Devicetree
可以把它理解成:
Kconfig 决定"编译哪些东西",Devicetree 决定"你的 SoC 上有哪些硬件,以及它们在哪里、怎么连接"。
1. 先建立整个 BSP 的世界观
假设你的公司做了一颗 SoC:
Company SoC │ ├── CPU │ └── Cortex-M4 │ ├── Clock Controller │ └── 0x4000_0000 │ ├── Reset Controller │ └── 0x4000_1000 │ ├── GPIO │ └── 0x4800_0000 │ ├── UART0 │ └── 0x4001_0000 │ ├── UART1 │ └── 0x4001_1000 │ ├── Timer0 │ └── 0x4002_0000 │ └── Interrupt Controller └── CPU private/system interrupt你当然可以直接在 C 代码里写:
# define UART0_BASE 0x40010000# define UART0_IRQ 5但这不是 Zephyr 推荐的方式。
Zephyr 希望你描述成:
Devicetree │ ▼ UART0node│ ├── reg ├── interrupts ├── clocks ├── status └── compatible │ ▼ UART Driver也就是说:
硬件描述和驱动代码分离。
2. Company SoC 的 Devicetree 放在哪里?
典型结构:
zephyr/ └── dts/ ├── arm/ ├── riscv/ ├── x86/ └── bindings/ soc/ └── company/ └── my_soc/ ├── my_soc.dtsi └──... boards/ └── company/ └── my_board/ ├── my_board.dts ├── my_board.yaml └──...一个很重要的原则:
SoC hardware ↓ SoC .dtsi Board hardware ↓ Board .dts例如:
my_soc.dtsi描述:
CPU UART0 UART1 GPIO CLOCK RESET TIMER PLIC/GIC/IRQ而:
my_board.dts描述:
LED Button Board connector External crystal Board-specific peripherals chosen aliases3. .dtsi 和 .dts 到底是什么关系?
这是学习 Zephyr Devicetree 时必须彻底理解的一点。
假设:
Company SoC有:
UART0 UART1 GPIO0 TIMER0那么可以建立:
my_soc.dtsi例如:
/{cpus{cpu0: cpu@0{compatible="company,my-cpu";device_type="cpu";};};soc{uart0: uart@40010000{compatible="company,my-uart";reg=<0x40010000 0x1000>;status="disabled";};uart1: uart@40011000{compatible="company,my-uart";reg=<0x40011000 0x1000>;status="disabled";};gpio0: gpio@48000000{compatible="company,my-gpio";reg=<0x48000000 0x1000>;status="disabled";};};};注意:
status="disabled";这并不是说硬件不存在。
而是:
这个 SoC 提供了这个硬件,但具体 Board 是否使用,由 Board DTS 决定。
4. Board DTS 再打开它
例如:
#include <company/my_soc.dtsi>/{model="Company MyBoard";compatible="company,my-board";};&uart0{status="okay";};&gpio0{status="okay";};最终形成:
│ │ ┌────────▼────────┐ │ Company SoC │ │ │ │ UART0 disabled │ │ UART1 disabled │ │ GPIO0 disabled │ └────────┬────────┘ │ │ include ▼ my_board.dts │ ┌─────┴─────┐ │ │ UART0 GPIO0 okay okay所以:
SoC DTSI 描述"芯片有什么",Board DTS 决定"这块板子用了什么"。
5. compatible 是 Devicetree 最重要的字段之一
例如:
uart0: uart@40010000{compatible="company,my-uart";reg=<0x40010000 0x1000>;};这里:
compatible="company,my-uart";实际上是在告诉 Zephyr:
“这个节点应该由哪一种 driver 来处理?”
对应 binding:
dts/bindings/ serial/ company,my-uart.yaml例如:
compatible:"company,my-uart"include: - base.yaml properties: reg: required:trueinterrupts: required:true于是关系变成:
DTS │ │ compatible ▼"company,my-uart"│ ▼ Binding │ ▼ Driver6. Devicetree Binding 到底是什么?
可以把 Binding 理解成:
Devicetree 节点的"类型定义"。
比如 C:
structuart{uintptr_tbase;intirq;uint32_tclock;}