Zephyr 快速指南

DTS 快速指南

DTS 详细介绍请参考 Zephyr 配置系统

DTS 语法和使用简介

快速入门请参考 DTS基本语法一个实际硬件的例子

修改 DTS 配置

Zephyr 中 DTS 是分散在多个文件中的, 详见 unit-address, 修改 DTS 时修改哪个文件要视情况而定

  1. 到对应 test/sample 源码目录下(test 通常是 zephyr/tests/*/boards, sample 通常是 zephyr/samples/*/boards)新增(如果没有)对应 board 的 overlay 文件并进行修改

  2. 如下 overlay 例子是增加一个 led 节点, 设置相关 gpio 口, 并为该节点取别名 led0 :

/ {
  aliases {
    led0 = &led_0;
  };

  gpio-led {
    compatible = "gpio-leds";
    led_0: led_0 {
      gpios = <&gpioa 25 0>;
    };
  };
};
  1. 如下例子是修改已有 DTS 中的配置(将 status 改为 okay)

&gpioa {
  status = "okay";
};

如何确认 DTS 最终配置是否正确

Zephyr DTS 因为涉及多个文件, 同一个配置项可能会被多处设置或重置, 所以直接看某个文件不能反映最终的结果, 如:

  1. 检查修改(overlay)是否生效

  2. 检查某个设备的状态(okaydisable)或属性设置是否符合预期

此时可以查看 build/zephyr/zephyr.dts 文件, 它是最终生成的 DTS 文件

如何查看某个设备的 binding 文件

binding 文件介绍请参考 DTS binding

  1. 到定义设备的 dts 中找到其 compatible 字段, 如: compatible = "realtek,ameba-rcc";

  2. 查找名为 realtek,ameba-rcc.ymal 文件, 确认其中 compatible 值为 "realtek,ameba-rcc", 则该文件为对应 binding 文件

注意

这种方法只有 binding 文件命名遵循了与 compatible 字段相同的规则才可以使用这种方法, 但这种规则在 zephyr 中不是强制的, 若不一致需要通过方法 2

在代码中如何访问 DTS

访问 DTS 通常是为了获取某个节点的属性或者驱动, 可以参照如下步骤:

  1. 假如有 DTS 内容如下:

/ {
    soc {
        serial0: serial@40002000 {
            reg = <0x4000200 0x100>
            status = "okay";
            current-speed = <115200>;
            /* ... */
        };
    };

    aliases {
        my-serial = &serial0;
    };

    chosen {
        zephyr,console = &serial0;
    };
};
  1. 首先获取某个节点标志符. DTS 中有多种方法标志一个节点, 所以也对用多种方法获取节点:

/* Option 1: by node label */
#define MY_SERIAL DT_NODELABEL(serial0)

/* Option 2: by alias */
#define MY_SERIAL DT_ALIAS(my_serial)

/* Option 3: by chosen node */
#define MY_SERIAL DT_CHOSEN(zephyr_console)

/* Option 4: by path */
#define MY_SERIAL DT_PATH(soc, serial_40002000)
  1. 上述代码中宏 MY_SERIAL 即表示节点标识符, 利用它可以进一步得到节点属性或驱动

/* Demo 1: conditional compilation based on node status */
#if DT_NODE_HAS_STATUS(MY_SERIAL, okay)
//Desired code when node is okay(enabled)
#endif

/* Demo 2: get normal property */
u32 speed = DT_REG_ADDR(MY_SERIAL);  //=0x4000200

/* NOTE: some property have exclusive macro */
/* Demo 3: get reg info */
u32 addr = DT_REG_ADDR(MY_SERIAL);  //=0x4000200
u32 size = DT_REG_SIZE(MY_SERIAL);  //=0x100
  1. 更多进阶用法参考 扩展阅读

在 Kconfig 中如何访问 DTS

参考: Kconfig 快速指南

在 CMake 中如何访问 DTS

参考: CMake 快速指南

