3. 设备树(Devicetree)与Flash节点

在Zephyr RTOS中,设备树(Devicetree)是描述硬件拓扑与资源配置的核心机制。Flash存储设备的定义、分区划分以及驱动绑定,均通过设备树节点完成。本章将深入讲解如何在设备树中定义Flash设备节点,包括reg属性、size属性、兼容性字符串以及节点层级结构。

核心概念:设备树是一种树形数据结构,每个节点代表一个硬件设备或总线,属性描述设备的特性。Zephyr在编译时通过设备树生成C语言宏,供驱动代码直接使用。

3.1 Flash设备节点结构

一个典型的Flash设备节点位于设备树的soc或pinctrl总线之下,包含以下关键属性:

  • compatible:兼容性字符串,用于匹配驱动。
  • reg:寄存器地址与大小,通常为<基地址 长度>。
  • size:Flash总容量(字节)。
  • erase-block-size:擦除块大小(字节)。
  • write-block-size:写入块大小(字节)。
  • status:设备状态,okay表示启用。
示例:以下是一个典型的SPI NOR Flash节点定义,位于boards/arm/your_board/your_board.dts中。
&spi1 {
    status = "okay";
    cs-gpios = &gpioa 4 GPIO_ACTIVE_LOW;

    w25q128: flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <104000000>;
        size = <0x1000000>;        /* 16 MB */
        erase-block-size = <4096>;
        write-block-size = <1>;
        status = "okay";
    };
};
属性 值 说明
compatible "jedec,spi-nor" 匹配Zephyr的SPI NOR Flash驱动
reg <0> 片选索引0(SPI总线上的设备编号)
size <0x1000000> 16 MB(16777216字节)
erase-block-size <4096> 4 KB擦除块
write-block-size <1> 单字节写入
注意:reg属性在SPI设备中通常表示片选编号,而非内存地址。对于内存映射Flash(如QSPI或并行NOR),reg表示基地址和地址空间长度。

3.2 兼容性字符串与驱动绑定

兼容性字符串是设备树与驱动代码之间的桥梁。Zephyr驱动通过DT_DRV_COMPAT宏定义匹配字符串,设备树节点中的compatible属性必须与驱动定义的字符串一致。

Flash类型 兼容性字符串 对应驱动
SPI NOR Flash "jedec,spi-nor" drivers/flash/spi_nor.c
NAND Flash "jedec,spi-nand" drivers/flash/spi_nand.c
内部Flash(STM32) "st,stm32-flash-controller" drivers/flash/stm32_flash.c
QSPI NOR Flash "jedec,spi-nor" + spi-tx-bus-width drivers/flash/spi_nor.c
调试技巧:使用west build -t menuconfig查看CONFIG_FLASH相关选项,确保目标Flash驱动已启用。驱动匹配失败时,检查compatible字符串是否与驱动中的DT_DRV_COMPAT一致。

3.3 内存映射Flash节点定义

对于内存映射Flash(如内部Flash或QSPI映射区域),reg属性直接描述地址范围。以下是一个STM32内部Flash的节点示例:

&flash0 {
    compatible = "st,stm32-flash-controller";
    reg = <0x08000000 0x100000>;   /* 基地址0x08000000,大小1MB */
    status = "okay";

    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        boot_partition: partition@0 {
            label = "mcuboot";
            reg = <0x00000000 0x00020000>;  /* 128KB */
        };
        slot0_partition: partition@20000 {
            label = "image-0";
            reg = <0x00020000 0x00060000>;  /* 384KB */
        };
        slot1_partition: partition@80000 {
            label = "image-1";
            reg = <0x00080000 0x00060000>;  /* 384KB */
        };
        storage_partition: partition@e0000 {
            label = "storage";
            reg = <0x000e0000 0x00020000>;  /* 128KB */
        };
    };
};
关键点:分区节点使用fixed-partitions兼容性,reg属性中的偏移量相对于Flash基地址。每个分区必须有唯一的label,用于在应用层通过DT_LABEL宏引用。

3.4 Flash节点层级关系SVG图

下图展示了设备树中Flash节点、分区节点与驱动绑定的层级关系:

设备树Flash节点层级关系 / (根节点) soc spi1 flash@0 (w25q128) partitions (fixed-partitions) boot_partition slot0_partition slot1_partition storage_partition 驱动: spi_nor.c (jedec,spi-nor) 说明:设备树节点通过compatible属性匹配驱动,分区节点通过fixed-partitions定义Flash布局

3.5 属性详解与常见错误

属性 数据类型 必填 常见错误
compatible 字符串 是 拼写错误或与驱动不匹配
reg 数组 是 地址/大小单位错误(字节 vs 字)
size 整数 是 未定义或与硬件不符
erase-block-size 整数 是 未定义导致擦除失败
write-block-size 整数 是 未定义导致写入异常
status 字符串 否 默认disabled导致设备不可用
常见错误:
  • reg属性单位混淆:在SPI节点中,reg表示片选索引,不是地址。在内存映射Flash中,reg表示基地址和长度,单位是字节。
  • 分区偏移量未对齐:分区reg的起始地址必须是擦除块大小的整数倍,否则驱动会报错。
  • 兼容性字符串大小写:Zephyr驱动匹配区分大小写,"jedec,spi-nor"与"JEDEC,SPI-NOR"不匹配。

3.6 使用设备树宏访问Flash节点

Zephyr在编译时根据设备树生成C语言宏,驱动和应用代码通过宏访问节点属性。以下是一些常用宏:

/* 获取Flash设备标签 */
#define FLASH_DEV_NODE DT_NODELABEL(w25q128)

/* 获取Flash设备基地址 */
#define FLASH_BASE_ADDRESS DT_REG_ADDR(FLASH_DEV_NODE)

/* 获取Flash设备大小 */
#define FLASH_SIZE DT_PROP(FLASH_DEV_NODE, size)

/* 获取擦除块大小 */
#define ERASE_BLOCK_SIZE DT_PROP(FLASH_DEV_NODE, erase_block_size)

/* 获取分区标签 */
#define STORAGE_PARTITION DT_NODELABEL(storage_partition)
#define STORAGE_LABEL DT_LABEL(STORAGE_PARTITION)
实践建议:在应用代码中优先使用DT_NODELABEL引用节点,避免硬编码地址。使用DT_PROP宏读取属性,确保编译时类型安全。

3.7 多Flash设备配置示例

在实际项目中,一个系统可能包含多个Flash设备(如内部Flash + 外部SPI Flash)。以下是一个多Flash配置示例:

&flash0 {
    /* 内部Flash */
    compatible = "st,stm32-flash-controller";
    reg = <0x08000000 0x100000>;
    status = "okay";
};

&spi2 {
    status = "okay";
    cs-gpios = &gpiob 12 GPIO_ACTIVE_LOW;

    mx25l256: flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <80000000>;
        size = <0x2000000>;        /* 32 MB */
        erase-block-size = <4096>;
        write-block-size = <1>;
        status = "okay";
    };
};

在应用层,可以通过device_get_binding函数分别获取两个Flash设备的驱动实例:

const struct device *flash_dev_internal;
const struct device *flash_dev_external;

flash_dev_internal = device_get_binding(DT_LABEL(DT_NODELABEL(flash0)));
flash_dev_external = device_get_binding(DT_LABEL(DT_NODELABEL(mx25l256)));
总结:设备树Flash节点的定义需要精确匹配硬件规格,包括地址、大小、擦除块和兼容性字符串。分区节点提供灵活的存储布局,是实现OTA升级、文件系统和参数存储的基础。正确理解reg属性的语义(片选 vs 地址)是避免配置错误的关键。