拿到一份新平台的SDK,尤其是像全志CQR40这种从零开始接触的BSP包,第一反应往往是“解压完就懵了”。顶层目录一列,各种缩写和命名规则扑面而来,buildroot、brandy、kernel、out、target……每个文件夹都像在暗示自己很重要,但互相之间到底是什么关系,从哪里改配置,从哪里看编译产物,如果没人带,光靠目录名猜,至少浪费两三天。
这篇文章就把我分析和整理全志CQR40平台SDK文件夹结构的过程完整记录下来。包括每个核心目录的职责划分、构建脚本的调用顺序、配置文件与板级目录的对应关系,以及实际开发中容易踩的坑。无论是刚拿到SDK准备做首次编译的新手,还是需要裁剪系统、替换内核、调整驱动的老手,这份梳理都能帮你省掉一部分自己摸索的时间。我会尽量用实际路径和真实操作中遇到的问题来讲,而不是只贴一个静态的目录树。
1. 拿到SDK之后的第一件事:先盘清楚顶层目录在干什么
解压SDK之后不要急着看代码,先花半小时把顶层目录过一遍。这个习惯很重要,因为全志的SDK目录组织方式与一些从芯片原厂直接流出的标准BSP有区别,它更多是为“快速出镜像”服务的,而不是为“阅读代码”服务的。如果顺序搞反了,很容易陷入“源码在哪里”的困惑。
CQR40平台的SDK顶层目录,我第一次看到的时候大致是下面这个形态:
brandy/:存放引导程序相关,包括U-Boot和ARM Trusted Firmware(ATF)的源码与编译脚本buildroot/:根文件系统构建系统,负责生成用户态工具、库、以及最终的rootfs镜像kernel/:Linux内核源码目录,包含全志平台相关驱动和设备树target/:板级配置目录,存放不同开发板的配置、分区表、环境变量、打包规则tools/:打包、签名、烧录辅助脚本和工具out/:编译输出目录,最终所有镜像都会汇集到这里scripts/:一些辅助脚本,比如检查环境、初始化编译环境Makefile:顶层Makefile,是整个SDK编译的总入口README或README.md:大多数SDK都会有一份说明文件,但全志的SOP往往藏在scripts或target目录里,README反而比较简略
单看目录数量,CQR40这套SDK不算最复杂,但它把“引导程序”和“根文件系统”分别放在brandy和buildroot两个独立目录里,这一点和部分只用单一buildroot或者单一yocto的SDK不同。也就是说,你至少要理解三套构建逻辑:U-Boot的构建、内核的构建、根文件系统的构建。这三者虽然最终由顶层Makefile统一调度,但各自的配置体系和产物类型完全不同。
我把这个过程类比成一条产线:target是订单规格书,告诉你做哪款产品、用什么方案;kernel和brandy是两个核心零部件车间,一个负责系统核心、一个负责启动引导;buildroot是组装车间,把工具、库、脚本装进文件系统;tools是质检与包装段,把零件组装成最终镜像并打包。理解了这条产线,后面看任何一个具体路径都不会再迷路。
1.1 先别碰代码,把“构建入口”找出来
我拿到任何SDK,第一件事都是定位顶层Makefile以及它的默认目标。全志CQR40的顶层Makefile结构不复杂,但你直接make大概率会得到一个提示,告诉你需要先选择平台或者板级配置。这一步其实是SDK在提醒你:必须先指定板级配置,否则它不知道该用哪套设备树、哪套内核配置、哪套分区表。
例如在target目录下,板级配置往往按target/[板级名称]组织,板级名称可能是某个开发板代号。你需要在Makefile里找到类似BOARD或者PLATFORM的变量,把它的值指向对应目录名。这一步设置正确了,后续make -j才能顺利跑起来,否则编译系统会在早期阶段就报错退出。
很多人习惯跳过这一步,直接进到某个子目录单独make,结果导致产生的镜像不完整或者配置文件对不上。我的建议是永远从顶层入口走,哪怕你只需要编译内核,也先用顶层命令初始化一次完整编译环境,让SDK把该生成的中间配置都生成好,然后再去子目录单独操作。
1.2 读懂SDK的“命名习惯”比背目录更快
目录结构本身不复杂,真正有信息量的是里面文件的命名习惯。全志CQR40的SDK里,你会频繁看到一些固定前缀和后缀,比如sunxi(全志一些平台通用代号)、cqr40(平台代号)、defconfig(默认配置)、env.cfg(环境变量配置)、image.cfg(镜像打包配置)。这些命名不是随便起的,它是整个SDK各阶段构建的线索。
一个比较常见的组合是这样的:某个板级目录target/某某下面放着board*.conf、env*.cfg、sys_config.fex这类文件。其中sys_config.fex在早期全志平台里是替代设备树的板级配置描述,它描述的是屏幕、内存、电源、IO复用等硬件参数,最终会被工具转换成二进制或者直接参与启动配置。CQR40的SDK里虽然已经全面走设备树路线,但sys_config.fex这类配置在部分场景下仍然存在,它和.dts设备树文件之间有一定的对应关系。如果后面调试外设时改了设备树却没生效,先检查是不是还有一份sys_config.fex在别的地方覆盖了它。
所以,分析SDK文件夹的第一步不是读代码,而是把目录结构和命名规则对应上。搞清楚“什么东西在哪”,后续所有操作都会顺手很多。
2. 核心目录逐层拆解:每层目录各自承担什么职责
顶层目录看完,接下来就要逐个拆解核心目录了。我会按启动顺序从底层往上层讲:先看引导程序brandy,再看内核kernel,最后看根文件系统buildroot。这个顺序和实际启动顺序一致,也符合U-Boot -> 内核 -> rootfs这条主线。
2.1brandy目录:引导程序的“双拼组合”不可拆开理解
brandy这个目录名有点特殊,它不是全志自创的词,但在这里被用来统一承载“引导程序架构”。拆开看,里面通常包含两个主要组件:U-Boot(或者说基于U-Boot的分支)和arm-trusted-firmware(ATF)。在ARM64架构平台(CQR40是64位平台),芯片上电后CPU先跑的是BootROM,BootROM加载ATF,ATF初始化安全世界并跳转到U-Boot,U-Boot最终负责加载内核和设备树。
很多第一次接触全志SDK的人会困惑:为什么U-Boot源码目录里还套着ATF?因为在部分其他平台中,ATF是作为独立代码仓库处理的,甚至可能是由芯片原厂在出厂时固化好,不需要你关心。但在CQR40的SDK里,ATF和U-Boot是一起编译、一起签名的,两者通过共享的编译脚本配合,生成最终写入Boot分区的镜像。
brandy目录下一般还有一个config或者board类型的目录,存放各个板型的U-Boot配置。如果你改了内存参数、DDR频率或者启动参数,要改的就是这里的配置文件。另外注意,U-Boot环境变量(比如bootcmd、bootargs)不一定都写在U-Boot源码里,有一部分定义在target目录的env*.cfg中。这意味着你要修改启动参数时,优先去target目录找,而不是翻U-Boot源码里的默认值。
2.2kernel目录:设备树、驱动模块、内核配置三线并行
进入kernel目录之后,内容看起来就是标准内核源码树的形态:arch、drivers、fs、net等子目录都在。但全志平台的内核目录里有一个需要特别注意的东西——平台相关的驱动代码通常不是通过标准内核主线入树的,而是直接以全量源码文件的形式存在。这导致你在git log时会看到整块整块的驱动初始提交,而不是精细的补丁历史。
CQR40平台的内核版本不算太新,但这不代表它缺乏对现代特性的支持。全志在这类SDK里往往把重心放在硬件驱动、显示框架、视频编解码模块以及电源管理上,对内核版本的升级并不是第一优先级。所以,在实际开发中,不要轻易做内核大版本升级,比如从5.4升级到6.1这种复杂度较高的操作。因为全志的驱动实现方式与上游内核差异较大,强行升级会带来比较大的适配成本,而CQR40这类嵌入式场景真正需要的稳定的基础内核,加上可靠的周边驱动,而不是最新的内核特性。
在kernel目录里还有一个高频使用的位置:设备树文件夹,通常在arch/arm64/boot/dts/sunxi/或者类似路径。CQR40的板级设备树文件命名通常是sun*_*.dts或者带有cqr40关键字。每块开发板或者每个产品方案,对应一个.dts文件和可能的一组.dtsi公共头文件。修改GPIO、I2C、SPI、LCD屏参、触摸屏型号等,大部分都在这个目录里进行。
关于内核配置,SDK里的做法也很有特点。你直接make menuconfig当然可以,但保存配置时一定要确认它所保存的位置。全志的SDK中,内核的defconfig通常存放在内核目录的arch/arm64/configs/下,并且命名带有平台特征。但要注意,有一些板级附加配置可能分散在target目录下,编译系统会在加载defconfig之后,再根据板级配置做增量调整。如果你只改了defconfig而没注意板级覆盖配置,最终编出来的镜像可能还是旧行为。
2.3buildroot目录:别被它的名称骗了,它不只是“一棵十字”
buildroot是CQR40 SDK里体积最大的源码目录之一,因为它要负责整个用户态根文件系统的构建。从交叉编译器以外的所有环节开始,buildroot都会管理:下载第三方源码包、配置、编译、安装到目标根文件系统目录。对于CQR40这种嵌入式Linux平台,buildroot的存在意味着你不需要像桌面发行版那样从仓库安装软件包,而是把所有东西都编译进去,做成一个不可变的精简rootfs。
打开buildroot之后,你首先要看的不是源码,而是配置菜单:make menuconfig(进入buildroot目录后运行)。这套配置界面里,你可以选择文件系统格式(比如ext4、squashfs、ubifs)、选择shell(比如busybox版本、是否包含bash)、选择C库(glibc、musl、uclibc)、选择要集成哪些工具包(网络工具、调试器、服务端软件等)。
CQR40平台SDK的buildroot通常会预置一套板级配置,就像内核的defconfig一样,板级配置存放在configs/目录或者由target目录里的脚本指定。它是在“通用buildroot”基础上定制的一套“产品SKU”。你需要在编译之前确认,当前选择的buildroot配置是否与你要构建的产品需求匹配。如果你只是改内核、改驱动,根文件系统不需要大动,那么保持默认即可;但如果要新增应用程序、添加动态库或者调整启动脚本,那么buildroot的配置就需要仔细梳理了。
有一个细节我提醒一下:buildroot在编译过程中会生成一个output目录,里面又分为build、host、target、images等子目录。其中images是最终生成的镜像文件所在目录,它会包含rootfs的多种打包格式,比如rootfs.ext4、rootfs.tar.gz等。CQR40 SDK顶层out目录里的rootfs镜像,通常就是从这里拷贝过去的。所以定位根文件系统产物时,可以去buildroot/output/images确认,也可以去顶层out目录确认,两者是同样的东西,但不存在时容易让人困惑。
2.4target目录:板级配置里的“隐藏大Boss”
如果说内核和buildroot是大家比较熟悉的部分,那么target目录就是全志CQR40 SDK里最带有平台烙印的地方。表面上它只是一些板级配置文件,但实际上它控制的是整个编译流程的最终产物形态:使用哪块板子的设备树、打包什么格式的镜像、分区表怎么划分、启动参数怎么设置。
target目录下的每一个子目录,通常就对应一个板级项目或者一个开发板代号。进入某个板级目录后,你会看到诸如:
board*.conf:配置板级相关参数,包括DDR参数、电源管理、外设初始化sys_config.fex:以文本形式描述的硬件初始化参数,最终可能由工具转换env*.cfg:U-Boot环境变量,决定启动流程image.cfg:镜像打包配置,决定如何生成烧录镜像Makefile:该板级的编译入口规则
在实际开发中,如果你们产品团队从零开始设计硬件,不直接使用原厂开发板,那么最常做的事情就是复制一份最接近的板级目录,然后按自己的硬件改动board*.conf、设备树和分区表。这个目录就相当于整套SDK的“项目入口”。
我见过不少人直接在原厂板级配置上改内存、改屏幕、改分区,结果改了PDF设备树、改了内核配置,但忽略了sys_config.fex里还有一份对应的参数,导致最终生成的镜像是“内核认为用了A配置,U-Boot认为用了B配置”的错位状态。这类问题排查起来非常难受。所以在CQR40平台上,修改硬件相关内容时,必须同步检查target目录下对应板级的所有配置文件,而不是只看一个位置。
2.5tools和scripts目录:打包与签名是SDK的最后一公里
tools目录和scripts目录在很多人眼里属于“辅助目录”,但实际上它们决定了一个SDK能否顺利产出可烧录镜像。全志平台历来有一些私有的图片打包工具、签名工具和格式转换工具,它们通常以二进制形式或者Python脚本形式存在于tools目录。个别版本里还会提供licensetool、imgrepacker这类工具,用于对最终镜像做重打包或解析。
scripts目录则更多包含编译环境的检测和初始化逻辑。比如说,SDK可能要求宿主机是Ubuntu 18.04/20.04,要求安装特定版本的gcc、make、libncurses等依赖。scripts目录里往往会有环境检查脚本、一键安装依赖脚本、设置交叉编译器路径的脚本。如果编译过程中遇到“工具链找不到”“头文件缺失”这类问题,先运行环境初始化脚本往往能解决大半。
关于交叉编译器,brandy的U-Boot、kernel的内核、buildroot的用户态程序,每一层编译都需要对应的编译器。全志SDK通常会自带一套交叉编译器,放在tools/toolchain或者类似目录,而不是依赖宿主机系统编译器。这个设计很好,避免了编译器版本漂移问题。但代价是,当你在一个全新的Linux发行版上编译时,可能会因为宿主机的glibc版本过新,导致自带的交叉编译器无法运行。这种情况我没少遇到,解决方式就是安装SDK支持的高版本库,或者换用SDK推荐的宿主机系统版本。
3. 构建流程与关键脚本解析:从敲下make到出镜像,中间发生了什么
你能理解目录结构只是一个静态的视图。真正让SDK动起来的,是顶层Makefile驱动的三段式构建流程。我用一个章节专门拆解这条调用链,把每一步对应的目录和产物说清楚。
3.1 第一次编译:环境准备与板级选择
假设你在干净的Ubuntu系统里第一次编译CQR40 SDK。先不要直接make,先跑一下scripts目录下的环境初始化脚本,它会检查并安装依赖。在这个阶段,最常见的报错是缺libncurses、缺python3或者缺特定的uuid-dev库。按照脚本提示装好即可。
然后你需要打开顶层Makefile,确认板级变量的默认值。在很多全志SDK模板里,默认板级可能是原厂开发板型号。如果你只需要编译原厂镜像,那直接make即可;如果你要编译自己定制的板级配置,需要把BOARD变量改成你的目标板目录名,或者通过命令行参数覆盖,比如:
make BOARD=cqr40_xxx_defconfigBOARD变量名具体是什么,取决于这份SDK的规则,有些版本用PLATFORM。无论命名是什么,这一步的作用本质都一样:告诉SDK使用哪套板级配置。
板级配置选好后,顶层Makefile会依次调用各子目录的构建入口。一般是先编译brandy,再编译kernel,最后编译buildroot,然后执行打包。之所以是这个顺序,是因为内核可能需要在buildroot生成的rootfs中加入模块,U-Boot需要先确定分区表才能打包镜像,而buildroot也可以先编好基础工具再去处理内核模块。SDK内部的具体顺序可能略有调整,但逻辑主线就是这样。
3.2 分步构建:内核、引导程序、根文件系统如何“各干各的”
如果你不需要一次性全量编译,也可以分步执行。比如只编译内核,你可以在顶层Makefile中找到对应目标,或者直接进入kernel目录执行:
make ARCH=arm64 CROSS_COMPILE=... sunxi_defconfig make ARCH=arm64 CROSS_COMPILE=... Image dtbs注意,这里的交叉编译器路径要正确指向tools/toolchain下的工具链,不要直接用系统的aarch64-linux-gnu-gcc版本,即使它能编译,也可能因为内核 makefile 的依赖规则而出现诡异错误。
编译U-Boot时,通常会使用板级目录下的board*.conf来生成U-Boot配置,然后执行:
make ARCH=arm64 CROSS_COMPILE=... <board>_defconfig make ARCH=arm64 CROSS_COMPILE=... -j$(nproc)编完的u-boot.bin并不是最终烧录格式。全志的镜像通常需要经过打包和签名,这一步是在顶层打包阶段完成的,不需要你手动干预。但如果你好奇为什么编完的u-boot.bin不能直接烧录,那就是因为缺少全志私有头部和签名信息。这种设计使得最终bootloader具备启动校验能力,能防止固件被篡改。
buildroot目录的编译相对耗时,因为它可能要从头编译整个用户态环境。我建议第一次编译时加足够的并发:
cd buildroot make -j$(nproc)如果网络较差,第三方代码包下载会很慢,甚至失败。CQR40 SDK的buildroot配置里一般会有本地下载缓存机制,或者从某个镜像服务器拉取。如果卡在下载阶段,可以手动把源码包放进buildroot的dl目录再做编译,这个目录名可能存在,具体以SDK实际为准。
3.3 镜像打包:最终产物在out目录里长什么样
当三个部分都编译成功后,顶层Makefile会执行打包,把所有中间产物转换成最终镜像,输出到out目录。查看out目录,你通常能看到:
boot.img或者boot0相关文件:包含U-Boot、ATF、可能的启动Logokernel相关镜像:可能是Image、zImage或者整合进某个分区的文件rootfs相关镜像:rootfs.ext4、rootfs.squashfs或者其他格式- 烧录工具配套的打包镜像:比如全志常用的
*.img整包镜像,用于PhoenixSuit或者命令行烧录
CQR40平台支持某种分区烧录方式,即通过live-suit或者phoenixsuit工具把整包镜像烧进eMMC/SD卡。整包镜像内部会按照分区表划分为多个条目,包括bootloader、boot、rootfs、misc等分区。out目录里的最终镜像文件名通常带有板级名称和日期信息,方便区分。
如果只是开发调试阶段,没必要每次都生成整包并烧录,更快的做法是只替换对应分区。例如你改了内核,把out目录下的内核镜像单独烧到boot分区即可,不需要重新烧rootfs。这个流程在实际开发中能节省不少时间,我特别推荐掌握。
4. 配置与裁剪:实际开发中如何用好这份SDK目录结构
理解目录结构和构建流程之后,最终要落到实际开发。CQR40这个系列的SDK,最常见的日常开发需求就几类:修改板级配置、裁剪内核、往rootfs里加应用程序、调整启动流程。每一类需求的落地路径,都和目录结构强相关。
4.1 修改板级配置:从拷贝目录到第一次适配
实际项目中很少有人直接在原厂开发板配置上改,更常见的做法是新建一个板级目录,复制相近的配置,然后逐步适配。操作路径大致是:
- 在
target目录下复制原厂开发板目录,重命名成你们的产品代号 - 修改
board*.conf里的DDR参数、电源配置等硬件相关项 - 修改
sys_config.fex或者设备树.dts,调整外设引脚、屏幕参数、传感器接入 - 修改分区表,如果产品有特殊的分区需求,需要在这里调整
- 配置
env*.cfg里的启动参数,比如串口波特率、启动logo、根文件系统挂载参数
修改完这些配置之后,重新编译,烧录新镜像。这类工作本质上是“硬件初始化参数与软件配置的匹配过程”,每改一次硬件,都要建立并维护这些配置与硬件实际状态的对应关系。
我做这套适配的时候有个经验:千万要把每处修改做成小步演进并记录日志。嵌入式开发里最容易出现的坑就是,明明只换了一个屏幕,结果内核对不上,于是把屏幕驱动、背光引脚、触摸IC全查了一遍,最后发现真正改漏的位置在sys_config.fex里的某个私有节点。没有系统性的修改记录,排查这种问题是纯浪费时间的。
4.2 裁剪内核:缩小镜像体积时要同步考虑驱动
CQR40平台的镜像空间大小往往是产品定义中明确要求的,有一些产品会对rootfs和Boot数据区有严格的容量限制。这个时候就涉及到裁剪,而裁剪动作主要集中在kernel和buildroot两个目录。
内核裁剪最常见的手段是调整defconfig。在kernel目录下运行:
make ARCH=arm64 CROSS_COMPILE=... menuconfig按设备需求关闭不需要的驱动和子系统。对于CQR40这种主要聚焦固定硬件场景的平台,通常可以把网络协议栈的部分功能关掉、关掉蓝牙、关掉不需要的USB设备类别、关掉一些不常用的文件系统支持,可以大幅缩小内核体积。但裁剪的时候要特别小心,比如某些外设驱动虽然你在当前产品里不用,但它的编译选项被某个其他子系统依赖,关掉后可能导致某个核心功能崩溃。
做完配置修改后,不光要保存配置,还要验证它真正被编译进内核,而不是被板级覆盖配置冲掉了。我之前遇到过好多次“改了配置,但编出来的内核没变化”的情况,后来养成了一个习惯:编译之后用strings检查产物中是否包含预期配置的符号,或者使用make savedefconfig对比行为。
裁剪rootfs相对更直接一些。在buildroot的make menuconfig中取消不需要的包,重新编译即可。如果只是临时减少体积,也可以只调整文件系统稀疏文件的分配策略,但这个属于更底层的空间优化,普通开发中不太常用。
4.3 添加应用程序:构建系统与目录结构如何配合
CQR40平台的应用开发,如果只是自己写的几个程序,不依赖复杂的第三方库,最直接的方式是直接在buildroot里新增加一个包目录。具体来说,在buildroot/package/下新建一个目录,编写Config.in和.mk文件,然后通过menuconfig选中它,构建系统会帮你交叉编译并在rootfs里安装。
如果你有一个完整的应用运行环境,比如带QT或者LVGL的图形界面,依赖较多的第三方库,那么建议优先级是先通过buildroot集成这些第三方库,再把自己的应用作为包加入。而不是在宿主机上交叉编译好应用,然后通过NFS挂载或者手动拷贝进rootfs,后者在开发初期快速验证一下可以,但作为产品方案,手动拷贝的构建方式不好维护,也不能复现。
在buildroot里加应用的一个好处是,它的配置文件、启动脚本、依赖关系可以在makefile中显式描述,系统在构建时会自动处理依赖顺序,不需要你手动在rootfs启动脚本里写一堆insmod、export LD_LIBRARY_PATH等脆弱的命令。
4.4 调整启动流程:从U-Boot环境变量到rootfs的init脚本
如果你想让产品开机更快、启动更干净,调整点主要在两个位置:U-Boot的env*.cfg和rootfs内的启动脚本。env*.cfg里可以修改bootcmd、bootargs,比如去掉不必要的启动打印、加快U-Boot的延时。rootfs内如果有/etc/init.d/或者/etc/inittab,则负责决定用户态启动服务。
CQR40平台对启动时间的优化一般可以从串口打印里推断各阶段耗时。先用原厂镜像掐秒表,找到耗时最高的阶段,再针对性优化。最常见的优化点:减少U-Boot等待按键的延时、关闭内核的某些初始化打印、精简rootfs的启动服务、用squashfs替换ext4以减少文件系统校验开销。这些优化都会落到相应目录的配置文件中,本质上又是回到目录结构里去做修改。
5. 常见问题与排查技巧实录:SDK目录分析后真正帮到我的几件事
最后这部分,我把自己在CQR40平台SDK使用过程中踩过的坑和排查经验整理成一个速查表,每一条都对应具体的目录和操作方式。这样你在遇到类似问题时,不至于对着黑乎乎的终端发愁。
5.1 编译到一半报错:目录路径里的“隐形陷阱”
SDK解压之后,我建议放在路径中没有空格的目录下,最好是纯英文路径。因为我遇到过几次编译到一半,包管理器或者顶层脚本因为路径包含空格或者中文出现诡异的引号错误。特别是在buildroot下载源码包时,路径问题会引发比较难排查的随机失败。把整个SDK放到位,比如~/workspace/cqr40_sdk这种路径,能减少很多低级的编译问题。
另外一个频发的坑是磁盘空间不足。全志SDK全量编译后占用空间不小,而且out目录和buildroot/output里会积累大量的中间文件。编译前确认磁盘剩余空间足够,否则编译到一半盘满了,报错信息可能完全没有规律。甚至后面重新编译时还会因为旧的中间产物损坏而连环报错,那种情况我推荐直接清理out目录和buildroot/output重新编译,往往比尝试修复更稳。
5.2 设备树改动不生效:检查是否被“第二份配置”盖住了
这是嵌入式平台上的经典问题。在CQR40 SDK里,设备树不只是唯一硬件描述来源,前面提到的sys_config.fex可能在某些板级配置中仍然参与设备信息传递。你在设备树里加了某个节点,结果Linux起来之后发现硬件号、引脚不对,那就要去查target目录下对应的板级配置文件是否也有类似定义。
排查这类问题,一个有效手段是编译之后,用工具导出设备树反编译文件,查看实际被内核使用的设备树内容,确认你的修改是否真的进入了最终产物。全志平台一般可以使用dtc工具将.dtb反编译为.dts文本,然后在里面搜索你修改的关键字。如果反编译结果没有你的修改,那么问题一定出在“还有另一份配置文件”覆盖或者打包阶段替换了设备树。
5.3 烧录后无法启动:从U-Boot串口日志定位是哪一阶段失败
CQR40平台上如果烧录之后起不来,首先不要慌。接上调试串口,观察打印信息。通常会有几种典型表现:
- 完全没有串口输出:多半是启动镜像本身没有烧对,或boot0没加载,检查烧录工具是否选择了正确的整包镜像
- U-Boot阶段挂掉:可能是DDR参数、时钟初始化不对,回到
board*.conf或sys_config.fex检查 - U-Boot正常、内核起不来:大概率是设备树和内核不匹配,或者启动参数没传对,检查
env*.cfg里的bootargs - 内核起来了、但rootfs挂载失败:检查rootfs格式和分区表,确认内核支持对应的文件系统格式
串口日志是整个启动流程的“心电图”,它能把问题范围缩小到具体目录和配置。一般U-Boot启动时都会打印当前板级配置下的时钟、DDR、启动设备信息,你要学会从中找到关键行。如果编译时关闭了串口打印,那就优先把板级配置里的调试串口打开,否则后续排查会非常吃力。
5.4 交叉编译器跑不起来:宿主系统兼容性解法
这个问题现在依然会碰到,尤其是在新版本系统(较新的Ubuntu发行版)下编译旧版SDK。自带CROSS_COMPILE工具链因为依赖宿主机的旧版库文件,而新版本系统可能已经不再包含这些库,导致提示找不到某些库文件,或者提示版本不兼容。
解决方案有几种,优先推荐换到SDK官方推荐的宿主系统版本,很多全志SDK的文档里都会明确标注支持的Ubuntu版本。如果不想换系统,可以尝试安装兼容库,或者从旧版本系统的软件源中提取对应的库文件手动放置到工具链的搜索路径。不过说实话,最省心的还是用官方推荐环境,或者准备一个干净容器环境,专门用来编译这套SDK。这样后面还能复现构建,避免宿主机环境的漂移问题。
6. 一些顺手总结:SDK分析能力的经验沉淀
前面几节已经把CQR40平台的目录结构、构建流程、开发操作和常见问题都过了一遍。这些内容写到这,基本就是一套“从那头懵到上手编译”的完整路径。但光能编译、能烧录还只是起步,真正体现对一个平台熟悉程度的,是你能不能在目录结构里快速准确定位问题域。
比如突然遇到一个USB转串口无法识别的问题,你能快速判断:这个问题出在内核USB设备驱动、设备树节点配置、还是buildroot的udev规则?再从目录结构上锁定应该去改哪个文件。这种“问题到文件”的路径映射能力,就是通过一次一次翻目录、一次一次踩坑积累出来的。
从我自己的经验来看,分析SDK文件夹这件事,本质上就是在建立“问题与文件”的映射关系。目录结构看起来是静态的,但当你对每个目录背后的构建职责、产物、常见改动点都心里有数之后,这套静态结构就变成了一张动态的调试地图。很多问题不再需要盲目搜索全局代码,直接定向到对应目录,效率会高很多。
如果你也刚拿到CQR40或者类似的平台,我的建议很简单:不要急着写代码,先花时间把目录结构、构建脚本、产物关系完整梳理一遍。这个投入会在之后每一次编译、每一次排查中持续回报你。甚至可以说,对SDK目录结构的理解深度,往往决定了一个人在这个平台上的开发效率上限。