2. 开发环境搭建:交叉编译工具链安装、SDK获取与编译、TFTP/NFS网络文件系统配置

说实话,很多做AI部署的同学,模型跑不起来的第一反应是去调算法。但我个人的经验是——八成的问题出在环境上。工具链版本不对、SDK编译选项没开、文件系统挂载不上,这些基础问题没搞定,后面调NPU驱动就是空中楼阁。

这一章,咱们就把地基打牢。我会带着你一步步把交叉编译工具链、SDK、TFTP/NFS这三样东西配好。嗯,都是我在项目里踩过坑之后总结出来的“最优路径”。

本章核心目标: 在PC端搭建一套完整的嵌入式AI开发环境,实现“PC编译 → 网络加载 → 目标板运行”的闭环。
开发环境整体架构 PC 开发机 (Ubuntu) 交叉编译工具链 + SDK 网络传输层 TFTP (内核/镜像) + NFS (根文件系统) 目标板 (ARM/RISC-V) 运行 AI 推理程序 工作流程: 1. PC端使用交叉编译工具链编译U-Boot、Kernel、设备树、NPU驱动 2. 编译产物通过TFTP传输到目标板DDR中运行(无需烧写) 3. 根文件系统通过NFS挂载到目标板,共享PC端的库和AI模型 4. 修改PC端代码 → 重新编译 → 目标板直接加载,实现秒级迭代

2.1 交叉编译工具链安装

交叉编译,说白了就是在X86的PC上编译出ARM架构能跑的二进制文件。你想想看,目标板那点算力,编译个Linux内核得等到天荒地老。所以这事儿必须在PC上干。

工具链的选择,我建议直接跟芯片原厂走。Rockchip用gcc-arm-10.3-2021.07,全志用gcc-linaro-7.5.0,NXP用gcc-arm-none-eabi。别自己去Linaro官网随便下,版本不对会出一些很诡异的链接错误。

安装步骤其实就三步:

  1. 下载解压:拿到原厂提供的gcc-arm-xxx.tar.xz,解压到/opt/目录
  2. 配置环境变量:把工具链的bin目录加到PATH里
  3. 验证安装:跑一下aarch64-linux-gnu-gcc -v,看到版本号就对了
我的习惯: 在 ~/.bashrc 里加一行 export PATH=/opt/gcc-arm-10.3-2021.07/bin:$PATH,然后 source ~/.bashrc。这样每次开终端都能直接用,不用重复export。

这里有个坑——64位和32位工具链别搞混。我见过有人用aarch64的工具链去编译32位的ARM程序,链接阶段报一堆“relocation truncated to fit”。嗯,检查一下你目标板的内核是32位还是64位,选对应的工具链。

目标架构 工具链前缀 常见芯片
ARMv7 (32位) arm-linux-gnueabihf- 全志V3s、NXP i.MX6ULL
ARMv8 (64位) aarch64-linux-gnu- Rockchip RK3588、瑞芯微RV1126
RISC-V (64位) riscv64-linux-gnu- 全志D1、算能SG2042

2.2 SDK获取与编译

拿到SDK之后,第一件事不是急着编译,而是检查README。真的,我每次接手新项目都会先看一遍SDK根目录下的README或者Wiki链接。里面通常会写明依赖的软件包版本、编译步骤、已知问题。

SDK的目录结构,不同厂家大同小异。以Rockchip的Linux SDK为例:

sdk/
├── u-boot/          # 引导程序
├── kernel/          # Linux内核 + 设备树
├── device/          # 板级配置
├── external/        # 第三方库(NPU驱动、多媒体库等)
├── buildroot/       # 根文件系统构建
└── docs/            # 文档

编译流程,我一般按这个顺序来:

  1. 配置编译环境:source build/envsetup.sh,选择对应的板型
  2. 编译U-Boot:./build.sh uboot,生成idbloader.img和uboot.img
  3. 编译Kernel:./build.sh kernel,生成boot.img(内含内核+设备树)
  4. 编译根文件系统:./build.sh rootfs,生成rootfs.img
  5. 打包固件:./build.sh firmware,生成完整烧写镜像
