☰
RK3506嵌入式Qt HMI开发实战:从交叉编译到systemd自启动
2026/9/27 1:23:31 网站建设 项目流程

最近接了一个工业设备控制面板的项目,要在瑞芯微 RK3506 工业开发板上跑一套 Qt GUI 应用。从交叉编译工具链的搭建,到 Qt 界面在 /dev/fb0 上亮起来,再到最后用 systemd 做开机自启动,整套流程走下来踩了不少坑,也沉淀出一套能直接复用的路径。这篇就把选型逻辑、环境搭建、应用层几个典型问题以及自启动配置完整记录下来,适合正打算在 ARM 嵌入式板上做 Qt 界面、或者刚拿到 RK3506 开发板想快速跑通 demo 的朋友参考。

1. 为什么是 RK3506 这块板子:工业 HMI 选型与硬件资源盘点

1.1 RK3506 在瑞芯微产品线里的定位

瑞芯微现在被大家熟悉的是 RK3568、RK3588 这类偏应用处理器的芯片,RK3506 在普通玩家圈子里存在感没那么高,但放在工业场景里其实非常合适。它属于嵌入式 SoC,面向的是工业 HMI、数据采集终端、设备控制器这类对实时性和稳定性要求更高的场景。相比 RK3568 这种四核 A55 平台,RK3506 在性能和功耗之间取了另一个平衡点:跑界面不需要多强的算力,但工业接口必须齐全、启动必须快、功耗必须低、长期供货必须有保障。

当时在 RK3506 和 RK3568 之间犹豫了一下。后来想清楚一个问题:这个项目只是做一块 7 寸屏的控制面板,界面上主要是设备状态、参数配置、历史曲线,没有视频编解码、没有深度学习推理,RK3568 的性能有一大半是浪费的。RK3506 的优势反而更契合:成本更低、PCB 设计更简单、整板功耗小,不需要主动散热片。如果你的项目也是这种“轻量级 GUI + 工业控制”的组合,RK3506 这个定位是对的。

1.2 拿到开发板后建议先做的三件事

不要一上来就折腾 Qt,先把板子的底子摸清楚。我建议按下面三步走:

  1. 确认固件和内核版本:用官方 SDK 构建的 Linux 镜像烧进去,确认内核版本、rootfs 类型(Buildroot 还是 Debian),这决定了后面 Qt 是放板子上本地编译还是走交叉编译。
  2. 确认显示接口和触摸屏方案:RK3506 开发板一般带 MIPI DSI、RGB 或者 LVDS 接口。项目用的屏是 RGB 并口加电容触摸,这点必须在硬件阶段确认,因为 Qt 的 linuxfb 或者 eglfs 后端都直接依赖 /dev/fb0。
  3. 打通两条调试通道:一条是串口 console,用于内核和系统启动阶段的日志;另一条是网络,方便后面用 NFS 挂载 rootfs 或者直接通过 SSH 往板子里拷文件。没有这两条通道,后面排错会非常痛苦。

1.3 显示链路的基本概念:framebuffer 是什么

嵌入式 Qt 开发绕不开 framebuffer 这个概念。可以把 /dev/fb0 理解成一块“直接映射到屏幕的内存画布”,应用程序往这块内存里写像素数据,显示控制器就把它刷到 LCD 上。Qt 的 linuxfb 插件做的事情很简单:打开 /dev/fb0,通过 mmap 把显存映射到用户空间,然后所有绘图操作都是对这一块内存的操作。

这块画布的分辨率和像素格式,由内核里的 display 驱动和 bootloader 里的 lcd 参数决定。如果启动之后 /dev/fb0 不存在,或者 fbset 看到的分辨率不对,后面 Qt 界面显示必然是花的、偏移的,甚至完全黑屏。所以我在搭 Qt 环境之前,先确保 bcat /dev/urandom > /dev/fb0 能直接把屏幕刷成雪花,这一步能确认显示链路是通的。

