资讯详情

IMX6ULL车载终端Qt调试:用rsync替代NFS实现高效部署

📅 2026/9/16 16:36:09 | 华诺云谱 👁 阅读
IMX6ULL车载终端Qt调试:用rsync替代NFS实现高效部署
简介基于IMX6ULL的智能车载终端项目代码专为正点原子IMX6ULL平台打造可在出厂镜像系统上直接运行面向嵌入式车载终端开发者和学习者省去复杂的环境移植与配置。代码注释详尽、框架兼容性好且采用Qt环境构建和模块化设计便于阅读维护与二次功能扩展。压缩包共25个文件、约39.19MB包含C源程序、编译中间文件、Makefile、说明文档及多段音频视频素材方便对照工程结构与资源使用方式。目前已有728人学习下载适合需要快速上手IMX6ULL图形界面应用开发的人群。除代码外包内还提供保姆级适配教程与文本说明即使初学者也能按步骤完成运行与适配通过QTMenu、QVideo、QMusicPlayer等模块可以直观理解车载终端的界面布局、视频播放与音乐播放等典型功能的实现思路对智能座舱、物联网信息终端等方向的二次开发也很有参考价值。1. 别在 NFS 里调 Qt 界面用 rsync 构建 IMX6ULL 车载终端标题里 build-QTMenu-IMX6U-rsync-Debug.rar 的四个关键词才是一线嵌入式工作流的重点源码是 QTMenu 车载菜单构建目标是 IMX6ULL部署走 rsync运行态是 Debug。正点原子 IMX6ULL 板和 Qt 交叉编译组合非常成熟真正浪费时间的是“编译完不知道程序放哪、放在板子上又刷不出界面”。我建议放弃 NFS直接用 rsync 同步 Debug 产物把 qt_qpa_platform_plugin_path 这类运行时变量一次性配好。下文按我平时做法写先做交叉编译再做 rsync 同步最后解决正点原子屏幕中文乱码、触摸和开机自启适合刚接触 imx6ull 项目、又不想被 NFS 折腾的开发者。2. 交叉编译把 QTMenu 构建成可部署到 IMX6ULL 的 Debug 产物imx6ull 的 Qt 工程必须在主机构建时就想清楚“构建目录会让 rsync 同步哪些内容”。我习惯在任何涉及 QT 的板级项目里都以构建类型做目录名比如 build-debug 和 build-release而不是默认的长目录。因为 Qt 的 moc、rcc 会生成大量中间文件没有分目录会导致源码树被moc_*.cpp塞满rsync 时除非用--exclude否则很容易把 PC 上的生成文件同步到开发板启动时出现莫名其妙的行为。2.1 在 IMX6ULL 平台上固定“按构建类型分目录”的编译习惯IMX6ULL 使用 Cortex-A7 核交叉工具链一般带有 arm-linux-gnueabihf 前缀Qt 运行时也分为 arm 版和 x86 版。如果主机/usr/bin/qmake是桌面版 Qt 5那么生成的 Makefile 会调用 x86 编译器制作出来的可执行文件一放到正点原子板子上就会报 Illegal instruction。因此先确认工程用的是 arm 版 qmake而不是笼统地敲qmake。常见做法是给板级 Qt 建立一个独立前缀例如/opt/imx6ull/qt5/bin/qmake然后所有构建脚本都改成显式写全路径不用 PATH 里的默认 qmake。mkdir -p build-debug cd build-debug /opt/imx6ull/qt5/bin/qmake ../QTMenu.pro \ CONFIGdebug \ QMAKE_CCarm-linux-gnueabihf-gcc \ QMAKE_CXXarm-linux-gnueabihf-g \ QMAKE_LINKarm-linux-gnueabihf-g make -j4CONFIGdebug会让 Qt 构建系统定义QT_DEBUG并保留调试符号方便后续配合 gdbserver。如果写成CONFIGrelease你会得到QT_NO_DEBUG_OUTPUT定义程序里的 qDebug 会被编译器去掉这在调试阶段不是我们想要的。QMAKE_CC、QMAKE_CXX、QMAKE_LINK三项覆盖交叉编译器即使你的 qmake 在配置时没有指向默认编译器也能在命令行里临时覆盖不影响其他工程。make -j4使用 4 个并行任务如果你的主机内存只有 4G建议改成-j2否则交叉编译过程中 Qt 每个编译单元平均内存占用比较大很容易 OOM。2.2 用 qmake 生成本地 Debug 构建并确认关键 Qt 参数编译完成后产出的可执行文件在build-debug/QTMenu。这时先别急着传板子用file命令确认架构file build-debug/QTMenu输出里应当出现 ARM说明是 32 位 ARM 可执行文件。如果看到 x86-64应回去查 2.1 的 qmake 路径。这一步可以写进 CI 的检查脚本防止有人误用 PC 版 Qt。然后确认工程在 .pro 里是否链接了需要的 Qt 模块。车载界面往往用到 Qt Widgets、Qt Network、Qt SerialPort 等但在嵌入式菜单里默认只有 core 和 gui。常见的 .pro 块QT core gui widgets greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET QTMenu TEMPLATE app如果工程里用到串口需要加QT serialport用到网络加QT network。这些模块必须与交叉编译出的 Qt 库匹配否则链接时会出现 undefined reference。不要在主机的 Qt 库里看到模块存在就想当然嵌入式 Qt 通常只编译你需要的模块。调试版本里的.qmake.stash和.o文件对目标设备没有意义在下一步用 rsync 同步时要排除。2.3 换成 CMake 时如何把输出路径里的 debug 去掉“cmake 输出路径去掉 debug”是真实存在的一个坑。CMake 默认的多配置生成器会在输出目录中加Debug层但 Linux 上用 Unix Makefiles 时如果不设置CMAKE_RUNTIME_OUTPUT_DIRECTORY输出会直接进入${CMAKE_BINARY_DIR}看起来没有 debug 层。真正让人混淆的是用了set(CMAKE_BUILD_TYPE Debug)后如果你把可执行文件输出到${CMAKE_BINARY_DIR}/bin目录名里不会出现 Debug但如果你在复制时拼了生成器表达式$CONFIG就会看到/Debug/路径。我建议的方向是把平台类型和构建类型都放进外层目录名而不是让路径里出现 debugset(CMAKE_BUILD_TYPE Debug CACHE STRING Build type) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/app) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) add_executable(QTMenu main.cpp) target_link_libraries(QTMenu Qt5::Widgets)这样做的好处是rsync build-debug/app/ rootboard:/opt/car-terminal/时不需要再关心路径里是否混着 Debug 层目录结构简单。同时把输出目录固定后CMake 在编译时不会因为构建类型不同而改变输出位置只要保证一次只构建一种类型就能避免 Debug 和 Release 输出互相覆盖。这里CMAKE_RUNTIME_OUTPUT_DIRECTORY只影响可执行文件动态库如果分开用需要同名的LIBRARY_OUTPUT_DIRECTORY。若希望 Debug 和 Release 两个版本都保留再把这几组目录变量配置成build-debug/app和build-release/app。简单说路径里别带 debug 单词目录能不能区分构建类型主要靠外层的 build-debug 目录名。2.4 设置 qt_qpa_platform_plugin_path让 Qt 找到插件构建完成只算第一步。Qt 5 的程序启动时会根据 QApplication 的构造选择 QPA 插件比如 linuxfb、eglfs、xcb。嵌入式车载屏最常用linuxfb它直接写入/dev/fb0不依赖 X11。但插件文件在交叉编译 Qt 的plugins/platforms/目录里如果可执行文件在开发板上找不到这个路径启动就会报qt.qpa.plugin: Could not find the Qt platform plugin linuxfb in 解决办法是在启动前把环境变量指定到完整路径而不是依赖 Qt 自动推断。在板子上直接执行export QT_QPA_PLATFORMlinuxfb export QT_QPA_PLATFORM_PLUGIN_PATH/opt/car-terminal/plugins/platforms export LD_LIBRARY_PATH/opt/car-terminal/lib ./QTMenuQT_QPA_PLATFORM_PLUGIN_PATH告诉 Qt 去哪里加载libqlinuxfb.so如果你的程序是/opt/car-terminal/QTMenu插件目录就放在同级的plugins/platforms。另一个写法是在代码里设置#include QApplication int main(int argc, char *argv[]) { qputenv(QT_QPA_PLATFORM, linuxfb); qputenv(QT_QPA_PLATFORM_PLUGIN_PATH, /opt/car-terminal/plugins/platforms); QApplication app(argc, argv); // ... }注意qputenv必须在 QApplication 构造之前调用否则插件管理器已经被初始化再改路径不会生效。调试标题里的 Debug 构建时插件也必须和应用程序版本一致Qt 5 的 Debug 库和 Release 库在 linuxfb 插件里 ABI 不匹配最常见的问题是加载插件时报 undefined symbol 或版本不匹配这时要用QT_DEBUG_PLUGINS1看具体缺少哪个符号。3. rsync 部署把 Debug 程序同步到正点原子开发板并远程调试Debug 产物最终要跑到板上我很少用 NFS 挂载板子上的 rootfs。原因很实际正点原子 IMX6ULL 开发板的网络文件系统在长时间运行后如果网线质量一般很容易出现 stale file handleQt 界面卡死在打开文件的操作上。rsync 与 ssh 的组合不会在运行期占用板子的网络同步完成后程序就完全在本地运行。标题里的 rsync 是整条流水线的关键点。3.1 为什么在 imx6ull 调试阶段更适合用 rsync 而不是 NFSNFS 的问题是运行期依赖网络且根文件系统通过 NFS 挂载时ldconfig、fc-cache这些操作可能会因为缓存命中问题而非常慢。rsync 只在启动时同步一次后续程序运行完全依赖板载存储不占用网络带宽。同时rsync 支持只传输差异对 Debug 构建来说Qt 的 moc 文件虽然每次都会变但大部分.o和资源文件不会变第二次同步通常只要几百毫秒。另一个隐含优势是可回滚性。Debug 构建可能改一个信号就把界面写花rsync 同步前在板子上备份上一次正常版本或者用--backup --suffix.bak保留旧文件比 NFS 回滚方便。如果你的开发机在 Windows 上用 CygwinCygwin 自带安装 rsync 的 setup 包命令行为也和 Linux 下相同下面命令可以照抄。3.2 开发板一方需要具备的条件ssh、rsync 和端口rsync 通过 ssh 隧道同步时要在目标机上执行rsync --server进程所以板子上需要同时具备 sshd 和 rsync。正点原子的出厂 rootfs 一般带有 dropbear 或 openssh但有些裁剪镜像里没有 rsync需要先在板上确认which sshd || which dropbear which rsync如果只有 ssh 没有 rsync使用--rsync-path/usr/bin/rsync指定远端绝对路径或者把 rsync 的二进制从 Ubuntu 交叉编译后放进 rootfs。也可以放弃 rsync 服务端仅用 scp 同步不过scp -r不支持--delete无法清理本地已经删除的旧文件调试一段时间就会积攒一堆过期 .so。建议先在主机传密钥避免每次输密码ssh-copy-id root192.168.1.120执行一次后后续 rsync 就不用交互输入密码。如果板子的 ssh 端口不是 22在-e ssh -p 1022里指定。3.3 最小同步命令与参数解释在主机build-debug目录运行rsync -avz --delete \ --exclude *.o \ --exclude moc_*.cpp \ --exclude .qmake.stash \ -e ssh \ app/ \ root192.168.1.120:/opt/car-terminal/这里app/是 2.3 节的 CMake 输出目录如果你用 qmake替换成build-debug。参数含义如下参数含义注意-a归档同步保留权限、时间戳、符号链接不加它可执行权限会丢-z传输时压缩图片资源多时提升明显--delete目标端删除源端不存在的文件同步根路径时慎用-e ssh指定远程 shell端口非 22 时改为 -e ssh -p 端口--exclude排除匹配文件中间构建文件不需要上传--delete会让目标端出现源端不存在的文件时自动删除所以用的时候要格外小心确保只同步程序目录不要把整个根目录同步过去。同步完成后执行ssh root192.168.1.120 chmod x /opt/car-terminal/QTMenu; syncsync保证写入到 Flash防止开发板直接断电时文件系统损坏。一个很容易忽略的问题是Qt 可执行文件的运行权限必须在拷贝时携带如果源文件没加执行权限rsync 后板子上的文件也是普通权限运行时报 permission denied。所以交叉编译后先执行一次chmod x app/QTMenu。3.4 配合 gdbserver 做远程调试的同步思路Debug 构建的目标不只是看日志还得能打断点。在板子上先启动 gdbservergdbserver :3333 /opt/car-terminal/QTMenu主机上再执行arm-linux-gnueabihf-gdb app/QTMenu (gdb) target remote 192.168.1.120:3333 (gdb) continue注意主机上的app/QTMenu必须和板子上的二进制一致。rsync 同步后如果又改了构造函数或初始化逻辑一定要再跑一次 3.3 的同步命令否则 gdb 里看到的行号和汇编不匹配。另外Qt 的很多信号槽代码是 moc 生成的打断点时不要试图在connect那一行直接设断点要到实际槽函数里打否则你会发现 gdb 停在 moc 文件里而不是你写的 cpp 文件里。远程调试还有一个好用的技巧同步时把源码目录用 rsync 放到板子上的同一相对位置然后在 gdb 里执行directory /opt/car-terminal/src设置源码路径。这样list命令能看到 Qt 源码和你的工程源码对于追踪信号槽入参很有帮助。gdbserver 默认监听 TCP如果板子和主机之间有防火墙记得放行 3333 端口。4. 正点原子 IMX6ULL 屏幕中文显示与触摸适配正点原子 IMX6ULL 开发板本身没有显示编码问题问题几乎都出在终端环境和 Qt 的字体库上。网上的提问集中在“板子屏幕终端中文显示乱码MobaXterm 正常”这句话能直接定位到串口终端字符编码而 Qt 界面里的中文乱码则是字体缺失或国际化资源没加载。4.1 先解决“屏幕终端中文乱码MobaXterm 正常”的显示问题如果程序不在板子上跑而是板子的 shell 界面本身中文乱码但 MobaXterm 用 ssh 登录后正常说明板子屏幕的串口终端没有做到 UTF-8。屏幕终端一般走的是板载 framebuffer console它的字符集由终端驱动和终端模拟器决定。先检查 localelocale如果LANG是空的或者C就执行export LANGzh_CN.UTF-8 export LC_ALLzh_CN.UTF-8为了重启后保留写到/etc/profile。如果出现 locale 相关报错说明系统里没有生成中文本地化数据执行localedef -i zh_CN -f UTF-8 zh_CN.UTF-8localedef在 busybox rootfs 里可能不提供更稳妥的做法是让 Qt 程序在运行环境里把字体路径指定到一个中文字体文件上。屏幕终端显示的是 Linux 控制台字库和 Qt 应用不是同一个体系所以这里只解决 shell 输出Qt 界面里的中文要用下一节处理。MobaXterm 正常的原因在于它使用的是 UTF-8 编码的终端而板子自带的屏幕终端默认不是 UTF-8。如果你在/etc/profile改完还是乱码检查/etc/default/locale是否覆盖了前面的环境变量。4.2 Qt 程序内的中文字体与国际化配置Qt 程序如果不指定字体会在系统字体目录里搜索默认字体正点原子原始 rootfs 可能只有少量点阵字体没有中文字形。一个通用做法是把开源中文字体放入/usr/share/fonts/truetype/然后执行fc-cache -fv。但嵌入式板子未必有 fontconfig所以我倾向于直接把字体文件放到程序目录在代码里加载#include QApplication #include QFontDatabase #include QFont int main(int argc, char *argv[]) { QApplication app(argc, argv); int id QFontDatabase::addApplicationFont(/opt/car-terminal/fonts/wqy-microhei.ttc); QString family QFontDatabase::applicationFontFamilies(id).at(0); app.setFont(QFont(family, 18)); // ... return app.exec(); }QFontDatabase::addApplicationFont可以在不依赖 fontconfig 的情况下注册系统没有的字体。wqy-microhei.ttc是常见的文泉驿微米黑也可以用正点原子资料包里自带的 ttc 字体只要字体文件可分发即可。需要提醒的是Qt 5 中QFont的像素大小和字号设置在不同屏幕密度下观感差异大车载屏一般用setPixelSize(32)而不是setPointSize(18)。国际化方面Qt 的tr()只是把源码字符串映射到QTranslator加载的翻译文件。建立.ts后再用lrelease转成.qm避免运行时做字符串替换lupdate QTMenu.pro -ts qtmenu_zh_CN.ts linguist qtmenu_zh_CN.ts lrelease qtmenu_zh_CN.ts -qm qtmenu_zh_CN.qm加载时QTranslator translator; if (translator.load(:/i18n/qtmenu_zh_CN.qm)) app.installTranslator(translator);注意lupdate会分析源文件里的tr()所以所有可见中文都应该写成tr(车辆状态)而不是直接setText(车辆状态)后者不会被 lupdate 扫描到。把.qm放进 Qt 资源文件然后rcc编译到二进制里避免同步时还必须带着额外的翻译文件。4.3 触摸屏设备节点与 TSLIB 环境变量正点原子 IMX6ULL 的电阻屏和电容屏在 Qt 下都支持但设备节点不是固定的/dev/input/event0。先查设备cat /proc/bus/input/devices找到含有触摸屏控制器名字的设备行比如H: Handlersevent2中的 event2。然后把环境变量一次性设好export TSLIB_CONFFILE/etc/ts.conf export TSLIB_TSDEVICE/dev/input/event2 export TSLIB_CALIBFILE/etc/pointercal export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event2如果 Qt 交叉编译时没有启用 tslib设置了TSLIB_*并不会生效因为 Qt 的 linuxfb 插件会直接读/dev/input/这时重点看QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS。如果触摸不工作先用hexdump /dev/input/event2试触屏幕上能看到数据说明内核没问题问题在 Qt 的 QPA 插件没选对。切换插件型号最直接的检查变量是QT_QPA_GENERIC_PLUGINSevdevtouch它会在 linuxfb 底层挂载额外的触摸驱动。常见错误是同时设置了TSLIB_TSDEVICE和QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS指向同一个节点导致 libinput 和 evdev 同时打开设备冲突表现为点击一次却触发两次。我的做法是二选一用 tslib 时只保留TSLIB_*用 evdev 时只保留QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS。4.4 让 QTMenu 开机自动启动的启动脚本在正点原子开发板的新版文件系统上大多支持 systemd。如果要保证开机自动进入 Qt 界面可以写一个 service[Unit] DescriptionQTMenu Car Terminal Aftersystemd-user-sessions.service [Service] Typesimple EnvironmentQT_QPA_PLATFORMlinuxfb EnvironmentQT_QPA_PLATFORM_PLUGIN_PATH/opt/car-terminal/plugins/platforms EnvironmentLD_LIBRARY_PATH/opt/car-terminal/lib EnvironmentLANGzh_CN.UTF-8 ExecStart/opt/car-terminal/QTMenu -platform linuxfb Restartalways RestartSec3 [Install] WantedBymulti-user.target保存为/etc/systemd/system/qtmenu.service然后systemctl daemon-reload systemctl enable qtmenu systemctl start qtmenu这里ExecStart里的-platform linuxfb和QT_QPA_PLATFORM作用相同保留一个即可写两个只是为了展示等价性。Restartalways可以保证程序被误杀后自动拉起适合车载前装场景但如果你还在用 gdbserver 调试这个策略会干扰断点调试阶段建议改成Restarton-failure。如果板子用的不是 systemd则在/etc/rc.local里加一行/opt/car-terminal/start.sh start.sh里设置环境变量并执行 QTMenu。两种方式都需要在启动脚本里设置上一节的触摸变量否则开机后 Qt 能看到 framebuffer 但触摸无响应。5. 把 Debug 构建改成可离线验证的 Release 部署包5.1 用 CMAKE_BUILD_TYPE 切掉 debug 输出Debug 同步调试接近尾声时需要发布一个接近真实性能的版本。CMake 项目中切换构建类型即可cmake -DCMAKE_BUILD_TYPERelease ../src这会让编译器使用-O2或-O3并定义QT_NO_DEBUG_OUTPUTqDebug 调用在编译时被剥离。Release 构建出来仍叫QTMenu需要特别注意输出目录不能和 Debug 混在一起。更推荐外层直接建build-release/app不要用同一个源码树的 build 目录来做两次构建。原因很简单CMake 的缓存里记录了一次构建的 Qt 路径和编译选项第二次运行时不会自动清掉旧缓存。5.2 把 Qt 运行库和插件打进同一目录PC 端 Qt 打包常听到 windeployqt到 IMX6ULL 上没有对应的“官方部署器”手动收集依赖更直接。目标板子上如果/usr/lib里的 Qt 库版本不完整运行时会报cannot open shared object file。一个可复制的命令mkdir -p lib ldd app/QTMenu | grep | grep -v linux-vdso \ | awk {print $3} | xargs -I{} cp -Ln {} lib/ cp -d /opt/imx6ull/qt5/plugins/platforms/libqlinuxfb.so lib/ 2/dev/null注意ldd必须在交叉工具链环境下执行不能是主机的 x86 版本否则列出的依赖是 PC 端 Qt 的路径。复制时用cp -L和-n保留真实内容且不覆盖已存在的同名库。插件目录plugins/platforms也要复制到/opt/car-terminal/plugins/platforms这与之前配置的QT_QPA_PLATFORM_PLUGIN_PATH对应。如果程序运行时追加字体文件还要复制fonts/目录。5.3 在板子上验证依赖和插件加载部署完成后先做三件事export QT_DEBUG_PLUGINS1 export LD_LIBRARY_PATH/opt/car-terminal/lib ./QTMenuQT_DEBUG_PLUGINS1会输出插件加载的过程例如looking at /opt/car-terminal/plugins/platforms如果看到Cannot load library用ldd检查插件缺失的依赖。验证通过后把QT_DEBUG_PLUGINS去掉再启动看界面刷新是否正常。验证触摸最直接的方式是运行 evtest 或者直接点击 Qt 按钮。如果触摸有偏移在/etc/pointercal放校准数据。最后检查一遍环境变量env | grep QT_QPA env | grep TSLIB此时一个可离线验证的嵌入式 Qt 部署包就完成了剩下的就是把/opt/car-terminal整个目录打进你的正点原子根文件系统以后每次改代码只需重跑 2.2、3.3 和 5.1 三条命令。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。