首页 / 资讯中心 / 文章详情

Zephyr BSP: 16-Zephyr Devicetree to C 代码生成全过程

Zephyr BSP: 16-Zephyr Devicetree to C 代码生成全过程 ★ FEATURED ARTICLE
摘要:本文深入剖析 Zephyr RTOS 中 Devicetree 从.dts文本到 C 代码中DT_*宏的完整生成链路。文章从整体流水线出发,依次讲解.dts/.dtsi/.overlay的合并与预处理、zephyr.dts与devicetree_generated.h的区别、EDT(Enhanced Devicetree)对象模型、Binding 的作用,以及DT_NODELABEL()、DT_PATH()、DT_ALIAS()、DT_CHOSEN()、DT_INST()、DT_REG_ADDR()、DT_IRQN()、DT_PROP()等核心宏的由来与用法。最终串联 Init Priority、Driver Dependency、Dependency Graph,揭示DEVICE_DT_DEFINE()如何将 Devicetree 节点转化为struct device,并给出 BSP 开发调试的实用检查清单。Zephyr Devicetree → C 代码生成全过程你前面已经把:13 Init Priority→14 Driver Dependency→15 Devicetree Dependency Graph串起来了。下一步非常关键:Devicetree 文件到底是怎么一步一步变成 C 代码里的 DT_NODELABEL()、DT_INST()、DT_PROP()、DEVICE_DT_DEFINE() 的?这篇建议重点解决一个问题:.dts 是文本,最终却能让 C 编译器看到各种 DT_* 宏。中间到底发生了什么?1. 先看完整流水线先建立一张总图:Devicetree Source │ │ ▼ board.dts / .dtsi │ │ │ C preprocessor │ ▼ merged devicetree │ │ ▼ dtc / edtlib │ ┌─────────────┴─────────────┐ │ │ ▼ ▼ zephyr.dts edt.pickle │ │ │ │ ▼ ▼ devicetree_generated.h Python EDT │ │ │ │ └─────────────┬─────────────┘ │ ▼ Zephyr build system │ ▼ C/C++ compiler │ ▼ Driversource│ ▼ DEVICE_DT_DEFINE(...)│ ▼ struct device │ ▼ final firmware这里最容易产生一个误解:Zephyr 不是简单地把 .dts 转换成一个 .h。实际上存在两条重要路线:DTS │ ┌────────┴────────┐ │ │ ▼ ▼ devicetree.h Python EDT │ │ │ └── binding / dependency / │ instance / property analysis │ ▼ C preprocessor │ ▼ DT_* macros │ ▼ Driver C code而EDT(Enhanced Devicetree)是理解现代 Zephyr Devicetree 的关键。2. 第一层:.dts 到底是什么?例如我们有:/{soc{uart0:serial@40004000{compatible="company,my-uart";reg=0x400040000x1000;interrupts=5;clock-frequency=48000000;status="okay";};};};这里描述的是:Node ├── name │ └── serial@40004000 │ ├── label │ └── uart0 │ ├── compatible │ └── company,my-uart │ ├── reg │ └── 0x40004000 0x1000 │ ├── interrupts │ └──5│ ├── clock-frequency │ └──48000000│ └── status └── okay注意:Devicetree 本身不是 C。它只是一个硬件描述语言。所以:DT_NODELABEL(uart0)并不是 .dts 里面天然存在的东西。它是 Zephyr 后续生成出来的C 宏接口。3. 第二层:.dtsi 和 .dts 先合并真实 Zephyr 项目中,通常不是一个 DTS 文件。例如:boards/ company/ my_board/ my_board.dts里面可能:#includelt;company/soc.dtsigt;uart0{status="okay";};而:soc.dtsi里面可能已经定义:uart0: serial@40004000{compatible="company,my-uart";reg=...;};所以最终并不是分别处理:soc.dtsi my_board.dts而是先形成:soc.dtsi │ │ board.dts │ ▼ C preprocessor │ ▼ merged DTSsource可以理解为:SoC 描述 + Board 描述 + Shield + overlay + chosen + aliases + 其他 .dtsi │ ▼ 最终 Devicetree4. Overlay 在什么时候进入?比如:boards/company/my_board/my_board.dts定义:uart0{status="okay";};你的应用又有:app.overlay里面:uart0{current-speed=lt;115200gt;;};最终得到:uart0 ├── compatible="company,my-uart"├── reg=...├── status="okay"└── current-speed=115200所以 overlay 并不是运行时配置。它是在build time修改 Devicetree。这一点非常重要:overlay │ ▼ buildtime│ ▼ Devicetree 被修改 │ ▼ C code 使用最终结果而不是:firmware running │ ▼ 读取 overlay完全不是这样。5. 第三层:C preprocessor 处理 DTS这是理解 Zephyr BSP 的一个关键点。Devicetree 的预处理阶段会处理:# include# define# if# ifdef之类的东西。因此:board.dts │ ├── include soc.dtsi ├── include bindings └── overlay │ ▼ preprocessed DTS之后才交给 Devicetree compiler / Zephyr Devicetree tooling。6. 第四层:生成 zephyr.dts构建过程中你通常可以在:build/zephyr/附近看到:zephyr.dts这个文件非常值得你直接打开看。它代表:最终合并之后的 Devicetree。例如:soc{uart0: serial@40004000{compatible="company,my-uart";reg=0x40004000 0x1000;interrupts=5;status="okay";};};此时:.dts .dtsi .overlay │ ▼ 最终 Devicetree │ ▼ build/zephyr/zephyr.dts7. zephyr.dts 和 devicetree_generated.h 不一样这是很多刚开始研究 Zephyr 的人最容易混淆的地方。你可以把它们理解成:zephyr.dts给人和 Devicetree 工具看的:Node ├── compatible ├── reg ├── interrupts ├── status └── propertiesdevicetree_generated.h给 C 编译器看的:# define DT_N_S_soc_S_serial_40004000 ...# define DT_N_S_soc_S_serial_40004000_REG_ADDR ...# define DT_N_S_soc_S_serial_40004000_IRQ ...也就是说:zephyr.dts │ │ Devicetree representation ▼ devicetree_generated.h │ │ C macro representation ▼ Csource8. 最关键的文件:devicetree_generated.h假设:uart0: serial@40004000{compatible="company,my-uart";reg=0x40004000 0x1000;interrupts=5;status="okay";};Zephyr 会生成大量宏。概念上类似:# define DT_N_S_soc_S_serial_40004000 ...然后:DT_NODELABEL(uart0)最终会通过宏展开找到对应 node identifier。9. 为什么 Devicetree Node 会变成奇怪的宏名字?这是 Zephyr Devicetree 最值得理解的机制之一。假设:soc{uart0: serial@40004000{...};};它有一个完整 path:/soc/serial@40004000Zephyr 需要把这个路径编码成 C identifier。于是概念上变成:/soc/serial@40004000 ↓ DT_N_S_soc_S_serial_40004000这里:/ → S @ → 处理成合法 identifier\- → 处理成合法 identifier具体编码规则比较复杂,但核心思想非常简单:把 Devicetree path 编码成合法的 C identifier。所以:Devicetreenode↓ canonicalnodeidentifier ↓ C preprocessor macro10. DT_NODELABEL(uart0) 是怎么来的?你写:# define UART_NODE DT_NODELABEL(uart0)看起来像:uart0直接变成 node。其实不是。可以把它理解成:DT_NODELABEL(uart0)│ ▼ DT_N_NODELABEL_uart0 │ ▼ DT_N_S_soc_S_serial_40004000也就是说:
阅读完成 · 觉得有帮助?
咨询建站