2. 交叉编译环境搭建:从 SDK 到第一行 Qt 代码上屏

2.1 交叉编译工具链的选择:用 SDK 自带的,别自己折腾

在 RK3506 开发板上本地编译 Qt 不是不行,但效率太低,而且嵌入式板子的存储资源有限,源码包解压一遍就要占掉不少空间。正确的做法是在 x86 的 Ubuntu 主机上做交叉编译,产物拷到板子上运行。

交叉编译工具链,优先用 SDK 里预编译好的那套,通常是arm-buildroot-linux-gnueabihf-或者类似前缀。不要自己去 Linaro 官网随意下,版本不匹配会在链接阶段出现各种奇怪报错。工具链不需要“安装”,解压之后把 bin 目录加进 PATH 就行。验证方式很简单:写一个 hello.c,用交叉 GCC 编译,然后 file 一下确认生成的是 ARM 架构的 ELF。

export PATH=/opt/arm-buildroot-linux-gnueabihf/bin:$PATH export CC=arm-buildroot-linux-gnueabihf-gcc export CXX=arm-buildroot-linux-gnueabihf-g++

这里要提醒一个细节:编译 Qt 和 tslib 时,CC、CXX这两个环境变量的值会不一样。因为configure脚本会显式传-xplatform,所以不一定依赖环境变量,但为了保险,先 export 出来能减少很多问题。

2.2 Qt 版本选择:为什么锁定了 Qt 5.15.2

Qt 现在都出到 Qt 6 了,但嵌入式领域大家依然对 Qt 5.15 情有独钟,因为它有 LTS 维护周期,而且生态里大量的第三方模块还是针对 Qt 5 的。我当时下载的是 Qt 5.15.2 的源码包,qt-everywhere-opensource-src-5.15.2.tar.xz,体积不算小,解压后源码目录 2-3 个 GB,编译前确认主机磁盘足够。

很多人会问:为什么不用 Qt 6?嵌入式场景下 Qt 6 的图形后端对 OpenGL ES / Vulkan 的依赖更重,而 RK3506 这类入门级工业 SoC 的 GPU 能力相对有限,在 linuxfb 这种 2D 场景下 Qt 5 反而更轻快。而且 Qt 5.15.2 的交叉编译资料非常多,踩坑时能搜到的参考也多。

configure 的时候,我的常用配置参考如下:

./configure \ -prefix /opt/qt_arm \ -xplatform linux-arm-gnueabihf-g++ \ -release \ -no-opengl \ -no-xcb \ -linuxfb \ -tslib \ -no-sql-mysql \ -no-sql-sqlite \ -no-gtk \ -no-cups \ -nomake examples \ -nomake tests

几个关键参数说明:

  • -xplatform linux-arm-gnueabihf-g++:指定平台配置,qmake 会根据这个名字找mkspecs目录下对应的配置文件。
  • -no-opengl:关掉 OpenGL,因为 linuxfb 根本用不上;如果后面想用 eglfs 做硬件加速再单独开。
  • -linuxfb:启用 linuxfb 平台插件,这是最核心的显示后端。
  • -tslib:启用 tslib 支持,后面触摸屏校准要靠它。
  • -no-xcb:去掉 X11 支持,嵌入式板上没有 X server。

2.3 tslib 的交叉编译:触摸屏校准的前置条件

触摸屏有两种常见方案:电阻屏和电容屏。电阻屏需要校准,电容屏一般出厂就带了坐标映射,但为了保险起见,工业项目里通常还是会接一层 tslib。tslib 的作用是把触摸驱动上报的裸坐标转换成屏幕像素坐标,并做滤波和校准。

编译 tslib 也比较标准:

./autogen.sh ./configure --host=arm-buildroot-linux-gnueabihf --prefix=/opt/tslib --enable-input make && make install

编译完之后,把/opt/tslib整个目录拷贝到开发板,然后在板子上设置环境变量:

