news 2026/4/2 16:44:33

手把手教程:为ARM64自定义板卡编写设备树

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教程:为ARM64自定义板卡编写设备树

从零开始为ARM64自定义板卡构建设备树:实战全解析

你有没有遇到过这样的情况?
新设计的ARM64开发板焊接完成,U-Boot也能启动了,但Linux内核一到初始化外设就卡住——串口没输出、I2C设备找不到、内存只识别出一半……最后翻遍日志才发现,问题不在驱动,也不在Bootloader,而是设备树写错了

这在嵌入式开发中太常见了。尤其当你使用的是非公版、自定义的ARM64板卡时,设备树就是你的“硬件说明书”。它不像是可有可无的配置文件,而是一份必须精准描述每一个硬件细节的技术文档。写对了,系统丝滑启动;写错一个地址或中断号,轻则功能异常,重则直接宕机。

本文将带你一步步构建适用于你自己ARM64板卡的设备树,不讲空话,不堆术语,只讲你在实际开发中最需要知道的东西:怎么写?为什么这么写?容易踩哪些坑?如何快速定位问题?


设备树到底是什么?为什么非得用它?

早年的嵌入式Linux,硬件信息是硬编码在内核里的。比如某个UART控制器固定在0x101f1000地址,内核代码就直接写死这个地址去访问。一旦换了块板子,哪怕只是改了个外设位置,也得重新编译内核。

这种方式显然无法适应如今千变万化的嵌入式场景。于是社区引入了设备树(Device Tree)——一种把硬件描述从内核代码中剥离出来的机制。

简单说,设备树就是一个“数据结构版”的电路原理图。它告诉内核:

  • 我有几个CPU核心?
  • 内存从哪开始、多大?
  • UART接在哪段地址?用哪个中断线?
  • GPIO引脚现在配成什么功能?

而这一切都通过一个.dts文本文件来表达,编译成.dtb后由U-Boot传给内核。同一个内核镜像,配合不同的.dtb,就能跑在不同硬件上。

对于自定义板卡开发者来说,这意味着:你可以不用动内核源码,也能让主线Linux支持你的硬件。只要设备树写得准。


先看个完整的骨架:你的第一个.dts长什么样?

我们先来看一个典型的ARM64板卡设备树模板:

/dts-v1/; #include "skeleton64.dtsi" #include "my-soc.dtsi" / { model = "My Custom ARM64 Board"; compatible = "mycompany,custom-board", "arm,foundation-model"; chosen { bootargs = "console=ttyAMA0,115200 earlycon loglevel=8"; stdout-path = "serial0:115200"; }; memory@80000000 { device_type = "memory"; reg = <0x0 0x80000000 0x0 0x80000000>; /* 2GB */ }; }; &uart0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; };

别急着逐行背诵,我们拆开来看每部分的实际意义和常见错误点。

/dts-v1/;#include

这是标准开头。/dts-v1/;声明版本,不能少。

#include引入公共头文件,比如:
-skeleton64.dtsi:提供基本框架(如/cpus,/memory占位)
-my-soc.dtsi:SoC级定义,包含所有片上外设的默认节点

✅ 小技巧:建议把SoC共性抽成.dtsi,不同板型只需写.dts补丁即可复用。

modelcompatible

model = "My Custom ARM64 Board"; compatible = "mycompany,custom-board", "arm,foundation-model";
  • model是给人看的型号名,出现在/proc/device-tree/model
  • compatible才是关键!它是内核匹配machine_desc的依据

顺序很重要:先具体后通用。如果找不到mycompany,custom-board的匹配项,内核会尝试回退到arm,foundation-model

⚠️ 踩坑提醒:如果你发现系统启动用了“默认平台”而不是你的板子,八成是compatible没对上。

chosen:早期控制台和启动参数

chosen { bootargs = "console=ttyAMA0,115200 earlycon loglevel=8"; stdout-path = "serial0:115200"; };

这两个字段决定了你能否看到启动日志:
-bootargs传给内核的命令行参数
-stdout-path明确指定哪个设备作为标准输出

注意ttyAMA0serial0要对应起来。通常aliases里会定义:

aliases { serial0 = &uart0; };

✅ 实战建议:加上loglevel=8可以看到更多早期调试信息,排查问题时非常有用。

memory:别让内核“看不见”你的RAM

memory@80000000 { device_type = "memory"; reg = <0x0 0x80000000 0x0 0x80000000>; };

