查看设备树和生成宏
原始的设备树都是在板子目录下,例如对于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