注意: 第一次编译SDK时,./build.sh 会自动下载一些依赖包。确保你的PC能访问外网,或者提前配置好本地源。我曾经在客户现场,网络受限,SDK编译到一半卡在下载阶段,折腾了半天才搞定离线包。

编译过程中如果报错,别慌。90%的错误都是缺少依赖包。常见的依赖有:libncurses5-dev、flex、bison、openssl、device-tree-compiler。用apt-get install装一下就好。

2.3 TFTP/NFS网络文件系统配置

为什么要配TFTP和NFS?因为开发阶段你不可能每次改个驱动就重新烧写一次固件。TFTP用来快速加载内核和设备树,NFS用来共享根文件系统。这样你改完代码,编译完,重启目标板就能看到效果。

2.3.1 TFTP服务器配置

TFTP说白了就是一个极简的文件传输协议,只支持从服务器往客户端传文件。配置步骤:

  1. 安装TFTP服务:sudo apt-get install tftpd-hpa
  2. 创建TFTP根目录:sudo mkdir -p /srv/tftp,并设置权限sudo chmod 777 /srv/tftp
  3. 修改配置文件:编辑/etc/default/tftpd-hpa,指定根目录为/srv/tftp
  4. 重启服务:sudo systemctl restart tftpd-hpa

验证方法:在/srv/tftp下放一个测试文件,然后在目标板的U-Boot里执行tftp 0x02000000 test.txt,能下载成功就说明配好了。

避坑指南: 我曾经遇到过TFTP传输到一半就卡死的情况。后来发现是防火墙把TFTP的69端口给拦了。执行 sudo ufw allow 69/udp 就能解决。

2.3.2 NFS服务器配置

NFS比TFTP复杂一点,但功能更强。它允许目标板把PC上的一个目录挂载为自己的根文件系统。配置步骤:

  1. 安装NFS服务:sudo apt-get install nfs-kernel-server
  2. 创建共享目录:sudo mkdir -p /srv/nfs/rootfs
  3. 导出根文件系统:把编译好的rootfs目录内容复制到/srv/nfs/rootfs
  4. 修改exports文件:编辑/etc/exports,添加一行/srv/nfs/rootfs *(rw,sync,no_subtree_check,no_root_squash)
  5. 重启NFS服务:sudo systemctl restart nfs-kernel-server

目标板启动时,在U-Boot里设置内核启动参数:

setenv bootargs 'console=ttyS0,115200 root=/dev/nfs nfsroot=192.168.1.100:/srv/nfs/rootfs rw ip=192.168.1.101:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off'

这里192.168.1.100是PC的IP,192.168.1.101是目标板的IP。记得把网线插好,IP别配错。

参数 说明 示例值
root=/dev/nfs 指定根文件系统来自NFS 固定写法
nfsroot NFS服务器IP和共享路径 192.168.1.100:/srv/nfs/rootfs
ip 目标板IP:服务器IP:网关:掩码 192.168.1.101:192.168.1.100:192.168.1.1:255.255.255.0
重要: no_root_squash 这个选项一定要加。不加的话,目标板上的root用户会被映射成nobody,导致很多系统命令无法执行。我刚开始搞NFS时没加这个,结果目标板上连 ls 都报权限错误,排查了半天才发现是这个问题。

2.4 环境验证:跑通第一个Hello World

环境搭好了,咱们验证一下。写一个简单的C程序:

#include <stdio.h>

int main() {
    printf("Hello Edge AI!\n");
    return 0;
}

用交叉编译器编译:

aarch64-linux-gnu-gcc -o hello hello.c

把编译好的hello文件放到NFS共享目录里,然后在目标板上执行:

./hello

看到Hello Edge AI!输出,就说明你的开发环境完全打通了。从PC编译到目标板运行,整个链路都是通的。

小结一下: 这一章我们搞定了三件事——交叉编译工具链、SDK编译、TFTP/NFS网络文件系统。这三样东西配好了,后面的设备树修改、NPU驱动调试才能跑得起来。别嫌麻烦,基础打牢了,后面会顺很多。