格式是(hi_addr_lo, lo_addr_lo, hi_size, lo_size),因为ARM64用64位表示。

常见错误:
- 地址写成0x80000000却漏掉高位<0 0x80000000 ...>→ 内核认为是32位地址空间
- 大小算错 → 系统报告内存比实际小

🛠️ 调试命令:启动后运行cat /proc/meminfo | grep MemTotal验证是否正确识别。


CPU节点怎么写?SMP启动失败怎么办?

多核ARM64平台必须正确定义/cpus节点,否则次级核心(secondary CPU)可能根本起不来。

cpus { #address-cells = <2>; #size-cells = <0>; cpu@0 { device_type = "cpu"; compatible = "arm,armv8"; reg = <0x0 0x0>; enable-method = "psci"; next-level-cache = <&L2_0>; }; cpu@1 { device_type = "cpu"; compatible = "arm,armv8"; reg = <0x0 0x1>; enable-method = "psci"; next-level-cache = <&L2_0>; }; };

重点说明几个易错点:

#address-cells = <2>必须加!

ARM64中CPU实例由64位MPIDR_EL1寄存器标识,所以要用两个cell表示地址。若省略,默认是1,会导致cpu@1解析失败。

reg字段是MPIDR值,不是编号!

虽然看起来像序号,但reg = <0 1>实际对应 MPIDR_EL1 =0x80000001。如果你的芯片手册规定CPU1的MPIDR是0x80000002,那你应该写<0 2>

enable-method = "psci"是现代标准

PSCI(Power State Coordination Interface)是ARM推荐的电源管理接口。旧平台可能用spin-table,但现在几乎都是PSCI。

❌ 典型症状:主核能跑,但dmesg显示 “CPU1: failed to come online” → 很可能是regenable-method错了。


SoC内部外设怎么挂?别忘了/soc这层桥

很多初学者直接在根节点下写UART、I2C,结果发现中断映射出错或者地址转换失败。正确的做法是在/soc下组织所有片上设备。

soc { #address-cells = <2>; #size-cells = <2>; compatible = "simple-bus"; interrupt-parent = <&gic>; ranges; uart0: serial@2a400000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x0 0x2a400000 0x0 0x1000>; interrupts = <0 17 4>; clocks = <&uart_clk>; status = "disabled"; }; };

这里的关键点:

ranges;必须存在!

它启用地址翻译机制,允许子设备使用局部地址(如@2a400000),并自动映射到物理内存空间。

没有它,设备可能无法被正确寻址。

interrupt-parent = <&gic>统一指定中断控制器

这样下面所有设备如果不特别指定,都会默认发中断给GIC。

status = "disabled"是安全默认

建议所有外设初始状态设为"disabled",只在真正要用时才改为"okay",避免资源冲突。


中断系统怎么配?GICv2 vs GICv3 差在哪?

ARM64标配GIC(Generic Interrupt Controller)。当前主流是GICv3,支持更多的中断线和虚拟化特性。

intc: interrupt-controller@gic { compatible = "arm,gic-v3"; reg = <0x0 0x1f000000 0x0 0x10000>, /* Distributor */ <0x0 0x1f020000 0x0 0x10000>; /* Redistributor */ #interrupt-cells = <3>; interrupt-controller; redist-region = <0 0x1f020000>; msi-controller; };

解释几个关键属性:

属性说明
#interrupt-cells = <3>中断描述符需三个值:(type, irq_num, flags)
reg包含两段分发器(Distributor)和重分发器(Redistributor)基地址
msi-controller支持MSI中断(用于PCIe设备)

✅ 推荐:优先使用GICv3。其支持ITS(Interrupt Translation Service),更适合高性能系统。


引脚复用控制:pinctrl才是“接通”外设的最后一环

即使你启用了UART控制器,如果RX/TX引脚没配成串口功能,照样收不到数据。

这就是 pinctrl 子系统的作用。

pinctrl { uart0_pins: uart0grp { fsl,pins = < SC_P_UART0_RXD_LSIO_GPIO1_IO17 0x06000020 SC_P_UART0_TXD_LSIO_GPIO1_IO18 0x06000020 >; }; }; &uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay"; };

每个pin的配置值(如0x06000020)由SoC厂商定义,通常包含:
- 上拉/下拉
- 驱动强度
- 复用模式选择

⚠️ 常见问题:设备树里使能了UART,但串口无输出 → 90%概率是pinctrl没配或配错了。


如何调试?这些工具你一定要会用

设备树写完不能直接烧,必须验证。以下是几个实用技巧:

1. 编译检查语法

dtc -I dts -O dtb -o board.dtb board.dts

如果有语法错误,dtc会报具体行号。

2. 查看生成内容

fdtprint board.dtb | grep uart

查看最终DTB是否包含预期节点。

3. 运行时查看

启动后进入系统:

cd /proc/device-tree find . -name name | xargs cat

可以确认设备树节点是否被正确加载。

4. 使用i2cdetect验证I2C总线

i2cdetect -y 1

如果设备没出现,先查设备树中的reg = <0x50>是否正确。


最后总结:一份高质量设备树的关键要素

模块关键点
整体结构使用.dtsi分离SoC与板级差异
CPU#address-cells=<2>,reg正确,enable-method=psci
Memory64位地址格式,大小准确
SOC桥ranges;compatible="simple-bus"
外设status="okay"+ 正确pinctrl+clocks链接
中断interrupt-parent设置合理,GIC版本匹配
调试开启earlycon,loglevel=8, 保留.dtb对比

到现在为止,你应该已经掌握了为ARM64自定义板卡编写设备树的核心能力。这不是一门“照抄手册”的技术,而是一种基于理解的工程实践。

记住:设备树的本质,是对硬件最忠实的数字化表达。你画的每一根线、焊的每一个电阻,最终都要反映在这个.dts文件里。

当你下次面对一块全新的板卡时,不妨拿出原理图,对照这篇指南,一步一步把它“翻译”成设备树。你会发现,原来让Linux认识你的硬件,并没有那么难。

如果你在实践中遇到了其他棘手的问题,欢迎留言交流——毕竟,每个踩过的坑,都是通往高手之路的垫脚石。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/22 7:08:52

cc2530与PC通信调试:IAR平台下的串口实战案例

从零打通CC2530与PC的串口链路&#xff1a;IAR实战调试全记录最近在做一个基于Zigbee的无线传感器项目&#xff0c;核心芯片选的是TI那颗经典的CC2530。虽然它发布多年&#xff0c;但在低功耗组网场景里依然能打——集成射频、8051内核、丰富外设&#xff0c;还自带Z-Stack协议…

作者头像 李华
网站建设 2026/3/21 17:40:55

5个关键步骤:用Building Tools插件实现建筑建模工作流革命

5个关键步骤&#xff1a;用Building Tools插件实现建筑建模工作流革命 【免费下载链接】building_tools Building generation addon for blender 项目地址: https://gitcode.com/gh_mirrors/bu/building_tools Building Tools作为Blender平台上的专业建筑生成插件&#…

作者头像 李华
网站建设 2026/3/14 2:31:44

B站缓存视频转换全攻略:m4s转MP4一键搞定

B站缓存视频转换全攻略&#xff1a;m4s转MP4一键搞定 【免费下载链接】m4s-converter 将bilibili缓存的m4s转成mp4(读PC端缓存目录) 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 还在为B站下载的视频无法在其他设备播放而烦恼吗&#xff1f;m4s-convert…

作者头像 李华
网站建设 2026/4/1 19:14:54

B站缓存视频转MP4终极指南:从发现痛点到精通使用

B站缓存视频转MP4终极指南&#xff1a;从发现痛点到精通使用 【免费下载链接】m4s-converter 将bilibili缓存的m4s转成mp4(读PC端缓存目录) 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经遇到过这样的情况&#xff1a;在B站收藏了心爱的视频&a…

作者头像 李华
网站建设 2026/3/27 8:35:00

m4s-converter:一键解决B站缓存视频播放难题

你是否曾经遇到过这样的情况&#xff1a;精心收藏的B站视频突然下架&#xff0c;那些缓存在手机或电脑里的m4s格式文件&#xff0c;却无法在其他播放器上观看&#xff1f;别担心&#xff0c;m4s-converter正是为你量身打造的解决方案&#xff0c;能够快速将B站m4s缓存视频转换为…

作者头像 李华
网站建设 2026/3/31 21:43:55

iperf3网络性能测试:从入门到精通的完整实战指南

还在为网络卡顿而烦恼吗&#xff1f;想要精准测量你的网络极限速度&#xff1f;iperf3就是你的网络性能检测神器&#xff01;作为专业的网络带宽测试工具&#xff0c;它能够帮你揭开网络性能的神秘面纱&#xff0c;让你对网速了如指掌。 【免费下载链接】iperf3-win-builds ipe…

作者头像 李华