如何定位 undefined reference 的 DTS 符号问题

  • DTS 中的定义的节点会在驱动代码中被定义为某个符号, 其标识符是按照某些编码规范生成, 如 __device_dts_ord_22

  • 实际使用中可能会遇到类似如下的编译错误:

    /opt/rtk-toolchain/asdk-12.3.1-4431/linux/newlib/bin/../lib/gcc/arm-none-eabi/12.3.1/../../../../arm-none-eabi/bin/ld.bfd: app/libapp.a(test_counter.c.obj):(.rodata.devices+0x0): undefined reference to `__device_dts_ord_22'
    
  • 出现该问题的原因通常是驱动层之上的代码引用了一个符号(驱动实例), 但链接时没有找到该驱动实例的符号定义(代码实现)

  • 这里无法直接通过名字 __device_dts_ord_22 定位到相关的代码区域, 下面给出一个针对上述错误的排查流程

  1. 首先定位出问题的 DTS 节点: 通过 test_counter.c.obj 可以确定引用该符号的源文件是 test_counter.c, 此时可以选择直接查看该文件, 分如下两种情况:

    1. 如果只有一个驱动实例引用, 就可以通过代码确定对应的 DTS 节点信息(节点名称, label 等), 例如可能有如下代码:

      #define I2C_DEV_NODE DT_ALIAS(i2c_0)
      const struct device *const i2c_dev = DEVICE_DT_GET(I2C_DEV_NODE);
      

      此时可以很容易判断是 i2c_0 这个 DTS 节点对应设备的驱动定义有问题

    2. 如果代码复杂, 有多个驱动实例引用或复杂的预处理逻辑, 则可以通过生成预处理文件直接精确定位 DTS 节点, 步骤如下

      1. 在文件 build/compile_commands.json 中查找 test_counter.c.obj, 可以定位到如下一个位置(表示部分省略 xxx 内容):

        {
          "directory": "/xxx/build",
          "command": "/xxx/arm-none-eabi-gcc xxx -o CMakeFiles/app.dir/src/test_counter.c.obj -c /xxx/zephyr/tests/drivers/counter/counter_basic_api/src/test_counter.c",
          "file": "/xxx/zephyr/tests/drivers/counter/counter_basic_api/src/test_counter.c",
          "output": "CMakeFiles/app.dir/src/test_counter.c.obj"
        },
        
      2. 其中 command 字段是编译 test_counter.c 的完整命令, 需要对完整的该命令稍作修改: -o 输出路径, -c 改为 -E ,然后在终端执行:

        /xxx/arm-none-eabi-gcc xxx -o test_counter.i -E /xxx/zephyr/tests/drivers/counter/counter_basic_api/src/test_counter.c",
        
      3. 此时会在执行目录生成一个文件 test_counter.i, 打开该文件搜索未定义符号 __device_dts_ord_22 可以找到如下内容:

        static const struct device *const devices[] = {
        # 63 "/xxx/zephyr/tests/drivers/counter/counter_basic_api/src/test_counter.c"
        
        # 124 "/xxx/zephyr/tests/drivers/counter/counter_basic_api/src/test_counter.c"
        (&__device_dts_ord_22), (&__device_dts_ord_23), (&__device_dts_ord_24), (&__device_dts_ord_25),
        # 144 "/xxx/zephyr/tests/drivers/counter/counter_basic_api/src/test_counter.c"
        };
        
      4. 通过上述第 4, 5 行可知 __device_dts_ord_22 是在源文件 124 行引用的, 定位到该行代码如下:

        #ifdef CONFIG_COUNTER_TMR_AMEBA
          DEVS_FOR_DT_COMPAT(realtek_ameba_counter)
        #endif
        
      5. 由此可知是 compatiablerealtek,ameba-counter 的 DTS 节点对应设备的驱动定义有问题

  2. 排查 DTS 节点对应的驱动定义问题, 可以参考以下思路:

    • DTS 节点不存在或未使能(status 未设置为 okay), 可以参考 如何确认 DTS 最终配置是否正确

    • DTS 节点对应驱动代码未加入编译, 这里要检查相关驱动的 CMakeLists.txt 或 Kconfig 配置

    • DTS 节点驱动代码中编码问题, 例如 DT_DRV_COMPAT 宏定义是否正确等

Kconfig 快速指南

Kconfig 中获取 DTS 中的信息

Zephyr 提供了系列接口支持在 Kconfig 中访问 DTS 中的信息: Devicetree-related Functions.

如下是一些使用示例:

config AMEBA_PSRAM
  def_bool y if $(dt_nodelabel_enabled,psram)  # init bool value by node state

config AMEBA_PSRAM_SIZE
  hex
  depends on AMEBA_PSRAM
  default $(dt_nodelabel_reg_size_hex,psram)  # init hex value by node reg's size cell

对应 DTS:

psram: memory@60000000 {
  compatible = "zephyr,memory-region";
  device_type = "memory";
  reg = <0x60000000 DT_SIZE_M(4)>;
  zephyr,memory-region = "PSRAM";
  status = "disabled";
};

CMake 快速指南

CMake 中获取 DTS 中的信息

Zephyr 提供了系列接口支持在 CMake 中访问 DTS 中的信息, 其函数实现可以参考: zephyr/cmake/modules/extensions.cmake

如下是一些 api 说明:

API

Description

dt_nodelabel()

Function for retrieving the node path for the node having nodelabel

dt_alias()

Get a node path for an /aliases node property

dt_node_exists()

Tests whether a node with path <path> exists in the devicetree

dt_node_has_status()

Tests whether <path> refers to a node which exists in the devicetree, and has a status property matching the <status> argument

dt_prop()

Get a devicetree property value. The value will be returned in the <var> parameter

dt_comp_path()

Get a list of paths for the nodes with the given compatible. The value will be returned in the <var> parameter

dt_num_regs()

Get the number of register blocks in the node's reg property

dt_reg_addr()

Get the base address of the register block at index <idx>, or with name <name>

dt_reg_size()

Get the size of the register block at index <idx>, or with name <name>

dt_has_chosen()

Test if the devicetree's /chosen node has a given property <prop> which contains the path to a node

dt_chosen()

Get a node path for a /chosen node property

NVIC 快速指南

NVIC 详细介绍请参考 NVIC 介绍

zephyr 中断使用说明

在 Zephyr 中开发驱动使用中断需要完成几个关键步骤, 即可正常响应中断。

下面将给出一个使用示例, 并列举其中的关键步骤。

代码示例

DTS 配置:

中断设备树配置示例
 1nvic: interrupt-controller@e000e100 {
 2   #address-cells = < 0x1 >;
 3   compatible = "arm,v8.1m-nvic";
 4   reg = < 0xe000e100 0xc00 >;
 5   interrupt-controller;
 6   #interrupt-cells = < 0x2 >;
 7   arm,num-irq-priority-bits = < 0x3 >;
 8   phandle = < 0x1 >;
 9};
10timer0: counter@40819000 {
11   compatible = "realtek,ameba-counter";
12   reg = <0x40819000 0x30>;
13   clocks = <&rcc AMEBA_LTIM0_CLK>;
14   interrupts = <7 0>;
15   clock-frequency = <32768>;
16   status = "disabled";
17};

C 代码实现:

中断驱动代码实现示例
 1#define DT_DRV_COMPAT realtek_ameba_counter
 2...
 3
 4void counter_ameba_isr(const struct device *dev)
 5{
 6}
 7
 8#define TIMER_IRQ_CONFIG(n)                                                                        \
 9   static void irq_config_##n(const struct device *dev)                                       \
10   {                                                                                          \
11      IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), counter_ameba_isr,          \
12            DEVICE_DT_INST_GET(n), 0);                                             \
13      irq_enable(DT_INST_IRQN(n));                                                       \
14   }
15
16#define AMEBA_COUNTER_INIT(n)                                                                      \
17   TIMER_IRQ_CONFIG(n)                                                                        \
18   ...
19
20DT_INST_FOREACH_STATUS_OKAY(AMEBA_COUNTER_INIT);

关键步骤

  1. 确认硬件支持, DTS 中配置中断信息。

    如上 中断设备树配置示例 中高亮行 14 行所示设置驱动的中断属性, interrupts 有两个入参, 第一个是中断号, 第二个是中断优先级。

  2. 驱动代码中实现中断服务函数, 注册中断时需要用到, 如上 中断驱动代码实现示例 4-6 行。

  3. 驱动代码中从 DTS 获取中断优先级等属性, 注册中断时需要用到, 如上 中断驱动代码实现示例 11 行。 详细介绍请参考 获取中断属性

    /*获取中断号*/
    DT_INST_IRQN(n);
    /*获取中断优先级*/
    DT_INST_IRQ(n, priority);
    
  4. 驱动代码中注册中断, 如上 中断驱动代码实现示例 11 行。 详细介绍请参考 中断注册

  5. 驱动代码中启用中断, 如上 中断驱动代码实现示例 13 行。 详细介绍请参考 启用/关闭中断

文件系统 快速指南

文件系统详细介绍参考 文件系统 介绍

LitteFS on FLASH

样例路径: zephyr/samples/subsys/fs/littlefs/

使用以下命令编译 LitteFS on FLASH 样例:

./nuwa.py build -a zephyr/samples/subsys/fs/littlefs/ -d rtl8721f_evb

当需要自定义 DTS 时,可参考 LittleFS on FLASH 样例 配置。

可通过最终生成的 DTS 文件 build/zephyr/zephyr.dts 进一步检查 DTS 配置的正确性:

  • spic 的 status = "okay"

  • LitteFS 使用的 partition 起始地址和大小符合预期

如果遇到链接错误,可能是某些功能未使能。 可以通过 build/zephyr/include/generated/zephyr/autoconf.h 检查最终的 conf 配置。

LitteFS on FLASH 需要以下配置处于使能状态:

  • CONFIG_FILE_SYSTEM=y

  • CONFIG_FILE_SYSTEM_LITTLEFS=y

  • CONFIG_FLASH=y

  • CONFIG_FLASH_MAP=y

FatFS on SD

样例路径: zephyr/samples/subsys/fs/fs_sample/

使用以下命令编译 FatFS on SD 样例:

./nuwa.py build -a zephyr/samples/subsys/fs/fs_sample/ -d rtl8721f_evb

如果遇到链接错误,可能是某些功能未使能。 可以通过 build/zephyr/include/generated/zephyr/autoconf.h 检查最终的 conf 配置。

FatFS on SD 需要以下配置处于使能状态:

  • CONFIG_FILE_SYSTEM=y

  • CONFIG_FAT_FILESYSTEM_ELM=y

  • CONFIG_DISK_ACCESS=y

  • CONFIG_DISK_DRIVERS=y

Settings 快速指南

Settings 详细介绍参考 Settings 介绍

样例路径: zephyr/samples/subsys/settings/

使用以下命令编译 Settings 样例:

./nuwa.py build -a zephyr/samples/subsys/settings/ -d rtl8721f_evb

可参考 settings 配置 配置 DTS。