NVIDIA Jetson自定义BSP制作指南:从环境定制到量产部署
1. 项目缘起为什么需要自定义BSP在嵌入式开发尤其是基于NVIDIA Jetson平台的项目中我们经常会遇到一个看似简单却至关重要的需求如何将我们精心配置好的开发环境包括系统镜像、内核驱动、用户空间库、配置文件乃至我们自己开发的应用程序打包成一个可以“一键部署”到其他设备上的完整包这就是自定义BSPBoard Support Package板级支持包的核心价值。你可能已经熟练使用NVIDIA官方提供的SDK Manager来刷写标准的JetPack镜像。这个流程对于原型验证和标准应用来说非常方便。但一旦项目进入量产阶段或者需要在成百上千台设备上部署一个完全一致、且包含所有定制内容的系统时重复地在每台设备上通过SDK Manager安装、配置、编译、部署就成了一场噩梦。效率低下不说一致性更是无法保证。今天在这台设备上编译的驱动版本和昨天那台可能就有细微差别为后续的维护和问题排查埋下巨大隐患。自定义BSP就是为了解决这个痛点而生的。它本质上是一个包含了完整Linux根文件系统、引导加载程序U-Boot、内核和设备树DTB的压缩包。通过制作自定义BSP你可以将开发主机上的“黄金镜像”固化下来然后像分发软件安装包一样将其快速、批量地刷写到目标Jetson设备上。这对于产品化、产线烧录、系统还原和版本管理来说是不可或缺的一环。2. 理解Jetson BSP的构成与官方工具链在动手之前我们必须先拆解一下Jetson BSP的“五脏六腑”并了解NVIDIA为我们提供了哪些“手术刀”。一个标准的Jetson BSP以L4T - Linux for Tegra为例主要包含以下几个层次BootloaderU-Boot这是设备上电后运行的第一段有效代码负责初始化最基本的硬件如内存、时钟并加载后续的Linux内核。在Jetson上U-Boot通常与NVIDIA的二级引导程序cboot协同工作。Linux内核Kernel这是系统的核心管理硬件资源、进程调度、内存管理等。Jetson的内核是NVIDIA基于特定版本如5.10深度定制的包含了Tegra SoC的专有驱动如GPU、视频编解码器VPU、图像信号处理器ISP等。设备树Device Tree Blob, DTB一个描述硬件拓扑和资源配置的数据结构文件。内核通过读取DTB来知道这块主板上具体接了哪些外设如哪个GPIO口连着LED哪个I2C总线挂了传感器而无需将硬件信息硬编码在内核中。这对于支持同一内核在不同载板Carrier Board上运行至关重要。根文件系统Root Filesystem这就是我们熟悉的Linux目录树/bin,/etc,/home,/usr等包含了所有系统命令、库文件、配置文件和用户应用程序。JetPack提供的根文件系统是基于Ubuntu的。NVIDIA提供了一套名为apply_binaries.sh和flash.sh的核心工具链它们位于L4T Driver PackageBSP包中。apply_binaries.sh的作用是将预编译好的内核模块、固件、用户空间库如CUDA, TensorRT, 多媒体API安装或“应用”到一个指定的根文件系统目录中。而flash.sh则利用安装好二进制文件的根文件系统结合内核、DTB等生成最终的系统镜像并刷写到设备。我们自定义BSP的过程就是围绕这套工具链对“指定的根文件系统”进行深度定制然后重新“打包”的过程。3. 环境准备与基础镜像获取工欲善其事必先利其器。开始制作自定义BSP前你需要准备以下环境开发主机一台运行Linux推荐Ubuntu 20.04或22.04的x86_64电脑。这是我们的“手术台”。虚拟机也可以但需要确保有足够的磁盘空间建议至少100GB空闲和良好的性能。目标设备你需要一块对应型号的Jetson开发套件如Jetson Orin NX AGX Orin等。在初期制作BSP时它主要用于验证。必要的软件包在开发主机上安装一些基础工具。sudo apt-get update sudo apt-get install -y qemu-user-static binfmt-support dpkg-cross git-lfsqemu-user-static是关键它允许我们在x86主机上运行ARM架构的程序这对于在主机上直接操作ARM根文件系统至关重要。接下来获取官方的基础材料——L4T BSP包和根文件系统。下载L4T Driver Package (BSP)访问NVIDIA开发者网站找到你的Jetson型号对应的“Driver”页面。例如对于Jetson Orin NX 16GB你可能下载一个名为Jetson_Linux_R35.3.1_aarch64.tbz2的文件。这个压缩包包含了Linux_for_Tegra/目录即我们的工作基础。tar -xjf Jetson_Linux_R35.3.1_aarch64.tbz2下载Sample Root Filesystem在同一个页面下载对应的根文件系统压缩包例如Tegra_Linux_Sample-Root-Filesystem_R35.3.1_aarch64.tbz2。这是一个最简化的Ubuntu基础系统。组装基础环境将根文件系统解压到BSP目录的正确位置。cd Linux_for_Tegra/rootfs/ sudo tar -xjpf ../../Tegra_Linux_Sample-Root-Filesystem_R35.3.1_aarch64.tbz2 cd ..此时Linux_for_Tegra/rootfs/目录下就有了一个完整的ARM Ubuntu根文件系统。应用基础二进制文件运行apply_binaries.sh脚本将NVIDIA专有的驱动、库文件安装到这个根文件系统中。sudo ./apply_binaries.sh这个步骤会将CUDA、TensorRT、多媒体API等核心组件部署到根文件系统里。现在你就拥有了一个“官方标准版”的BSP基础。4. 深度定制打造你的专属根文件系统拥有了基础镜像后我们就可以开始“装修”了。定制根文件系统是BSP制作中最灵活、也最能体现项目需求的部分。主要操作都在Linux_for_Tegra/rootfs/目录下进行。由于这是ARM架构的文件系统我们需要借助chroot和之前安装的qemu-user-static来“进入”这个系统进行操作。4.1 使用chroot进入目标系统环境直接修改ARM系统的文件可能会遇到架构不兼容的问题比如尝试运行一个ARM的可执行文件。chroot可以改变当前进程及其子进程的根目录并搭配qemu-aarch64-static来模拟ARM环境。首先确保qemu-aarch64-static被复制到根文件系统内sudo cp /usr/bin/qemu-aarch64-static Linux_for_Tegra/rootfs/usr/bin/然后挂载必要的虚拟文件系统并执行chrootcd Linux_for_Tegra/ sudo mount -t proc /proc rootfs/proc sudo mount -t sysfs /sys rootfs/sys sudo mount -o bind /dev rootfs/dev sudo mount -o bind /dev/pts rootfs/dev/pts sudo chroot rootfs /bin/bash执行完最后一条命令你的命令行提示符可能会变化此时你就“进入”了目标ARM系统。可以运行uname -m验证应该显示aarch64。重要提示所有在chroot环境下的操作都需要sudo权限并且会直接影响你的BSP。建议在进行大规模修改前先备份整个rootfs目录。4.2 常见的定制化操作在chroot环境下你可以像管理一台真正的Ubuntu ARM机器一样进行操作安装软件包使用apt安装项目所需的任何软件。apt-get update apt-get install -y python3-pip openssh-server vim network-manager # 例如安装一个ROS2 Humble apt-get install -y ros-humble-desktop配置系统服务设置服务开机自启。systemctl enable ssh systemctl enable systemd-networkd创建用户和设置密码为产线或最终用户创建默认账户。useradd -m -s /bin/bash myuser echo myuser:mysecurepassword | chpasswd # 将用户添加到sudo组 usermod -aG sudo myuser部署应用程序将你自己开发的应用程序、脚本或配置文件复制到合适的位置如/opt/your_app/或/usr/local/bin/。cp -r /host_path/to/your_app /opt/注意/host_path/to/your_app需要是在chroot前从宿主机挂载进来的路径或者你提前拷贝到rootfs内的路径。修改系统配置编辑/etc/network/interfaces、/etc/hosts、/etc/fstab等文件。清理无用内容为缩小镜像体积可以清理APT缓存和临时文件。apt-get clean rm -rf /var/lib/apt/lists/* rm -rf /tmp/*完成所有定制后退出chroot环境并卸载挂载点exit # 退出chroot cd Linux_for_Tegra/ sudo umount rootfs/dev/pts sudo umount rootfs/dev sudo umount rootfs/sys sudo umount rootfs/proc4.3 内核与设备树的定制有时定制不仅限于用户空间。你可能需要修改内核配置启用或禁用某些内核模块。这需要在开发主机上使用NVIDIA提供的源码和工具链重新编译内核。过程涉及获取内核源码、配置make menuconfig、编译和替换Linux_for_Tegra/kernel/下的相关文件。这是一项进阶操作需要谨慎处理。修改设备树如果你的载板硬件有改动比如增加了某个I2C设备修改了GPIO定义你必须修改对应的设备树源文件.dts并重新编译成设备树二进制文件.dtb。设备树源文件通常位于Linux_for_Tegra/sources/或kernel/kernel-5.10/arch/arm64/boot/dts/nvidia/目录下。修改后需要使用DTC编译器生成新的.dtb并替换Linux_for_Tegra/kernel/dtb/中的文件。踩坑心得内核和设备树的修改是BSP定制中最容易出错的部分。一个错误的配置可能导致设备无法启动。强烈建议在每次修改前备份原文件并且每次只做一项改动然后刷机测试确保其工作正常后再进行下一项。同时务必查阅NVIDIA官方文档中关于你特定型号Jetson的引脚复用Pinmux表格设备树的修改必须与之匹配。5. 打包与生成自定义BSP当根文件系统、内核如果需要和设备树都定制完成后就可以打包生成最终的BSP了。NVIDIA的flash.sh脚本在刷机过程中实际上会动态地从Linux_for_Tegra/目录下的各个子目录收集文件来创建镜像。但我们也可以生成一个独立的、可分发的压缩包。一个常见且可靠的方法是直接打包整个Linux_for_Tegra目录当然可以先删除一些中间构建文件以减小体积。但更规范的做法是利用NVIDIA提供的nv_build_bsp脚本如果存在于你的BSP版本中或者遵循其文档指引。一个手动创建可分发BSP包的简单流程如下清理中间文件进入Linux_for_Tegra目录删除一些在刷机过程中生成的大型临时文件。cd Linux_for_Tegra sudo rm -rf bootloader/system.img.* # 删除旧的系统镜像临时文件 sudo rm -rf tools/version* # 清理版本文件缓存可选 # 注意不要删除 kernel/, rootfs/, bootloader/ 等核心目录创建版本标识为了管理不同版本的BSP最好创建一个版本文件。echo MyCustomBSP-v1.0.0 version.txt echo Based on L4T R35.3.1 version.txt echo Build Date: $(date) version.txt打包整个目录使用tar命令创建压缩包。建议使用.tbz2格式以保持与官方包一致。cd .. # 退回到 Linux_for_Tegra 的上级目录 sudo tar -cjf MyCustom_Jetson_Orin_NX_BSP_R35.3.1_v1.0.0.tbz2 Linux_for_Tegra/现在MyCustom_Jetson_Orin_NX_BSP_R35.3.1_v1.0.0.tbz2就是你的自定义BSP包。你可以将它分发给团队成员或者送到产线上。6. 刷机验证与量产部署得到BSP包后下一步就是在目标设备上验证它。解压BSP包在用于刷机的电脑上可以是同一台开发主机也可以是产线工控机解压你的自定义BSP包。tar -xjf MyCustom_Jetson_Orin_NX_BSP_R35.3.1_v1.0.0.tbz2 cd Linux_for_Tegra/将Jetson设备置于恢复模式Force Recovery Mode断开设备电源。用Micro-USB线对于老型号或USB-C线对于新型号并需要连接至恢复口将Jetson的恢复端口连接到主机。按住Jetson上的“Force Recovery”按钮通常是一个小孔需要用针戳不松开。给设备上电。继续按住按钮约2秒后松开。在主机上运行lsusb命令应该能看到一个NVIDIA Corp.的设备表示设备已进入恢复模式。执行刷机脚本sudo ./flash.sh jetson-orin-nx-devkit mmcblk0p1这里的jetson-orin-nx-devkit是目标板配置mmcblk0p1指定刷写到eMMC存储。请根据你的Jetson型号选择正确的配置具体参数可参考flash.sh的帮助信息或官方文档。等待刷机完成脚本会自动进行分区、格式化、写入引导程序、内核、设备树和根文件系统等操作。整个过程可能需要几分钟。完成后设备会自动重启。系统验证设备启动后进行验证使用你创建的用户名和密码登录。检查你安装的软件包如ros2 --version是否存在且版本正确。检查你部署的应用程序是否能正常运行。检查网络、外设等配置是否生效。量产部署建议 对于产线上述手动进入恢复模式的方式效率太低。NVIDIA提供了“OTAHeadless刷机”或使用“Balena Etcher”等工具直接写入SD卡/eMMC镜像的方案。更高效的做法是使用flash.sh的-r--raw和-G--generate-image选项直接生成一个完整的、可直接写入存储介质的.img文件。sudo ./flash.sh -r -G my_custom_image.img jetson-orin-nx-devkit mmcblk0p1将这个.img文件交给产线他们可以使用高速烧录器如DediProg、Teledyne LeCroy等直接烧录到设备的eMMC芯片中或者用读卡器工具批量烧录到SD卡上速度远超USB刷机。7. 版本管理与迭代维护自定义BSP不是一劳永逸的。随着软件更新、bug修复和需求变化你需要管理BSP的版本。使用版本控制系统强烈建议将你的定制过程脚本化并将Linux_for_Tegra/rootfs目录下你修改的部分例如一个包含所有定制脚本和配置文件的custom/目录纳入Git管理。而庞大的、由官方提供的原始根文件系统和二进制文件则可以通过.gitignore忽略或者作为单独的压缩包引用。差分更新对于已部署的设备如果只是更新应用程序或配置文件可能不需要重新刷写整个BSP。可以考虑制作差分包如使用rsync生成文件列表差异或通过包管理器如APT私有仓库进行增量更新。但对于内核、驱动等底层变更全量刷写通常更稳妥。文档记录为每个BSP版本维护一个CHANGELOG.md清晰记录变更内容、对应的需求或Bug编号、测试结果和已知问题。我在为一个机器人项目维护多个型号Jetson的BSP时建立了一个简单的仓库结构bsp_factory/ ├── scripts/ # 所有自动化脚本 │ ├── 01_base_setup.sh │ ├── 02_install_apps.sh │ └── 03_apply_custom_config.sh ├── configs/ # 配置文件模板 │ ├── network/ │ └── systemd/ ├── artifacts/ # 存放最终生成的 .tbz2 和 .img 文件.gitignore └── README.md # 构建说明每次需要新版本时只需在一个干净的官方BSP基础上顺序执行脚本最后打包。这极大地保证了可重复性和一致性。制作自定义BSP是一个从理解平台基础到深入定制再到工程化部署的完整流程。它连接了单点开发和批量部署是将Jetson项目从实验室推向市场的关键一步。虽然初期搭建有一定学习成本但一旦流程跑通将为整个项目的生命周期管理带来巨大的效率和可靠性提升。