Zephyr 编写设备树文件
Zephyr 可以通过设备树文件描述芯片/板卡的外设。具体的设备模型、设备树、设备模型的说明参见官方文档:
https://docs.zephyrproject.org/latest/kernel/drivers/index.html
https://docs.zephyrproject.org/latest/build/dts/index.html
https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html
本文描述的是如何针对已有的板卡(如 STM32 官方开发板 Nucleo F411RE,本文以此为例)通过添加 Overlay 文件进行改造。这是符合工程直觉和开发流程的做法。我们不会从 0 开始新建一个板卡,而是选择在现有的板卡的基础上进行改造。
关于 Overlay 文件在设备树中的位置,参见:Zephyr 工作逻辑。本文以添加 SPI 外设为例进行演示。
设备树绑定
Zephyr 中所有的设备树绑定文件都在 zephyr/dts/bindings/ 文件夹下。对于该板卡,其描述 SPI 的设备数绑定文件(.yaml)为 zephyr/dts/bindings/spi/st,stm32-spi.yaml。我们编写的设备树节点必须满足该文件描述的规则。
可以通过安装 fzf 等工具,实现基于文件名的快速检索。如,搜索
st,stm32-spi即可快速定位到文件位置,在终端中利用 Ctrl + Click 即可快速打开。如下图所示。

该文件的内容如下:
description: STM32 SPI controller
compatible: "st,stm32-spi"
include: st,stm32-spi-common.yaml
其中 description 字段描述了该绑定文件的作用对象,即 STM32 的 SPI 外设控制器。compatible 字段为身份标识,任何 stm32 spi 外设均需在设备树文件中添加此属性,表示适用于该设备数绑定描述的规则。include 表示引用。
通过 include 字段可以发现,设备树绑定文件是存在继承关系的,即该设备树绑定文件继承了 st,stm32-spi-common.yaml 中描述的规则。拥有该属性的设备树节点也需要满足其父文件规定的规则。
可以利用 Ctrl + Click 打开其父文件。其父文件 st,stm32-spi-common.yaml 的内容如下所示。
include: [spi-controller.yaml, pinctrl-device.yaml]
properties:
reg:
required: true
interrupts:
required: true
pinctrl-0:
required: true
pinctrl-names:
required: true
ioswp:
type: boolean
description: Swap SPI MOSI and MISO pins
# ...
properties 字段表示属性,是满足该设备数绑定文件 compatible 字段的设备树可以(必须)添加的属性列表。如上图所示,reg interrupts pinctrl-0 等字段是必须填写的。所以在设备树中,需要写:
spi5: spi@40015000 {
compatible = "st,stm32-spi";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x40015000 0x400>;
clocks = <&rcc STM32_CLOCK(APB2, 20)>;
interrupts = <85 0>;
st,spi-data-width = "limited-8-16-bit";
status = "disabled";
};
同时,其还 include 了 spi-controller.yaml,内容包含了:
properties:
clock-frequency:
type: int
description: |
Clock frequency the SPI peripheral is being driven at, in Hz.
"#address-cells":
required: true
const: 1
"#size-cells":
required: true
const: 0
cs-gpios:
type: phandle-array
description: |
An array of chip select GPIOs to use. Each element
in the array specifies a GPIO. The index in the array
corresponds to the child node that the CS gpio controls.
# ...
上面的设备树文件是从 stm32f411.dts 中截取的。可以发现,ST 作为 Zephyr 的赞助商已经为我们定义好了绝大部分设备树节点。在大部分情况下,我们仅需要在 overlay 文件中对其进行少许处理,比如调整其
status字段为"okay"等。此外,还可以用cs-gpios字段指定设备等。
需要注意的是,在 yaml 文件中,字段 pinctrl-0 是必须定义(required)的,但是 st 官方缺并没有填写这一字段。这就需要我们在 overlay 中填写这个字段,实现引脚复用。
Pin Controller
Zephyr 通过一系列 pinctrl 文件来实现引脚复用。比如对于 STM32F411 芯片,其在文件 modules/hal/stm32/dts/st/f4/stm32f411r(c-e)tx-pinctrl.dtsi 中定义了相关的引脚信息。截取一部分:
# ...
/omit-if-no-ref/ spi5_miso_pa12: spi5_miso_pa12 {
pinmux = <STM32_PINMUX('A', 12, AF6)>;
bias-pull-down;
slew-rate = "very-high-speed";
};
/* SPI_MOSI */
/omit-if-no-ref/ spi1_mosi_pa7: spi1_mosi_pa7 {
pinmux = <STM32_PINMUX('A', 7, AF5)>;
bias-pull-down;
slew-rate = "very-high-speed";
};
# ...
可以发现,其将芯片上所有的引脚都进行了定义,绑定到了对应的外设上,命名方式为 <外设名称>_<引脚名称>_<物理引脚编号>。通过这种方式,我们可以很方便的指定某个引脚的复用信息。
Overlay
通过以上的内容,我们就可以很容易地编写 overlay 文件:
&spi3 {
status = "okay";
pinctrl-0 = <&spi3_sck_pb12 &spi3_miso_pc11 &spi3_mosi_pc12>;
pinctrl-names = "default";
cs-gpios = <&gpioa 4 GPIO_ACTIVE_LOW>;
spi-device@0 {
reg = <0>;
};
};
其中只包含了引脚复用和开启/关闭信息以及 SPI 从设备信息,易于编写。
代码使用
Zephyr 通过设备模型使得所有类型的 SoC 和板卡都可以通过统一的 API 进行外设访问。了解如何访问有两种方式,一种是利用官方文档的 API 说明,这种方法显然比较低效。
另一种比较高效的方式是查看 samples 示例。Zephyr 在 zephyr/samples/drivers 中提供了对于绝大部分外设的使用示例,以 spi 外设 spi_fujitsu_fram 为例:
#include <zephyr/drivers/spi.h>
// ...
static uint8_t data[MAX_USER_DATA_LENGTH], cmp_data[MAX_USER_DATA_LENGTH];
static int mb85rs64v_access(const struct device *spi,
struct spi_config *spi_cfg,
uint8_t cmd, uint16_t addr, void *data, size_t len)
{
uint8_t access[3];
struct spi_buf bufs[] = {
{
.buf = access,
},
{
.buf = data,
.len = len
}
};
struct spi_buf_set tx = {
.buffers = bufs
};
access[0] = cmd;
if (cmd == MB85RS64V_WRITE_CMD || cmd == MB85RS64V_READ_CMD) {
access[1] = (addr >> 8) & 0xFF;
access[2] = addr & 0xFF;
bufs[0].len = 3;
tx.count = 2;
if (cmd == MB85RS64V_READ_CMD) {
struct spi_buf_set rx = {
.buffers = bufs,
.count = 2
};
return spi_transceive(spi, spi_cfg, &tx, &rx);
}
} else {
tx.count = 1;
}
return spi_write(spi, spi_cfg, &tx);
}
int main() {
// ...
int err = mb85rs64v_access(spi, spi_cfg,
MB85RS64V_WRITE_ENABLE_CMD, 0, NULL, 0);
// ...
}
这样,就可以打通从外设配置到代码调用的过程了。