
嵌入式Linux开发里把Qt程序从PC搬到开发板上跑是很多人绕不开的一道坎。我前后在RV1109和RV1126这两颗芯片上做过好几个带界面的项目从最早的“编译报错一整天”到后来能稳定量产中间踩的坑足够写一本小册子。这篇就把整套流程摊开讲清楚怎么选交叉编译工具链、怎么配Qt的编译参数、怎么把程序丢到开发板上跑起来、以及那些文档里不会写的坑。不管你是刚拿到RV1126开发板的新手还是已经能编译但总在部署环节翻车的老手下面这些内容应该都能帮你省下不少时间。1. 先搞清楚RV1109/RV1126这颗芯片到底适合跑什么Qt很多人一上来就问“Qt怎么交叉编译”但更该先问的是“我这块板子到底该跑Qt的哪个版本、用哪种渲染后端”。RV1109和RV1126是瑞芯微面向IPC和边缘视觉场景的SoCCPU部分是Cortex-A7双核RV1109或四核RV1126主频在1.2GHz到1.5GHz之间内存通常配512MB到1GB。这个配置跑Qt完全没问题但前提是你得选对版本和配置否则要么编译不过要么跑起来卡成幻灯片。1.1 为什么是Qt 5.12而不是Qt 5.15或Qt 6网上关于Qt版本的选择吵得很凶我实测下来的结论是RV1109/RV1126优先用Qt 5.12.10或5.12.12。原因有三点。第一瑞芯微官方SDK里自带的Qt版本就是5.12系列buildroot配置里直接能勾选工具链、依赖库、字体、插件都是配套验证过的。你换成5.15.2理论上能编但会遇到一堆依赖库版本对不上的问题比如libinput、libxkbcommon的API变动改起来很烦。第二Qt 5.15对C标准的要求更高部分模块默认用C17编译而RV1109/RV1126的交叉工具链通常是gcc 8.3或9.3虽然支持但配合glibc版本容易出链接错误。Qt 5.12对C11的支持更稳编译成功率明显更高。第三Qt 6直接不用考虑。Qt 6的图形栈依赖OpenGL ES 3.0以上和更新的CMake构建体系RV1109/RV1126的GPU是Mali-G52RV1126或Mali-G31RV1109驱动对Qt 6的EGL集成支持不完善跑起来大概率黑屏。提示如果你非要用Qt 5.15建议用5.15.2这个版本它是5.15系列里相对稳定的LTS版本社区里交叉编译成功的案例也最多。但要做好改配置文件的准备。1.2 渲染后端选EGLFS还是LinuxFB这是新手最容易忽略、但直接影响程序能不能显示的问题。Qt在嵌入式Linux上有几种平台插件platform plugin常用的两种是EGLFS和LinuxFB。EGLFS走的是GPU加速路径通过EGL和OpenGL ES渲染性能好适合有动画、视频、复杂界面的场景。RV1126带Mali-G52 GPU用EGLFS能发挥硬件性能。但EGLFS对驱动要求高需要内核里DRMDirect Rendering Manager配置正确否则启动就报“EGL not initialized”。LinuxFB是纯软件渲染直接写framebuffer不依赖GPU。性能差一些但兼容性极好几乎不会因为驱动问题跑不起来。如果你的界面比较简单按钮、文本、少量图片LinuxFB完全够用而且调试省心。我的建议是先用LinuxFB把流程跑通确认Qt程序能在板子上正常显示后再切换到EGLFS做性能优化。这样出问题时你能快速定位是Qt配置的问题还是GPU驱动的问题。配置方式是在运行程序时指定平台插件# 用LinuxFB ./myapp -platform linuxfb # 用EGLFS ./myapp -platform eglfs或者在编译Qt时通过-qpa参数指定默认插件。交叉编译Qt时./configure里加-qpa linuxfb或-qpa eglfs。1.3 触摸屏和输入设备的前置确认界面能显示只是第一步触摸能不能用是另一回事。RV1109/RV1126的开发板通常配电容触摸屏通过I2C接口连接内核里需要加载对应的触摸驱动比如gt9xx、gt1x系列。在部署Qt程序之前先在板子上确认触摸设备节点存在ls /dev/input/ # 应该能看到event0、event1之类的节点 cat /proc/bus/input/devices # 查看触摸设备是否被识别然后用evtest工具测试触摸事件是否正常上报。如果触摸设备节点存在但Qt程序没反应多半是环境变量没配对export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1 export QT_QPA_GENERIC_PLUGINSevdevtouch这两个变量告诉Qt去哪里找触摸设备。event1要换成你板子上实际的触摸设备节点具体是哪个用evtest试出来。2. 交叉编译工具链的选型与验证工具链选错后面全是白费功夫。RV1109/RV1126的官方SDK里自带交叉编译工具链位置一般在prebuilts/gcc/linux-x86/arm/目录下名字类似arm-rockchip830-linux-uclibcgnueabihf或arm-rockchip830-linux-gnueabihf。这两个的区别在于C库uclibc版本体积小适合资源紧张的场景gnueabihf版本用glibc兼容性好适合跑Qt这种依赖较多的程序。2.1 工具链的获取与安装如果你用的是官方SDK工具链直接就有不用额外下载。如果是自己搭环境可以从瑞芯微的开发者网站下载对应的工具链包。安装就是解压到某个目录然后加到PATH里# 解压工具链 tar -xzf arm-rockchip830-linux-gnueabihf.tar.gz -C /opt/ # 加到环境变量 export PATH/opt/arm-rockchip830-linux-gnueabihf/bin:$PATH # 验证 arm-rockchip830-linux-gnueabihf-gcc -v能打印出gcc版本信息就说明装好了。注意工具链的架构要和你的目标板匹配RV1109/RV1126都是32位ARM Cortex-A7所以用arm-linux-gnueabihf不要用aarch64的。2.2 用一个小程序验证工具链是否可用装完工具链别急着编Qt先写个hello world验证一下// test.c #include stdio.h int main() { printf(hello rv1126\n); return 0; }编译arm-rockchip830-linux-gnueabihf-gcc test.c -o test file test # 应该显示: ELF 32-bit LSB executable, ARM, EABI5把test拷到开发板上运行能打印出内容就说明工具链没问题。这一步看着简单但能帮你排除掉“工具链本身有问题”这个变量后面出问题时就少一个排查方向。2.3 sysroot的配置交叉编译最容易翻车的地方交叉编译Qt时sysroot是最关键也最容易出错的配置。sysroot本质上是目标板根文件系统的一个副本编译时编译器会从这里找头文件和库。官方SDK里通常有一个sysroot目录路径类似buildroot/output/rockchip_rv1126_rv1109/host/arm-buildroot-linux-gnueabihf/sysroot。配置Qt的./configure时要指定--sysroot/path/to/sysroot如果sysroot路径不对或者里面缺库编译时会报“cannot find -lxxx”或者“header not found”。我的经验是编译Qt之前先确认sysroot里有Qt需要的所有依赖库包括libzzliblibpng、libjpeg图片格式支持freetype字体渲染libinput或tslib输入设备libEGL、libGLESv2如果用EGLFS这些库在buildroot配置里勾选后会自动编进sysroot。如果缺要么在buildroot里补上重新编译要么自己交叉编译后手动拷进sysroot。注意sysroot里的库版本要和开发板上实际运行的库版本一致。我遇到过一次sysroot里的libpng是1.6.37板子上是1.6.40编译链接没问题但运行时符号找不到直接崩溃。所以部署前一定要核对版本。3. Qt源码交叉编译的完整配置流程这部分是重头戏。Qt的交叉编译配置项很多配错一个就可能编不过或者编出来跑不了。我下面给出一套在RV1126上实测通过的配置并解释每个关键参数的作用。3.1 下载Qt源码与准备编译目录Qt 5.12.10的源码可以从Qt官方归档下载国内建议用镜像站加速。下载qt-everywhere-src-5.12.10.tar.xz后解压tar -xf qt-everywhere-src-5.12.10.tar.xz cd qt-everywhere-src-5.12.10Qt源码体积很大解压后有好几个G确保磁盘空间充足。编译过程建议在单独的build目录里进行不要污染源码目录mkdir build-rv1126 cd build-rv11263.2 configure参数逐项拆解下面是我在RV1126上用的configure命令逐项解释../configure \ -prefix /opt/qt5.12.10-rv1126 \ -opensource -confirm-license \ -release \ -static \ -no-pch \ -xplatform linux-arm-gnueabi-g \ -sysroot /path/to/sysroot \ -no-opengl \ -linuxfb \ -no-xcb \ -no-gtk \ -no-cups \ -no-iconv \ -qt-zlib \ -qt-libpng \ -qt-libjpeg \ -qt-freetype \ -nomake examples \ -nomake tests \ -skip qtwebengine \ -skip qtquickcontrols \ -skip qtlocation \ -v逐项说明-prefix安装路径编译完成后make install会装到这里。-static静态编译。嵌入式场景推荐静态编译生成的程序不依赖板子上的Qt库部署简单。缺点是程序体积大编译时间长。如果你板子存储空间紧张可以用动态编译但要把Qt库也拷到板子上。-xplatform linux-arm-gnueabi-g指定目标平台。这个对应qtbase/mkspecs/linux-arm-gnueabi-g/qmake.conf文件你需要修改这个文件里的编译器和sysroot路径。-no-opengl如果不用EGLFS直接关掉OpenGL减少依赖。用EGLFS的话要改成-opengl es2。-linuxfb启用LinuxFB平台插件。-no-xcb禁用X11相关嵌入式不需要。-qt-zlib -qt-libpng -qt-libjpeg -qt-freetype用Qt自带的这些库避免依赖sysroot里的版本。这样编译出来的程序更独立。-skip qtwebengine跳过WebEngine模块这个模块编译极其耗时且RV1126跑不动。-nomake examples -nomake tests不编译示例和测试节省大量时间。3.3 修改mkspecs文件适配工具链-xplatform linux-arm-gnueabi-g指向的是qtbase/mkspecs/linux-arm-gnueabi-g/qmake.conf。你需要编辑这个文件把里面的编译器改成你的工具链# qmake.conf MAKEFILE_GENERATOR UNIX CONFIG incremental QMAKE_INCREMENTAL_STYLE sublib include(../common/linux.conf) include(../common/gcc-base-unix.conf) include(../common/g-unix.conf) # 修改这里 QMAKE_CC arm-rockchip830-linux-gnueabihf-gcc QMAKE_CXX arm-rockchip830-linux-gnueabihf-g QMAKE_LINK arm-rockchip830-linux-gnueabihf-g QMAKE_LINK_SHLIB arm-rockchip830-linux-gnueabihf-g QMAKE_AR arm-rockchip830-linux-gnueabihf-ar cqs QMAKE_OBJCOPY arm-rockchip830-linux-gnueabihf-objcopy QMAKE_NM arm-rockchip830-linux-gnueabihf-nm -P QMAKE_STRIP arm-rockchip830-linux-gnueabihf-strip load(qt_config)改完保存再执行configure。这一步如果没改对configure会报“compiler not found”。3.4 编译过程中的常见报错与处理Qt编译时间长中途报错很常见。我列几个遇到过的报错一Project ERROR: Unknown module(s) in QT: serialport这个是因为Qt的serialport模块没有编译。如果你程序里用了QSerialPort需要在configure时确保serialport模块被包含。默认情况下Qt 5.12会编译serialport但如果sysroot里缺libudev这个模块会被跳过。解决办法是在buildroot里勾选libudev或者手动交叉编译libudev后放进sysroot。报错二cannot find -lGLESv2用EGLFS时需要链接GLESv2库。如果sysroot里没有要么补上要么改用LinuxFB。报错三编译到qtdeclarative时内存不足Qt的QML模块编译很吃内存如果编译机内存小于8GB可能会被OOM kill。解决办法是减少并行编译任务数make -j4 # 而不是 -j8 或 -j16或者加swap。报错四fatal error: bits/libc-header-start.h: No such file or directory这是sysroot路径配错或者工具链和sysroot不匹配。检查--sysroot路径是否正确以及工具链的C库类型uclibc还是glibc是否和sysroot一致。编译完成后make -j4 make install安装到/opt/qt5.12.10-rv1126里面会有bin、lib、include等目录。4. 用qmake构建你的Qt应用并部署到开发板Qt编译好了接下来是编你自己的程序。这里有个关键点必须用交叉编译出来的qmake不能用PC上的qmake。4.1 配置Qt Creator的交叉编译套件如果你用Qt Creator开发需要在“工具-选项-套件”里配置交叉编译套件编译器添加C和C编译器指向工具链的gcc和gQt版本添加qmake指向/opt/qt5.12.10-rv1126/bin/qmake设备配置开发板的SSH连接如果支持网络部署配置好后选择这个套件编译生成的程序就是ARM架构的。如果不用Qt Creator直接用命令行/opt/qt5.12.10-rv1126/bin/qmake myproject.pro make -j4编译出来的可执行文件用file命令确认是ARM架构。4.2 静态编译与动态编译的部署差异静态编译的程序体积大几十MB很正常但部署简单直接拷一个文件到板子上就能跑scp myapp root192.168.1.100:/userdata/动态编译的程序体积小但需要把依赖的Qt库也拷到板子上。用ldd查看依赖arm-rockchip830-linux-gnueabihf-ldd myapp然后把对应的.so文件拷到板子的/usr/lib或程序同目录下并设置LD_LIBRARY_PATHexport LD_LIBRARY_PATH/userdata/lib:$LD_LIBRARY_PATH我的建议是开发阶段用动态编译改代码后重新编译快部署也快只传程序本身量产阶段用静态编译避免库版本不一致导致的问题。4.3 开发板上的运行环境配置程序拷到板子上后直接运行可能报错。需要配置几个环境变量# 指定平台插件 export QT_QPA_PLATFORMlinuxfb # 指定触摸设备 export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1 # 指定字体目录如果程序用了自定义字体 export QT_QPA_FONTDIR/usr/share/fonts # 指定插件路径动态编译时需要 export QT_PLUGIN_PATH/userdata/plugins这些变量可以写进/etc/profile或程序启动脚本里避免每次手动设置。如果程序启动后黑屏先检查QT_QPA_PLATFORM是否设置正确再用strace跟踪strace -f ./myapp 21 | grep -i open\|error看是哪个文件或设备打不开。4.4 开机自启动的配置产品化时程序要开机自启动。RV1109/RV1126的buildroot系统用init.d或systemd管理启动项。最简单的方式是在/etc/init.d/下加一个启动脚本#!/bin/sh case $1 in start) export QT_QPA_PLATFORMlinuxfb export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1 /userdata/myapp ;; stop) killall myapp ;; esac然后创建软链接到启动目录ln -s /etc/init.d/myapp /etc/rc3.d/S99myapp注意S99的编号要足够大确保在网络、显示等基础服务启动后再启动你的程序。5. 那些文档里不会写的踩坑记录这部分是我觉得最有价值的内容。下面这些坑每一个都让我浪费过半天以上的时间。5.1 触摸坐标偏移和镜像问题触摸屏能用但点击位置和实际显示位置对不上这是很常见的问题。原因通常是触摸设备的坐标范围和屏幕分辨率不匹配或者触摸屏装反了。解决办法是通过环境变量做坐标变换# 交换X和Y轴 export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1:swapxy # 反转X轴 export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1:invertx # 反转Y轴 export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1:inverty # 组合使用 export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1:swapxy:invertx具体用哪个组合要看你屏幕的实际安装方向。我的经验是先试swapxy如果X轴对了Y轴反了再加inverty一般两三次就能试对。5.2 中文字体显示方块Qt程序在板子上跑起来中文全变成方块这是因为板子上没有中文字体。解决办法有两个一是把中文字体文件比如wqy-zenhei.ttc或NotoSansCJK.ttc拷到板子的字体目录mkdir -p /usr/share/fonts cp wqy-zenhei.ttc /usr/share/fonts/ export QT_QPA_FONTDIR/usr/share/fonts二是在程序里用QFontDatabase::addApplicationFont()加载字体文件int fontId QFontDatabase::addApplicationFont(/userdata/fonts/wqy-zenhei.ttc); QStringList fontFamilies QFontDatabase::applicationFontFamilies(fontId); if (!fontFamilies.isEmpty()) { QFont font(fontFamilies.at(0)); qApp-setFont(font); }第二种方式更可靠因为字体文件跟着程序走不依赖板子上的字体配置。5.3 程序崩溃但没有任何输出程序在板子上跑起来就崩终端也没打印错误信息。这种情况通常是动态库加载失败或者段错误。排查步骤第一步用ldd确认所有依赖库都能找到arm-rockchip830-linux-gnueabihf-ldd myapp # 如果有 not found说明缺库第二步如果库都在用gdb调试gdb ./myapp (gdb) run # 崩溃后 (gdb) bt看backtrace定位崩溃位置。如果板子上没有gdb可以在PC上用gdbserver远程调试。第三步检查是不是内存不足。RV1109通常只有512MB内存如果程序加载了大量图片或数据可能OOM。用free和top查看内存使用。5.4 静态编译后程序体积过大静态编译的Qt程序动辄50MB以上如果板子存储空间紧张需要做裁剪。几个有效的手段configure时加-no-feature-xxx禁用不需要的特性用strip去掉符号表arm-rockchip830-linux-gnueabihf-strip myapp用upx压缩但嵌入式上upx可能不兼容慎用只编译用到的Qt模块-skip掉其他模块我做过一个项目通过裁剪把程序从60MB压到了18MB主要靠禁用WebEngine、Multimedia、3D等模块以及strip符号表。5.5 屏幕旋转后触摸不跟随如果屏幕做了90度旋转比如竖屏应用触摸坐标也要跟着旋转。Qt本身不自动处理这个需要手动配置# 屏幕旋转90度 export QT_QPA_EGLFS_ROTATION90 # 触摸也要相应旋转 export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event1:rotate90注意rotate参数的值要和屏幕旋转角度一致否则触摸会错位。这个参数在Qt 5.12里支持但不同小版本行为可能有差异建议实测确认。6. 性能调优与稳定性加固程序能跑起来只是及格要跑得流畅、跑得稳还需要做一些优化。6.1 减少界面重绘提升流畅度RV1109/RV1126的CPU性能有限如果界面频繁重绘会明显卡顿。几个优化手段用QWidget::setAttribute(Qt::WA_OpaquePaintEvent)告诉Qt这个控件不透明减少背景重绘避免在paintEvent里做耗时操作把计算逻辑放到后台线程用QStaticText代替QPainter::drawText绘制静态文本图片预缩放到目标尺寸避免运行时缩放如果用了QML开启QSG_RENDER_LOOPbasic可以降低渲染线程的复杂度在低性能设备上反而更流畅。6.2 用EGLFS提升渲染性能如果界面有动画或视频LinuxFB的软件渲染会力不从心这时候要切到EGLFS。切换步骤第一步重新编译Qtconfigure时把-no-opengl -linuxfb改成-opengl es2 -eglfs。第二步确认板子内核里DRM配置正确/dev/dri/card0存在。第三步运行时指定export QT_QPA_PLATFORMeglfs export QT_QPA_EGLFS_INTEGRATIONeglfs_kms ./myappeglfs_kms是RV1126上验证过的集成方式通过KMS/DRM直接管理显示。6.3 看门狗与异常重启产品化场景下程序崩溃后要能自动重启。可以用一个简单的守护脚本#!/bin/sh while true; do /userdata/myapp echo myapp exited, restarting... sleep 1 done配合硬件看门狗RV1109/RV1126内置WDT在程序里定期喂狗如果程序卡死看门狗会复位系统。6.4 日志输出与远程调试开发阶段建议把日志输出到文件方便排查./myapp /userdata/myapp.log 21 Qt的日志可以通过qInstallMessageHandler自定义把qDebug、qWarning等输出重定向到文件或网络。量产版本可以关掉调试日志减少IO开销。远程调试用gdbserver# 板子上 gdbserver :2345 ./myapp # PC上 arm-rockchip830-linux-gnueabihf-gdb ./myapp (gdb) target remote 192.168.1.100:2345这样可以在PC上单步调试板子上的程序比在板子上直接调试方便得多。7. 从开发到量产的几个关键决策最后聊几个实际项目里会遇到的决策点这些没有标准答案但我的经验可以给你参考。7.1 静态编译还是动态编译前面提过开发阶段动态、量产阶段静态。但还有一个考量如果你的产品需要OTA升级动态编译更合适因为升级时只需要替换程序本身不用重新烧录整个固件。静态编译的程序虽然部署简单但每次升级都要传大文件。我的做法是核心程序动态编译把Qt库打包进固件程序通过OTA单独升级。这样兼顾了升级灵活性和部署便利性。7.2 用buildroot还是自己搭根文件系统瑞芯微官方SDK用的是buildroot配置好之后一键编译整个系统包括内核、根文件系统、Qt库。这是最省事的方式推荐新手直接用。如果你需要更精细的控制比如裁剪掉不需要的组件、集成特定的库可以自己用busybox搭根文件系统但工作量大很多。我的建议是先用buildroot跑通遇到buildroot解决不了的问题再考虑自己搭。7.3 Qt版本锁定与升级策略项目一旦选定Qt版本就不要轻易升级。Qt 5.12.10和5.12.12之间虽然是小版本差异但交叉编译出来的库可能有ABI不兼容。如果团队多人开发一定要统一Qt版本和工具链版本最好把工具链和Qt库都放进版本控制或者内部服务器避免“我这里能编你那里编不过”的问题。升级Qt版本时先在独立分支上验证确认所有功能正常、性能没有退化后再合并。我见过因为升级Qt导致触摸驱动不兼容、界面卡顿的案例回滚花了不少时间。7.4 开发板与量产板的差异处理开发板通常配置高1GB内存、16GB存储量产板可能缩水512MB内存、4GB存储。在开发板上跑得好好的程序到量产板上可能因为内存不足崩溃。所以尽早拿到量产板做验证不要等到最后才发现问题。另外量产板的屏幕、触摸芯片可能和开发板不同驱动和设备节点都会变。部署前用evtest和ls /dev/input/重新确认设备节点更新环境变量配置。这套流程我在RV1109和RV1126上都跑通过核心步骤是一样的差异主要在GPU驱动和内存配置上。RV1126性能更强可以开EGLFS跑复杂界面RV1109建议用LinuxFB界面做简单些。实际做项目时先把最小系统跑通一个空白窗口能显示、能响应触摸再逐步加功能这样出问题容易定位。交叉编译这事坑是躲不完的但把上面这些关键点抓住能省下大部分折腾的时间。