4、Zephyr设备树绑定(Binding):YAML绑定文件的结构、compatible属性、required与optional属性

好,咱们接着聊设备树。上一章我讲了设备树源文件(.dts)怎么写,但有个问题你肯定遇到过:你写了一个节点,比如 my_led: led@0,Zephyr 怎么知道这个节点对应哪个驱动?它的寄存器地址是什么格式?哪些属性是必须的?

答案就在绑定文件里。说白了,绑定文件就是设备树节点的“身份证”加“说明书”。它告诉 Zephyr 的构建系统:这个节点是什么硬件,需要哪些信息才能正常工作。

4.1 绑定文件长什么样?

绑定文件是 YAML 格式的,后缀是 .yaml。我习惯把它们放在 dts/bindings/ 目录下,按厂商或外设类型分文件夹存放。比如 GPIO 的绑定在 dts/bindings/gpio/ 里。

来看一个最简单的例子。这是我手头一个项目中用到的 LED 绑定文件:

# dts/bindings/led/mycompany,myled.yaml
description: MyCompany's custom LED controller

compatible: "mycompany,myled"

include: [gpio.yaml]

properties:
  reg:
    required: true
    type: array
    description: Register base address and size

  label:
    required: true
    type: string
    description: Human-readable device label

  max-brightness:
    required: false
    type: int
    default: 255
    description: Maximum brightness value

嗯,这里要注意几个关键点。第一行 description 是给人看的,写清楚这个绑定是干什么的。我见过有些团队直接写“LED binding”,太敷衍了。你想想看,半年后你自己回来看,可能都忘了这个 LED 是 I2C 的还是 SPI 的。

4.2 compatible 属性——匹配的钥匙

compatible 是绑定文件里最重要的字段。它就像一把钥匙,把设备树节点和驱动代码连接起来。

在设备树里,你写:

/ {
    myled: led@40001000 {
        compatible = "mycompany,myled";
        reg = <0x40001000 0x1000>;
        label = "board-led0";
        max-brightness = <200>;
    };
};

在驱动代码里,你用 DT_DRV_COMPAT(mycompany_myled) 来匹配。注意这里下划线代替了逗号,这是 Zephyr 的命名规则。

我曾经踩过一个坑:compatible 字符串写错了大小写。设备树里写的是 "mycompany,MyLED",绑定文件里写的是 "mycompany,myled"。结果驱动死活加载不上,查了半天才发现是大小写不匹配。Zephyr 对大小写是敏感的,这一点千万记住。

注意:compatible 字符串必须完全一致,包括大小写和标点符号。建议统一用小写字母加连字符,比如 "mycompany,my-led"

4.3 required 与 optional 属性

绑定文件里,每个属性都要声明是 required: true 还是 required: false。这直接决定了设备树节点能不能通过编译。

我个人的习惯是:能设为 optional 的就尽量设为 optional。为什么?因为硬件设计经常变。比如某个芯片的版本 A 有中断引脚,版本 B 把它砍了。如果你把中断属性设为 required,那版本 B 的设备树就编译不过。

来看一个更完整的例子,这是我做的一个 I2C 传感器绑定:

description: MyCompany temperature sensor

compatible: "mycompany,tempsensor"

include: [i2c-device.yaml]

properties:
  vdd-supply:
    required: false
    type: phandle
    description: Power supply regulator

  measurement-interval-ms:
    required: false
    type: int
    default: 1000
    description: Measurement interval in milliseconds

  alert-gpios:
    required: false
    type: phandle-array
    description: Alert interrupt GPIO

  temp-offset:
    required: false
    type: int
    default: 0
    description: Temperature calibration offset in millidegrees

你看,这里只有 reglabel 是 required 的(从 i2c-device.yaml 继承而来),其他都是 optional。这样设计的好处是:

  • 基本功能只需要 I2C 地址就能工作
  • 高级功能(如中断、校准)按需配置
  • 兼容不同硬件版本

4.4 属性类型详解

Zephyr 支持多种属性类型。我整理了一个表格,方便你查阅:

类型 说明 示例
boolean 布尔值,存在即为 true status = "okay";
int 32 位整数 max-brightness = <255>;
string 字符串 label = "board-led0";
array 整数数组 reg = <0x40001000 0x1000>;
phandle 指向另一个节点的引用 vdd-supply = <&regulator>;
phandle-array 带参数的 phandle 数组 alert-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
string-array 字符串数组 compatible = "mycompany,tempsensor", "generic,tempsensor";
小技巧:如果你不确定某个属性该用什么类型,去 Zephyr 源码里找一个类似的绑定文件参考。比如 GPIO 相关的看 dts/bindings/gpio/ 目录下的文件,那里有最权威的示例。

4.5 include 机制——复用已有的绑定

你注意到前面的例子里有 include: [gpio.yaml]include: [i2c-device.yaml] 吗?这是 Zephyr 绑定文件的一大特色——支持继承。

比如你的设备是 I2C 接口的,直接 include: [i2c-device.yaml],就能自动获得 reg(I2C 地址)和 label 这两个 required 属性。你只需要写自己特有的属性就行。

我建议你:

  • 优先使用 Zephyr 自带的绑定文件作为基础
  • 只添加硬件特有的属性
  • 不要重复定义已经存在的属性

这样做的好处是,当 Zephyr 升级时,基础绑定的更新会自动应用到你的设备上。

4.6 实战:写一个完整的绑定文件

好,咱们动手写一个。假设你有一个 SPI 接口的 DAC 芯片,型号是 ACME1234。我会这样写:

# dts/bindings/dac/acme,acme1234.yaml
description: ACME ACME1234 12-bit SPI DAC

compatible: "acme,acme1234"

include: [spi-device.yaml]

properties:
  vref-mv:
    required: true
    type: int
    description: Reference voltage in millivolts

  settling-time-us:
    required: false
    type: int
    default: 10
    description: Output settling time in microseconds

  power-down-gpios:
    required: false
    type: phandle-array
    description: Power-down control GPIO

对应的设备树节点:

&spi1 {
    dac0: dac@0 {
        compatible = "acme,acme1234";
        reg = <0>;  // SPI chip select 0
        spi-max-frequency = <10000000>;
        vref-mv = <3300>;
        settling-time-us = <15>;
        power-down-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
    };
};

你看,spi-max-frequency 是从 spi-device.yaml 继承来的,我不需要再定义。这就是 include 机制的威力。

核心要点:
  • 绑定文件是设备树节点和驱动代码之间的桥梁
  • compatible 字符串必须完全匹配
  • required 属性必须提供,否则编译报错
  • optional 属性按需配置,尽量给默认值
  • 善用 include 机制,避免重复劳动

最后说一句,绑定文件写得好不好,直接影响到你的驱动代码能不能优雅地获取设备信息。我见过太多人图省事,把所有属性都设为 required,结果硬件改版时改得焦头烂额。记住:设计绑定文件时,多想想未来可能的变化。