拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

瑞芯微RK3506上Qt 5.15.2交叉编译完整实战指南

瑞芯微RK3506上Qt 5.15.2交叉编译完整实战指南 干嵌入式Linux这行的人估计都经历过类似场景拿到一块新开发板内核跑起来了交叉编译链也装了结果卡在“把Qt应用跑起来”这一步。尤其是在瑞芯微RK3506这种性价比芯片上做Qt开发光是搞明白交叉编译、sysroot、平台插件这些概念就够喝一壶的。这篇文章就按我实际操作的顺序从零开始带你走一遍完整流程把那些文档里没写明白、光靠搜答案搜不清楚的细节都摊开讲。适用人群很明确刚接触RK3506或类似ARM开发板、想在板子上跑Qt界面的工程师和学生。全文围绕一个最小可运行的Qt Widgets程序展开通过实际代码和命令把Qt 5.15.2从编译到运行的全链路打通。顺便会解决一个很常见的头疼问题“unknown module in qt: serialport”。1. 项目整体设计与方案选型1.1 RK3506平台定位与Qt选型背景瑞芯微RK3506是面向工业控制、物联网关、边缘计算等场景的四核Cortex-A35处理器主频最高能到1.5GHz左右发热低、性价比高这类SoC在最近两年出货量非常大。Cortex-A35属于ARMv8-A架构也就是说它是64位处理器这直接决定了你的交叉编译链必须选择aarch64版本而不是传统的armv7软浮点那套。在UI方案上我最终选择了Qt 5.15.2。原因有几点第一5.15是开源版本里最后支持Widgets长期维护的LTS版本之一社区资料丰富踩坑容易搜到答案第二Qt的跨平台抽象做得比较完整同样是这套代码调试时可以在x86电脑上跑发布时交叉编译到板子开发效率高很多第三RK3506这类芯片虽然有Mali GPU或者简单2D加速但很多场景下用的还是CPU渲染Qt的linuxfb和eglfs平台插件对硬件要求宽裕适配成本低。不选LVGL的原因是产品需要复杂业务逻辑和网络交互不选Android是因为开机速度和实时性要求不同。这只是个取舍没有绝对对错但基于这个项目定位Qt是最稳的选择。1.2 整体技术架构与流程拆解整个项目流程可以拆成四个阶段宿主机准备一台64位Ubuntu系统作为编译服务器安装基础编译工具。工具链搭建下载aarch64交叉编译工具链并整理目标板的sysroot系统根文件系统头文件和库。Qt源码交叉编译用工具的mkspec描述交叉编译环境configure后生成适用于RK3506的Qt库和平台插件。应用开发与部署编写qmake工程交叉编译出ARM架构的可执行文件拷贝到板子运行。这里必须解释为什么不能像用PC一样直接在板子上跑Qt的安装包。RK3506板载存储和计算资源有限编译大型Qt库耗时不说如果后续做量产不可能每台板子上都跑编译器。交叉编译的意义在于在性能强的PC上生成目标架构的二进制文件板子只负责运行这是嵌入式开发的通用模式。1.3 方案对比手动交叉编译 vs Buildroot vs 在线安装市面上获取Qt的方式主要有三种我一开始也犹豫过实际对比后还是选了手动交叉编译方案优点缺点适用场景Qt在线安装器在x86 PC上安装快模块全安装的是PC版库无法在ARM板运行仅用于PC端界面开发调试Buildroot集成Qt一键生成包含Qt的固件定制模块麻烦修改Qt源码困难构建时间长只跑固定功能、不折腾的产线工程手动源码交叉编译完全可控模块可选调试方便步骤多配置复杂需要理解sysroot需要深度定制Qt、调试底层问题手动交叉编译看起来步骤多但它能让你真正理解Qt构建系统的逻辑之后遇到任何“缺模块”“平台插件找不到”的问题都能自己定位。本文采用的方式就是方案三。2. 环境准备与工具链搭建2.1 宿主机环境与基础依赖安装我使用的宿主机是Ubuntu 20.04 LTS64位系统。如果你用Ubuntu 22.04或更新的发行版请特别注意系统自带gcc版本不要和交叉工具链混用尽量保持独立。首先更新系统并安装基础依赖sudo apt update sudo apt upgrade -y sudo apt install -y build-essential chrpath diffstat gawk libncurses5-dev \ libssl-dev python3 python3-pip unzip texinfo gdisk wget xz-utils \ libxkbcommon-dev libgl1-mesa-dev libfontconfig1-dev libdbus-1-dev这些包里面build-essential提供本机的gcc/make工具虽然本机编译器和交叉编译器不同但configure阶段很多检测脚本还是依赖本机的编译环境。另外像libgl1-mesa-dev这类库是让Qt configure脚本能够正确识别OpenGL/EGL相关特性如果没有会在后面报错。还要检查一下系统是不是已经装了交叉编译可能要用的Python和Perlpython3 --version perl -v | head -n 2Qt构建过程会调用perl处理一些脚本没有的话编译到一半会卡住。2.2 交叉编译工具链安装RK3506平台是aarch64架构官方SDK一般会提供工具链常见的有gcc-arm-10.3-x86_64-aarch64-none-linux-gnuLinaro提供的aarch64-linux-gnu-系列工具链我这边用的是GCC 10.3的aarch64-none-linux-gnu版本。下载后解压到/opt目录sudo mkdir -p /opt/rk3506 sudo tar -xJf gcc-arm-10.3-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt/rk3506设置环境变量建议写到~/.bashrc里省得每次开终端都重新导一遍export PATH/opt/rk3506/gcc-arm-10.3-x86_64-aarch64-none-linux-gnu/bin:$PATH验证一下工具链是否可用aarch64-none-linux-gnu-gcc -v能看到gcc version 10.3.0的输出就算成功了。这里强调一下工具链版本太老比如gcc 4.9编译高版本Qt会有很多模板兼容性报错最好选8.3以上的版本。2.3 sysroot准备与目录结构sysroot是交叉编译里最容易被忽略、却最关键的环节。它的本质是一个“模拟目标板的根文件系统”里面放着目标板系统的头文件/usr/include和库文件/usr/lib、/lib。交叉编译器编译时需要从sysroot里找到标准C库的依赖头文件和库。获取sysroot有三个办法从开发板或SDK固件中提取根文件系统目录把usr/include、usr/lib、lib、usr/local等目录拷出来。用Buildroot构建一个基础根文件系统。使用SDK里自带的sysroot目录如果有的话。我使用的是第一种方式。在板子上执行tar -zcf sysroot.tar.gz /usr/include /usr/lib /lib /usr/local/lib /usr/local/include把生成的sysroot.tar.gz拷回PC解压到一个固定目录mkdir -p /opt/rk3506/sysroot sudo tar -zxf sysroot.tar.gz -C /opt/rk3506/sysroot注意解压后要保证目录结构是/opt/rk3506/sysroot/usr/include这样不要多套一层目录。检查一下关键库是否存在find /opt/rk3506/sysroot -name libstdc.so* find /opt/rk3506/sysroot -name libc.so*如果这两个基础库在sysroot基本完整。但这个sysroot只是“基础环境”后续编译Qt时真正使用的是Qt自己编译出来的库所以不需要板子预装Qt这一点很多人会弄混Qt交叉编译的输出不是给板子原来的系统用的而是编译完以后以独立目录形式一起部署到板子上。3. Qt 5.15.2交叉编译核心实操3.1 修改设备规格文件mkspecQt用“mkspec”来描述不同编译目标平台的规则。默认编译PC版使用linux-g我们这里要为aarch64新建一个独立的mkspec。进入Qt源码目录qt-everywhere-src-5.15.2后先找到参考模板ls qtbase/mkspecs/ | grep linux其中linux-arm-gnueabi-g是32位ARM的模板可以直接复制一份改造成aarch64版本cp -r qtbase/mkspecs/linux-arm-gnueabi-g qtbase/mkspecs/linux-aarch64-rk3506-g编辑qtbase/mkspecs/linux-aarch64-rk3506-g/qmake.conf修改完整后的内容如下# # qmake configuration for building with aarch64-linux-gnu-g # MAKEFILE_GENERATOR UNIX CONFIG incremental QMAKE_INCREMENTAL_STYLE sublib include(../common/linux.conf) include(../common/gcc-base-unix.conf) include(../common/g-unix.conf) # modifications to g.conf QMAKE_CC aarch64-none-linux-gnu-gcc QMAKE_CXX aarch64-none-linux-gnu-g QMAKE_LINK aarch64-none-linux-gnu-g QMAKE_LINK_SHLIB aarch64-none-linux-gnu-g # modifications to linux.conf QMAKE_AR aarch64-none-linux-gnu-ar cqs QMAKE_OBJCOPY aarch64-none-linux-gnu-objcopy QMAKE_NM aarch64-none-linux-gnu-nm QMAKE_STRIP aarch64-none-linux-gnu-strip QMAKE_RANLIB aarch64-none-linux-gnu-ranlib load(qt_config)关键点是四个编译器和归档工具全部指向aarch64工具链。如果你是别的工具链前缀比如arm-linux-gnueabihf-改成对应的即可。3.2 configure配置与参数解析Linux下Qt的configure参数非常多但嵌入式裁剪时的核心参数就那么十几个。我从实际调试中沉淀了一套相对精简且常用稳定的配置./configure \ -prefix /usr/local/qt5-rk3506 \ -xplatform linux-aarch64-rk3506-g \ -sysroot /opt/rk3506/sysroot \ -opensource -confirm-license \ -release -optimize-size \ -no-opengl \ -no-tslib \ -no-xcb \ -no-cups \ -no-gtk \ -nomake examples -nomake tests \ -qt-zlib -qt-libpng -qt-libjpeg -qt-freetype \ -ltcg \ -skip qtwebengine -skip qt3d -skip qtcharts -skip qtsensors \ -skip qtconnectivity -skip qtdoc -skip qtgamepad -skip qtvirtualkeyboard \ -no-feature-remoteobjects -no-feature-sql -no-feature-xml逐个解释一下这里为什么这么写-prefix /usr/local/qt5-rk3506安装路径因为板子的根文件系统比较简我习惯安装到独立目录部署时整个目录同步到板子。-xplatform linux-aarch64-rk3506-g指向上一步新建的mkspec。-sysroot /opt/rk3506/sysroot指向目标板的sysroot。-no-opengl如果你的板子和我的使用场景一样只跑2D界面、不搞OpenGL渲染这步可以显著减少编译时间和依赖。-no-xcb没有X11环境不需要XCB插件。-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype强制使用Qt自带的三方库避免和板子系统库版本打架。嵌入式系统里最怕系统库缺失或者符号链接不完整直接静态捆绑是省心方案。-ltcg链接时优化能减少最终生成的动态库体积对嵌入式存储空间有好处。-skip qtsensors -skip ...Qt自带的模块非常多跳过用不到的模块能大幅缩短编译时间还能减少部署时的磁盘占用。如果以后需要某个模块再加回来重编就行。确认无误后执行make -j$(nproc) 21 | tee build.log这一步在我的机器8核16线程上大约需要40到60分钟。如果中途有错误查看build.log的尾部即可定位。3.3 SerialPort模块的编译与常见坑很多工业板子都要和串口打交道Qt里对应模块是QtSerialPort。但默认configure时如果你没特别处理编译完的Qt可能没有这个模块于是你编译应用时就会遇到很典型的报错Project ERROR: unknown module(s) in qt: serialport网上搜这个报错答案常常是“重新安装qt-serialport”但在交叉编译场景里就是在Qt源码编译时没有把serialport模块选进去。在5.15.2的源码里qtserialport是独立子模块默认在configure时应该被包含但如果你用了某个裁剪过的源码包或者个别模块依赖检测失败比如缺少某些perl规则它会在configure阶段被自动跳过。排查方法看configure结尾的输出列表要领是找Qt Serial Port: yes这样的字样。如果发现是no可以在configure命令里追加-skip qtwebsockets不对这个参数不加而是确认你已经没有skip qtserialport并且优先检查系统是否缺少perl模块。如果确认configure输出了serialport相关信息但编译时仍然找不到通常是你的工程pro文件里少写了QT core gui serialport如果前面configure阶段直接把serialport编译进去了通过make输出里能看到qtserialport目录的编译日志那这个error基本就是工程配置里漏module引用。还有一种特殊场景你不想为整个Qt启用serialport但某个应用单独需要。这种情况可以单独进qtserialport目录用同样的mkspec独立编译模块然后把生成的库拷贝到sysroot的Qt安装目录里属于高级玩法本文不展开。核心思路只有一个确认configure阶段模块是否被编译。3.4 安装与sysroot同步编译完成后执行安装到prefix目录make install完成后检查一下/usr/local/qt5-rk3506目录ls /usr/local/qt5-rk3506/lib/正常能看到libQt5Core.so.5.15.2、libQt5Gui.so、libQt5Widgets.so、libQt5SerialPort.so等文件以及plugins/platforms/目录下应该有一个libqlinuxfb.solinuxfb平台插件这是我们运行Qt应用的核心。为了方便后续应用编译时找到Qt库把Qt的qmake链接到PATH里export PATH/usr/local/qt5-rk3506/bin:$PATH同时为了让交叉编译器找到我们刚编译出来的Qt头文件可以把include和lib软链到sysroot下mkdir -p /opt/rk3506/sysroot/usr/local ln -sf /usr/local/qt5-rk3506 /opt/rk3506/sysroot/usr/local/qt5这一步做完相当于sysroot里也有了Qt的头文件和库应用编译时不需要手动指定一堆-I和-L参数。4. Qt应用编写与交叉编译部署4.1 最小可运行示例程序先写一个极简但带实际意义的Qt Widgets程序功能是打开串口并发送数据。这个例子既覆盖了界面显示又覆盖了serialport模块的使用非常典型。main.cpp#include QApplication #include QWidget #include QVBoxLayout #include QComboBox #include QPushButton #include QSerialPort #include QDebug int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.setWindowTitle(RK3506 Qt Demo); QVBoxLayout *layout new QVBoxLayout(window); QComboBox *portBox new QComboBox(window); QPushButton *btn new QPushButton(Send, window); foreach (const QSerialPortInfo info, QSerialPortInfo::availablePorts()) { portBox-addItem(info.portName()); } QObject::connect(btn, QPushButton::clicked, []() { QSerialPort serial; serial.setPortName(portBox-currentText()); serial.setBaudRate(115200); if (serial.open(QIODevice::ReadWrite)) { serial.write(hello from rk3506\n); serial.close(); qDebug() send ok; } else { qDebug() open failed; } }); layout-addWidget(portBox); layout-addWidget(btn); window.resize(480, 320); window.show(); return app.exec(); }这个程序在PC上编译运行也可以用于逻辑调试。在板子上它又能真的操作串口一举两得。rk3506_qt_demo.proQT core gui serialport greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET rk3506_qt_demo TEMPLATE app SOURCES main.cpp注意QT widgets这个写法5.15版本必须写否则会报“widgets module not found”。4.2 使用交叉qmake构建应用构建应用时千万不要用系统自带的qmake一定要用第3步安装的交叉编译qmakeexport PATH/usr/local/qt5-rk3506/bin:$PATH mkdir build cd build qmake ../rk3506_qt_demo.pro make -j$(nproc)构建完成后用file查看产物架构file rk3506_qt_demo正常输出包含ELF 64-bit LSB executable, ARM aarch64看到ARM aarch64就说明这个程序只能在ARM板子上跑。如果你看到的是x86-64说明qmake路径配错了还在用PC的Qt。检查依赖库aarch64-none-linux-gnu-readelf -d rk3506_qt_demo | grep NEEDED能看到libQt5Widgets.so.5、libQt5Core.so.5、libQt5SerialPort.so.5这些依赖。4.3 部署到开发板的三种方式部署Qt程序到板子通常有三种途径方式操作适用场景scp拷贝scp rk3506_qt_demo root板子IP:/usr/bin/单台调试最简单直接NFS挂载PC共享目录板子mount到/mnt/nfs频繁修改代码免重复拷贝SD卡拷贝编译产物放到SD卡板子挂载SD卡现场部署离线环境我开发阶段最依赖NFS。先在PC上安装并配置NFS服务sudo apt install nfs-kernel-server sudo vim /etc/exports # 加上一行 # /home/user/nfsroot *(rw,sync,no_subtree_check,no_root_squash) sudo systemctl restart nfs-server板子端执行mount -t nfs -o nolock 192.168.1.100:/home/user/nfsroot /mnt/nfs这样板子上/mnt/nfs里的程序就是PC上最新编译出来的调试循环非常快。量产阶段再用scp拷贝最终版本。4.4 板端运行与环境变量配置拷贝程序到板子后还要把Qt运行库同步过去。最简单的办法是把整个/usr/local/qt5-rk3506目录打包拷到板子相同的路径下tar -czf qt5-rk3506.tar.gz -C /usr/local qt5-rk3506 # 在板子上解压到 /usr/local/运行前设置环境变量export QT_QPA_PLATFORMlinuxfb export LD_LIBRARY_PATH/usr/local/qt5-rk3506/lib:$LD_LIBRARY_PATH export QT_QPA_FB_DRM1 export QT_QPA_GENERIC_PLUGINSevdev:/dev/input/event0 ./rk3506_qt_demoQT_QPA_PLATFORMlinuxfb是指定Qt使用linuxfb作为窗口系统平台插件也就是Qt直接向framebuffer设备/dev/fb0绘制内容不依赖任何窗口管理器最适合嵌入式单进程场景。LD_LIBRARY_PATH必须要指向Qt库目录否则会报libQt5Core.so.5: cannot open shared object file。QT_QPA_FB_DRM1是启用DRM后端部分新版内核的fb设备需要这个开关如果程序起不来可以试着去掉再调试。如果你的板子有显示设备但屏幕是HDMI或LVDS这一步大概率能直接出画面。要是没有屏幕也可以用-platform offscreen把程序跑起来做逻辑验证。运行成功后串口设备节点一般出现在/dev/ttyS0、/dev/ttyS1等位置。如果Qt通过QSerialPortInfo搜索不到串口检查当前用户是否在dialout组里或者直接以root运行。5. 常见问题与排查技巧实录5.1 编译期问题速查表我把自己踩过的编译期坑整理成一个表格按症状和解决方案对应来看症状定位方法解决方法unknown module(s) in qt: serialport查看configure日志确认qtserialport是否编译补编SerialPort模块或确认pro文件里QT serialportcannot find -lGL编译Qt时提示找不到OpenGL库使用-no-opengl或者安装mesa开发库Qt5WidgetsConfig.cmake not found应用用CMake构建找不到Qt检查CMAKE_PREFIX_PATH指向交叉编译的Qt目录aarch64-none-linux-gnu-g: command not found环境变量未设置检查PATH是否包含工具链bin目录error: GLES2/gl2.h: No such file开启了OpenGL相关特性关闭-opengl相关选项或用-no-openglmkspec报错Unknown test functionqmake.conf语法错误检查行尾有没有多余空格重新复制模板再改5.2 运行期问题速查表症状定位方法解决方法libQt5Core.so.5: cannot open shared object fileldd检查依赖找不到Qt库设置LD_LIBRARY_PATH或把库放到/usr/libqt.qpa.plugin: Could not find the Qt platform plugin linuxfb检查plugins/platforms目录确认libqlinuxfb.so已安装或者指定-platform offscreen测试程序启动后黑屏或直接退出dmesg查看是否段错误检查fb设备是否存在尝试去掉QT_QPA_FB_DRM或换eglfs串口搜索不到设备ls /dev/ttyS*确认节点存在检查用户权限加-qt-zlib无关要加udev规则或改用户组framebuffer显示花屏检查分辨率配置确认内核命令行video参数和屏幕分辨率匹配5.3 项目中最典型的两个坑第一个坑是sysroot路径写错导致的头文件混乱。一开始我把sysroot指定成/opt/rk3506/sysroot但工具链本身自带的sysroot优先级更高如果编译器内部库版本和外部sysroot冲突会出现各种奇奇怪怪的链接错误。解决方式是把工具链内部的sysroot干脆不用只在configure里用外部sysroot指向板子系统的文件。第二个坑是QPA平台插件选错。同一个Qt版本linuxfb和eglfs对硬件要求完全不同。我一开始想当然用了eglfs结果没有EGL实现直接黑屏换成linuxfb后秒出画面。如果你的板子没有GPU老老实实用linuxfb最稳定有GPU的话eglfs能提供更好的性能但需要额外保证Mali驱动装载正确。5.4 开发效率提升技巧最后分享几个提高效率的小技巧。用rsync代替scp同步库增量传输速度快很多rsync -avz /usr/local/qt5-rk3506/ root192.168.1.150:/usr/local/qt5-rk3506/在PC上写一个交叉编译的环境变量脚本每次new一个终端就source一下避免重复exportsource /opt/rk3506/env.sh把U-Boot和内核版本固定住不要频繁升级系统因为sysroot里的库版本会跟着变内核升级可能导致驱动接口变化进而影响Qt运行。调试阶段建议用NFS而不是反复烧写SD卡一个编译循环能缩短到几秒钟体验完全不同。做好这几点整个开发节奏会顺畅很多。结语与个人经验总结我自己在实际项目里把这些流程走通后最大的体会是交叉编译环境一旦搭建好就是一笔长期复用的资产。工具链、sysroot、Qt库目录都固定下来后后续开发新应用的时间成本会显著下降。不要每次拿到一块新板子都从头折腾一遍环境统一才是最省钱的做法。最后再分享一个小经验遇到Qt相关的疑难杂症先用qmake -v确认路径再用file确认架构最后查ldd确认依赖90%的问题都能用这三板斧定位。如果问题在configure阶段一定保留config.log和build.log这两个文件是排障的第一手资料。希望这篇实战记录能帮你少走弯路顺利在RK3506或类似ARM开发板上跑起自己的第一个Qt应用。
返回列表