Qt嵌入式从零基础到精通(01):Qt嵌入式开发全景图:从桌面Qt到ARM Linux

Qt嵌入式从零基础到精通(01):Qt嵌入式开发全景图:从桌面Qt到ARM Linux

系列定位:Qt 5.15.2 + C++ + qmake + Embedded Linux,兼顾 QWidget/QML,面向希望从桌面 Qt 进入 ARM Linux 嵌入式开发的工程师。

文章目录

  • [Qt嵌入式从零基础到精通(01):Qt嵌入式开发全景图:从桌面Qt到ARM Linux](#Qt嵌入式从零基础到精通(01):Qt嵌入式开发全景图:从桌面Qt到ARM Linux)
    • 本章学习目标
    • 前言
    • [1. Qt 嵌入式到底在解决什么问题](#1. Qt 嵌入式到底在解决什么问题)
    • [2. 桌面 Qt 与嵌入式 Qt 的真正差异](#2. 桌面 Qt 与嵌入式 Qt 的真正差异)
    • [3. 一条完整的数据与运行链路](#3. 一条完整的数据与运行链路)
    • [4. QWidget 与 QML 的定位](#4. QWidget 与 QML 的定位)
    • [5. 这套系列会学到什么](#5. 这套系列会学到什么)
    • [本章书稿级扩展:建立完整的 Qt 嵌入式工程认知](#本章书稿级扩展:建立完整的 Qt 嵌入式工程认知)
      • [1. 从"能运行"到"能交付"是两回事](#1. 从“能运行”到“能交付”是两回事)
      • [2. Qt 在整个系统中的位置](#2. Qt 在整个系统中的位置)
      • [3. 一个工业 Qt 应用的典型模块](#3. 一个工业 Qt 应用的典型模块)
      • [4. 嵌入式 Qt 的四种时间尺度](#4. 嵌入式 Qt 的四种时间尺度)
      • [5. 跨平台代码应该怎么写](#5. 跨平台代码应该怎么写)
      • [6. 建立"数据链路图"习惯](#6. 建立“数据链路图”习惯)
      • [7. 一个最低限度的产品基线](#7. 一个最低限度的产品基线)
      • [8. 本章工程练习](#8. 本章工程练习)
    • 常见问题与排查表
    • 动手实验与验收
    • 案例推演:从现象到根因
      • [ARM 工业屏"界面能开但设备数据全为空"](#ARM 工业屏“界面能开但设备数据全为空”)
    • API、命令与工程要点速查
    • 端到端实战任务
    • 工程决策原则
    • 章节复习题
    • 工程检查清单
    • 本篇小结
    • 系列导航

本章学习目标

完成本章后,你不仅应该"知道是什么",还应能够:

  • 解释关键机制为什么这样设计;
  • 根据日志、调用链和系统状态判断故障属于哪一层;
  • 写出一个可运行、可验证、可退出的小型工程;
  • 把本文方法迁移到真实 ARM Linux 项目。

前言

很多 Qt 开发者第一次接触"嵌入式 Qt"时,会误以为需要重新学一套 Qt。实际上,Qt 的 API 大部分仍然是熟悉的 QObject、信号槽、QWidget/QML、QThread、QNetwork、QSerialPort;真正新增的是 CPU 架构、Linux 运行环境、交叉编译、目标板图形栈和硬件通信。

这一篇不急着写代码,而是先把整个技术栈串起来。后面 35 篇文章,基本都可以挂到这张"全景图"上。

1. Qt 嵌入式到底在解决什么问题

典型设备包括工业控制屏、采集终端、检测设备、车载屏、医疗设备、广告机、机器人控制面板等。

可以把系统分为五层:

text 复制代码
┌────────────────────────────┐
│ Qt Application             │  QWidget / QML / C++
├────────────────────────────┤
│ Qt Framework               │  Core / Gui / Widgets / Network
├────────────────────────────┤
│ QPA / Graphics Backend     │  EGLFS / LinuxFB / Wayland / XCB
├────────────────────────────┤
│ Embedded Linux             │  glibc / systemd / device node
├────────────────────────────┤
│ Hardware                   │  ARM / GPU / LCD / UART / CAN / USB
└────────────────────────────┘

Qt 主要帮助我们解决"应用层跨平台开发"和"图形 UI + 事件驱动 + 通信"的问题;驱动、内核、BSP 仍然属于 Linux/芯片平台范畴。

2. 桌面 Qt 与嵌入式 Qt 的真正差异

桌面 Windows 上:

text 复制代码
Qt Creator → MSVC → x86_64 exe → Windows

嵌入式 Linux 上:

text 复制代码
Qt Creator → ARM 交叉编译器 → ARM ELF → Linux 开发板

代码层面可能一模一样:

cpp 复制代码
auto *button = new QPushButton(tr("启动"), this);
connect(button, &QPushButton::clicked, this, &MainWindow::startDevice);

但构建产物完全不同。最常见的新手错误就是:拿 x86_64 程序复制到 ARM 板上运行 ,最后得到 cannot execute binary file

3. 一条完整的数据与运行链路

把"写代码到设备运行"完整展开:

text 复制代码
源码
↓
qmake / CMake
↓
ARM GCC/G++
↓
链接 ARM Qt + ARM 第三方库
↓
生成 ARM ELF
↓
scp / rsync / OTA
↓
开发板
↓
动态链接器加载 Qt .so
↓
QPA 选择 eglfs/linuxfb/wayland/xcb
↓
GPU/DRM/FB 驱动
↓
LCD

任何一层不匹配,都可能导致程序"编译成功但运行失败"。因此嵌入式调试的核心不是猜,而是逐层验证

4. QWidget 与 QML 的定位

工业项目中 QWidget 依然大量存在,优势是 C++ 绑定直接、控件成熟、维护成本低。

QML/Qt Quick 更适合:

  • 动画丰富
  • 触控交互
  • 车载/消费电子
  • 需要 GPU 场景图加速的 UI

常见工程边界:

text 复制代码
QML / QWidget:展示层
        ↓
Controller / ViewModel
        ↓
Service
        ↓
Protocol / Device
        ↓
Linux / Hardware

不要因为用了 QML 就把串口协议、业务状态机全部写进 QML。

5. 这套系列会学到什么

后续内容分成七段:

  1. Linux、ARM、ABI、交叉编译、Sysroot
  2. Qt 5.15.2 交叉编译与 Qt Creator Kit
  3. QObject、信号槽、事件循环、QThread
  4. 串口、TCP、UDP、CAN、GPIO、I2C、SPI、USB
  5. 配置、日志、SQLite、WIdget/QML
  6. EGLFS/LinuxFB/Wayland、部署、systemd、性能、崩溃
  7. 架构、综合项目、发布检查与进阶路线

目标不是"看完 36 篇",而是具备独立定位 Qt 嵌入式问题的能力。

本章书稿级扩展:建立完整的 Qt 嵌入式工程认知

1. 从"能运行"到"能交付"是两回事

刚开始学习时,我们通常把目标定义为:把一个带按钮的 Qt 界面运行到开发板上。这个目标很重要,但只完成了产品工程的一小部分。真正能交付的嵌入式 Qt 软件还需要同时满足:

text 复制代码
可启动
可通信
可恢复
可诊断
可升级
可长期运行

例如串口设备被拔掉以后,程序是崩溃、卡住,还是进入 Offline 并自动恢复;服务器关闭以后,TCP 是否无限高频重连;数据库所在磁盘写满以后,程序是继续吃内存还是进入可控降级。这些才是产品级软件和 Demo 的区别。

2. Qt 在整个系统中的位置

不要把 Qt 想象成一个"操作硬件的万能框架"。Qt 主要位于用户空间:

text 复制代码
业务逻辑 / UI
        ↓
Qt Core / Gui / Widgets / Quick / Network
        ↓
Linux libc / socket / ioctl / epoll / filesystem
        ↓
Linux Kernel Driver
        ↓
UART / CAN / USB / GPU / LCD / GPIO

因此,当 QSerialPort::open() 失败时,必须继续向下看:设备节点是否存在、权限是否正确、驱动是否加载、USB 是否枚举成功。反过来,当 /dev/ttyUSB0 正常,而 UI 仍无数据显示,就应该向上检查协议拆包、线程信号槽和 UI 刷新。

3. 一个工业 Qt 应用的典型模块

建议从第一天就把系统理解为模块,而不是把所有逻辑堆到 MainWindow:

text 复制代码
Application
├── UI
│   ├── MainWindow
│   ├── DevicePage
│   └── SettingsPage
├── Device
│   ├── DeviceService
│   ├── SerialWorker
│   └── ProtocolCodec
├── Network
│   └── TcpClient
├── Storage
│   ├── Repository
│   └── DbWorker
├── Config
│   └── ConfigManager
└── Diagnostics
    └── LogManager

这里最重要的不是目录名字,而是依赖方向:UI 可以依赖 Service,但 Protocol 不应该反过来依赖 QWidget。

4. 嵌入式 Qt 的四种时间尺度

开发时很容易只关注"当前点击有没有反应",但产品运行涉及不同时间尺度:

  • 毫秒级:串口收包、UI 刷新、事件循环。
  • 秒级:心跳、超时、重连。
  • 小时级:日志滚动、缓存增长。
  • 天/月级:内存泄漏、数据库膨胀、FD 泄漏、升级兼容。

所以一个设计看起来在 10 分钟内完全正常,并不能说明它适合设备长期运行。

5. 跨平台代码应该怎么写

不要在业务层到处这样写:

cpp 复制代码
#ifdef Q_OS_WIN
// 一套逻辑
#else
// 另一套逻辑
#endif

更好的方式是封装平台能力:

cpp 复制代码
class IDeviceEnumerator
{
public:
    virtual ~IDeviceEnumerator() = default;
    virtual QStringList enumerate() = 0;
};

Windows、Linux 分别提供实现。业务层只面向接口。

6. 建立"数据链路图"习惯

任何功能都可以画成数据链路。例如设备温度显示:

text 复制代码
传感器
→ UART
→ Linux tty driver
→ QSerialPort::readyRead
→ rxBuffer
→ ProtocolCodec
→ DeviceFrame.temperature
→ DeviceService::temperatureChanged
→ MainWindow slot
→ QLabel

出现"温度不更新",沿这条链逐点加证据即可。这个方法比在 UI 层反复修改代码有效得多。

7. 一个最低限度的产品基线

第一版产品至少应该具备:

  1. 启动日志:版本、Qt 版本、编译时间、目标架构。
  2. 通信状态:串口/TCP 在线与离线原因。
  3. 所有耗时任务不阻塞 UI。
  4. 关键缓存有最大上限。
  5. 线程和 QObject 有明确销毁路径。
  6. 设备断开和网络断开可以恢复。
  7. 可收集日志、配置和 Core Dump。

这些内容会贯穿整套系列。

8. 本章工程练习

建立一个 HelloEmbeddedQt 工程,不需要真实硬件,但要求包含:

text 复制代码
MainWindow
DeviceService(Mock)
QTimer 模拟设备数据
状态栏显示 Online/Offline
日志输出版本和线程 ID

每 100ms 生成数据,但 UI 只每 500ms 刷新一次。通过这个小工程提前理解"采集频率"和"UI 刷新频率"为什么应该分离。

常见问题与排查表

现象 优先排查
ARM板提示 cannot execute binary file 先用 file MyApp 检查是否误编成 x86_64;再用 uname -m 检查目标 CPU。
程序架构正确但启动失败 继续检查动态链接器、Qt 动态库和 QPA,不要再纠结 CPU。
同一套源码 Windows 能跑、Linux 不能跑 从平台相关代码、文件路径、大小写、权限、动态库和 QPA 分层排查。

动手实验与验收

建议不要只阅读,本篇至少完成下面的实验:

  1. 在 PC 上分别记录 uname -m 与一个 Qt 可执行文件的 file 输出。
  2. 画出自己的目标项目从 UI 到硬件的五层架构图。
  3. 列出项目里 3 个属于 Qt 问题、3 个属于 Linux/驱动问题的例子。

验收标准: 完成实验后,不看文章也能解释关键调用链、失败时的排查顺序,以及哪些问题属于 Qt 层、Linux 层或硬件层。

案例推演:从现象到根因

ARM 工业屏"界面能开但设备数据全为空"

现场现象

程序启动正常,UI 可操作,但温度、电压始终显示"-"。

证据收集与分析

先确认 /dev 设备节点与驱动,再确认 SerialWorker 是否收到字节,随后检查 ProtocolCodec 是否输出业务 Frame,最后确认跨线程 signal 是否到达 UI。

根因

最终发现设备串口节点从 ttyUSB0 变成 ttyUSB1,而路径被 UI 代码写死。

修复与验证

将设备枚举集中到 DeviceService,UI 不再直接持有设备路径;拔插 20 次均可恢复。

这个案例体现了本系列反复强调的方法:不要从"最像的原因"开始改代码,而要沿数据链、线程链或系统链逐层排除,并把验证结果写进日志或测试记录。

API、命令与工程要点速查

名称 本章用途
QApplication 本章核心工具/API;建议在实验中实际使用并记录结果。
QObject 本章核心工具/API;建议在实验中实际使用并记录结果。
QWidget/QML 本章核心工具/API;建议在实验中实际使用并记录结果。
QThread 本章核心工具/API;建议在实验中实际使用并记录结果。
QSerialPort 本章核心工具/API;建议在实验中实际使用并记录结果。
QTcpSocket 本章核心工具/API;建议在实验中实际使用并记录结果。
Linux QPA 本章核心工具/API;建议在实验中实际使用并记录结果。
systemd 本章核心工具/API;建议在实验中实际使用并记录结果。

端到端实战任务

建立一个 Mock 设备监控程序:100ms 生成数据、500ms 刷新 UI,显示 Online/Offline,并输出版本和线程信息。

建议按下面的工程流程完成,而不是只复制代码:

  1. 先写出输入、输出、线程和数据链路。
  2. 只实现最小成功路径并编译通过。
  3. 增加日志,能够看到每个关键状态变化。
  4. 主动制造至少两种失败条件。
  5. 观察 CPU、内存、线程和 FD 是否符合预期。
  6. 记录人工验证步骤与预期现象。
  7. 保存最终代码、日志和结论,作为后续章节的基线。

工程决策原则

  • 先分层再修复: 先判断属于 UI、Qt 机制、Linux、协议还是硬件层。
  • 先证据再结论: 使用日志、系统命令、调用栈或抓包验证,不靠猜测。
  • 先最小改动再扩展: 能在局部解决的问题,不做无关重构。
  • 先长期边界再上线: 缓存、线程、FD、日志和数据库都必须有上限或回收策略。

章节复习题

  1. 为什么嵌入式 Qt 不是另一套 Qt?
  2. QPA 位于哪一层?
  3. 为什么 UI 与硬件访问要分层?
  4. 长期运行与普通桌面 Demo 的质量标准有什么差异?
  5. 遇到无数据显示时如何画数据链路?

建议在不看正文的情况下口头回答。如果无法解释"为什么",说明还停留在 API 记忆阶段,需要重新完成本章实验。

工程检查清单

  • 能解释 x86_64 程序为什么不能直接运行在 ARM64
  • 知道 Qt 应用、Qt 库、第三方库必须与目标 ABI 匹配
  • 能说清 EGLFS/LinuxFB/Wayland/XCB 属于哪一层
  • 明确 QWidget/QML 与硬件驱动不是同一层问题

本篇小结

Qt 嵌入式不是另一套 Qt,而是 Qt 运行环境从桌面 OS 变成了 Embedded Linux。真正新增的能力是交叉编译、系统环境、硬件通信和长期稳定性。

系列导航

  • 上一篇:无(系列第一篇)
  • 下一篇:嵌入式Linux基础:Qt开发者必须掌握的命令与排查思路

建议:真正掌握本篇内容的标准不是"看懂",而是能在自己的开发板、虚拟机或测试工程中复现关键流程,并能解释失败时该从哪一层排查。

相关推荐
lczllx2 小时前
从 TCP 90μs 到 SHM 28μs:我的 RPC 框架零拷贝优化历程
linux·后端
海清河晏1112 小时前
Qt实战:从零构建美化登录界面
开发语言·c++·qt
盐焗鹌鹑蛋3 小时前
【Linux】权限
linux
Fu_Lin_3 小时前
《Qt嵌入式从零基础到精通》前言与阅读指南
开发语言·qt
小此方3 小时前
Re:Linux系统篇(四十六)信号篇·四:一文串联操作系统底层:时钟中断、内核态切换、系统调用与 Linux 信号处理全解析
linux·驱动开发·信号处理
无人生还别怕4 小时前
如何制作一个linux标准iso镜像
linux·iso·opencloudos·openanolisos
Yana.nice12 小时前
Linux 只保留 30 天内日志(find命令删除日志文件)
linux·运维·chrome
DFT计算杂谈16 小时前
无 Root 权限在 Tesla K80 零门槛部署 DeepSeek 大模型
linux·服务器·网络·数据库·机器学习
Zhang~Ling17 小时前
从 fopen 到 struct file:从零开始拆解 Linux 文件 I/O
linux·运维·服务器