Zephyr Devicetree 详解-如何读懂 Devicetree
对于 Zephyr 来说要读懂一个 Devicetree 需要具备以下知识基础:
- Devicetree 语法
- Zephyr Devicetree 绑定
- 基本的硬件知识
- 本文中不对基本的硬件知识展开
本文以一个实例说明如何读懂 Zephyr 的 DeviceTree。实例基于 esp32c3_devkitm 开发板,使用 SPI 去驱动 ws2812
west build -b esp32c3_devkitm zephyr/samples/drivers/led_strip
组合完整的 dts 链接到标题
Zephyr 的原生代码中并不存在一个完整的 Devicetree,Zephyr 的 Devicetree 基本是以「片段的形式「存在,这些片段就是前文提到的:.dts, .dtsi,.overlay, 在 Zephyr 构建过程中会从不同的目录获取相应的 Devicetree 片段将其组合到一起。下面是读 esp32c3_devkitm
开发板 Devicetree 的过程。
1 找主板 dts 链接到标题
Zephyr 中的硬件是以开发板为单位,因此从一个开发板的 dts 入手分析,对于 esp32c3_devkitm 开发板的 dts 就在其 board 目录 zephyr/boards/espressif/esp32c3_devkitm/
下找到 esp32c3_devkitm.dts
,开发板的 dts,是在 soc 提供的 dtsi 下添加和板子相关的节点,按照主板的硬件特性对 soc 提供的硬件特性进行覆盖,按照主板/应用的设计对 Zephyr 的默认设备进行选择和覆盖。主板的 dts 内容如下:
/*表明 dts 版本是 v1*/
/dts-v1/;
/* soc 包含文件*/
# include <espressif/esp32c3/esp32c3_mini_n4.dtsi>
/* pin contorl 包含文件*/
# include "esp32c3_devkitm-pinctrl.dtsi"
/* input event 包含文件, 获取头文件中的宏*/
# include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
/* model 节点通用于描述设备或板子的具体型号*/
model = "Espressif ESP32C3-DevkitM";
/* 根的 compatible */
compatible = "espressif,esp32c3";
/* chose 节点,为 Zephyr 选择默认设备 */
chosen {
zephyr,sram = &sram0;
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,bt-hci = &esp32_bt_hci;
};
/* aliases 节点, 为应用选择默认设备 */
aliases {
sw0 = &user_button1;
i2c-0 = &i2c0;
watchdog0 = &wdt0;
};
/* 将 gpio0.9 作为 user_button1, 硬件默认上拉,低电平有效,案件值绑定 INPUT_KEY_0 */
gpio_keys {
compatible = "gpio-keys";
user_button1: button_1 {
label = "User SW1";
gpios = <&gpio0 9 ( GPIO_PULL_UP | GPIO_ACTIVE_LOW )>;
zephyr,code = <INPUT_KEY_0>;
};
};
};
/* 后面都是对 soc 下的 dtsi 中节点进行修改和覆盖 */
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
&usb_serial {
/* requires resoldering of resistors on the board */
status = "okay";
};
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_STANDARD>;
pinctrl-0 = <&i2c0_default>;
pinctrl-names = "default";
};
&trng0 {
status = "okay";
};
&spi2 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
};
&gpio0 {
status = "okay";
};
&wdt0 {
status = "okay";
};
&timer0 {
status = "disabled";
};
&timer1 {
status = "disabled";
};
&twai {
/* requires external CAN transceiver or jumper on RX and TX pins for loopback testing */
status = "disabled";
pinctrl-0 = <&twai_default>;
pinctrl-names = "default";
};
&flash0 {
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
/* Reserve 60kB for the bootloader */
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000F000>;
read-only;
};
/* Reserve 1024kB for the application in slot 0 */
slot0_partition: partition@10000 {
label = "image-0";
reg = <0x00010000 0x00100000>;
};
/* Reserve 1024kB for the application in slot 1 */
slot1_partition: partition@110000 {
label = "image-1";
reg = <0x00110000 0x00100000>;
};
/* Reserve 256kB for the scratch partition */
scratch_partition: partition@210000 {
label = "image-scratch";
reg = <0x00210000 0x00040000>;
};
storage_partition: partition@250000 {
label = "storage";
reg = <0x00250000 0x00006000>;
};
};
};
&esp32_bt_hci {
status = "okay";
};
2 回溯覆盖节点 链接到标题
在 esp32c3_devkitm.dts
中对 soc 的 dtsi 已有的节点进行覆盖,覆盖的节点一般是采用 lable 引用,例如&uart0
,可以很清晰的看到覆盖节点有:
- 设置参数并启用:uart0, i2c0, spi2, flash0
- 启用设备:usb_serial, trng0, gpio0, wdt0, esp32_bt_hci
- 禁用设备:timer0, timer1, twai
对于启用设备的 uart0,i2c0,spi2 配置了默认参数并通过 pinctrl 的节点设置引脚复用,引脚复用的 Devicetree 用法请参考 Zephyr-Pinctrl 相关文章。
未覆盖的参数和启用设备的默认参数要通过 soc dtsi 来查找,在本例中 soc dtsi 就是 dts 中包含的 esp32c3_mini_n4.dtsi
,内容如下,
# include "esp32c3_common.dtsi"
/* 4MB flash */
&flash0 {
reg = <0x0 DT_SIZE_M ( 4 )>;
};
这里可以看到 flash0 的大小为 4M,再下一步回溯到 esp32c3_common.dtsi
,这就是 esp32c3 soc 的共用 dts 文件,里面可以找到 esp32c3 所支持的片上硬件。一般情况下我们只会关心主板要用的硬件设备,这里这里我们以 spi2 和 flash0 为例来说明如何回溯,在 esp32c3_common.dtsi
中
spi2: spi@60024000 {
compatible = "espressif,esp32-spi";
reg = <0x60024000 DT_SIZE_K ( 4 )>;
interrupts = <SPI2_INTR_SOURCE>;
interrupt-parent = <&intc>;
clocks = <&rtc ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "disabled";
};
flash: flash-controller@60002000 {
compatible = "espressif,esp32-flash-controller";
reg = <0x60002000 0x1000>;
#address-cells = <1>;
#size-cells = <1>;
flash0: flash@0 {
compatible = "soc-nv-flash";
reg = <0 0x400000>;
erase-block-size = <4096>;
write-block-size = <4>;
};
};
将 esp32c3_devkitm.dts
和 esp32c3_mini_n4.dtsi
对其进行覆盖得到
spi2: spi@60024000 {
compatible = "espressif,esp32-spi";
reg = <0x60024000 DT_SIZE_K ( 4 )>;
interrupts = <SPI2_INTR_SOURCE>;
interrupt-parent = <&intc>;
clocks = <&rtc ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "okay";
#address-cells = < 0x1 >;
#size-cells = < 0x0 >;
pinctrl-0 = < &spim2_default >;
pinctrl-names = "default";
};
flash: flash-controller@60002000 {
compatible = "espressif,esp32-flash-controller";
reg = < 0x60002000 0x1000 >;
#address-cells = < 0x1 >;
#size-cells = < 0x1 >;
flash0: flash@0 {
compatible = "soc-nv-flash";
reg = <0x0 DT_SIZE_M ( 4 )>;
erase-block-size = < 4096 >;
write-block-size = < 4 >;
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = < 0x1 >;
#size-cells = < 0x1 >;
/* Reserve 60kB for the bootloader */
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000F000>;
read-only;
};
/* Reserve 1024kB for the application in slot 0 */
slot0_partition: partition@10000 {
label = "image-0";
reg = <0x00010000 0x00100000>;
};
/* Reserve 1024kB for the application in slot 1 */
slot1_partition: partition@110000 {
label = "image-1";
reg = <0x00110000 0x00100000>;
};
/* Reserve 256kB for the scratch partition */
scratch_partition: partition@210000 {
label = "image-scratch";
reg = <0x00210000 0x00040000>;
};
storage_partition: partition@250000 {
label = "storage";
reg = <0x00250000 0x00006000>;
};
};
};
};
当几个 Devicestree 文件中有相同属性时,包含文件中属性的值优先级大于被包含的文件中的值,上面 esp32c3_devkitm.dts
包含了 esp32c3_mini_n4.dtsi
,esp32c3_mini_n4.dtsi
包含了 esp32c3_common.dtsi
,那么其属性的覆盖优先级就是 esp32c3_devkitm.dts
> esp32c3_mini_n4.dtsi
>esp32c3_common.dtsi
处理 overlay 链接到标题
覆盖文件 overlay,用于对主板附加/修改/添加硬件的状况下修改 Devicetree,它是在主板 dts 的基础上进行覆盖操作,本例中基于 esp32c3_devkitm 添加了外部的 spi led 灯带,通过 overlay 在 Devicetree 中添加灯带的硬件,其 overlay 文件是 zephyr/samples/drivers/led_strip/boards/esp32c3_devkitm.overlay
, 内容如下
/* 在 spi2 下挂 ws2812 */
&spi2 {
/* Workaround to support WS2812 driver */
line-idle-low;
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";
/* SPI */
reg = <0>; /* ignored, but necessary for SPI bindings */
spi-max-frequency = <6400000>;
/* WS2812 */
chain-length = <1>; /* arbitrary; change at will */
spi-cpha;
spi-one-frame = <0xf0>; /* 11110000:625 ns high and 625 ns low */
spi-zero-frame = <0xc0>; /* 11000000:312.5 ns high and 937.5 ns low */
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
};
/* 将 spi2 的 mosi 连接到 gpio8 */
&pinctrl {
spim2_default: spim2_default {
group2 {
pinmux = <SPIM2_MOSI_GPIO8>;
};
};
};
/ {
aliases {
led-strip = &led_strip;
};
};
经过 ovelay 的覆盖 spi2 节点就变为如下
spi2: spi@60024000 {
compatible = "espressif,esp32-spi";
reg = <0x60024000 DT_SIZE_K ( 4 )>;
interrupts = <SPI2_INTR_SOURCE>;
interrupt-parent = <&intc>;
clocks = <&rtc ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "okay";
#address-cells = < 0x1 >;
#size-cells = < 0x0 >;
pinctrl-0 = < &spim2_default >;
pinctrl-names = "default";
line-idle-low;
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";
/* SPI */
reg = <0>; /* ignored, but necessary for SPI bindings */
spi-max-frequency = <6400000>;
/* WS2812 */
chain-length = <1>; /* arbitrary; change at will */
spi-cpha;
spi-one-frame = <0xf0>; /* 11110000:625 ns high and 625 ns low */
spi-zero-frame = <0xc0>; /* 11000000:312.5 ns high and 937.5 ns low */
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
};
通过前面的 include 和 overlay,就可以得到一个全面的 dts 文件。在 Zephyr 构建过程中也会产生一个完整 zephyr.dts 最终文件,本例中 zephyr.dts 中的 spi2 如下
spi2: spi@60024000 {
compatible = "espressif,esp32-spi";
reg = < 0x60024000 0x1000 >;
interrupts = < 0x13 >;
interrupt-parent = < &intc >;
clocks = < &rtc 0xb >;
dma-clk = < 0x18 >;
dma-host = < 0x0 >;
status = "okay";
#address-cells = < 0x1 >;
#size-cells = < 0x0 >;
pinctrl-0 = < &spim2_default >;
pinctrl-names = "default";
line-idle-low;
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";
reg = < 0x0 >;
spi-max-frequency = < 0x61a800 >;
chain-length = < 0x1 >;
spi-cpha;
spi-one-frame = < 0xf0 >;
spi-zero-frame = < 0xc0 >;
color-mapping = < 0x2 0x1 0x3 >;
};
};
和我们自己通过阅读 Devicetree 的几乎一样,只是引用头文件宏的地方被替换成了值,可读性会差一些。
读懂节点 链接到标题
从上一节我们可以整合出一个完整的 Devicetree,而 Devicetree 是由各个节点组成,读懂节点就是读懂了 Devicetree 描述的硬件。这里以 ws2812 为例,一直回溯到根节点,进行分析
/ {
soc {
#address-cells = < 0x1 >;
#size-cells = < 0x1 >;
compatible = "simple-bus";
ranges;
spi2: spi@60024000 {
compatible = "espressif,esp32-spi";
reg = <0x60024000 DT_SIZE_K ( 4 )>;
interrupts = <SPI2_INTR_SOURCE>;
interrupt-parent = <&intc>;
clocks = <&rtc ESP32_SPI2_MODULE>;
dma-clk = <ESP32_GDMA_MODULE>;
dma-host = <0>;
status = "okay";
#address-cells = < 0x1 >;
#size-cells = < 0x0 >;
pinctrl-0 = < &spim2_default >;
pinctrl-names = "default";
line-idle-low;
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";
/* SPI */
reg = <0>; /* ignored, but necessary for SPI bindings */
spi-max-frequency = <6400000>;
/* WS2812 */
chain-length = <1>; /* arbitrary; change at will */
spi-cpha;
spi-one-frame = <0xf0>; /* 11110000:625 ns high and 625 ns low */
spi-zero-frame = <0xc0>; /* 11000000:312.5 ns high and 937.5 ns low */
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
};
};
}
}
节点的父子关系 /
->soc
->spi2
->ws2812
.
soc 的属性 链接到标题
compatible = "simple-bus";
表示是一个简单总线,Zephyr 并不提供专用的绑定。
ranges;
没有给定地址范围,所以 soc 的子节点使用的地址是绝对地址。
#address-cells = < 0x1 >;
和 #size-cells = < 0x1 >;
用于描述 soc 子节点的寄存器信息,有一个地址和一个长度。
spi2 子节点 链接到标题
spi2: spi@60024000
spi 的总线地址为 0x60024000, lable 为 spi2。
compatible = "espressif,esp32-spi";
使用 espressif,esp32-spi.yaml 绑定,后续的 spi2 属性解释都要参考 espressif,esp32-spi.yaml。
espressif,esp32-spi.yaml
include 了 spi-controller.yaml, pinctrl-device.yaml
, spi 节点的解释可以从 3 部分找:
spi-controller.yaml
SPI 控制器共有部分pinctrl-device.yaml
引脚控制复用部分espressif,esp32-spi.yaml
esp32c3 SPI 特有部分
SPI 控制器共有部分 链接到标题
这些属性的含义都从 spi-controller.yaml 中去查找,而 spi-contorller.yaml 又 include base.yaml, 也需要从 base.yaml 中查找:
reg = <0x60024000 DT_SIZE_K ( 4 )>;
spi2 的寄存器基地址为 0x60024000 ( 占用 1 个单位对应其父节点 soc 的 #address-cells = < 0x1 >;
) , 寄存器组的大小为 4K ( 占用 1 个单位对应其父节点 soc 的 #size-cells = < 0x1 >;
) 。
interrupts = <SPI2_INTR_SOURCE>;
使用 SPI2_INTR_SOURCE 中断源
interrupt-parent = <&intc>;
中断控制器为 intc
clocks = <&rtc ESP32_SPI2_MODULE>
时钟源为用于 ESP32_SPI2_MODULE 的 rtc
#address-cells = < 0x1 >;
和 #size-cells = < 0 >;
用于描述 SPI 子节点的寄存器信息,有一个地址和一个长度。
引脚控制复用部分 链接到标题
这部分属性的作用从 pinctrl-device.yaml
找
pinctrl-0 = < &spim2_default >;
使用 spim2_default 配置其引脚配置
pinctrl-names = "default";
默认配置
esp32c3-SPI 特有部分 链接到标题
这部分属性的作用从 espressif,esp32-spi.yaml
找
dma-clk = <ESP32_GDMA_MODULE>;
DMA clock source
dma-host = <0>;
DMA Host - 0 -> SPI2,1 -> SPI3
line-idle-low;
Default MISO and MOSI pins GPIO level when idle. Defaults to high by default.
ws2812 子节点 链接到标题
ws2812 挂在 spi 下
led_strip: ws2812@0
ws2812 的总线地址为 0 ( 对于 spi 来说总线地址就是片选,如果 spi2 下还挂有一个就是 ws2812_1@1
) , lable 为 led_strip。
compatible = "worldsemi,ws2812-spi";
使用 worldsemi,ws2812-spi.yaml 绑定,后续的 ws2812 属性解释都要参考 worldsemi,ws2812-spi.yaml。
worldsemi,ws2812-spi.yaml
include 了 spi-device.yaml, ws2812.yaml
, ws2812 节点的解释可以从 3 部分找:
spi-device.yaml.yaml
SPI 设备共有部分ws2812.yaml
ws2812 部分worldsemi,ws2812-spi.yaml
ws2812-spi 特有部分
SPI 控制器共有部分 链接到标题
reg = <0>;
表示片选 ( 对应到 spi2 下 #address-cells = < 0x1 >;
和 #size-cells = < 0 >;
)
spi-max-frequency = <6400000>;
SPI 速度设置为 6.4M, 一个 bit 156.25ns
spi-cpha
CPHA 在第二个时钟边沿采样数据
ws2812 部分 链接到标题
chain-length = <1>;
The number of devices in the daisy-chain. LED 灯带链上有几个 LED
color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>;
Channel to color mapping ( or pixel order ) . 数据通道到颜色的映射,这里是 RGB
ws2812-spi 特有部分 链接到标题
spi-one-frame = <0xf0>;
8-bit SPI frame to shift out for a 1 pulse, 8bit spi 帧对应表示出 1 的 pulse , 0xf0=b11110000:4 个高 bit 625 ns ,4 个低 bit 625 ns ( 4*156.25 )
spi-zero-frame = <0xc0>;
8-bit SPI frame to shift out for a 0 pulse, 8bit spi 帧对应表示出 0 的 pulse 0xc0=b11000000:2 个高 bit 312.5 ns ( 2*156.25 ) 6 个低 bit 937.5 ns ( 6*156.25 )
这这里为什么要将 spi 的速度设置为 6.4M, frame 的数据选择,和 ws2812 的硬件知识相关。
小结 链接到标题
从本文的分析过程可以看到,读懂 Zephyr Devicetree 需要 2 步:第一步是整合得到完整的 Device tree,第二步是根据绑定文件的解释理解每个节点的作用。在这一过程中又依赖 Devicetree 语法,Zephyr Devicetree 绑定和硬件的基础知识。0