设备树 DTS 工控硬件配置:串口、CAN、GPIO、看门狗硬件资源配置
设备树 DTS 工控硬件配置:串口、CAN、GPIO、看门狗硬件资源配置
设备树是Linux内核的"硬件简历"——告诉内核板子上有什么资源、怎么接线,比源码里硬编码优雅一万倍。
一、设备树概念与作用
在Linux 3.x之前,硬件信息硬编码在内核源码的arch/arm/mach-xxx目录里,每换一块板子就要改内核代码、重新编译——痛苦指数极高。
设备树(Device Tree)的出现改变了这一切:硬件配置信息从内核代码中剥离出来,变成独立的.dts文件描述。内核启动时加载设备树,按描述初始化硬件——换板子只换设备树文件,不改内核。
类比理解:设备树就像一份"硬件清单"——内核是装修队,设备树告诉装修队"这个房间有3个插座、2个水龙头、1个空调接口",装修队照单施工。
核心术语:
| 术语 | 说明 |
|---|---|
| DTS | Device Tree Source,人类可读的文本源文件 |
| DTSI | Device Tree Source Include,可被其他DTS包含的公共片段 |
| DTB | Device Tree Blob,编译后的二进制文件,内核实际加载的格式 |
| DTC | Device Tree Compiler,DTS→DTB的编译工具 |
| compatible | 兼容字符串,匹配内核驱动的关键标识 |
二、DTS文件语法基础
2.1 节点与属性
DTS的基本结构是节点树,每个节点用{}包裹,属性用key = value定义:
/dts-v1/; /* DTS版本声明 */ / { /* 根节点,代表整块板子 */ model = "MyBoard v2.0"; /* 板子名称 */ compatible = "myvendor,myboard", "arm,cortex-a7"; /* 兼容字符串 */ #address-cells = <1>; /* 地址用1个32位单元格表示 */ #size-cells = <1>; /* 大小用1个32位单元格表示 */ cpus { cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; /* CPU编号 */ }; }; memory@80000000 { device_type = "memory"; reg = <0x80000000 0x20000000>; /* 起始地址+大小 = 512MB */ }; };关键语法要点:
compatible是驱动匹配的核心——内核根据这个字符串找对应驱动reg属性格式取决于父节点的#address-cells和#size-cells- 字符串属性用双引号,数值用
<>,布尔属性写上名字就行(存在即true)
2.2 兼容字符串(compatible)
compatible是DTS中最重要的属性,格式为"vendor,device":
compatible = "ti,am3352", "ti,am33xx"; /* 优先匹配ti,am3352驱动,找不到再匹配ti,am33xx */内核驱动中注册的ID表必须与compatible一致,否则驱动不会绑定到这个节点。
三、DTS编译流程
DTS源文件 (.dts/.dtsi) ↓ dtc编译器 (Device Tree Compiler) ↓ DTB二进制文件 (.dtb) ↓ bootloader传递给内核 (U-Boot: bootm / fdt) ↓ 内核解析DTB,创建设备节点3.1 dtc编译命令
# 安装dtcapt-getinstalldevice-tree-compiler# Debian/Ubuntu# 编译DTS为DTBdtc-Idts-Odtb-omyboard.dtb myboard.dts# 反编译DTB为DTS(调试时有用)dtc-Idtb-Odts-omyboard.dts myboard.dtb# 带包含路径编译dtc-Idts-Odtb-iinclude/-omyboard.dtb myboard.dts3.2 DTB加载方式
U-Boot加载DTB有两种方式:
# 方式1:DTB单独加载tftp 0x82000000 myboard.dtb tftp 0x80008000 zImage bootz 0x80008000 - 0x82000000# 方式2:DTB附加在内核镜像后(现代方式)# 内核编译时CONFIG_ARM_APPENDED_DTB=ycatzImage myboard.dtb>zImage_with_dtb内核启动后,设备树信息可以在/proc/device-tree/查看:
ls/proc/device-tree/# 可以看到所有节点目录cat/proc/device-tree/model# 输出板子名称四、串口配置实战
串口是工控最基础的通信接口,DTS配置决定了内核能识别出哪些串口、用什么参数。
4.1 UART节点配置
/* AM335x平台串口配置示例 */ uart0: serial@44e09000 { compatible = "ti,am3352-uart", "ti,omap3-uart"; reg = <0x44e09000 0x2000>; /* 寄存器地址范围 */ interrupts = <72>; /* 中断号 */ clock-frequency = <48000000>; /* 输入时钟频率 */ status = "okay"; /* okay=启用, disabled=禁用 */ /* 自定义属性:默认波特率 */ current-speed = <115200>; /* DMA配置(可选,大数据量时启用) */ dmas = <&edma 28 0>, <&edma 29 0>; dma-names = "rx", "tx"; }; /* 工控扩展串口(连接PLC) */ uart1: serial@48022000 { compatible = "ti,am3352-uart"; reg = <0x48022000 0x2000>; interrupts = <73>; status = "okay"; current-speed = <9600>; /* PLC通信波特率9600 */ };关键属性解析:
status:只有写"okay"才生效,"disabled"的节点内核不会注册current-speed:部分驱动支持从DTS读取默认波特率- DMA配置:高速串口(如48MHz时钟)配合DMA可减少CPU中断开销
4.2 引脚复用(pinctrl)
串口引脚可能和其他功能复用同一个物理pin,必须在DTS中明确声明:
&am33xx_pinmux { uart0_pins: pinmux_uart0_pins { pinctrl-single,bits = < 0x170 0x30 /* RXD */ 0x174 0x00 /* TXD */ >; }; }; &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; };五、CAN配置实战
CAN总线是工控的"标配"通信方式,DTS配置CAN控制器和波特率。
/* CAN节点配置 */ dcan0: can@481cc000 { compatible = "ti,am3352-dcan"; reg = <0x481cc000 0x2000>; interrupts = <52>; clocks = <&dcan0_fck>; status = "okay"; /* CAN波特率配置 */ /* CAN标准波特率通过clock-freq和bit-timing计算 */ /* 500kbps典型配置 */ }; /* CAN引脚复用 */ &am33xx_pinmux { dcan0_pins: pinmux_dcan0_pins { pinctrl-single,bits = < 0x178 0x20 /* CAN_RX */ 0x17c 0x20 /* CAN_TX */ >; }; }; &dcan0 { pinctrl-names = "default"; pinctrl-0 = <&dcan0_pins>; };CAN波特率通过ip命令配置(内核启动后):
iplinksetcan0typecan bitrate500000iplinksetcan0 up六、GPIO配置实战
GPIO是工控的"万能接口"——控制继电器、读取开关状态、驱动LED,全靠它。
6.1 GPIO控制器节点
gpio0: gpio@44e07000 { compatible = "ti,am3352-gpio"; reg = <0x44e07000 0x1000>; interrupts = <96>; gpio-controller; /* 标记这是一个GPIO控制器 */ #gpio-cells = <2>; /* 引用时需要2个参数: bank编号+偏移 */ ngpio = <32>; /* GPIO总数 */ status = "okay"; };6.2 GPIO作为设备引脚使用
/* LED设备,使用GPIO pin */ leds { compatible = "gpio-leds"; led0 { label = "status-led"; gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; /* gpio0 bank, pin21, 高电平亮 */ default-state = "off"; }; led1 { label = "alarm-led"; gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; /* 低电平亮 */ default-state = "off"; }; }; /* 继电器控制 */ relay-controller { compatible = "myvendor,relay"; gpios = <&gpio2 0 GPIO_ACTIVE_HIGH>, /* 继电器1 */ <&gpio2 1 GPIO_ACTIVE_HIGH>; /* 继电器2 */ relay-count = <2>; };<&gpio0 21 GPIO_ACTIVE_HIGH>含义:引用gpio0控制器,第21号引脚,高电平有效。
6.3 pinctrl引脚复用
&am33xx_pinmux { gpio_pins: pinmux_gpio_pins { pinctrl-single,bits = < /* 配置为GPIO模式而非串口/SPI等 */ 0x1a0 0x7f /* GPIO2_0 */ 0x1a4 0x7f /* GPIO2_1 */ >; }; };七、看门狗配置实战
看门狗(Watchdog)是工控系统的"保险丝"——程序卡死时自动重启,防止设备永久挂死。
/* 看门狗节点 */ wdt: watchdog@44e35000 { compatible = "ti,am3352-wdt"; reg = <0x44e35000 0x1000>; interrupts = <93>; status = "okay"; /* 看门狗超时时间(秒) */ timeout-sec = <30>; /* 喂狗方式: * - "magic-close": 关闭设备文件时必须写入特定字符'V'才停止看门狗 * 防止意外关闭导致看门狗失效 */ };用户空间喂狗程序:
#include<stdio.h>#include<fcntl.h>#include<unistd.h>#include<linux/watchdog.h>intmain(void){intfd=open("/dev/watchdog",O_WRONLY);if(fd<0){perror("open watchdog");return-1;}/* 设置超时30秒 */inttimeout=30;ioctl(fd,WDIOC_SETTIMEOUT,&timeout);while(1){/* 喂狗:写入任意一个字节 */write(fd,"\0",1);printf("喂狗成功\n");sleep(10);/* 每10秒喂一次,小于30秒超时 */}/* 正常退出时写入'V'停止看门狗 */write(fd,"V",1);close(fd);return0;}注意:如果程序崩溃退出没写’V’,看门狗会继续计时,30秒后触发硬件重启——这正是看门狗的"保险"作用。
八、自定义DTS覆盖板级配置(Overlay机制)
设备树Overlay(DTO)允许在基础DTS上叠加修改,不改原始文件。适合工控场景:基础板配置固定,扩展板动态叠加。
/* 基础板DTS: myboard.dts */ / { uart3: serial@48024000 { status = "disabled"; /* 默认禁用,扩展板启用 */ }; }; /* Overlay文件: myboard-can-extension.dtso */ /dts-v1/; /plugin/; / { fragment@0 { target = <&uart3>; __overlay__ { status = "okay"; /* 启用串口3 */ current-speed = <115200>; }; }; };编译和应用Overlay:
dtc-Idts-Odtb -@-ooverlay.dtbo overlay.dtso# 内核运行时动态加载mkdir/sys/kernel/config/device-tree/overlays/my-overlaycatoverlay.dtbo>/sys/kernel/config/device-tree/overlays/my-overlay/dtbo九、常见问题排查
9.1 修改DTS后串口不出现
原因排序:
- status没改okay——最常见的低级错误,节点默认
disabled - pinctrl没配置——引脚还复用给其他功能,串口拿不到物理pin
- compatible字符串不匹配——内核找不到对应驱动
- reg地址写错——寄存器地址和硬件手册不一致
排查步骤:
# 查看内核是否识别了设备节点ls/proc/device-tree/serial@44e09000/# 查看内核日志dmesg|grepuart# 查看pinctrl状态cat/sys/kernel/debug/pinctrl/pinctrl-maps# 查看驱动匹配cat/sys/bus/platform/devices/44e09000.serial/driver/modalias9.2 引脚冲突
两个设备要求同一个pin做不同功能,内核只会给先申请的那个:
# 查看引脚当前配置cat/sys/kernel/debug/pinctrl/pinctrl-maps|greppin48# 输出会显示pin48被哪个设备占了# 解决:在DTS中确保每个pin只被一个设备引用十、完整DTS配置示例
以下是一份工控板的完整DTS配置(基于AM335x平台),涵盖串口、CAN、GPIO、看门狗:
/dts-v1/; #include "am335x.dtsi" /* 芯片级基础定义 */ / { model = "ICBoard-AM335x-v2"; compatible = "myvendor,icboard", "ti,am3352"; chosen { stdout-path = &uart0; /* 系统console用uart0 */ }; memory@80000000 { device_type = "memory"; reg = <0x80000000 0x20000000>; }; /* LED指示灯 */ leds { compatible = "gpio-leds"; pinctrl-names = "default"; pinctrl-0 = <&led_pins>; led-status { label = "status"; gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; linux,default-trigger = "heartbeat"; }; led-alarm { label = "alarm"; gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; default-state = "off"; }; }; /* 继电器 */ relays { compatible = "gpio-relays"; pinctrl-names = "default"; pinctrl-0 = <&relay_pins>; gpios = <&gpio2 0 GPIO_ACTIVE_HIGH>, <&gpio2 1 GPIO_ACTIVE_HIGH>; }; }; /* 引脚复用配置 */ &am33xx_pinmux { uart0_pins: pinmux_uart0_pins { pinctrl-single,bits = <0x170 0x30 0x174 0x00>; }; uart1_pins: pinmux_uart1_pins { pinctrl-single,bits = <0x180 0x20 0x184 0x00>; }; dcan0_pins: pinmux_dcan0_pins { pinctrl-single,bits = <0x178 0x20 0x17c 0x20>; }; led_pins: pinmux_led_pins { pinctrl-single,bits = <0x1a0 0x7f>; }; relay_pins: pinmux_relay_pins { pinctrl-single,bits = <0x1a4 0x7f 0x1a8 0x7f>; }; }; /* 串口配置 */ &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay"; current-speed = <115200>; }; &uart1 { pinctrl-names = "default"; pinctrl-0 = <&uart1_pins>; status = "okay"; current-speed = <9600>; /* PLC通信 */ }; /* CAN配置 */ &dcan0 { pinctrl-names = "default"; pinctrl-0 = <&dcan0_pins>; status = "okay"; }; /* 看门狗 */ &wdt { status = "okay"; timeout-sec = <30>; };设备树把硬件配置从内核代码中解放出来,DTS文件清晰可读、修改方便、不用重编译内核。串口、CAN、GPIO、看门狗——工控四大硬件资源的配置,全在这一份文本文件中搞定。改配置、换板子,从"改内核源码+重新编译"的噩梦,变成了"改几行文本+编译dtb"的轻松操作。