
拿到香橙派5这台板子的第一周我干了一件不怎么明智的事直接在板子上去编译程序。结果就是8GB内存被吃到见底风扇呼呼转一个很简单的apt包等上十几分钟。说句实话都上了RK3588这颗芯片还在板端折腾编译工具链等于放着A76大核的性能优势不用非要在A55小核上玩耐力赛——正确的姿势应该是交叉编译。这个系列走到第6篇前面几篇已经把板子从开箱、烧录到系统跑通了今天做一件特别小但特别关键的事在PC上用交叉编译工具链编译一个hello world然后把它通过scp传到香橙派5上跑起来。别小看这一步这是整个RK3588yolov5s部署之旅的第一块基石。工具链通不通、链接器路径对不对、动态链接库能不能对上全在这个hello里暴露出来。后面你要交叉编译OpenCV、ncnn、RKNN Toolkit这些大工程靠的都是今天验证过的那套编译器环境。这篇就按我实际操作的完整过程来写从为什么必须交叉编译到工具链怎么拿、CMake怎么配、坑怎么避一步步来。1. 为什么在RK3588上跑yolov5s前要先用hello world磨刀1.1 板载编译的痛点内存、功耗和时间成本先说说为什么非要把交叉编译这件事单独拎出来讲。很多人在树莓派或者香橙派上做开发习惯就是直接在板子上装gcc、装pip、然后原地编译。在ARMv7时代的树莓派上这么做还勉强能忍但到了RK3588这个级别你要是还走板载编译的老路就会遇到几个特别现实的问题。第一是内存压力。RK3588的丐版开发板通常是8GB内存听起来不小但你要是在板子上交叉编译一个带依赖的C工程比如先编OpenCV、再编ncnn、再编RKNN的runtime编译器进程一多8GB根本不够看系统随时可能触发OOM直接卡死或者杀掉编译进程。第二是磁盘开销。开发板上跑的是精简版Ubuntu/Debian系统eMMC或者TF卡的容量往往就32GB、64GB。而编译OpenCV这类大型库中间产物动辄几个GB装一套完整的编译工具链加依赖磁盘空间很快就见底了。而PC上你随便划一个目录几百GB空间随便造。第三是时间成本。RK3588虽然是8核架构4xA76 4xA55但板载编译的时钟频率和散热条件都受限制实际编译效率远不如一台普通的x86_64桌面CPU。我实测过同样一份代码PC上30秒编完板子上可能要跑三四分钟。放到大型工程上这个差距是灾难性的。所以交叉编译是RK3588上做AI部署项目的必经之路。你在PC上用aarch64目标的编译器直接生成ARM64架构的程序然后通过局域网传到板子上运行。整个过程行云流水主机的高性能得到了利用板子只管运行不用承担编译的重活。1.2 hello world在这个链条里扮演的角色有人可能会问交叉编译个hello world有什么好讲的网上搜一下到处都是教程不就是一条gcc命令吗但其实你如果把手把手从零部署yolov5s到RK3588作为一个目标那这个hello world的意义就完全不一样了。它不仅仅是为了打印一行字符串而是为了验证一整条编译-链接-打包-传输-运行链路交叉编译工具链是否可用、是否与目标平台架构匹配x86_64编译出来的程序在AArch64上用不了反过来也一样。动态链接器的路径配置是否正确。ARM64 Linux程序运行时内核会按ELF文件头里记录的interpreter路径去寻找动态链接器默认是/lib/ld-linux-aarch64.so.1。如果这个路径在板子上不存在程序会直接报No such file or directory。从PC到板子的文件传输通道是否通畅。后面你编译出来的可执行文件、模型文件、动态库、打包的脚本都要走这条路。你的CMake交叉编译工程结构是否为后续大型项目做好了铺垫。换句话说hello是最小可行性验证。你在这一步花半小时把工具链调通后面面对OpenCV、ncnn、RKNN这些重量级依赖时只是往这个框架里填更多库而已你如果连hello都没跑通后面就算是把yolov5s的源码拿到手编出来的程序也一定跑不起来到时候排错范围就大到无从下手了。所以在教程这个位置放一个交叉编译的hello其实是用最小的代价逼出最大的确定性。2. 准备工作主机环境、板子网络与工具链的三种获取路线2.1 主机侧需要提前装好的东西做交叉编译你需要的是一台Linux x86_64的电脑。我用的主力机是Ubuntu 22.04 LTS如果你用的是Windows建议装一个WSL2或者直接用虚拟机跑Ubuntu后面所有命令都在这个Linux环境里执行。在开始之前先确认主机上有几个基础工具sudo apt update sudo apt install -y build-essential cmake git ssh net-toolsbuild-essential会带来gcc、g、make这些基础编译工具链cmake用于后续构建大型工程ssh和net-tools分别用于远程连接和查看网络状态。这里有个小提醒最好先把主机的CMake版本确认一下新版的RKNN、ncnn工具链对CMake有最低版本要求。我在做的过程中就吃过亏Ubuntu 20.04自带的CMake是3.16结果后面构建一个依赖库时报了最低3.20的错误最后只能手动装了新版。你可以在动手之前执行cmake --version如果低于3.20推荐去CMake官网下载安装包别用apt装的老版本省得后面返工。2.2 板子侧确认系统架构和网络连通把香橙派5开机进入系统后先确认两件事uname -m输出应该是aarch64这就是ARM64架构的标志。如果看到别的输出说明你当前系统跑的不是64位内核需要重刷64位系统镜像。再看一次IP地址后面传输文件要用ip addr记下eth0或wlan0那段的IP比如192.168.1.100。然后回到PC上测试一下到板子的连通性ping 192.168.1.100 -c 4能Ping通就说明二层网络没问题。接下来用SCP测试文件传输通道ssh orangepi192.168.1.100默认密码在官方wiki里一般是orangepi。登录成功后建议加一条SSH Key信任关系后面scp就不用每次输密码了。这个细节在后续频繁传文件时非常省事。2.3 交叉编译工具链的三种来源我推荐哪种RK3588用的工具链是基于GCC的aarch64交叉编译版本目标平台是ARM64 Linux。获取渠道大致有三条第一条瑞芯微SDK自带的工具链。如果你之前下载过Rockchip官方的SDK比如Linux SDK里面一般会内置一套完整的prebuilts/gcc/linux-x86/aarch64交叉编译器。这套工具链经过瑞芯微自家的调校兼容性不错版本也比较稳定适合从零开始就跟着SDK走的用户。第二条直接从网上下载Linaro版的工具链安装包。Linaro是ARM生态里的专业组织发布的gcc-aarch64工具链在嵌入式圈子里用得非常广。我自己用的就是这条路线。解压完不需要安装直接导出一个环境变量指向工具链目录就行。具体下载地址在Linaro官方发布页选择aarch64-linux-gnu的版本带-linux-gnu的是面向Linux的选择带有aarch64-linux-gnu前缀的压缩包即可。第三条Ubuntu发行版的软件仓库。执行sudo apt install gcc-aarch64-linux-gnu也能装上但版本通常比上面两条路线老一些而且仓库里的工具链有时候和瑞芯微提供的固件依赖库之间存在细微的ABI差异。如果只是简单的hello world用仓库版问题不大但后面积累到大型工程时A/B选型上的差异会突然冒出来。我实际采用的组合拳是以Linaro工具链为主把工具链解压到~/toolchains目录写一个环境变量脚本每次开终端时一句话加载。这样既能控制版本也不会污染系统。2.4 安装目录布局我的PC上目录规划是这样的~/rk3588_tools/ ├── toolchain # 交叉编译工具链 │ ├── bin │ ├── aarch64-linux-gnu │ └── lib ├── my_yolo_workspace # 后续放yolov5s工程 └── hello_project # 今天的hello world工具链解压之后bin目录下关键是这三个可执行文件aarch64-linux-gnu-gccC编译器aarch64-linux-gnu-gC编译器aarch64-linux-gnu-objdump/aarch64-linux-gnu-readelf检测工具后面排查问题全靠它们先在终端执行一次验证export PATH$PATH:~/rk3588_tools/toolchain/bin aarch64-linux-gnu-gcc --version如果能看到版本号输出说明工具链本体OK。3. hello项目结构、CMakeLists与toolchain.cmake的落地写法3.1 为什么这个项目必须用CMake而不是一条gcc命令很多初学者会想一个hello world直接写aarch64-linux-gnu-gcc main.c -o hello一条命令搞定的事干嘛非要上CMake这个判断逻辑放在单独做hello这个点上确实成立但如果你把目光放到RK3588部署yolov5s这个更大目标上就会发现后续所有依赖库ncnn、OpenCV、RKNN Toolkit、protobuf的构建系统全部是CMake的生态。如果你现在只图省事用裸命令行跑通hello那等于把最小验证这件事限定在了编译器的层面而你需要的是整个工程构建链路的验证。所以我建议从hello开始就直接用CMake组织工程把交叉编译的工具链文件toolchain.cmake搭起来。后面每年要编译的大工程只需要在这个toolchain.cmake基础上增加依赖库的搜索路径就能无缝复用。3.2 工程结构和main.cpp内容先建目录和文件mkdir -p ~/rk3588_tools/hello_project cd ~/rk3588_tools/hello_project创建main.cpp#include iostream int main() { std::cout Hello, RK3588! std::endl; #ifdef __aarch64__ std::cout Architecture: AArch64 std::endl; #else std::cout Architecture: unknown (not aarch64) std::endl; #endif std::cout Hello from cross-compile. std::endl; return 0; }这里我故意加了__aarch64__宏判断目的是让运行时能直观确认编译产物确实是ARM64版本。如果这个宏没有定义说明你编译出来的是主机x86架构的程序那样的话后面跑在板子上一定会报错。3.3 toolchain.cmake的内容拆解创建toolchain.cmakeset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CROSS_COMPILE_PATH $ENV{HOME}/rk3588_tools/toolchain/bin/aarch64-linux-gnu-) set(CMAKE_C_COMPILER ${CROSS_COMPILE_PATH}gcc) set(CMAKE_CXX_COMPILER ${CROSS_COMPILE_PATH}g) set(CMAKE_STAGING_PREFIX $ENV{HOME}/rk3588_tools/sysroot/staging) set(CMAKE_FIND_ROOT_PATH $ENV{HOME}/rk3588_tools/sysroot) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)这几个配置项分别是什么意思拆开来讲CMAKE_SYSTEM_NAME设置为Linux告诉CMake目标系统不是Windows或其他平台CMAKE_SYSTEM_PROCESSOR设置为aarch64则明确目标CPU架构。CMAKE_C_COMPILER和CMAKE_CXX_COMPILER指向了工具链bin目录下的交叉编译器。注意我用了前缀变量后面如果在同一个工具链里还有aarch64-linux-gnu-ar、aarch64-linux-gnu-strip等工具也可以在CMake里通过同样方式引用。CMAKE_STAGING_PREFIX是一个很重要的概念。交叉编译的时候你下载、编译的依赖库通常会安装到一个暂存目录——比如你自己编译的OpenCV安装到这里将来板子上的程序再去这个目录里找头文件和库文件。这个目录不一定是板子的真实根目录但后续大型工程的依赖管理全靠它。CMAKE_FIND_ROOT_PATH是交叉编译时搜索头文件和库文件的最关键路径。如果你的main.cpp里还有#include opencv2/opencv.hppCMake就需要判断去哪里找这个头文件。设定了Root Path以后CMake会在你给的这几个路径下寻找。CMAKE_FIND_ROOT_PATH_MODE_PROGRAM设成NEVER意思是CMake在查找程序比如编译工具时不会把Root Path当成程序搜索路径去处理MODE_LIBRARY和MODE_INCLUDE设成ONLY则是告诉CMake在查找库文件和头文件时只在这几个Root Path指定的范围内找不要跑到主机系统的/usr/include和/usr/lib里去瞎逛。为什么这么设因为在交叉编译时如果你不小心让CMake找到了主机的x86版本依赖库链接阶段会出现格式不匹配的报错而且特别难排查。把Root Path严格限定住就是给这个依赖搜索加上了铁笼子。3.4 CMakeLists.txt写法创建CMakeLists.txtcmake_minimum_required(VERSION 3.16) project(hello_rk3588 CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(hello main.cpp)对于hello来说CMakeLists非常简单。核心就是add_executable(hello main.cpp)这句话指定生成目标的名字为hello。接下来在工程目录下新建一个build目录作为构建目录mkdir build cd build然后执行CMake配置cmake .. -DCMAKE_TOOLCHAIN_FILE../toolchain.cmake注意-DCMAKE_TOOLCHAIN_FILE../toolchain.cmake这个参数一定要在cmake ..里指定。如果把这一行漏了CMake默认会在主机上用x86编译器所有努力都白费。再执行编译make -j$(nproc)如果你按上面的步骤操作应该会在build目录下看到生成的hello文件。4. 编译、传输与板端运行从file命令到ldd的全链路验证4.1 用file命令确认编译产物是ARM64格式这是交叉编译后第一个必须要做的验证。在你自己的电脑上执行file build/hello如果一切正常输出末尾应该是类似这样的内容ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0...这里面的关键信息是ELF 64-bit LSB executable它是一个64位Linux可执行文件。ARM aarch64目标架构是ARM64和开发板一致。dynamically linked动态链接意味着运行时光需要依赖系统里的动态链接器和动态库。interpreter /lib/ld-linux-aarch64.so.1程序运行时内核会加载这个动态链接器。这个路径在板子的根文件系统里必须真实存在。如果file输出显示的是x86-64别犹豫一定是你命令写错了或者CMake配置没生效。这时候不要继续往下走先把工具链路径和CMake配置检查一遍。4.2 传送到RK3588板子确认把构建结果准备好之后切换到板子所在的网络环境局域网用scp发送scp build/hello orangepi192.168.1.100:/tmp/hello传输完成后登录板子ssh orangepi192.168.1.100进入目录后给文件加执行权限cd /tmp chmod x hello ./hello如果你的整个链路是通的你会在板子终端上看到Hello, RK3588! Architecture: AArch64 Hello from cross-compile.当“Architecture: AArch64”这一行出现在板子屏幕上时你的交叉编译环境就已经完全打通了。4.3 如果运行报错用readelf和ldd快速定位哪怕报错也别慌用工具去拆解。比如你报bash: ./hello: No such file or directory其实文件是存在的那就要怀疑动态链接器路径对不对。在PC上执行aarch64-linux-gnu-readelf -l build/hello | grep interpreter查看输出里的动态链接器路径。如果写的是/lib/ld-linux-aarch64.so.1而板子的ls -l /lib/ld-linux-aarch64.so.1不存在那就说明板子系统缺少ARM64的基础根文件系统组件这通常出现在精简过的极简系统镜像上。如果你想在PC上检查程序依赖了哪些动态库可以用aarch64-linux-gnu-objdump -p build/hello | grep NEEDED输出会列出libstdc.so.6、libc.so.6等等。这些库必须能在板子的/lib/aarch64-linux-gnu目录下找到。如果某条依赖缺失把对应库从PC的工具链sysroot里拷贝到板子上或者改用静态链接的方式重新编译。5. 第一次踩坑实录权限、链接器路径与架构不匹配的典型表现5.1 壳子套错了architecture mismatch这是最典型的交叉编译新手坑具体表现是你在PC上编译出了可执行文件也scp到了板子上一运行就报-bash: ./hello: cannot execute binary file: Exec format error出现这条错误99%的情况是编译出来的可执行文件不是你板子对应的架构。用file看一眼file hello如果显示的是ELF 64-bit LSB executable, x86-64那就说明CMake根本没走交叉编译配置——你八成漏了-DCMAKE_TOOLCHAIN_FILE../toolchain.cmake或者CMake缓存里残留了主机编译器信息。遇到过这种情况后建议删除build目录重来rm -rf build mkdir build cd build cmake .. -DCMAKE_TOOLCHAIN_FILE../toolchain.cmake不要试图在一个已有缓存里强行切换编译器太多老状态会导致CMake内部缓存混乱。删干净是最快的解决办法。5.2 文件存在却报No such file or directory这是一个让人摸不着头脑的报错。你明明用ls看到了文件执行时却告诉你No such file or directory。这类问题的根因往往不在文件本身而在动态链接器。交叉编译产出的动态可执行文件需要由内核根据ELF文件头里的interpreter字段去加载特定的动态链接器。ARM64版程序默认的链接器路径是/lib/ld-linux-aarch64.so.1。如果你的板子系统很精简或者根文件系统被裁剪过这个文件可能不存在内核就找不到动态链接器于是抛出一个误导性的No such file or directory。排查方法很简单在板子上执行ls -l /lib/ld-linux-aarch64.so.1如果确实没有你可以从PC的工具链sysroot目录里找到这个文件scp到板子对应路径下或者用符号链接把它补上sudo ln -s /lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 /lib/ld-linux-aarch64.so.1如果不想依赖板子上的动态库还有一个选项在编译时加-static参数做全静态链接这样程序不再依赖动态链接器但二进制文件体积会大不少而且后续yolo工程里的大库全静态链接并不现实所以这里还是推荐把动态链接器路径理顺。5.3 运行时提示缺少libstdc等动态库如果你发现hello已经成功开始执行却在某一行输出后中断或者直接弹出类似于error while loading shared libraries: libstdc.so.6: cannot open shared object file: No such file or directory说明程序运行依赖的C标准库在板子上没找到。一般板上的Ubuntu系统会自带的但如果用极简镜像就可能缺。解决办法有几个在板子上安装对应软件包sudo apt update sudo apt install -y libstdc6同时确认系统基础库完整。从工具链的sysroot目录手动拷贝对应libstdc.so.6到/lib/aarch64-linux-gnu/。在编译时配置CMAKE_EXE_LINKER_FLAGS强制静态链接libstdc和高版本libgcc比如-static-libgcc -static-libstdc但整个工程里OpenCV、ncnn这类库还是动态链接为主这种方法只能救急。5.4 板子上没有联网的尴尬RK3588的开发板如果处于离线环境——比如内网隔离、没插网线、Wi-Fi没配起来——那连scp传输这一关都过不去。这种时候我也会用U盘拷贝把编译好的可执行文件或整个依赖目录拷到U盘再插到板子的USB口上。虽然麻烦但对比在板子上现场编译还是省时省力得多。在离线场景下建议在PC上提前把后面要依赖的所有库交叉编译安装到sysroot然后把整个sysroot打包复制过去一次搞定。6. 从hello到yolov5s这套工具链接下来怎么用6.1 hello验证了哪些能力当hello在香橙派5上成功打印出“AArch64”时你手里拥有的是一套经过真实验证的交叉编译基础设施其中包括可用的aarch64交叉编译工具链已经过hello的编译和运行双重测试。一套可复用的CMake工具链文件明确指定了编译器、sysroot、暂存路径后续NCNN、OpenCV、RKNN这些库的构建都能基于它扩展。一套已经跑通的PC→板卡文件传输链路后面模型文件、动态库、脚本都会走这条路。一套排错方法论遇到二进制跑不起来先file、再readelf、再确认链接器路径这个顺序在后续更复杂的工程里能帮你快速收敛问题。6.2 下一步交叉编译依赖库时sysroot和staging怎么演变hello项目里没有第三方依赖所以sysroot还只是个空壳。等你要把yolov5s的推理程序跑起来你会需要OpenCV图像预处理、可视化都依赖它。交叉编译OpenCV时要把FFmpeg、JPEG、PNG等依赖也一起交叉编译并安装到同一个staging目录。ncnn如果你想在RK3588上用ncnn框架跑yolov5sncnn本身就提供了一套比较完善的CMake交叉编译支持你只要把它的toolchain.cmake替换成我们自己的配置或者通过-DCMAKE_TOOLCHAIN_FILE指向同一个文件即可。RKNN Toolkit如果走瑞芯微NPU路线还需要编译librknnrt.so并把它放到板子上PC端还需要跑模型转换工具。这里的交叉编译思路和hello完全一样只是要同时管理PC端和板端两套产物。protobuf如果你用RKNN去解析模型配置经常需要交叉编译protobuf然后把它安装到staging目录。在这些工程里尽量保持同一个staging目录比如~/rk3588_tools/sysroot/staging。这样后续你写程序头文件搜索路径时只需要告诉CMake去staging里找即可。依赖库统一、版本可控后续踩的坑会少非常多。6.3 板子上调试yolov5s程序时的经验交叉编译好的yolo推理程序传到板子上运行时如果发现某些库找不到别再回PC上瞎试。推荐先ldd hello如果你交叉编译了一个带运行场景的demo可以直接对demo执行ldd看看缺哪些动态库。注意板子系统里未必有完整的aarch64版ldd工具没有就在PC上用交叉编译工具链里的aarch64-linux-gnu-objdump代替。在PC上确认依赖、手动拷库、上板复测这个循环在我跑yolo的整个过程中重复了无数遍。每次跑通一个库就把它安装到staging目录并顺手拷贝到板子上确保两边保持一致。这个过程本身也是交叉编译能力的一次次升级。6.4 给后续工程预留的坑位现在的hello_project目录其实已经是一个模板了。后续新开一个工程直接复制这个目录替换main.cpp的代码然后在CMakeLists里增加你需要的target和依赖库就能立刻进入状态。我习惯的做法是建一个通用的cmake/目录专门放公共的toolchain.cmake所有工程通过路径引用它避免每工程拷贝一份配置将来改依赖路径时只要改一处就行。有一件事建议提前做把工具链的路径写进一个env.sh脚本里每次新开终端source env.sh就能加载PATH而不是靠记忆力敲全路径。交叉编译这种东西路径一旦错后面全错。hello只是起手式但它定义了后面所有复杂度的最小前提。我确信把这段交叉编译链路彻底理顺之后你在RK3588上交叉编译yolov5s工程时会感觉非常顺滑——因为那时的你已经知道工具链选对了一切才有意义。