export TSLIB_ROOT=/opt/tslib export TSLIB_CONSOLEDEVICE=none export TSLIB_FBDEVICE=/dev/fb0 export TSLIB_TSDEVICE=/dev/input/event1 export TSLIB_PLUGINDIR=$TSLIB_ROOT/lib/ts export TSLIB_CALIBFILE=/etc/pointercal

注意TSLIB_TSDEVICE如果不对,ts_calibrate会提示打不开设备。开发板上有多个输入设备时可以先cat /proc/bus/input/devices确认触摸屏对应哪个 event 节点。

2.4 用 NFS 挂载调试:改代码后不用反复烧写 rootfs

开发调试阶段,我不建议每次把编译好的 Qt 库和应用都拷到板载 flash 里。先在 Ubuntu 主机上搭一个 NFS 共享目录,把 Qt 的 lib、plugins 和应用放里面,板子启动后通过 NFS 挂载来运行。这样在主机上交叉编译出新版本,板子重启一下就生效,迭代速度会快很多。

板子端挂载命令大致是:

mkdir -p /mnt/qt mount -t nfs 192.168.1.100:/opt/qt_arm /mnt/qt

挂载成功之后,设置 Qt 运行环境变量:

export QTDIR=/mnt/qt export LD_LIBRARY_PATH=$QTDIR/lib:$QTDIR/plugins/platforms export QT_QPA_PLATFORM=linuxfb export QT_QPA_PLATFORM_PLUGIN_PATH=$QTDIR/plugins/platforms export QT_QPA_FB_TSLIB=1

然后运行一个最简单的 Qt Widgets 程序。如果屏幕上能出现窗口,说明从编译到显示这条链路已经通了,接下来就可以安心搞应用层。

3. 应用层三大硬骨头:型号校准、中文字体、串口模块

3.1 触摸坐标异常:从“越点越偏”到校准文件的作用

Qt 界面跑起来之后,第一个让人头疼的问题就是触摸不准。我当时碰到的情况是:点屏幕左上角,鼠标箭头在右下角,点击事件落点完全对不上。这个问题在电气上一般两种可能:屏幕没有校准,或者内核触摸驱动坐标轴方向和屏不一致。

先做校准。在板子上运行 tslib 自带的工具:

ts_calibrate

它会依次显示五个十字光标,依次点完后会在/etc/pointercal里生成一组校准参数。如果你的 Qt 启动脚本里没有ts_calibrate这一步,可以直接拷贝别人板子上做好的 pointercal 文件,注意别拷错了平台,因为不同触摸屏的校准参数不通用。

但校准完了还有一个坑:Qt 的 linuxfb 插件默认不认 tslib。需要在环境变量里显式告诉 Qt 使用 tslib 作为输入设备:

export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1

这里有一个很容易踩的细节:QT_QPA_FB_TSLIB=1是 Qt 5 之前的老用法,Qt 5.15 里更推荐通过QT_QPA_GENERIC_PLUGINS指定。如果你发现触摸没反应,检查一下环境变量里是否同时设置了这两个,有时候老变量会把新逻辑搅乱。

如果校准后依然不对,那就不是用户态的问题了。可以用evtest /dev/input/event1看看裸触摸事件上报的原始坐标范围。比如屏幕分辨率是 1024x600,但上报的 X 范围是 0~32767,那说明驱动没有做 coordinate scaling,这种情况靠 pointercal 救不回来,得修改内核设备树里的touchscreen-inverted-x/y或者调整 X/Y 坐标翻转标志。

3.2 中文显示成方块:字体文件与 fontconfig 的关系

界面上的英文和数字显示正常,中文全部变成一个个“口口口”,这是嵌入式 Qt 里出现频率最高的问题,本质就是系统里没有中文字体文件。

解决思路有三步,缺一不可。

第一,准备字体文件。我在主机上下载了开源的文泉驿微米黑或者思源黑体的 TTF 文件,拷贝到板子的/usr/share/fonts/truetype/目录下。文件名不要用中文,用wqy-microhei.ttf这种纯英文命名,避免一些底层代码对非 ASCII 路径处理有问题。

