1. 项目缘起:为什么需要自定义BSP?
在嵌入式开发,尤其是基于NVIDIA Jetson平台的项目中,我们经常会遇到一个看似简单却至关重要的需求:如何将我们精心配置好的开发环境,包括系统镜像、内核驱动、用户空间库、配置文件乃至我们自己开发的应用程序,打包成一个可以“一键部署”到其他设备上的完整包?这就是自定义BSP(Board 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为例)主要包含以下几个层次:
- Bootloader(U-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 Package(BSP包)中。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/然后,挂载必要的虚拟文件系统并执行chroot:
cd 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提供了“OTA(Headless)刷机”或使用“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项目从实验室推向市场的关键一步。虽然初期搭建有一定学习成本,但一旦流程跑通,将为整个项目的生命周期管理带来巨大的效率和可靠性提升。