设备树编写
在Zephyr中,板载的绝大部分的外设默认情况下都是disabled的,如果需要使用某个功能,就需要在overlay中使能对应的节点,并添加外设的初始化参数,某些情况下可能还需要使用 pinctrl 进行IO复用。
下面是ADC例程中的设备树文件:
/ {
zephyr,user {
io-channels =
<&adc0 0>,
<&adc1 0>;
};
};
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};
&adc1 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1_4";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};
-
上面分别使用了adc0和adc1的0通道作为ADC的输入端口,zephyr,user 是一个特殊的节点,在里面可以存放任意的属性而不需要编写绑定文件,这意味着当你需要用到一些简单的属性时可以把它当作便捷的容器。
-
以 phandle 作为节点名称时,如果SOC设备树文件中存在该节点,与此处同名的属性会被覆盖,而此处新增的属性则会和将其添加到最后生成的设备树中,如果此处不以 phandle 类型做节点名,而是与SOC中的节点同名,那么此处的节点会替换 SOC设备树文件中的节点。
-
在Zephyr中,设备树的编译需要与对应的绑定文件相匹配,如果不知道参数ADC参数如何设置,可通过SOC设备树文件中对应节点的 compatible 属性搜索包含该关键字的 yaml 文件,里面提供了对应的模板和使用实例,哪些属性是必须的,哪些属性是可选的。
-
如果需要修改ADC的通道,需要修改三个地方,第一个是 zephyr,user 下 io-channels 数组中的第二个参数,第二个是channel节点后的地址,第三个是reg中的地址,由于reg是对节点地址信息更详细的描述,所以也必须要一致,如果忘记修改会导致初始化失败。
设备树节点访问
#define DT_ADC_NODE_SPEC_GET(node_id, prop, index) \
ADC_DT_SPEC_GET_BY_IDX(node_id, index),
static const struct adc_dt_spec adc_channels[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_ADC_NODE_SPEC_GET)
};
-
ADC_DT_SPEC_GET_BY_IDX 可以获取到ADC设备结构体,设备树中的ADC配置信息,由于io-channels 是一个 phandle 类型的数组,要想获取到所有节点的信息只能通过遍历函数来实现。
-
将上述宏展开后得到的结果如下:
static const struct adc_dt_spec adc_channels[] = {
ADC_DT_SPEC_GET_BY_IDX(DT_N_S_zephyr_user, 0),
ADC_DT_SPEC_GET_BY_IDX(DT_N_S_zephyr_user, 1),
};
-
除了 ADC_DT_SPEC_GET_BY_IDX ,驱动中还提供了其他的宏用于获取配置,这些宏在使用时都需要将设备树中将对应的属性设置为 phandle 数组,类似 <&adc0, 1> 这样的形式。
- ADC_DT_SPEC_GET_BY_IDX(node_id, idx)
- ADC_DT_SPEC_INST_GET_BY_IDX(inst, idx)
- ADC_DT_SPEC_GET(node_id)
- ADC_DT_SPEC_INST_GET(inst)
-
后面两种方式分别是通过 compatible 属性和节点ID来获取设备树信息的,这里大家可能会有些疑问,为什么通过节点ID就可以获取到 phandle 数组,Zephyr是如何获取到我们的信息是保存在哪个属性中的呢?
-
这个答案我们可以从上面的展开结果中找到,在展开之后我们看到这个宏丢弃了一个参数,这个参数就是 io_channels,那么为什么不使用呢,这是由于在 ADC_DT_SPEC_GET_BY_IDX 已经包含了 io_channels,因此也就自动忽略了 io_channels 参数,换句话说, 如果你自定义一个其他的节点,要想通过上面的宏获取设备树信息,那么其中必须包含 io-channels 属性。
#define ADC_DT_SPEC_GET_BY_IDX(node_id, idx) \
ADC_DT_SPEC_STRUCT(DT_IO_CHANNELS_CTLR_BY_IDX(node_id, idx), \
DT_IO_CHANNELS_INPUT_BY_IDX(node_id, idx))
- 下面是Zephyr的示例代码
#define DT_ADC_NODE_SPEC_GET(node_id, prop, index) \
ADC_DT_SPEC_GET_BY_IDX(DT_N_S_zephyr_user, index),
static const struct adc_dt_spec adc_channels[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_ADC_NODE_SPEC_GET)
};
void main(void)
{
uint16_t buf = 0;
struct adc_sequence sequence = {
.buffer = &buf,
.buffer_size = sizeof(buf),
};
for(size_t i = 0; i < ARRAY_SIZE(adc_channels); i++)
{
if (!device_is_ready(adc_channels[i].dev))
{
printk("%s channel %d not ready\r\n", adc_channels[i].dev->name, adc_channels[i].channel_id);
}
if ( 0 > adc_channel_setup_dt(&adc_channels[i]))
{
printk("%s channel %d not setup\r\n", adc_channels[i].dev->name, adc_channels[i].channel_id);
}
}
while (1)
{
for(size_t i = 0; i < ARRAY_SIZE(adc_channels); i++)
{
adc_sequence_init_dt(&adc_channels[i], &sequence);
adc_read(adc_channels[i].dev, &sequence);
printk("%s channel %d : %d\r\n", adc_channels[i].dev->name, adc_channels[i].channel_id, buf);
}
k_sleep(K_SECONDS(1));
}
}