第二,确认 Qt 的字体引擎支持 TTF。这取决于 Qt 编译时有没有带 FreeType 支持。如果 configure 的时候指定了-no-freetype,那不管你怎么拷字体文件都没用。用下面命令验证一下板子上的 Qt 库是否链接了 freetype:

ldd libQt5Gui.so | grep freetype

没有输出的话,只能回主机重新配置编译 Qt。

第三,在应用里指定字体。可以在 main.cpp 里设置全局字体:

QFont font("wqy-microhei"); font.setPixelSize(18); QApplication::setFont(font);

也可以把字体文件放在应用目录,运行时动态加载,这种方式对系统目录不可写的情况更友好:

QFontDatabase::addApplicationFont("/usr/share/fonts/truetype/wqy-microhei.ttf");

我个人的习惯是优先用QFontDatabase::addApplicationFont,因为它在应用层就能解决,不用依赖系统 fontconfig 的配置,调试的时候隔离性更好。

3.3 Qt SerialPort 模块报错:unknown module(s) in qt: serialport

这个坑在热搜词里反复出现,说明不是只有我一个人碰见。在 pro 文件里写了QT += serialport,编译时却在 qmake 阶段就报:

Project ERROR: unknown module(s) in QT: serialport

原因是:Qt 的 SerialPort 模块不是默认编译的。在 Qt 5.15.2 里,SerialPort 属于独立的qtserialport模块,如果下载的是 Qt base 源码包(qtbase),里面根本不会包含这个模块。要单独拿到qtserialport-everywhere-src-5.15.2.tar.xz,解压后交叉编译安装:

cd qtserialport-everywhere-src-5.15.2 /path/to/qt_arm/bin/qmake make -j4 make install

编译完成后,确认/opt/qt_arm/lib下生成了libQt5SerialPort.so,并且mkspecs/modules目录下出现了对应的 pri 文件。然后再回到应用工程里,qmake 能正常识别。

写代码的时候还要注意一个点:SerialPort 的打开和读写千万不要放在 UI 线程里同步阻塞。嵌入式板上串口波特率 115200,一次读几十字节问题不大,但如果数据量大或者对端设备响应慢,UI 线程会被阻塞,界面卡死。我的做法是把串口对象放进一个 QThread,或者用 Qt 的moveToThread把 QSerialPort 对象迁移到工作线程:

class SerialWorker : public QObject { Q_OBJECT public: explicit SerialWorker(QSerialPort *port) : m_port(port) { connect(m_port, &QSerialPort::readyRead, this, &SerialWorker::onReadyRead); } void onReadyRead() { QByteArray data = m_port->readAll(); emit dataReceived(data); } signals: void dataReceived(const QByteArray &data); };

3.4 一个辅助思路:用 Qt 模拟鼠标点击事件排查触摸问题

如果你碰到触摸事件不响应,但不确定是硬件问题还是应用层事件处理问题,可以在应用里用 QTest 模拟鼠标点击事件来定位。在 main.cpp 里写一小段测试代码:

QTimer::singleShot(3000, []() { QTest::mouseClick(QApplication::activeWindow(), Qt::LeftButton); });

如果模拟点击能触发按钮响应,说明 Qt 事件系统没问题,问题在触摸设备驱动或 tslib;如果模拟点击也没反应,说明应用自身的信号槽连接或者事件过滤器有问题。这个方法能快速把问题范围缩小一半,非常实用。

4. GUI 框架选型与运行性能观测:QWidget 还是 QML

4.1 工业 HMI 界面的选择逻辑

RK3506 的 CPU 性能在 ARM 嵌入式里不算强,所以 GUI 框架选型直接决定了最终体验。QWidget 和 QML 选了哪一个,后面整个项目路径都不一样。

