ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

香橙派RK3588交叉编译入门:从Hello到yolov5s部署的必经之路

香橙派RK3588交叉编译入门:从Hello到yolov5s部署的必经之路 写一个 hello 还要单独开一篇教程乍看确实有点小题大做。但真正在香橙派 RK3588 上折腾过 yolov5s 部署的朋友应该能理解交叉编译这道坎迈不过去后面的一切都是空中楼阁。这个系列从第 01 篇更到现在板子系统、SSH 连接这些基础都过了一遍接下来要面对的是一个完全绕不开的问题你的开发环境是 x86 的 PC目标板子是 ARM 架构的香橙派两边不是同一个语言体系。写 hello 是这个体系里最小、最容易验证的闭环把“在电脑上编译、拷到板子上运行”这条路走通了后面编译 yolov5s 的推理程序、链接 NPU 的 runtime 库用的都是同一套思路。这篇文章我会按自己实际操作时踩过的坑来讲工具链怎么选、Makefile 怎么写、传上去报错怎么排你都能直接照着做。1. 为什么在香橙派 RK3588 上跑 yolov5s反而要先学 hello1.1 一块能上网、能装系统的板子为什么不能直接编译香橙派 5 本质上是一台完整的 ARM64 小电脑CPU 是瑞芯微 RK3588八核 A76A55性能并不弱甚至比很多老旧笔记本还强。所以我第一次接触它时也有过同样的疑问既然能装 Ubuntu那我在板子上直接装个 gcc现场编译不行吗答案是行但分场景。编一个 hello、编一个小工具板子上直接 gcc 完全没问题RK3588 的算力绰绰有余。可一旦涉及 yolov5s 这条链路局面就变了。你要编译的不再是一个源文件而是带 OpenCV、带推理框架、带一堆第三方依赖的完整工程。在板子上跑 cmake、 make内存和存储会先顶不住SD 卡或 eMMC 的 IO 瓶颈也会让编译时间变得很难看一个中等工程动辄几十分钟期间稍微有点散热问题还可能降频整个体验非常痛苦。更现实的是很多产品环境下 RK3588 板子是作为嵌入式模块存在的没有屏幕、没有键盘、也没有完整的开发工具链你总不能为了编译一个程序给它接一套显示器键鼠上去。所以主流的做法是清晰且唯一的高性能的 x86 电脑负责编译板子只负责运行。这就是交叉编译存在的根本理由。1.2 本地编译和交叉编译最大的区别不是换了个 gcc很多新手会有一个误区觉得交叉编译就是把 gcc 换成一个带前缀的版本比如 aarch64-linux-gnu-gcc然后其他操作照旧。这个理解方向是对的但不够深入。本地编译是 x86 到 x86交叉编译是 x86 到 ARM两个平台的 CPU 指令集完全不同。x86 是复杂指令集指令多、变长讲究兼容性ARM 是精简指令集指令相对规整、讲究能效。gcc 只是编译器的前端真正决定生成什么机器码的是后端的目标平台描述。同一个 gcc后端指定为 x86_64-linux-gnu 和指定为 aarch64-linux-gnu产出的可执行文件完全是两种东西。这也是为什么你会在很多教程里看到“目标三元组”target triplet这个词它就代表了最终代码要运行在什么架构、什么系统上。两者产生的文件在文件头里就有明显区别。你可以用 file 命令看本地编译出来的是 ELF 64-bit LSB executable, x86-64交叉编译出来的是 ELF 64-bit LSB executable, ARM aarch64。这一步很关键因为后面你会遇到一种非常经典的报错把 x86 编译出来的程序拷贝到板子上执行时报 Exec format error就是因为你根本没在做交叉编译或者工具链选错了。1.3 hello 虽小但它完整跑通了一个闭环把 hello 交叉编译到香橙派上跑起来这件事本身没有任何实用价值但它完整覆盖了后续所有板端开发的公共骨架在 x86 主机上用交叉工具链生成 ARM 可执行文件、通过 SSH 或拷贝把文件送到板子、在板子上赋予权限并执行。yolov5s 部署到 RK3588 上说白了也是这个闭环的放大版只是源码更多、依赖更复杂、库文件也需要一并传到板子上而已。而且 hello 这个例子特别好排错它几乎没有外部依赖程序逻辑也是输出一行文字一旦跑不起来问题大概率集中在工具链配置、链接方式、或传输过程这些基础设施上。把这些基础设施问题全部排除干净下一阶段编译真正的推理程序时你才能把注意力集中在业务代码上而不是被环境问题反复折磨。我个人的观点是嵌入式部署没有一步到位的捷径把最小的例子跑通是最稳妥的起步方式。2. 交叉编译工具链的选择与安装三条路我最后选了这条2.1 三种主流方案横向对比获取交叉编译工具链的渠道很多我实际见过、也用过的有三类各有各的适用场景。方案获取方式优点缺点适合人群发行版软件源sudo apt install gcc-aarch64-linux-gnu安装最快依赖自动解决环境干净版本相对保守sysroot 里库不够全入门首选跑通 hello 完全够用芯片厂商 SDK 内置瑞芯微官方 SDK 里的工具链目录版本与芯片匹配度高通常带完整库和 sysroot路径复杂环境变量需要自己配部分还有版本要求后续编译完整 yolov5s 推理程序推荐手动下载Linaro/GNU 工具链官网更新及时可定制性强需要自己配环境变量和 sysroot容易踩坑有经验的开发者按需使用我在 hello 阶段用的是第一种也就是 apt 直接安装。原因很简单这个阶段我们只需要验证编译、传输、运行这个链路不需要复杂的第三方库刻意上厂商 SDK 反而给自己增加配置成本。但我也必须提前说一句如果你已经确定后续要走 C/C 部署 yolov5s 这条路瑞芯微 SDK 里配套的工具链和 sysroot 一定要重视起来后面链接 OpenCV、链接 RKNN runtime 的时候会省很多事。这不是二选一而是先之后深、分阶段使用。另外提一嘴网上搜“rk3588 交叉编译工具链下载”能找到很多来源有些是网友打包好的有些是第三方镜像里的下载前最好看一眼版本说明和校验信息不要图省事随便拿一个。工具链的版本直接决定了你能链接什么版本的库后期出了问题排查起来非常头疼。2.2 实际安装与验证如果你和我一样用的是 Ubuntu 或 Debian 系的开发机安装过程非常简单两条命令的事sudo apt update sudo apt install -y gcc-aarch64-linux-gnu装完先不要急着写代码验证一下工具链是否正常工作aarch64-linux-gnu-gcc --version which aarch64-linux-gnu-gcc第一条命令会输出编译器的版本信息第二条会告诉你这个命令实际在哪个路径。正常情况下你应该能在 /usr/bin/ 下找到它。到这里交叉编译工具链就已经可用了。我见过有人在这里犯过一个很低级的错只装了 gcc-aarch64-linux-gnu却没有装 g-aarch64-linux-gnu。如果你后面要编译 C 工程比如带 OpenCV 的推理程序记得把 g 也装上。hello 用不到但这条早晚会用到。sudo apt install -y g-aarch64-linux-gnu2.3 提前认识 sysroot现在不急着懂但早晚要懂交叉编译的复杂程度和这个程序依赖了多少库成正比。hello 只依赖一个 libc所以工具链装完就能编。但 yolov5s 推理程序会依赖 OpenCV、RKNN runtime、RGA 等等这个时候链接器就需要找到这些库在“目标平台”下的头文件和库文件。这些文件的根目录就叫 sysroot。你可以把它理解成一块虚拟的 ARM 板子文件系统放在你开发机的某个目录里。交叉编译时编译器会去这个目录下找 include 和 lib而不是去开发机本身的 /usr/include 和 /usr/lib因为那些是 x86 的库链接进来毫无意义。apt 安装的 aarch64 工具链默认会带一个基础的 sysroot位于 /usr/aarch64-linux-gnu里面包含 libc 等基础库所以你现在编 hello 感受不到 sysroot 的存在。但等到交叉编译 OpenCV 的时候你就需要把编译好的 ARM 版 OpenCV 库、头文件都放进 sysroot或者用 --sysroot 参数指定一个自定义目录。我现在先把概念抛出来你心里有个印象后续那篇真正编译推理程序时就不会觉得突兀。如果现在就想验证可以在编译时加一个参数看看aarch64-linux-gnu-gcc -print-sysroot这条命令会打印出当前工具链默认的 sysroot 路径不同环境结果会不一样但它就是那个隐藏的“虚拟板子文件系统”。3. 写 Hello 源码与 Makefile编译出第一批 ARM 机器码3.1 最小可用的 hello.c 和 Makefile工具链就绪后进入正题。我习惯在一个干净的目录里做这件事目录名就叫 cross-hello避免和板子上、和其他工程混淆。// hello.c #include stdio.h int main(void) { printf(Hello from RK3588!\n); return 0; }代码本身没有任何特殊之处和你在 x86 上写的 hello 一模一样。真正的区别在编译方式上。为了提高复用性我建议直接用 Makefile而不是每次手工敲长长的 gcc 命令CROSS_COMPILE ? aarch64-linux-gnu- CC : $(CROSS_COMPILE)gcc all: hello hello: hello.c $(CC) -o $ $ -static clean: rm -f hello这里有一个经验Makefile 里 recipe 行必须用 Tab 缩进不能用空格。很多新手在这个地方反复报错Makefile 会提示 missing separator说的就是缩进字符不对。CROSS_COMPILE 变量我用了一个 ?意思是如果你在命令行里显式指定了就优先用你的指定值否则默认用 aarch64-linux-gnu-。这样一来同一个 Makefile你在 x86 本地编译时不需要交叉编译时默认就是 ARM 目标。以后想换芯片平台比如换成 rk3568、换成树莓派的工具链只要改这一个变量就行。3.2 编译并验证产物确实属于 aarch64执行编译make clean make如果一切正常目录下会多出一个 hello 文件。这时候千万不要急着往板子上拷先在开发机上验证一下这个文件的真实身份file hello你应该会看到类似这样的输出hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, BuildID[sha1]..., not stripped注意两个关键词ARM aarch64 和 statically linked。前者说明这是一份 ARM 架构的可执行文件后者说明它是静态链接的这个我们下一节细说。如果你看到的是 x86-64那说明你的 make 用的还是系统自带的 gccMakefile 里的 CROSS_COMPILE 没有生效回头检查一下变量名。再用 readelf 看一眼文件头进一步确认aarch64-linux-gnu-readelf -h hello | grep Machine输出应该是 Machine: AArch64。到这里这份可执行文件在架构层面已经属于香橙派了。3.3 静态编译还是动态编译这不是一道选择题我在 Makefile 里故意加了 -static 参数现在来解释为什么。动态链接编译出来的程序体积小运行时会去系统的动态链接器加载共享库。静态链接则是把依赖的库代码直接塞进可执行文件里体积变大但运行时不依赖目标系统安装了什么库。hello 只依赖 libc动态版本可能只有 16KB 左右静态版本会膨胀到 900KB 上下看起来静态编译很吃亏对不对但嵌入式部署里静态编译有着不可替代的优势目标板子的 rootfs 不可控。你没法保证香橙派上的系统镜像一定有你编译时预期的那版动态链接器、那版 libc。如果系统里缺少某个库程序执行时就会直接报错而且报错方式非常迷惑最常见的就是“No such file or directory”文件明明存在却告诉你没有。这个问题我在第 4 节会专门展开。所以我的建议是hello 阶段用 -static 编译可以最大程度屏蔽目标系统环境差异让问题定位集中在交叉编译本身。但也要提前说一下到了 yolov5s 推理程序阶段不建议整体静态编译。因为 RKNN 的 runtime 库 librknnrt.so 通常只提供动态库很多第三方库也只发行 .so强行静态链接只会给自己找麻烦。到那时候主流方案是动态编译然后把所有依赖的 .so 一并拷贝到板子上用 LD_LIBRARY_PATH 指定路径。hello 只是用来理解机制的不是用来套用到所有场景的。4. 把 hello 送进香橙派传输、运行与经典报错4.1 先确认板子在线SSH 可用编译完成后的下一步是把 hello 从开发机拷贝到香橙派上。在此之前先确认两件事板子和开发机在同一个局域网且 SSH 服务正常。我用的是最朴素的方式先 ping 一下板子的 IPping 192.168.1.100这个 IP 要换成你自己板子的实际地址。在板子上执行 ip addr 可以查到或者直接看你路由器的后台也行。ping 通了之后再试一下 SSH 连接ssh orangepi192.168.1.100用户名根据你的系统镜像而定香橙派官方 Ubuntu 镜像默认用户一般是 orangepi也有部分镜像默认 root如果你在烧录时自定义过用户那就用自己的。能成功登录说明 SSH 服务正常传输通道可用。4.2 用 scp 把 hello 拷过去SSH 能用scp 就能用它是基于同一个协议的拷贝命令。回到开发机终端执行scp hello orangepi192.168.1.100:~/命令的意思是把当前目录的 hello 文件拷贝到板子上用户主目录。执行后会要求输入密码输入板子的登录密码传输就完成了。你会看到像进度条一样的信息最后显示文件大小和传输耗时。除了 scp也可以把 hello 放到 U 盘里插到板子上手动拷贝或者用 MobaXterm、WinSCP 这类带图形界面的工具拖拽上传本质都一样。我个人习惯 scp一条命令、脚本化方便后面批量传依赖库时也更好复用。传完后顺手查看一下文件在不在、权限如何ls -l hello正常的话文件应该在你的用户主目录下权限通常是 -rw-r--r--也就是当前用户可读可写其他用户只读。4.3 在板子上执行并验证执行前先赋上可执行权限这一步很容易被新手忽略chmod x hello然后运行./hello如果一切顺利终端会打印出Hello from RK3588!到这一步你已经完成了第一次完整的交叉编译部署闭环体验一下这个时刻后面所有板端程序都按这个流程走。如果还想做一次更彻底的确认可以在板子上执行uname -m输出应该是 aarch64说明当前系统确实是 ARM64 架构。再执行file hello在板子上看到的 file 输出和你在开发机上看到的应该一致都是 ARM aarch64。两边交叉验证基本可以确定你的工具链、编译参数、传输流程都是正确的。4.4 经典报错全集与排查优先级跑通是理想情况但我知道大概率有人会卡在某个报错上。我把这个过程中最常遇到的四种情况整理成一张表报错信息含义可能原因解决方法Permission denied没有执行权限文件复制后默认权限不含 xchmod x helloNo such file or directory文件不存在或解释器不存在动态链接可执行文件缺少对应链接器改用静态编译或补齐板端动态库Exec format error文件格式不匹配编译成了 x86 格式或工具链选错用 file 确认重新交叉编译hello: command not found命令找不到没有加 ./ 前缀或当前目录不在 PATH运行 ./hello其中第二类报错最阴险明明 ls -l hello 文件就在那里执行却告诉你 No such file or directory。这其实不是文件不存在而是内核找不到这个程序需要的动态链接器它默认去 /lib/ld-linux-aarch64.so.1 找你的 rootfs 里没有或者路径不匹配顺手就返回一个“文件不存在”的误导信息。这也是我在第 3 节坚持让你用静态链接的原因。如果你非要用动态编译可以执行 aarch64-linux-gnu-readelf -l hello | grep interpreter 查看解释器路径再对照板端的实际路径排查。5. 这条交叉编译的路径后面 yolov5s 会沿着它走到哪里5.1 yolov5s 部署到 RK3588 的完整链路hello 跑通之后很多朋友会兴奋地想把 yolov5s 立刻部署上来我劝你先别急但可以提前看看这条路由 hello 延伸出去会怎么走。完整的 yolov5s 部署链路大致是这几个环节先在 PC 上用 PyTorch 训练或拿到一个已经训练好的 yolov5s 模型然后用瑞芯微提供的 rknn-toolkit2 工具把 PyTorch 模型转换成 RK3588 NPU 能直接识别的 RKNN 格式接着在板端编写或移植推理程序调用 RKNN runtime 的 C API 或者 Python API最后把推理程序和依赖库一起部署到板子上运行。这里面的交叉编译主要出现在第三步的 C/C 推理程序上。你要用 aarch64-linux-gnu-g 编译一个会链接 librknnrt.so、OpenCV、可能还有 RGA 库的工程。编译流程和 hello 一模一样源码在 PC 上、交叉工具链在 PC 上、编译产物是 ARM 格式最后用 scp 传到板子上运行。区别只是依赖更多、Makefile 变成 CMake 工程、文件传输从单个文件变成整个目录。5.2 为什么训练和转换在 PC 上做推理程序却要交叉编译很多人对这个会有困惑既然 yolov5s 模型转换是在 PC 上用 Python 工具做的那为什么推理程序不直接在板子上用 Python 写其实如果你只是做原型验证完全可以在板子上用 Python 直接跑 rknn-toolkit-lite2 的 API这个方案在很多项目里是够用的。但如果你要落地成一个产品或者对推理延迟、资源占用有硬性要求C/C 版本几乎成了必然选择。这时候问题就来了你的开发环境是 x86板子环境是 ARM你不可能为了编译一个程序把整个 OpenCV 源码在板子上重编一遍那样既慢又不可控。用交叉工具链在 PC 上编译依赖库通过 sysroot 管理编译完成后整体打包部署才是嵌入式开发的工业标准流程。我可以给你一个最简的 CMake 交叉编译工具链文件作为参考以后编译推理程序时会用得上set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g) set(CMAKE_SYSROOT /usr/aarch64-linux-gnu)在 CMake 工程里通过 -DCMAKE_TOOLCHAIN_FILExxx.cmake 指定这个文件cmake 就会自动使用交叉工具链而不是本机的 x86 工具链。这和 Makefile 里的 CROSS_COMPILE 变量是同一个思路只是 CMake 的写法更系统化。5.3 建议提前掌握的三个编译工具趁现在 hello 刚跑通概念还热乎我建议你把三个工具用熟。第一个是 file判定文件格式这是交叉编译排错的第一道关卡一眼就能看出产物是不是 ARM第二个是 readelf查看可执行文件的头部信息、段信息、依赖的动态库排查链接问题离不开它第三个是 objdump反汇编、查看机器码到了性能优化阶段会用得多现在可以先不深究。这三个工具都有对应的交叉编译版本也就是带 aarch64-linux-gnu- 前缀。hello 阶段你可能只需要 file但等你开始编译 yolov5s 推理程序、链接各种 .so 时readelf 会成为你最高频使用的命令。提前把它们放进工具箱后面会顺手很多。6. 实操复盘我踩过的坑和一套可复制的排查法6.1 一次真实排错从 No such file or directory 说起我在第一次做 RK3588 交叉编译时跳过了静态链接这个操作用的是默认的动态编译。把 hello 传到板子上chmod x执行结果就是那句让人一头雾水的./hello: No such file or directory我的第一反应是文件没传成功于是 ls -l 确认文件明明在。第二反应是文件权限问题但权限已经是可执行。折腾了几分钟后我才想到用 file 看格式输出显示 ELF 64-bit ARM aarch64动态链接没问题啊。最后用 readelf 查看解释器路径发现它指向 /lib/ld-linux-aarch64.so.1而板子的镜像里根本没有这个文件。问题就出在这。动态链接的可执行文件在执行时内核先去找动态链接器找不到就直接返回一个误导性的错误。我当时有两个解决方向一是补齐板端系统库二是改成静态编译。hello 阶段我选了静态编译一秒解决但我也意识到后续真正部署 yolov5s 推理程序时系统里缺什么库就得补什么库这个排查思路必须提前练熟练。6.2 几个容易被忽视的小细节复盘整个过程有几个细节我认为值得单独拎出来说。第一个是文件传输后要检查权限。scp 默认会保留原文件的权限位所以如果你在开发机上本来就给 hello 加了执行权限传到板子上大概率也是可执行的。但如果你是通过 U 盘拷贝或者经过某些文件共享服务权限位可能会被重置这时候 ls -l 看一下顺手 chmod x 就能避免莫名其妙的 Permission denied。第二个是 Makefile 中的变量覆盖方式。如果你在 Makefile 里用了 CC : gcc 这种硬编码命令行传 CROSS_COMPILE 是覆盖不掉的。我习惯用 ? 和 这类运算符来保留灵活性但也要注意这会让 Makefile 的行为依赖环境变量团队协作时最好统一约定。第三个是注意开发机和板子的系统差异。有些工具链会依赖开发机上的 32 位兼容库比如缺少 lib32gcc 系列时工具链本身都启动不了报错类似 No such file or directory但这次是发生在开发机上。遇到这种情况别慌按报错信息把对应依赖装上就行。Ubuntu 20.04 之后的发行版对 32 位库的支持已经比较克制遇到缺少依赖时优先用 apt 解决。6.3 跑通 hello 之后的行动清单关于交叉编译 hello这篇的内容就是这些了。动手能力强的朋友跑通之后不妨再做几件事把基础打得更实一点写一个调用数学库函数的程序编译时分别试试 -static 和不带用 readelf 观察两个版本在依赖上的差异把 Makefile 里的 CROSS_COMPILE 改成其他平台的工具链前缀感受一下同一个工程换个目标平台编译有多轻松也可以试着编译一个 C 版本的 hello确认 g 工具链也正常工作。这些都是很小的实验但对理解交叉编译帮助巨大。下一篇进入 yolov5s 的模型转换时我希望你已经把这条“编译、传输、运行”的链路跑得足够熟因为接下来的步骤会在它的基础上不断叠加复杂度。
返回列表