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节点、分区节点与驱动绑定的层级关系:
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 地址)是避免配置错误的关键。