QWidget 是传统的 C++ 图形组件,编译型、贴近系统底层,内存占用相对可控,在低性能设备上启动速度更快。QML 是声明式语言,做动画、过渡、滑动效果天然流畅,但运行时多一层 QML 引擎,资源占用会高一些。如果你做的界面主要是“参数表格 + 状态指示灯 + 曲线绘制”这种偏工控的布局,QWidget 完全够用,而且开发调试简单。如果你需要做一个带动态转场、手势滑动、皮肤换色效果的精美界面,QML 体验会好很多。

我个人在这个 RK3506 项目里选了 QWidget,原因很直接:产品需求里没有酷炫动效,操作人员每天面对的是固定的几个页面,稳定性比灵活性更重要。

4.2 资源占用与插件裁剪:减少 40% 体积的打包策略

Qt 应用部署到板子上,第一件事是瘦身。默认安装的 Qt 库体积很大,全部塞进 rootfs 会影响烧录和启动速度。我当时用两个手段压缩体积。

第一个手段是只拷贝需要的库和插件,依赖库通过ldd命令追踪。比如项目里用到了 Widgets、SerialPort、Network,那就只拷贝对应的libQt5Widgets.so、libQt5SerialPort.so、libQt5Network.so,以及它们依赖的基础库libQt5Core.so、libQt5Gui.so。插件目录里只保留platforms/libqlinuxfb.so和imageformats/libqjpeg.so、libqpng.so,不要整个 plugins 目录直接拷过去。

第二个手段是用 strip 去掉符号表。嵌入式系统的 rootfs 不需要调试符号,strip后每个 so 能缩减 20%~30% 体积:

arm-buildroot-linux-gnueabihf-strip libQt5Core.so libQt5Widgets.so

一个 Qt Widgets 应用,依赖裁剪 + strip 之后,从原来的几百 MB 打包目录缩减到 60~80 MB 左右,对 eMMC 和启动时间都友好得多。

4.3 UI 线程刷新与数据采集线程的协作模式

在工控设备里,串口或者 TCP 会不停上报数据,UI 需要实时更新状态、曲线和日志。如果直接在底层线程里改控件属性,轻则界面卡顿,重则直接崩溃。Qt 应对这个问题的正统做法是信号槽,利用队列连接(QueuedConnection)在线程间传递数据。

我在代码里把数据采集和 UI 刷新彻底解耦:

  • SerialWorker 负责读串口,读取完成后通过信号把原始数据发出去;
  • MainWindow 里连接这个信号,在槽函数里解析数据、更新表格和曲线;
  • 跨线程的信号槽会自动转换为队列连接,槽函数在 UI 线程执行,不会抢占 UI 线程的执行权。

刷新频率也要注意。有些数据一秒来 100 帧,如果每一帧都触发一次控件重绘,CPU 会扛不住。实际项目里我会用定时器做限流:每 100ms 触发一次 UI 刷新,把最近一次的数据批量更新上去,既能保证显示不滞后,又大幅降低重绘次数。

5. 上电自启动:systemd 服务、开机黑屏的排查

5.1 用 systemd 管理自启动应用:一个标准的 service 文件

如果 rootfs 用的是 Buildroot 加 systemd,那开机自启 Qt 应用最标准的方式是写一个 systemd service 文件。

先创建一个/etc/systemd/system/qt-hmi.service:

[Unit] Description=Qt HMI Application After=systemd-user-sessions.service After=local-fs.target [Service] Type=simple WorkingDirectory=/home/app/hmi Environment=QT_QPA_PLATFORM=linuxfb Environment=QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1 Environment=LD_LIBRARY_PATH=/usr/local/qt/lib:/usr/local/qt/plugins/platforms Environment=QTDIR=/usr/local/qt ExecStart=/home/app/hmi/hmi_app Restart=on-failure RestartSec=3 [Install] WantedBy=multi-user.target

