查看设备树和生成宏
原始的设备树都是在板子目录下,例如对于NRF5340的板子目录就在ncs_sdk/zephyr/boards/arm/nrf5340dk_nrf5340,在这个目录下就会有两个核的设备树分别为:nrf5340dk_nrf5340_cpuapp.dts和nrf5340dk_nrf5340_cpuappns.dts。
在应用程序下,我们可以建立<board>.overlay来覆盖板子的默认设备树。
当工程编译了之后,我们可以直接查看编译目录下的zephyr.dts来查看最终生成的设备树。设备树生成的宏文件为devicetree_unfixed.h,也存储在编译目录下。
从设备树中获取设备结构体
struct device 设备结构体是我们可以调用Zephyr的API进行设备操作的基础。
例如,对于此设备树片段,您可能希望获取serial@40002000的设备结构体:
/ {soc {serial0: serial@40002000 {status = "okay";current-speed = <115200>;/* ... */};};aliases {my-serial = &serial0;};chosen {zephyr,console = &serial0;};};
首先我们需要获取节点标识符。可以通过如下方式获取:
/* 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)
获取节点标识符之后可以通过以下两种方式获取设备结构体:
通过
节点标识符的标签,获取设备结构体const struct device *uart_dev = device_get_binding(DT_LABEL(MY_SERIAL));
通过
节点标识符,直接获取设备结构体const struct device *uart_dev = DEVICE_DT_GET(MY_SERIAL);
获取设备结构体之后,需要检测一个设备是否准备就绪,否则如果直接调用API来操作设备会导致死机。
if (!device_is_ready(uart_dev)) {/* Not ready, do not use */return -ENODEV;}
之后,就可以直接调用Zephyr的外设API来操作外设。
设置设备树覆盖
CMake变量DTC_OVERLAY_FILE包含要使用的覆盖文件的空格或分号分隔列表。如果DTC_OVERLAY_FILE指定了多个文件,则C预处理器将按该顺序包含这些文件。
如果未设置DTC_OVERLAY_FILE,则生成系统将执行以下步骤,在应用程序源目录中查找要用作设备树覆盖的文件:
- 如果
boards/<BOARD>.overlay该文件存在,则将使用它。 - 如果当前主板有多个修订版并且
boards/<BOARD>_<revision>.overlay存在,则将使用它。此外,如果两者都存在,还将使用boards/<BOARD>.overlay此文件。 - 如果在前面的步骤中找到一个或多个文件,则生成系统将停止查找,只使用这些文件。
- 否则,如果
<BOARD>.overlay存在,它将被使用,并且构建系统将停止查找更多文件。 - 否则,如果
app.overlay存在,将使用它。DTC_OVERLAY_FILE值存储在CMake缓存中,并在后续生成中使用。
使用设备树覆盖
设备树覆盖主要就是为了覆盖默认的板子的设备树。
修改属性的值
例如,如果BOARD.dts包含如下节点:
/ {soc {serial0: serial@40002000 {status = "okay";current-speed = <115200>;/* ... */};};};
可以通过以下的方式覆盖current-speed的值:
/* Option 1 */&serial0 {current-speed = <9600>;};
/* Option 2 */&{/soc/serial@40002000} {current-speed = <9600>;};
添加别名和选择节点
可以在设备树覆盖文件中添加别名节点。
/ {aliases {my-serial = &serial0;};};
可以在设备树覆盖文件中添加选择节点。
/ {aliases {my-serial = &serial0;};};
删除属性
可以在设备树覆盖文件中删除一些无用的属性。
&serial0 {/delete-property/ some-unwanted-property;};
添加子节点
可以使用设备树覆盖添加子节点。例如,要在现有总线节点上配置SPI或I2C子器件,请执行如下操作:
/* SPI device example */&spi1 {my_spi_device: temp-sensor@0 {compatible = "...";label = "TEMP_SENSOR_0";/* reg is the chip select number, if needed;* If present, it must match the node's unit address. */reg = <0>;/* Configure other SPI device properties as needed.* Find your device's DT binding for details. */spi-max-frequency = <4000000>;};};/* I2C device example */&i2c2 {my_i2c_device: touchscreen@76 {compatible = "...";label = "TOUCHSCREEN";/* reg is the I2C device address.* It must match the node's unit address. */reg = <76>;/* Configure other I2C device properties as needed.* Find your device's DT binding for details. */};};
其他总线设备可以类似地配置:
- 将设备创建为父总线的子节点
- 根据其绑定设置其属性
使用设备树API编写设备驱动程序
- 定义设备树节点
- 定义设备树绑定文件
- 编写设备驱动
设备树节点
定义设备树节点
/ {soc {mydevice0: dev@0 {compatible = "vnd,my-device";};mydevice1: dev@1 {compatible = "vnd,my-device";};};};
定义设备树绑定文件
定义和设备树节点的compatible匹配的绑定文件
description: <Human-readable description of your binding>compatible: "vnd,my-device"include: base.yaml
编写设备驱动
设备驱动的编写有两中方式:
- 通过获取节点标识符的标签的方式编写驱动
- 通过实例的方式编写驱动
实例方式的驱动
通过实例方式获取节点标识符。
#define DT_DRV_COMPAT vnd_my_device
定义一个实例化的宏,来注册驱动。
/* my_driver.c */#include <drivers/some_api.h>/* Define data (RAM) and configuration (ROM) structures: */struct my_dev_data {/* per-device values to store in RAM */};struct my_dev_cfg {uint32_t freq; /* Just an example: initial clock frequency in Hz *//* other configuration to store in ROM */};/* Implement driver API functions (drivers/some_api.h callbacks): */static int my_driver_api_func1(const struct device *dev, uint32_t *foo) { /* ... */ }static int my_driver_api_func2(const struct device *dev, uint64_t bar) { /* ... */ }static struct some_api my_api_funcs = {.func1 = my_driver_api_func1,.func2 = my_driver_api_func2,};/** This instantiation macro is named "CREATE_MY_DEVICE".* Its "inst" argument is an arbitrary instance number.** Put this near the end of the file, e.g. after defining "my_api_funcs".*/#define CREATE_MY_DEVICE(inst) \static struct my_dev_data my_data_##inst = { \/* initialize RAM values as needed, e.g.: */ \.freq = DT_INST_PROP(inst, clock_frequency), \}; \static const struct my_dev_cfg my_cfg_##inst = { \/* initialize ROM values as needed. */ \}; \DEVICE_DT_INST_DEFINE(inst, \my_dev_init_function, \NULL, \&my_data_##inst, \&my_cfg_##inst, \MY_DEV_INIT_LEVEL, MY_DEV_INIT_PRIORITY, \&my_api_funcs);
最后,将实例化宏传递给DT_INST_FOREACH_STATUS_OKAY():
/* Call the device creation macro for each instance: */DT_INST_FOREACH_STATUS_OKAY(CREATE_MY_DEVICE)
DT_INST_FOREACH_STATUS_OKAY扩展到对每个已启用节点调用一次CREATE_MY_DEVICE的代码,其兼容性由CREATE_MY_DEVICE确定。它不会在CREATE_MY_DEVICE扩展的末尾附加分号,因此宏的展开必须以分号或函数定义结尾才能支持多个设备。
标签方式的驱动
通过标签方式获取节点标识符。
#define MYDEV(idx) DT_NODELABEL(mydevice ## idx)
驱动程序可以使用设备树中的节点标签在特定设备节点上运行:
/* my_driver.c */#include <drivers/some_api.h>/* Define data (RAM) and configuration (ROM) structures: */struct my_dev_data {/* per-device values to store in RAM */};struct my_dev_cfg {uint32_t freq; /* Just an example: initial clock frequency in Hz *//* other configuration to store in ROM */};/* Implement driver API functions (drivers/some_api.h callbacks): */static int my_driver_api_func1(const struct device *dev, uint32_t *foo) { /* ... */ }static int my_driver_api_func2(const struct device *dev, uint64_t bar) { /* ... */ }static struct some_api my_api_funcs = {.func1 = my_driver_api_func1,.func2 = my_driver_api_func2,};/** This is a convenience macro for creating a node identifier for* the relevant devices. An example use is MYDEV(0) to refer to* the node with label "mydevice0".*/#define MYDEV(idx) DT_NODELABEL(mydevice ## idx)/** Define your instantiation macro; "idx" is a number like 0 for mydevice0* or 1 for mydevice1. It uses MYDEV() to create the node label from the* index.*/#define CREATE_MY_DEVICE(idx) \static struct my_dev_data my_data_##idx = { \/* initialize RAM values as needed, e.g.: */ \.freq = DT_PROP(MYDEV(idx), clock_frequency), \}; \static const struct my_dev_cfg my_cfg_##idx = { /* ... */ }; \DEVICE_DT_DEFINE(MYDEV(idx), \my_dev_init_function, \NULL, \&my_data_##idx, \&my_cfg_##idx, \MY_DEV_INIT_LEVEL, MY_DEV_INIT_PRIORITY, \&my_api_funcs)
最后,手动检测每个已启用的设备树节点,并用于实例化每个设备驱动:
#if DT_NODE_HAS_STATUS(DT_NODELABEL(mydevice0), okay)CREATE_MY_DEVICE(0)#endif#if DT_NODE_HAS_STATUS(DT_NODELABEL(mydevice1), okay)CREATE_MY_DEVICE(1)#endif
