OpenSTLinux 6.6 Yocto 这套东西我在一块 STM32MP2 的板子上折腾了整整一个多星期。手头的板子从最初只有串口打印到完整把 Weston 桌面跑起来中间绕的弯路十个手指头数不过来。这一篇是这个系列的第六篇前面几篇分别讲了 BSP 选型、构建主机准备、Yocto 源码拉取、STM32CubeMX 工程生成和首轮镜像构建。今天要解决的问题更具体STM32CubeMX 生成的引脚配置、时钟树、DDR 参数这些硬件信息怎么才能干净利落地汇入 OpenSTLinux 的 Yocto 构建链让 U-Boot 和内核按我们自己的板子跑起来。适合谁读手里有 STM32MP2 开发板已经会基本 Yocto 操作但被设备树和 U-Boot 兼容性卡住的工程师以及想把 CubeMX 生成结果用进 Linux BSP 的硬件工程师。1. 整体设计与思路拆解1.1 为什么 STM32MP2 需要“CubeMX Yocto”两条腿走路STM32MP2 不是裸机 MCU它内部是应用处理器加实时内核的复合架构。应用处理器上跑的是 Linux这一层从下往上依次是 TF-A安全世界和启动固件、U-Boot引导加载器、Linux 内核、根文件系统。OpenSTLinux 本质就是一个经过厂商适配的 Yocto 发行版把这一整套用 bitbake 编排起来。OpenSTLinux 6.6 的“6.6”指的是 Linux 内核版本号这是当前一个较新的长期支持内核设备树、驱动模型、图形栈都比老内核新了不少。在这个架构下传统裸机开发的思路是行不通的。裸机开发拿 CubeMX 生成一段初始化代码放进工程就完事但 Linux 场景下 CubeMX 真正的产出不是 C 代码而是设备树源文件。设备树就是描述硬件的一张“配置单”哪个引脚是 UART 调试口、哪个 GPIO 接到了 LED、DDR 的时序参数是多少、以太网 PHY 的复位脚是谁。Linux 内核和 U-Boot 都是靠这个配置单去理解硬件的。为什么强调两条腿走路因为手工写设备树太容易出错了。STM32MP2 这种级别的处理器引脚复用动辄上百个时钟树和 DDR 参数的组合爆炸级别靠人眼核对原理图和芯片手册几乎不可能一次写对。CubeMX 的价值在于图形化地管理引脚冲突、自动计算时钟树并且能把 DDR 初始化参数也调出来。而 Yocto 的价值在于把这份设备树编译成内核和 U-Boot 都能识别的 dtb 文件打包进镜像最终部署到板子上。两者是互补关系CubeMX 负责“设计”Yocto 负责“施工”。我自己的体会是这个协作模式最妙的地方在于有些硬件级的问题能在设计阶段就暴露。比如两个外设抢同一个引脚CubeMX 当场就会报冲突DDR 参数和板子实际贴的颗粒不匹配在 CubeMX 里调 DDR tuning 的时候就能发现时序收敛不了。这些问题如果留到板子焊好再排查那就真的是灾难了。1.2 一条从硬件描述到系统启动的信息链路把 CubeMX 和 Yocto 串起来之后整个信息流是这样的首先在 CubeMX 里建立 STM32MP2 工程配置好引脚、时钟、DDR然后生成工程。生成结果里会出现一批以 dts、dtsi、defconfig 结尾的文件。这些文件还不能直接参与 Yocto 构建需要把它们放进一个自定义的 Yocto BSP 层里。接下来内核构建配方会把这些 dts 文件作为源文件纳入编译最终生成 dtb 二进制。U-Boot 构建配方会拿到另一份用于 U-Boot 的设备树源文件和 defconfig编译生成 U-Boot 需要的 dtb。TF-A 部分会拿到 DDR 初始化相关参数参与 FIP 打包。这里的“FIP”是 ARM 标准固件包里的一种封装格式把 BL31、BL32、U-Boot 等都打包进去TF-A 的 BL2 在启动时从 FIP 里解析加载。启动链路大致是上电后 ROM 里的 BootROM 加载 TF-ATF-A 初始化 DDR 和时钟然后从 FIP 里加载 U-Boot 到 DDR 运行U-Boot 再读取内核和设备树把控制权交给 Linux。这个过程中如果 TF-A 用的 DDR 参数和板子不匹配整个系统会在最开始就死掉串口一条 log 都看不到如果 U-Boot 的设备树和内核的设备树对同一个外设的理解不一致后面大概率也是起不来或者外设工作异常。这条链路里最需要重视的就是“一致性”。我见过不止一次有人只在内核侧改了设备树U-Boot 还是用官方评估板的配置结果启动阶段 U-Boot 把 DDR 初始化成错误的时序内核数据一加载就崩。所以整个系列的主题可以浓缩成一句话保证 TF-A、U-Boot、内核三份硬件描述严格一致。1.3 为什么这次放在第六篇才讲这个系列前几篇的节奏是先搭环境、拉代码、通读构建系统的用法、生成 CubeMX 工程然后第一轮完整构建出一套能在官方评估板上跑起来的镜像。如果前面的步骤还没完成我强烈建议先回去把基础镜像跑通再来接着读不然后面排查问题的时候会分不清到底是环境问题还是配置问题。之所以把这个“接入”环节放在第六篇是因为它处在从“能跑”到“能自己定义硬件”的分水岭上。前期构建出来的东西是“官方板能用”而这篇做完之后你的板子就从“开发板”变成了“自己的板子”。CubeMX 工程里的每一次改动经过一系列转换会真正决定最终镜像里内核怎么初始化硬件。另外一个现实原因是这个环节涉及的构建时间跨度和错误模型都比较复杂。第一个镜像全量构建可能要几个小时到半天加上设备树编译、U-Boot 重编、FIP 打包这些环节调试一个低级错误往往要付出很大的时间成本。所以一定要理解整个流程之后再去动手不然浪费的都是自己的时间。2. 环境准备与工程配置要点2.1 构建主机的基础要求与 OpenSTLinux 拉取先说构建主机。Yocto 不是个轻量工具一次全量构建会编译整个工具链、内核、U-Boot、TF-A 以及一堆用户空间包。我实际使用下来建议至少满足这些条件64 位 Ubuntu 22.04 LTS 或等效发行版可用磁盘空间不低于 300GB强烈建议用 SSDCPU 至少 8 核内存不少于 16GB。如果只有 4 核 8GB 的机器能不能构建能但首次全量构建可能会超过五六个小时中途内存不够还会直接让构建进程被杀掉体验极差。拉取 OpenSTLinux 源码通常使用 repo 工具。repo 是一个多 git 仓库管理工具用来一次性拉取 manifest 里定义的所有层和组件。命令大概是这样的repo init -u 官方manifest仓库地址 -b 对应6.6内核的稳定分支 repo sync其中对应6.6内核的稳定分支在厂商发布时会标明一般和内核版本线对应。如果网络环境不稳定repo sync中断了可以直接再执行一次repo 会断点续传不会从头拉。源码拉完以后会看到一个包含layers、build等目录的目录结构。进入构建环境前需要加载环境配置脚本。厂商的 OpenSTLinux 发行版通常提供一个封装好的脚本一般叫envsetup.sh路径大概在layers/meta-st/scripts/下。执行时会让你选择 DISTRO 和 MACHINEcd 源码根目录 source layers/meta-st/scripts/envsetup.sh常见的 DISTRO 有openstlinux-westonMACHINE 则对应芯片系列STM32MP2 系列一般会有一个统一的 machine 名再靠设备树来区分具体板子。选好之后脚本会设置好 bitbake 需要的环境变量后面所有构建命令都要在这个环境里执行。这里有个经验如果只需要软件包层面的定制比如加几个应用、改启动脚本可以不用每次都重新配置环境但一旦动了设备树、U-Boot defconfig 这类 BSP 底层文件建议重新开一个干净的终端再 source 一遍环境避免残留变量干扰构建。2.2 STM32CubeMX 工程里必须做对的关键设置CubeMX 这边第一步是确认安装了解对应芯片系列的固件支持包。支持包没有装好的话创建工程时选不到芯片。选型以后核心工作集中在三块引脚配置、时钟树、DDR 参数。引脚配置要格外小心默认占用。STM32MP2 这类处理器很多引脚上电时有默认功能比如 JTAG、调试串口、PMIC 相关信号。如果不注意很容易把调试口或者安全启动需要的引脚挪用了后面板子直接变砖或者连日志都看不到。我的建议是第一版工程只配最核心的东西调试串口、SDMMC用来启动系统、电源管理相关、以太网或者 USB 等基础外设。LED、按键这类简单 GPIO 可以一起配上方便验证。其他外设等系统跑通了再一点点加回来。时钟树的配置在 CubeMX 里可以自动求解。但要注意最终生成的系统时钟频率、DDR 时钟、总线时钟数值。特别是 DDR 时钟要严格对照板子上内存颗粒的规格。然后就是 DDR 参数的初始化这个很容易被忽视。CubeMX 提供的 DDR tuning 功能可以扫描时序参数生成一组能通过读写测试的配置。实际操作时这些参数会放到最终生成的设备树和 TF-A 相关的配置里。最后生成工程时务必选择生成 Linux 相关的目标而不是裸机工程。生成选项里可能包含对 U-Boot 和内核的支持选项建议在生成前把相关选项都勾上。生成的目录里文件后缀能直观看出用途以-u-boot.dtsi结尾的属于 U-Boot 侧设备树片段普通 dts/dtsi 是内核侧或公共设备树defconfig 是内核或 U-Boot 的默认配置。2.3 生成后先别急着构建认识这几个文件CubeMX 生成完工程后先花五分钟把输出文件过一遍。一个典型的工程会包含这些关键文件文件作用后续去向.ioc工程文件CubeMX 工程源改引脚重新生成用保留*.dts/*.dtsi内核设备树源文件拷贝到内核配方*-u-boot.dtsiU-Boot 设备树片段拷贝到 U-Boot 配方引导器和分区相关配置FIP、分区布局描述按需调整defconfig内核或 U-Boot 默认配置拷贝到对应配方这些文件不能直接去动厂商源码包里的对应层文件那样会在下一次源码更新时被覆盖而且会污染构建环境。正确做法是把所有定制的文件全部收拢到自己的 BSP 层里用 bbappend 追加配方的方式去覆盖。这就是下面一节要讲的核心实操。3. 核心实操把 CubeMX 的“硬件配置”搬进 Yocto 层3.1 新建一个属于你自己的 BSP 层在 Yocto 里“层”就是一组配方的集合目录。自定义板级支持几乎总是需要新建一个层用来放自己的设备树、U-Boot 补丁、镜像定制。进入源码根目录下包含bitbake命令的环境后执行cd 源码根目录/layers bitbake-layers create-layer ../meta-myboard bitbake-layers add-layer ../meta-myboardcreate-layer会生成一个标准层模板里面有conf/layer.conf以及recipes-*目录结构。add-layer会把层注册到bblayers.conf里。层名起得清楚些后续日志和排错都方便。新建的层默认有一个优先级一般建议把自定义层的优先级调得比官方层高一些比如不小于 6这样设备树等文件以自定义层的版本为准。在conf/layer.conf里会有BBFILE_PRIORITY变量按需修改。层的目录通常这样组织recipes-kernel/linux/内核相关 bbappend放设备树源文件recipes-bsp/u-boot/U-Boot 相关 bbappend放 U-Boot 设备树和 defconfigrecipes-core/images/自定义镜像配方recipes-bsp/tf-a/TF-A 相关补丁或配置3.2 把设备树源文件接进内核编译接下来把 CubeMX 生成的内核设备树源文件拷贝进来。假设 CubeMX 输出目录下有一批myboard.dts、myboard-common.dtsi之类的文件mkdir -p 层目录/recipes-kernel/linux/files cp CubeMX工程目录/*.dts* 层目录/recipes-kernel/linux/files/然后在同一目录下新建一个 bbappend 文件名字对应内核配方名加后缀比如linux-stm32mp_%.bbappend。内容大致是这样FILESEXTRAPATHS:prepend : ${THISDIR}/files: SRC_URI:append file://myboard.dts file://myboard-common.dtsi KERNEL_DEVICETREE myboard.dtbFILESEXTRAPATHS是 bitbake 找本地文件的关键它告诉构建系统额外到哪几个目录找文件SRC_URI:append把设备树源收录进构建输入KERNEL_DEVICETREE决定内核构建完要把哪个 dtb 安装到部署目录。这个变量一般填不带路径的文件名构建系统会自动在源码树的设备树目录里找匹配的源文件。这里有个很容易踩的细节设备树源文件里的#include引用路径必须能被内核构建系统找得到。CubeMX 生成的 dts 通常会引用芯片厂商提供的一些头文件或 dtsi如果内核版本和生成时不一致可能找不到对应的 include 目标。遇到编译报错先看是不是 include 路径的问题必要时在 bbappend 里把头文件也通过 SRC_URI 加进去或者调整内核设备树搜索路径。3.3 让 U-Boot 识别你的开发板U-Boot 这边同样要接到自己的设备树。CubeMX 生成的*-u-boot.dtsi和板级 defconfig 需要单独处理。先拷贝到 U-Boot 配方目录mkdir -p 层目录/recipes-bsp/u-boot/files cp CubeMX工程目录/*-u-boot.dtsi 层目录/recipes-bsp/u-boot/files/然后创建u-boot-stm32mp_%.bbappend之类的文件把 U-Boot 侧源文件追加到构建输入FILESEXTRAPATHS:prepend : ${THISDIR}/files: SRC_URI:append file://myboard-u-boot.dtsi光有设备树还不够还要告诉 U-Boot 默认使用哪个设备树。这通常在 U-Boot 的 defconfig 里设置。ST 官方的 U-Boot 配方一般允许通过 bbappend 覆盖或追加配置片段。较常见的做法是把CONFIG_DEFAULT_DEVICE_TREEmyboard写进 U-Boot 的板级配置里。U-Boot 在编译时会把默认设备树编进自身启动时如果没有特别指定就会使用这个默认值。千万别忽略这块。很多人只改了内核设备树U-Boot 还是原来的官方板设备树就会出现 U-Boot 阶段能跑但启动内核时传入的设备树不对或者 DDR 初始化和内核不匹配的问题。改完 U-Boot 配置后必须重新构建 U-Boot并且重新生成 FIP否则 U-Boot 的改动不会生效。TF-A 和 U-Boot 都在 FIP 里所以常见操作是让 TF-A 和 U-Boot 一起构建、一起打包。整个过程里我最常遇到的错误就是有人把*-u-boot.dtsi当成了内核设备树的一部分拷进内核层。U-Boot 的设备树片段和内核设备树是两套体系虽然语法相似但内容上有很多和启动引导相关的特殊节点放错位置会让内核构建或者 U-Boot 构建报奇怪的错误。3.4 让镜像按照你的需求组装软件包设备树和引导固件搞定后再处理系统镜像层面的事。默认的 OpenSTLinux 镜像可能带了较多图形和工具组件如果做产品化通常要定制。一种做法是新建一个自己的镜像配方继承官方镜像再追加内容。比如创建recipes-core/images/my-iot-image.bbrequire recipes-core/images/st-image-weston.bb IMAGE_INSTALL:append \ my-application \ packagegroup-network-tools \ i2c-tools \ require关键字把官方镜像配方的内容全部引入IMAGE_INSTALL:append追加目标包。Yocto 解析这个配方时会继承官方镜像的所有配置然后加上你自己定义的包。如果只想加一些文件、开机脚本也可以把这些文件打包成一个只包含数据文件的配方再在镜像配方里追加这个包。开机初始化脚本要注意文件权限和安装路径。我踩过的坑是脚本写好了也加入了镜像但忘记赋可执行权限结果系统起来后脚本根本没有执行。如果手头有多台机器或者经常要反复全量构建建议把 Yocto 的共享状态缓存sstate目录和下载目录DL_DIR放到独立位置并做好缓存共享。这样避免每台机器都重新拉取全部源码和编译一遍工具链能省下大量时间。4. 完整构建与烧录启动实录4.1 一条命令构建完整 OpenSTLinux 镜像完成设备树和 U-Boot 定制后就可以构建完整镜像了。在 envsetup 环境下执行bitbake st-image-westonst-image-weston是带图形桌面Weston/Wayland的完整镜像目标。如果只想跑一个最小控制台系统可以选择官方提供的 core 镜像目标。构建过程会依次编译 TF-A、U-Boot、内核、设备树、根文件系统最后生成完整镜像。首次构建的时间取决于机器配置和网速快的话两小时左右慢的话半天。如果只是改了设备树和 U-Boot后面的构建通常会快很多因为大部分包还有 sstate 缓存。但是要注意设备树和 U-Boot 改动比较底层会触发内核镜像和 FIP 的重新打包建议利用bitbake -c clean对改动的组件做一次清理再重编不要迷信增量构建。构建结束后的部署目录通常在这里源码根目录/build-DISTRO/tmp-glibc/deploy/images/MACHINE/tmp-glibc是构建根目录deploy/images是最终产出物所在地。这个目录里同时存在 TF-A、U-Boot、内核、dtb、根文件系统镜像和整个 SD 卡镜像。4.2 产出物清单与烧录实操细节构建完成后重点关注的产出物有这些文件作用tf-a-*.fip/*.fip包含 BL31、BL32、U-Boot 等启动链核心u-boot.binU-Boot 二进制一般会打包进 FIPfitImage或Image内核镜像*.dtb设备树二进制*.wic完整的 SD 卡镜像*.ext4/rootfs*.tar*根文件系统烧录 SD 卡时建议直接使用.wic镜像它会同时创建分区表和文件系统包含引导固件分区、内核和设备树分区、根文件系统分区。以 Linux 主机烧录为例sudo dd ifst-image-weston-machine.wic of/dev/sdX bs4M convfsync statusprogress/dev/sdX是 SD 卡在主机上的设备名一定要确认清楚别把自己电脑的硬盘写了。烧录完成后插入板子连接串口波特率一般 115200数据位 8、无校验、1 停止位。启动时串口日志会依次出现 TF-A banner、U-Boot banner、内核解压信息、文件系统挂载信息最后出现登录提示符。看到登录提示符说明最基层的启动链路已经通了。4.3 首次启动时检查哪些关键点系统起来之后不要急着高兴先做几个快速验证。第一查看设备树模型名称是否和你板子定义一致cat /proc/device-tree/model第二查看网络设备是否识别比如以太网接口是否出现eth0。第三检查默认 GPIO LED 是否能控制。这些验证能确认 CubeMX 里的配置文件是否真正生效。如果系统起不来按日志位置分阶段定位。卡在 TF-A 阶段多半是 DDR 或时钟配置问题U-Boot 阶段崩溃多半是 FIP 打包异常或者 U-Boot 设备树不对内核阶段 panic多半是设备树、内核配置或者根文件系统挂载参数问题。这些问题在下一节专门展开。5. 常见问题与排查技巧实录5.1 高频问题速查表现象常见根因排查建议上电串口完全无输出DDR 初始化失败、时钟配置错误、BootROM 未识别启动介质核对 DDR 参数、检查启动介质分区表U-Boot 阶段反复重启FIP 内组件不匹配、U-Boot 设备树错误重新构建 TF-A 和 U-Boot重新打包 FIP设备树编译报重复节点官方 dtsi 和自定义 dts 定义了同名节点对比冲突节点删除自定义层中的重复内容内核启动后找不到根文件系统分区表错误、root参数不对检查.wic分区布局和启动参数网络不通PHY 设备树配置错误、复位 GPIO 顺序不对对照原理图检查 PHY 地址和复位引脚串口输出乱码时钟配置导致波特率不准检查串口时钟源和系统时钟LED/GPIO 不工作引脚复用冲突或被其他外设占用回 CubeMX 检查复用表这张表是我实际调试中遇到过的真实高频问题“排查建议”一列给出了最先应该确认的方向。有时候一个问题背后叠着两三个原因比如网络不通可能是 PHY 地址和设备树不一致同时复位引脚的 GPIO 也被错误复用一下子看起来就像“网口设备消失”容易误判成硬件故障。5.2 三个最容易踩且恢复成本高的坑先说第一个坑直接在厂商源码层里改 dts 和 defconfig。这在初期看着方便但后续一旦重新拉源码、切换分支或者构建时被官方层覆盖所有定制就全部丢了而且排查起来很难搞清楚当前构建到底用的哪份文件。规范做法一定是把定制文件放在自己的 BSP 层用 bbappend 叠加。第二个坑改了设备树但构建时总觉得“我明明改了怎么没生效”。这是非常典型的 Yocto 缓存的坑。bitbake 对配方输入文件有哈希跟踪如果文件名没变、内容变了有时候需要显式清理。处理方法是bitbake -c clean 内核或u-boot配方名然后删掉部署目录里对应的旧 dtb 或 FIP再重新构建。构建完成后可以到部署目录看文件时间戳确认生成的是新的。还有个更笨但最可靠的办法在构建日志里找设备树编译的实际命令路径然后去看临时源码目录里的 dts 文件内容确认包含你的改动。第三个坑FIP 没有包含新的 U-Boot。U-Boot 和 TF-A 往往在同一构建流程中被打包进 FIP只重编 U-Boot 而不重新打包 FIP启动时加载的还是旧的。所以改动 U-Boot 后一定要让 FIP 目标重新构建比如执行一次对应镜像目标的完整构建或者手动清理后重建tf-a和u-boot相关目标。FIP 内部结构对新手不太直观但只要记住“U-Boot 的改动必须通过 FIP 才能生效”这一点就能少走很多弯路。5.3 反向验证硬件配置是否真正生效的小技巧系统跑起来之后如何确认实际生效的就是我们想要的那份配置我常用的办法有三个。第一直接在运行时导出设备树内容。Linux 启动后会把设备树放到/sys/firmware/devicetree/base可以用dtc工具把它反编译回文本格式来检查dtc -I fs -O dts /sys/firmware/devicetree/base这个反编译结果能够直接看到当前内核使用的引脚、外设状态、时钟频率等是验证 CubeMX 配置是否落到实处的第一证据。第二查 pinctrl 和 GPIO 的实际状态。运行时有 debugfs 的话可以看/sys/kernel/debug/pinctrl下的引脚复用表确认某个引脚确实被设置为期望的功能也可以看/sys/kernel/debug/gpio确认某个 GPIO 的输入输出状态。第三用简单的硬件操作验证。比如配置一个 GPIO 控制 LED如果能在用户空间直接点亮熄灭说明设备树里的 gpio-leds 节点、引脚复用、驱动绑定这一整条链路都通了。这比盯着屏幕看日志更直观。这套“运行时反向验证”的方法能帮你在开发初期快速建立信心。我自己的习惯是每次只要改了 CubeMX 工程里的配置就先构建一个最小控制台镜像启动后依次验证串口、GPIO、网络这三个最基础的功能再深入做复杂应用。这样每次改动的影响范围都能控制得比较小出了问题也容易定位。最后分享一个坚持了很久的习惯在 Yocto 构建环境里我会同时保留两个构建目标一个是全功能的图形镜像另一个是最小控制台镜像。日常验证硬件配置基本只构建最小系统几分钟就能看到结果等所有硬件功能验证通过再构建完整图形镜像做产品级验证。这样把长耗时构建压缩到一次调试循环快了很多你可以试试。
阅读完成 · 觉得有帮助?