有几个细节值得注意:

  • restart=on-failure是工控项目里的刚需。应用在运行中如果因为野指针、内存碎片崩溃了,systemd 会自动拉起,避免设备面板变成一块黑屏。
  • 环境变量不要靠应用内部 setenv,全部收敛在 service 文件里,这样调试阶段可以通过systemctl show qt-hmi看到 ApplicationEnvironment 里实际生效的参数,方便排查。
  • Type=simple适合长时间运行的 GUI 应用;如果你的启动脚本里还有复杂的初始化顺序,可以考虑Type=forking,但多数情况 simple 就够了。

写完之后执行:

systemctl daemon-reload systemctl enable qt-hmi systemctl start qt-hmi systemctl status qt-hmi

5.2 没有 systemd 的老 rootfs:init.d 脚本的写法

有些 RK3506 的官方出厂系统是基于 Buildroot 但没启用 systemd,用的是 BusyBox init。这种情况下自启动逻辑放在/etc/init.d/S99qt-hmi,S99 表示在 init 脚本排序里最后一个执行。

脚本内容大致是:

#!/bin/sh case "$1" in start) echo "Start Qt HMI Application" export QT_QPA_PLATFORM=linuxfb export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1 export LD_LIBRARY_PATH=/usr/local/qt/lib /home/app/hmi/hmi_app & ;; stop) killall hmi_app ;; esac exit 0

这里使用&放到后台执行,因为 init 脚本不能阻塞住后面的系统初始化。同样要加上 chmod +x,保证脚本可执行。

5.3 开机黑屏问题:从服务启动顺序到设备节点就绪

自启动最容易翻车的场景就是“手动运行没问题,开机自启就黑屏”。我在 RK3506 上遇到过一次,当时的表现是:systemd 显示服务已经 active,但屏幕始终是黑的,SSH 进去手动执行二进制又可以正常显示。

排查思路是这样的:

第一步,确认应用进程是否真的起来。ps | grep hmi_app,如果进程在,说明启动脚本本身没有错。

第二步,看 Qt 日志。linuxfb 插件启动失败会在 stderr 打印类似 “Could not open /dev/fb0” 或者 “Failed to open input device” 的信息。通过journalctl -u qt-hmi能看到。

当时的问题就是/dev/fb0在应用启动时还没创建。原因是 systemd 的多用户目标里,显示驱动的加载和应用服务的启动顺序没有强制约束,应用启动太早,帧缓冲设备还没注册。

解决办法有两种。第一种粗暴而有效:在 service 里加一个小的 sleep 等待:

ExecStartPre=/bin/sleep 2

第二种更正规:写成一条脚本判断设备节点存在后再执行,例如:

ExecStartPre=/bin/sh -c 'until [ -e /dev/fb0 ]; do sleep 0.5; done'

生产环境我一般用第二种,因为它不依赖固定延迟,系统启动快慢都能适配。

还有一个被忽略的坑是 tty 抢占。如果启动过程中有其他服务往 tty1 打印大量启动日志,Qt 的 framebuffer 应用在 tty1 上显示可能被覆盖,屏幕看起来像花屏。这时候可以把 qt 应用的虚拟终端切到 tty2 或 tty3:内核启动参数里加fbcon=map:2,Qt 应用跑在 tty2 上,启动日志就不会和界面混在一起。

5.4 应用崩溃后的兜底:日志记录与远程查看

自启动的应用最好把 stdout 和 stderr 重定向到日志文件,这样即使 systemd 的场景下 journalctl 没抓到信息,也能保留一份现场日志。service 里可以加:

StandardOutput=file:/home/app/logs/hmi.log StandardError=file:/home/app/logs/hmi_err.log

同时建议在应用内部做一个简单的心跳机制,每隔一段时间往 /tmp/hmi_alive 写时间戳。现场排查时,只要看这个文件最近修改时间就能判断应用是否还活着,比登进去看进程状态更快更直观。

6. 现场调试与部署:连接方式、日志与固件固化

6.1 开发调试阶段:NFS 挂载 rootfs 的完整流程

