Zephyr Devicetree 详解-如何写 Devicetree
写 Zephyr Devicetree 的方法就是读 Devicetree 的过程反过来:
- 根据硬件内容添加单个节点
- 根据绑定文件和硬件配置特性写节点属性
什么时候需要写 Devicetree 链接到标题
在 Zephyr 中有下面几种情况需要写 Devicetree
- 添加 SOC
- 添加新的主板
- 硬件调整和修改
- 同硬件不同应用功能
SOC 的 Devicetree 是依据 SOC 的硬件特性进行编写,一般情况下是由 SOC 供应商来提供。因此本文的内容描述情况 2~4。
写主板 Devicetree 链接到标题
写主板设备树最快速的方法就是「抄」,找一个使用了相同 SOC 的主板复制过来然后修改即可。为了对写主板 Devicetree 有一个认识,本文则是基于 SOC 开始写。
本文以 ESP32-C3-LCDkit 的硬件为例说明如何建立主板 Devicetree。 ESP32-C3-LCDkit 的原理图参考:https://docs.espressif.com/projects/esp-dev-kits/zh_CN/latest/_static/esp32-c3-lcdkit/schematics/SCH_ESP32-C3-C6-LCDkit-MB_V1.1_20230417.pdf
外部设备有 链接到标题
- 外部 UART 接口: TXD0,RXD0, 使用 uart0
- USB: IO18,19, 使用片上 usb
- EC11 旋转编码器:IO6,9,10
- SPI LCD: IO0-2,5,7,18,19,22,23,使用 spi2
- 按键: IO9
- 按键: EN, EN 为 reset,不需要出现在 Devicetree
- WS2812C LED: IO8, SPI 被 LCD 占用,RMT 无驱动,因此不用该设备
- IR: IO4, Zephyr 驱动目前不支持红外遥控,因此不用该设备
- PA 功放: IO3, 使用 I2S 的 PDM 模式,Zephyr 驱动目前不支持 esp32c3 的 i2s,因此不用该设备
外部 UART 接口 链接到标题
使用 uart1, 波特率默认配置为 115200,将其作为 console 输出
/ {
chosen {
zephyr,console = &uart0;
};
}
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
current-speed
- 设置默认波特率pinctrl-0/pinctrl-names
- 为uart0配置引脚,关于 pinctrl 之后文统一说明。
USB 链接到标题
esp32c3 的 USB 只含有 CDC-ACM 虚拟串口及 JTAG 适配器功能,这里将其作为 usb 串口,并且 shell 使用该串口通信
/ {
chosen {
zephyr,shell-console = &usb_serial;
};
}
&usb_serial {
status = "okay";
};
EC11 旋转编码器 链接到标题
旋转编码器使用 EC11,硬件连线如下:
- A-IO10
- B-IO6
- D-IO9
- C, E-GND
其中 AB,作为旋转编码器的编码线用于检查相位信号变化, D 单独作为按键。
在 Zephyr 中有旋转编码器的驱动,找到对应的绑定文件 zephyr/dts/bindings/input/gpio-qdec.yaml
, 参考说明添加
# include <zephyr/dt-bindings/input/input-event-codes.h>
/{
ec11_qdec: qdec{
compatible = "gpio-qdec";
gpios = <&gpio0 6 ( GPIO_PULL_UP | GPIO_ACTIVE_HIGH )>,
<&gpio0 10 ( GPIO_PULL_UP | GPIO_ACTIVE_HIGH )>;
steps-per-period = <4>;
zephyr,axis = <INPUT_REL_X>;
sample-time-us = <2000>;
idle-timeout-ms = <200>;
};
}
在绑定中的属性描述并不一定能理解属性的作用,这时需要具备 ec11 硬件驱动的知识和去看旋转编码器的驱动 zephyr/drivers/input/input_gpio_qdec.c
,但这不是本文的重点,这里就简要说明一下:
- steps-per-period
- 旋转编码器有两个输出信号,称为 A 和 B。这两个信号在编码器旋转时会产生方波,它们之间有 90 度的相位差。编码器的旋转方向和步长就可以通过这两个信号的变化来确定。
- 信号在一个周期内有一次上升和一次下降,A/B 两条信号线就是两次上升,两次下降,
steps-per-period
可取的值有- 4: A 上升 -> B 上升 -> A 下降 -> B 下降,每个边沿计数,边沿出现的顺序确定方向,也叫做「Full-period mode」
- 2: A 上升 -> A 下降, 只有信号 A 的变化被计数, 信号 B 的高低电平用于确定方向,也叫做"Half-period mode"
- 1: A 上升,只计数 A 信号的上升沿,B 信号仍用于确定方向, 也叫做「Quarter-period mode」
Full-period mode:
B: ¯¯¯|___|¯¯¯|___|¯¯¯
A: _|¯¯¯|___|¯¯¯|___|
1 2 3 4 1 2 3 4 ( 计数点 )
Half-period mode:
B: ¯¯¯|___|¯¯¯|___|¯¯¯
A: _|¯¯¯|___|¯¯¯|___|
1 2 1 2 ( 计数点 )
Quarter-period mode:
A: ¯¯¯|___|¯¯¯|__|¯¯¯
B: _|¯¯¯|___|¯¯¯|___|
1 1 ( 计数点 )
zephyr,axis
- 旋转编码器对应的 input eventINPUT_REL_X
sample-time-us
- 从边沿中断产生开始多长时间后对 A/B 电平进行采样idle-timeout-ms
- 据边沿中断产生多长时间后无信号变化就当作是旋转停止
旋转编码器的 D 作为普通按键,连接到 IO9,使用 gpio-keys
绑定,为其关联 input event INPUT_KEY_0
/{
gpio_keys {
compatible = "gpio-keys";
ec11_btn: btn {
label = "EC11 BTN";
gpios = <&gpio0 9 ( GPIO_PULL_UP | GPIO_ACTIVE_LOW )>;
zephyr,code = <INPUT_KEY_0>;
};
};
}
SPI 屏 链接到标题
SPI 屏使用 1.28 寸 TFT 屏幕,硬件连线如下: https://docs.espressif.com/projects/esp-dev-kits/zh_CN/latest/_static/esp32-c3-lcdkit/schematics/SCH_ESP32-C3-LCDkit-DB_V1.0_20230329.pdf
- LCD_BL_CTRL-IO5
- LCD_D/C-IO2
- LCD_CS-IO7
- LCD_SCL-IO1
- LCD_SDA-IO2
屏的规格如下: https://docs.espressif.com/projects/esp-dev-kits/zh_CN/latest/_static/esp32-c3-lcdkit/datasheets/1.28_TFT_240x240_SPI_%E5%B1%8F.pdf 与设备树相关的参数如下:
- Driver: GC9A01
- 大小: 240*240
- 写时钟最小周期:10ns
- 读时钟最小周期:150ns
Zephyr 所有的 SPI 屏幕都纳入到 MIPI-DBI 模型下,因此需要查看绑定文件 zephyr/dts/bindings/mipi-dbi/zephyr,mipi-dbi-spi.yaml
,屏的驱动使用 GC9A01,还要查看绑定文件 zephyr/dts/bindings/display/galaxycore,gc9x01x.yaml
/{
/* MIPI DBI */
mipi_dbi {
compatible = "zephyr,mipi-dbi-spi";
spi-dev = <&spi2>;
dc-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
write-only;
#address-cells = <1>;
#size-cells = <0>;
gc9a01: gc9a01@0 {
status = "okay";
compatible = "galaxycore,gc9x01x";
reg = <0>;
mipi-max-frequency = <100000000>;
pixel-format = <PANEL_PIXEL_FORMAT_RGB_888>;
display-inversion;
width = <240>;
height = <240>;
};
};
}
&spi2 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 7 GPIO_ACTIVE_LOW>;
};
spi-dev
表示选择使用的 SPI 设备dc-gpios
数据命令选择信号write-only
只写不读gc9a01
作为子节点挂在 mipi dbi 下mipi-max-frequency
通信频率- 由于使用的是 SPI,因此这里就算指 SPI SCL 的频率,因为只写,而规格书写的最小写时钟周期为 10ns,换算为频率就是 100M
pixel-format
颜色格式为 RGB888display-inversion
显示反转模式。从帧存储器到显示器的每个位都反转width
显示器的宽height
显示器的高
由于 MIPI DBI 使用了 spi2,也要对 spi2 进行配置
pinctrl-0/pinctrl-names
引脚的复用配置cs-gpios
片选引脚
除了以上内容外,SPI 屏还有一条背光线 LCD_BL_CTRL-IO5, 我们使用 pwm 可以将其驱动起来,在 zephyr/dts/riscv/espressif/esp32c3/esp32c3_common.dtsi
已经添加了 pwm 节点 ledc0
,我们按照 zephyr/dts/bindings/pwm/espressif,esp32-ledc.yaml
对其添加参数既可以。PWM LED 要参考 zephyr/dts/bindings/led/pwm-leds.yaml
绑定
/ {
pwmleds {
compatible = "pwm-leds";
pwm_lcd_backlight: pwm_led_gpio0_5 {
label = "LCD BACKLIGHT";
pwms = <&ledc0 0 1000 PWM_POLARITY_NORMAL>;
};
};
};
&ledc0 {
pinctrl-0 = <&ledc0_default>;
pinctrl-names = "default";
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel0@0 {
reg = <0x0>;
timer = <0>;
};
};
pwmleds
节点是将一组使用 pwd 的 led 集合在一起,其子节点 pwm_lcd_backlight 才是实际的 lcd 背光节点label
- 指定节点的 LABLEpwms
- 说明 LCB 背光关联的是哪个 pwm,参数为多少pinctrl-0/pinctrl-names
- 引脚的复用配置,后文说明
channel0 下
reg
指定使用 channel 0 的 pwm 通道timer
使用 timer 0
内部设备 链接到标题
内部设备选用
- 内置的 flash
- 蓝牙
- WIFI
Flash 配置 链接到标题
这个项目 flash 有 4M,不使用 mcuboot,并且要引入 nvs 和 lfs,具体如下
- 最开始 1.5M 放 image
- 中间的 0.5M 放 nvs
- 最后 2M 放 lfs
在 flash0 的节点下添加 partitions 子节点,写法参考 zephyr/dts/bindings/mtd/fixed-partitions.yaml
。添加 3 个 partition。
通过 zephyr,flash
指定 zephyr 使用 flash0,zephyr,code-partition
指定用 image_partition 放可执行代码。
添加 fstab
节点,用于放置文件系统节点 lfs
, 写法参考 zephyr/dts/bindings/fs/zephyr,fstab,littlefs.yaml
/{
chosen {
zephyr,flash = &flash0;
zephyr,code-partition = &image_partition;
};
fstab {
compatible = "zephyr,fstab";
lfs: lfs {
compatible = "zephyr,fstab,littlefs";
mount-point = "/lfs";
partition = <&lfs_part>;
automount;
read-size = <16>;
prog-size = <16>;
cache-size = <64>;
lookahead-size = <32>;
block-cycles = <512>;
};
};
}
&flash0 {
status = "okay";
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
image_partition: partition@0 {
label = "image";
reg = <0x00000000 0x00180000>;
read-only;
};
storage_partition: partition@180000 {
label = "storage";
reg = <0x00180000 0x00080000>;
};
lfs_part: partition@220000 {
label = "lfs";
reg = <0x00200000 0x00200000>;
};
};
};
mount-point
- mount 路径partition
- 指定文件系统使用的 partitionautomount
- 开机自动 mountread-size
- 文件系统读取的单位大小prog-size
- 文件系统写的单位大小cache-size
- 缓存大小lookahead-size
- 前瞻缓冲区的大小 ( 以字节为单位 ) 。block-cycles
- 将数据移至另一个块之前的擦除周期数,用于磨损均衡;
蓝牙 链接到标题
soc 的 dtsi 中已存在蓝牙节点 esp32_bt_hci
, 直接启用,然后将 zephyr,bt-hci
指定使用该节点既可。
/{
chosen {
zephyr,bt-hci = &esp32_bt_hci;
};
&esp32_bt_hci {
status = "okay";
};
}
WIFI 链接到标题
soc 的 dtsi 中已存在 WIFI 节点 wifi
, 直接启用既可。
&wifi {
status = "okay";
};
引脚配置 链接到标题
前面的串口,SPI, PWM 引用了引脚配置
uart0_default
spim2_default
ledc0_default
这些我们可以建立一个单独的文件 esp32_c3_lcdkit-pinctrl.dtsi 进行,具体的写法可以参考 Zerphyr-pinctrl
#include <zephyr/dt-bindings/pinctrl/esp-pinctrl-common.h>
#include <dt-bindings/pinctrl/esp32c3-pinctrl.h>
#include <zephyr/dt-bindings/pinctrl/esp32c3-gpio-sigmap.h>
uart0_default: uart0_default {
group1 {
pinmux = <UART0_TX_GPIO21>;
output-high;
};
group2 {
pinmux = <UART0_RX_GPIO20>;
bias-pull-up;
};
};
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MOSI_GPIO2>,
<SPIM2_SCLK_GPIO1>,
<SPIM2_CSEL_GPIO7>;
};
};
ledc0_default: ledc0_default {
group1 {
pinmux = <LEDC_CH0_GPIO5>;
output-enable;
};
};
Devicetree 整合 链接到标题
实际操作过程中会建立一个 esp32_c3_lcdkit.dts,依次添加各个片段,整合到一起,新的节点放到根节点 /{}
下,修改/覆盖 soc 中已经存在的节点和 /{}
同级。此外为 zephyr,sram
指定使用 sram0,最后再加上 include 的 dtsi 就完成了。
/* esp32c3 内置封装 4M Flash soc 的 dtsi*/
# include <espressif/esp32c3/esp32c3_mini_n4.dtsi>
/* 引脚配置的 dtsi*/
# include "esp32_c3_lcdkit-pinctrl.dtsi"
/* 引用按键值 */
# include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
chosen {
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &image_partition;
zephyr,console = &uart0;
zephyr,shell-console = &usb_serial;
zephyr,display = &gc9a01;
zephyr,bt-hci = &esp32_bt_hci;
};
/* 旋转编码器旋转轴 */
ec11_qdec: qdec{
compatible = "gpio-qdec";
gpios = <&gpio0 6 ( GPIO_PULL_UP | GPIO_ACTIVE_HIGH )>,
<&gpio0 10 ( GPIO_PULL_UP | GPIO_ACTIVE_HIGH )>;
steps-per-period = <4>;
zephyr,axis = <INPUT_REL_X>;
sample-time-us = <2000>;
idle-timeout-ms = <200>;
};
/* 旋转编码器按键 */
gpio_keys {
compatible = "gpio-keys";
ec11_btn: btn {
label = "EC11 BTN";
gpios = <&gpio0 9 ( GPIO_PULL_UP | GPIO_ACTIVE_LOW )>;
zephyr,code = <INPUT_KEY_0>;
};
};
/* SPI LCD 屏 */
mipi_dbi {
compatible = "zephyr,mipi-dbi-spi";
spi-dev = <&spi2>;
dc-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
write-only;
#address-cells = <1>;
#size-cells = <0>;
gc9a01: gc9a01@0 {
status = "okay";
compatible = "galaxycore,gc9x01x";
reg = <0>;
mipi-max-frequency = <100000000>;
pixel-format = <PANEL_PIXEL_FORMAT_RGB_888>;
display-inversion;
width = <240>;
height = <240>;
};
};
/* SPI LCD 屏背光 LED */
pwmleds {
compatible = "pwm-leds";
pwm_lcd_backlight: pwm_led_gpio0_5 {
label = "LCD BACKLIGHT";
pwms = <&ledc0 0 1000 PWM_POLARITY_NORMAL>;
};
};
/* 文件系统表挂载 lfs */
fstab {
compatible = "zephyr,fstab";
lfs: lfs {
compatible = "zephyr,fstab,littlefs";
mount-point = "/lfs";
partition = <&lfs_part>;
automount;
read-size = <16>;
prog-size = <16>;
cache-size = <64>;
lookahead-size = <32>;
block-cycles = <512>;
};
};
}
/* 启用 USB 串口 */
&usb_serial {
status = "okay";
};
/* 启用串口,并配置默认波特率和引脚 */
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
/* 为 LCD 启用 spi2,并配置引脚 */
&spi2 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 7 GPIO_ACTIVE_LOW>;
};
/* 为 LCD 背光启用 pwm,并配置引脚和使用的 pwm 通道 */
&ledc0 {
pinctrl-0 = <&ledc0_default>;
pinctrl-names = "default";
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel0@0 {
reg = <0x0>;
timer = <0>;
};
};
/* 启用蓝牙 */
&esp32_bt_hci {
status = "okay";
};
/* 启用 WIFI */
&wifi {
status = "okay";
};
应对硬件调整 链接到标题
Zephyr应对硬件调整非常简单,就是修改Devicetree,例如我调整了SPI LCD的连线
- LCD_CS-IO13
- LCD_SCL-IO14
- LCD_SDA-IO15
只用对esp32_c3_lcdkit-pinctrl.dtsi中spi2的引脚配置进行修改
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MOSI_GPIO15>,
<SPIM2_SCLK_GPIO14>,
<SPIM2_CSEL_GPIO13>;
};
};
同硬件不同应用功能 链接到标题
硬件不变的情况下,针对不同应用,我想做一些调整,例如:
- 不使用蓝牙
- 不使用uart0
- 将usb_serial作为console
这种情况下适合使用覆盖文件,建立lcdkid_cut.overlay,内容如下
/ {
chosen {
/*删除属性 zephyr,bt-hci*/
/delete-property/ zephyr,bt-hci;
};
};
/ {
chosen {
/* 修改属性 */
zephyr,console = &usb_serial;
};
};
/* 禁用 uart0 */
&uart0 {
status = "disabled";
};
/* 禁用 蓝牙 */
&esp32_bt_hci {
status = "disabled";
};