设备树是一种数据结构,包含多个节点,用于描述硬件设备及其配置信息,它通常用于嵌入式系统中,尤其是在Linux操作系统中,帮助操作系统识别和管理硬件资源,设备树不是代码,而是一种用数据描述硬件信息的方式
设备树通常是以一种树形结构来表示硬件各个部分的层次关系
设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等
树的主干就是系统总线,IIC 控制器、GPIO 控制器、SPI 控制器等都是接
到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种,其中 IIC1 上接了 FT5206 和 AT24C02这两个 IIC 设备,IIC2 上只接了 MPU6050 这个设备。DTS 文件的主要功能就是按图所示的结构来描述板子上的设备信息,DTS 文件描述设备信息是有相应的语法规则要求的
设备树的结构:
设备树的结构采用类似树形结构,包含多个节点,每个节点代表一个硬件设备或设备的某些特性。每个节点可以包含一些属性,描述该设备的详细信息。
例如:
节点:代表硬件设备,比如CPU、内存、串口、存储、网络接口等。
属性:描述该设备的特性或配置信息,如设备的地址、类型、IRQ(中断请求)、驱动程序等。
设备树的语法:
设备树通常使用一种简洁的描述语言(Device Tree Source,简称DTS)来表示。DTS文件是纯文本文件,后缀通常为.dts,它们被编译成二进制的设备树二进制格式(Device Tree Blob,简称DTB),该二进制文件会被操作系统加载和使用。
简单的设备树示例:
为什么要有设备树?
1、硬件和内核解耦
以前,硬件信息(比如CPU型号、内存地址、外设位置)直接写在内核代码里。换一块硬件板子,就得重新改内核、重新编译。设备树把硬件信息抽离出来,变成一个单独的文件(.dts),内核只需读取这个文件就能适配不同硬件。
2、支持多种硬件平台
比如树莓派3和树莓派4的硬件不同,但可以用同一个内核+不同的设备树文件启动,内核无需为每块板子单独写代码。
3、方便维护
厂商更新硬件时,只需修改设备树文件(描述硬件),不用动内核代码(驱动逻辑)。
设备树的作用
1、告诉内核硬件在哪里
比如:“CPU是四核的”、“内存从地址0x80000000开始”、“I2C控制器在地址0x40005000,连着触摸屏和温度传感器”。
2、描述硬件之间的关系
比如:“USB控制器挂载在PCI总线的第3个插槽”、“GPIO引脚12连接了LED灯”。
3、配置硬件参数
比如:“屏幕分辨率是1920x1080”、“以太网MAC地址是00:11:22:33:44:55”。
假设嵌入式板子上有一个LED灯,连接在GPIO的第5个引脚,没有设备树时,需要在驱动代码里硬编码gpio5,换到gpio6就得改代码、重新编译内核;
有设备树时,设备树文件里写gpios = <&gpio 5 0>; 驱动代码只需读取设备树中的gpios属性,自动适配到gpio5。换引脚时只需改设备树,内核代码不用动
设备树就像硬件的“身份证”+“说明书”,让内核能动态识别硬件,而不是把硬件信息写死在内核里。它的核心作用:解耦硬件配置和内核代码,让Linux能灵活适配不同硬件
示例代码 alphaled 节点 alphaled { #address-cells = <1>; #size-cells = <1>; compatible = "atkalpha-led"; status = "okay"; reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */ 0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */ 0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */ 0X0209C000 0X04 /* GPIO1_DR_BASE */ 0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */ };
这个节点描述的是一个LED灯的硬件控制信息
它需要告诉内核:“LED灯的位置在哪里?如何配置硬件寄存器才能控制它亮灭?” 就像给内核一张 “LED操作手册” ,说明控制这个LED需要操作哪些寄存器(开关)。
#address-cells = <1>;
#size-cells = <1>;
作用:指定“地址”和“长度”用几个数字表示(单位是32位,即4字节)。
这里都用1个数字。类比:假设你要描述一本书的位置:地址 = 书架编号(1个数字)长度 = 占用的格子数(1个数字)为何重要:后续的 reg 属性依赖这两个值来解析地址和长度。
compatible = "atkalpha-led";
作用:匹配内核中的驱动程序!内核会寻找支持 "atkalpha-led"
的驱动来操作这个设备。类比:告诉内核:“这个LED要用说明书编号为‘atkalpha-led’的驱动来操作”。关键点:驱动代码里必须有对应的兼容性标识,否则设备无法被识别!
status = "okay";
作用:启用这个设备。如果设为 "disabled",内核会忽略它。类比:给设备通电(okay)或断电(disabled)。
reg = < 0X020C406C 0X04 // CCM_CCGR1(时钟控制寄存器)0X020E0068 0X04 // SW_MUX_GPIO1_IO03(引脚复用控制)0X020E02F4 0X04 // SW_PAD_GPIO1_IO03(引脚电气属性配置)0X0209C000 0X04 // GPIO1_DR(GPIO数据寄存器)0X0209C004 0X04 // GPIO1_GDIR(GPIO方向寄存器)
>;
作用:列出控制这个LED所需的所有寄存器地址和长度(单位:字节)。逐项解释:CCM_CCGR1 (0X020C406C)
控制时钟的开关。LED所在的GPIO模块需要时钟才能工作,类似“总电源开关”。SW_MUX_GPIO1_IO03 (0X020E0068)
配置引脚功能。比如将某个引脚设置为“GPIO模式”而非其他功能(如UART)。SW_PAD_GPIO1_IO03 (0X020E02F4)
配置引脚的电气属性,如上拉/下拉电阻、驱动强度等。GPIO1_DR (0X0209C000)
GPIO数据寄存器。写0或1控制引脚输出电平(低电平亮/灭,高电平反之)。GPIO1_GDIR (0X0209C004)
GPIO方向寄存器。设置引脚为输入(0)或输出(1),这里需设为输出模式。类比:你要控制一台电视,需要知道:电源开关位置(CCM_CCGR1)遥控器配对方式(SW_MUX)音量默认设置(SW_PAD)换台按钮(GPIO_DR)按钮功能分配(GPIO_GDIR)为什么需要这么多寄存器?
硬件控制是精细活:
在嵌入式系统中,控制一个LED可能需要多个步骤:开时钟:GPIO模块需要时钟信号才能工作。配引脚功能:确保这个引脚被用作GPIO,而不是其他功能(比如串口)。配电气属性:避免信号干扰,确保稳定。设GPIO方向:输出模式才能控制电平。写数据寄存器:输出高/低电平控制LED亮灭。
通常,Linux内核提供了更简洁的GPIO控制方法,比如:
led {
compatible = “gpio-leds”;
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; // 直接指定GPIO引脚 };
内核会自动处理时钟、复用等配置,无需手动写寄存器地址。
文档写法是**“底层直操作”**,通常用于特定需求或学习目的。