如果只是编译一个 Qt 程序,通过 SSH/scp 拷到板子就能跑。但如果你还在频繁修改 Qt 库、或者需要调整系统级的库依赖,反复拷贝太浪费时间。我习惯在开发阶段用 NFS 挂载整个 rootfs,这样板子上的根文件系统直接用主机上的一个目录,主机改代码、板子重启即生效。

开发机 Ubuntu 上配置/etc/exports:

/home/workspace/rk3506_rootfs 192.168.1.0/24(rw,sync,no_root_squash,no_subtree_check)

板子 u-boot 启动参数里指定 root=/dev/nfs,或者先启动到一个小的 initramfs,再手动挂载 NFS 后 pivot_root。各家的 SDK 脚本不一样,这里给的思路是通用的:只要能通过 NFS 看到主机上的 rootfs,后面改库、改应用都变得非常高效。

调试稳定之后,再切回 eMMC 固化,避免现场设备网络不稳定导致系统启动失败。

6.2 现场排查的优先级:console 日志永远是第一现场

嵌入式设备部署到现场之后,出问题没法像在实验室那样随时接显示器。这时候第一现场永远是串口 console。我建议开发板上留一个 TTL 串口,波特率 1500000 或 115200,通过 USB 转串口连接电脑,始终开着日志输出。

排查优先级我通常这样定:

  1. 串口 console 有没有报错:内核 panic、驱动加载失败、OOM 都会在串口体现。
  2. systemd 服务状态:确认 qt-hmi 服务是否 active、退出码是什么。
  3. 查看应用日志文件:如果应用有完整日志输出,可以把崩溃前的最后几行和串口日志交叉比对。
  4. 设备节点检查:确认 /dev/fb0、/dev/input/eventX、/dev/ttyS3 等是否存在。

在这个项目里,有一次现场反应“设备启动后偶尔屏幕没反应”,我通过串口日志发现是应用在轮询串口时读到了超时数据,然后陷入一个死循环,CPU 满载,UI 无法刷新。这类问题如果不看日志,光看“屏幕没反应”这个表象,根本猜不到根因。

6.3 固件固化:从 NFS 切回 eMMC 的正确姿势

开发收敛之后,把 NFS rootfs 打包成可烧录的镜像。打包根文件系统一般用 tar 打包,在主机上处理后生成 ext4 镜像,再用 SDK 的烧录工具烧到板子 eMMC 里。

dd if=/dev/zero of=rootfs.ext4 bs=1M count=1024 mkfs.ext4 rootfs.ext4 mkdir -p /mnt/rootfs mount rootfs.ext4 /mnt/rootfs cp -a /home/workspace/rk3506_rootfs/* /mnt/rootfs/ umount /mnt/rootfs

烧录之前记得把启动参数里的root=/dev/nfs改回root=/dev/mmcblk0pX,不然板子还是会试图从网络找 rootfs。这个问题我踩过一次,烧完新的 rootfs 一拔网线就起不来。

固化完成之后,还要验证一个关键场景:断电重启 10 次、20 次,确认每次都能正常进入到 Qt 界面。嵌入式系统里偶发性的启动失败,往往在 10 次以内就能暴露出来。

6.4 现场部署的一个小经验:留一个 reset 入口

工控面板在现场如果陷入死机状态,操作人员能做的只有断电重启。但有些场景不太方便断电,这时候最好在界面上留一个隐藏的“退出到命令行”入口,或者在系统层做一个看门狗。硬件看门狗如果 kernel 里有,可以直接用/dev/watchdog;软件层面则在 systemd 里配置:

Restart=on-failure RestartSec=3

如果应用在运行中崩溃,systemd 3 秒后自动重新拉起,绝大多数场景下面板都能恢复到正常显示。我在实际部署中发现,配合这个机制,设备在无人值守环境下跑两三个月没有出现需要人工干预的情况。

至于说应用卡死但进程还活着的情况,那就得上硬件看门狗了,让内核定时喂狗,一旦应用假死导致喂狗中断,系统自动重启,这也是工业现场稳定运行的最后一道保险。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询