嵌入式GUI技术全景
# 01.嵌入式GUI技术全景
从嵌入式 GUI 选型到 Qt/QML 架构全景——为什么 QML 是嵌入式交互界面的最优解?本文是整个专栏的"地图"。
# 目录介绍
- 1.1 案例引入
- 1.1.1 一个嵌入式项目的 GUI 选型困境
- 1.1.2 五大主流方案的"第一印象"
- 1.1.3 本文要回答的核心问题
- 1.2 嵌入式 GUI 的本质
- 1.2.1 GUI 在嵌入式中的定位
- 1.2.2 嵌入式与桌面 GUI 的本质差异
- 1.2.3 图形渲染的硬件基础
- 1.3 主流技术方案深度对比
- 1.3.1 裸机方案:直接操作 Framebuffer
- 1.3.2 LVGL:轻量级嵌入式图形库
- 1.3.3 Qt Widgets:传统桌面控件体系
- 1.3.4 Qt QML:声明式 GPU 加速界面
- 1.3.5 HTML5/Electron:Web 技术栈方案
- 1.3.6 Flutter Embedded:跨平台新势力
- 1.4 选型决策与量化评估
- 1.4.1 六维评估模型
- 1.4.2 选型决策树
- 1.4.3 典型场景推荐
- 1.5 Qt/QML 技术栈全景
- 1.5.1 Qt 框架模块家族
- 1.5.2 Qt5 vs Qt6 核心差异
- 1.5.3 QML 的设计哲学
- 1.6 QML 渲染管线概览
- 1.6.1 从 .qml 文件到屏幕像素
- 1.6.2 Scene Graph 批处理原理
- 1.6.3 QML vs HTML5 渲染管线对比
- 1.7 嵌入式运行环境
- 1.7.1 QPA 平台抽象层
- 1.7.2 EGLFS:无窗口系统的 GPU 渲染
- 1.7.3 LinuxFB:纯软件渲染退路
- 1.7.4 Wayland:现代合成器方案
- 1.8 QML 嵌入式生态能力
- 1.8.1 硬件通信(串口/CAN/GPIO)
- 1.8.2 多媒体(摄像头/音视频)
- 1.8.3 网络与 OTA 升级
- 1.8.4 数据库与本地存储
- 1.9 完整项目示例
- 1.9.1 项目结构
- 1.9.2 CMakeLists.txt
- 1.9.3 main.cpp
- 1.9.4 QML 界面
- 1.9.5 交叉编译与运行
- 1.10 学习路径与速查表
- 1.10.1 从 0 到 1 的十周路径
- 1.10.2 技术选型速查表
- 1.10.3 本专栏路线图
# 1.1 案例引入
# 1.1.1 一个嵌入式项目的 GUI 选型困境
想象你接到一个项目:为一款工业 HMI 触摸屏开发 GUI 界面,硬件参数如下:
┌─────────────────────────────────────────┐
│ 硬件规格 │
│ • SoC: ARM Cortex-A53 四核 1.2GHz │
│ • RAM: 512MB DDR3 │
│ • GPU: Mali-400 MP2(支持 OpenGL ES 2.0)│
│ • 屏幕: 7 英寸 1024×600 电容触摸屏 │
│ • 存储: 4GB eMMC │
│ • OS: Yocto Linux 5.x │
├─────────────────────────────────────────┤
│ 功能需求 │
│ • 实时显示 8 路传感器数据(10Hz 刷新) │
│ • 流畅的页面切换动画(目标 60fps) │
│ • CAN 总线通信 + 串口数据采集 │
│ • 支持 OTA 远程升级 │
│ • 开机到首屏 < 3 秒 │
└─────────────────────────────────────────┘
团队内部意见分歧:
- 张工主张 LVGL:"轻量,资源占用低"
- 李工推荐 Qt Widgets:"生态成熟,控件丰富"
- 王工建议 HTML5 + Chromium:"前端人才好招"
- 赵工力挺 Qt QML:"GPU 加速,动画流畅"
到底选哪个? 这不是拍脑袋能回答的——需要从渲染原理、硬件适配、性能天花板、开发效率、维护成本五个维度做系统评估。
# 1.1.2 五大主流方案的"第一印象"
┌───────────────────────────────────────────────────────────┐
│ 嵌入式 GUI 技术栈全景图 │
├─────────────┬────────────┬──────────┬──────────┬──────────┤
│ 裸机 FB │ LVGL │ Qt Widget│ Qt QML │ HTML5 │
│ 手动画点 │ C 语言 │ C++控件 │ 声明式 │ Web引擎 │
│ 最低资源 │ MCU级别 │ CPU渲染 │ GPU加速 │ 最重 │
│ 无抽象 │ 中等抽象 │ 高抽象 │ 高抽象 │ 最高抽象 │
├─────────────┼────────────┼──────────┼──────────┼──────────┤
│ RAM: <1MB │ RAM: 32KB+│ 32MB+ │ 64MB+ │ 256MB+ │
│ Flash:<64K │ Flash:64K+│ 16MB+ │ 32MB+ │ 128MB+ │
└─────────────┴────────────┴──────────┴──────────┴──────────┘
# 1.1.3 本文要回答的核心问题
- 嵌入式 GUI 的本质是什么?与桌面 GUI 有何根本差异?
- 各方案的渲染原理是什么?性能天花板在哪里?
- 如何建立量化的选型决策框架?
- Qt/QML 的技术架构是怎样的?为什么适合嵌入式?
- QML 如何在没有 X11/Wayland 的情况下直接输出到屏幕?
- 一个完整的嵌入式 QML 项目长什么样?
# 1.2 嵌入式 GUI 的本质
# 1.2.1 GUI 在嵌入式中的定位
┌─────────────────────────────────────────────┐
│ 嵌入式系统分层架构 │
│ │
│ ┌────────────────────────────────────┐ │
│ │ 应用层(业务逻辑 + GUI 界面) │ │
│ ├────────────────────────────────────┤ │
│ │ 中间件(通信协议/数据库/OTA) │ │
│ ├────────────────────────────────────┤ │
│ │ GUI 框架(Qt/LVGL/Web Engine) │ ←── 本文焦点
│ ├────────────────────────────────────┤ │
│ │ 图形后端(OpenGL ES/DRM/FB) │ │
│ ├────────────────────────────────────┤ │
│ │ Linux 内核(驱动/调度/内存管理) │ │
│ ├────────────────────────────────────┤ │
│ │ 硬件(SoC/GPU/LCD/触摸屏) │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
GUI 框架的核心职责:
- 布局计算:确定每个 UI 元素的位置和尺寸
- 渲染输出:将 UI 元素转化为像素数据写入显存
- 事件处理:接收触摸/按键输入并派发到对应元素
- 状态管理:维护 UI 与业务数据的同步
# 1.2.2 嵌入式与桌面 GUI 的本质差异
| 维度 | 桌面 GUI | 嵌入式 GUI |
|---|---|---|
| 窗口系统 | 有(X11/Wayland/Win32) | 通常没有(直接输出到 LCD) |
| GPU | 强大的独立显卡 | 集成 GPU(Mali/Vivante/PowerVR),可能无 GPU |
| 内存 | 8~32GB | 64MB~1GB |
| 多窗口 | 多应用多窗口 | 通常单应用全屏 |
| 分辨率 | 1080p~4K | 480×320~1920×1080 |
| 帧率要求 | 60fps(桌面操作) | 30~60fps(看场景) |
| 启动速度 | 可接受 10 秒以上 | 要求 < 3 秒到首屏 |
| 功耗约束 | 无限制 | 严格(电池/散热) |
| 运行环境 | 完整 OS | 精简 Linux/RTOS |
核心差异:嵌入式 GUI 往往独占整个屏幕(没有窗口管理器),与硬件的耦合更紧密,资源约束更苛刻。
# 1.2.3 图形渲染的硬件基础
理解 GUI 选型,必须先理解渲染管线的硬件基础:
┌──────────────────────────────────────────────────────────────┐
│ LCD 显示原理 │
│ │
│ CPU/GPU → Framebuffer(显存) → LCD 控制器 → 液晶屏 │
│ │
│ Framebuffer 本质:一块连续内存,存储每个像素的颜色值 │
│ 1024×600×4字节(ARGB) = 2.4MB/帧 │
│ 60fps 需要 144MB/s 的带宽 │
└──────────────────────────────────────────────────────────────┘
两种渲染路径:
┌─────────────────────────────────────────────────────────────┐
│ 路径 A:CPU 软件渲染(Software Rendering) │
│ CPU 逐像素计算颜色值 → 写入 Framebuffer │
│ 优点:无需 GPU 驱动,兼容性好 │
│ 缺点:CPU 占用高,复杂动画容易掉帧 │
├─────────────────────────────────────────────────────────────┤
│ 路径 B:GPU 硬件加速(Hardware Accelerated) │
│ CPU 提交绘制指令 → GPU 并行处理 → 输出到 Framebuffer │
│ 优点:适合大面积填充/纹理贴图/矩阵变换,CPU 几乎空闲 │
│ 缺点:需要 GPU 驱动(EGL/OpenGL ES),增加系统复杂度 │
└─────────────────────────────────────────────────────────────┘
关键指标:在 ARM Cortex-A53 + Mali-400 的平台上:
- 纯 CPU 软件渲染:复杂界面约 15~25fps
- GPU 加速渲染:同样界面轻松 60fps,CPU 占用仅 5~10%
# 1.3 主流技术方案深度对比
# 1.3.1 裸机方案:直接操作 Framebuffer
原理:打开 /dev/fb0 设备文件,直接往 Framebuffer 内存中写像素数据。
// Linux Framebuffer 直接绘制——最底层的方式
#include <linux/fb.h>
#include <sys/mman.h>
#include <fcntl.h>
int fd = open("/dev/fb0", O_RDWR);
struct fb_var_screeninfo vinfo;
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
// 计算 framebuffer 大小
int screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
char *fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 画一个红色矩形 (100,100) ~ (200,200)
for (int y = 100; y < 200; y++) {
for (int x = 100; x < 200; x++) {
long offset = (y * vinfo.xres + x) * 4; // 4 字节/像素 (ARGB)
fbp[offset + 0] = 0xFF; // Blue
fbp[offset + 1] = 0x00; // Green
fbp[offset + 2] = 0x00; // Red (BGR 格式在某些平台)
fbp[offset + 3] = 0xFF; // Alpha
}
}
优势:
- 零依赖,二进制极小(几 KB)
- 完全掌控每一个像素
- 启动速度极快(无框架初始化开销)
劣势:
- 没有布局系统(手动计算坐标)
- 没有事件处理(手动读取
/dev/input/eventX) - 没有字体渲染(需要自己集成 FreeType)
- 没有动画框架
- 开发效率极低
适用场景:Boot Logo 显示、极简状态指示灯界面、资源极度受限(Flash < 64KB)。
# 1.3.2 LVGL:轻量级嵌入式图形库
架构原理:
┌──────────────────────────────────────────────┐
│ LVGL 架构 │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 应用代码(C 语言 API 调用) │ │
│ ├──────────────────────────────────┤ │
│ │ 控件层(btn/label/chart/table) │ │
│ ├──────────────────────────────────┤ │
│ │ 布局引擎(Flex/Grid) │ │
│ ├──────────────────────────────────┤ │
│ │ 绘制引擎(抗锯齿/Alpha混合) │ │
│ ├──────────────────────────────────┤ │
│ │ 显示驱动接口(flush_cb) │ │
│ │ 输入驱动接口(read_cb) │ │
│ └──────────────────────────────────┘ │
│ ↕ │
│ ┌──────────────────────────────────┐ │
│ │ 硬件:SPI/RGB LCD + 触摸 IC │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────────┘
渲染机制:
- 采用"脏区域"策略:只重绘变化的区域
- CPU 软件渲染(可选 DMA2D 硬件加速)
- 双缓冲或部分缓冲(减少内存需求)
// LVGL 典型用法
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Click Me");
性能数据(STM32H743, 480MHz, 无 GPU):
| 场景 | LVGL 帧率 | CPU 占用 |
|---|---|---|
| 静态界面 | 30fps | 5% |
| 列表滑动 | 25fps | 40% |
| 图表实时更新 | 20fps | 55% |
| 复杂动画 | 15fps | 80% |
适用边界:
- ✅ MCU(STM32/ESP32/NXP i.MX RT)
- ✅ 资源受限(RAM < 256KB, Flash < 2MB)
- ✅ 简单交互(按钮/列表/表盘)
- ❌ 复杂动画(粒子效果/页面过渡)
- ❌ 高分辨率(> 800×480 时 CPU 扛不住)
# 1.3.3 Qt Widgets:传统桌面控件体系
架构原理:
┌──────────────────────────────────────────────┐
│ Qt Widgets 渲染管线 │
│ │
│ QWidget::paintEvent(QPaintEvent*) │
│ ↓ │
│ QPainter(高级绘图 API) │
│ ↓ │
│ QPaintEngine(绘图引擎抽象) │
│ ├── QRasterPaintEngine(CPU 软件渲染) │
│ └── QOpenGLPaintEngine(GPU 加速,可选) │
│ ↓ │
│ QBackingStore → Framebuffer / 窗口合成器 │
└──────────────────────────────────────────────┘
核心特点:
- 每个 Widget 是一个矩形区域,拥有自己的
paintEvent() - 默认使用 CPU 软件渲染(QRasterPaintEngine)
- 控件继承体系:
QObject → QWidget → QAbstractButton → QPushButton - 布局管理器:
QHBoxLayout/QVBoxLayout/QGridLayout
嵌入式适配问题:
- 默认 CPU 渲染——在 ARM 上复杂界面帧率低
- 控件系统设计时假设有窗口管理器(无窗口模式需要 hack)
- 不支持硬件加速的动画
- QSS 样式效率不高(每次重绘都要解析)
性能数据(ARM Cortex-A53, 1.2GHz, 无 GPU 加速):
| 场景 | Qt Widgets 帧率 | CPU 占用 |
|---|---|---|
| 静态界面 | 60fps | 2% |
| 列表滑动 | 30fps | 45% |
| 自定义绘图 | 25fps | 60% |
| 复杂动画 | 15fps | 85% |
# 1.3.4 Qt QML:声明式 GPU 加速界面
架构原理(核心优势所在):
┌──────────────────────────────────────────────────────────┐
│ Qt QML 渲染管线(Scene Graph 架构) │
│ │
│ .qml 文件 │
│ ↓ QML 引擎解析 │
│ QObject 对象树(属性绑定/信号槽) │
│ ↓ 同步阶段(主线程) │
│ Scene Graph 节点树 │
│ ↓ 渲染阶段(渲染线程,可并行) │
│ OpenGL ES 绘制指令 │
│ ↓ │
│ GPU 执行 → Framebuffer → 屏幕 │
│ │
│ 关键特性: │
│ • 双线程模型(主线程 + 渲染线程) │
│ • 批量渲染(合并相同材质的节点,减少 Draw Call) │
│ • 距离场字体渲染(GPU 加速的矢量文字) │
│ • 纹理图集(多个小图合并为一张大纹理) │
└──────────────────────────────────────────────────────────┘
为什么 QML 适合嵌入式:
┌──────────────────────────────────────────────────────────┐
│ QML 的五大嵌入式优势 │
│ │
│ ① GPU 加速是默认行为 │
│ - 所有可视元素自动通过 OpenGL ES 渲染 │
│ - 动画/变换/透明度全部由 GPU 处理 │
│ - CPU 几乎只负责业务逻辑 │
│ │
│ ② 声明式语法 → 开发效率高 │
│ - UI 描述 = 最终呈现(所见即所得) │
│ - 属性绑定自动更新 UI(响应式) │
│ - 热重载(修改 .qml 文件,界面实时刷新) │
│ │
│ ③ 双线程模型 → UI 不卡顿 │
│ - 主线程处理逻辑/事件 │
│ - 渲染线程独立处理 GPU 提交 │
│ - 即使主线程有短暂延迟,动画仍然流畅 │
│ │
│ ④ 无窗口系统直接运行 │
│ - EGLFS 插件:直接输出到 DRM/KMS │
│ - 无需 X11/Wayland,节省 50~100MB 内存 │
│ - 启动更快(省去窗口管理器初始化) │
│ │
│ ⑤ C++ 后端无缝集成 │
│ - QML 负责 UI 表现,C++ 负责性能敏感逻辑 │
│ - 元对象系统实现自动绑定(无需手写胶水代码) │
└──────────────────────────────────────────────────────────┘
性能数据(ARM Cortex-A53 + Mali-400, OpenGL ES 2.0):
| 场景 | Qt QML 帧率 | CPU 占用 | GPU 占用 |
|---|---|---|---|
| 静态界面 | 60fps | 1% | 5% |
| 列表滑动 | 60fps | 8% | 25% |
| 复杂页面切换动画 | 60fps | 12% | 40% |
| 粒子效果(500粒子) | 55fps | 5% | 60% |
| 实时图表(10Hz) | 60fps | 10% | 30% |
# 1.3.5 HTML5/Electron:Web 技术栈方案
架构原理:
┌──────────────────────────────────────────────────────────┐
│ HTML5 嵌入式渲染管线 │
│ │
│ HTML/CSS/JS │
│ ↓ 解析 │
│ DOM 树 + CSSOM 树 │
│ ↓ Style Recalculation │
│ Render Tree │
│ ↓ Layout (Reflow) │
│ Layout Tree(每个元素的位置/尺寸) │
│ ↓ Paint │
│ Paint Records(绘制指令列表) │
│ ↓ Composite │
│ 合成层 → GPU Rasterization → 屏幕 │
│ │
│ 问题: │
│ • DOM 操作触发全量 Reflow(性能杀手) │
│ • V8/SpiderMonkey 引擎本身占 50~100MB │
│ • Chromium 内核 > 200MB 存储 │
│ • 垃圾回收导致帧率不稳定(GC 停顿) │
└──────────────────────────────────────────────────────────┘
嵌入式 Web 方案的现实问题:
- 内存:Chromium 基础占用 150~300MB(远超多数嵌入式设备)
- 启动速度:引擎初始化 3~8 秒(不含 JS 加载)
- 帧率稳定性:GC 触发时掉帧明显(20~50ms 停顿)
- 人才假象:前端开发者不了解嵌入式调试、交叉编译、驱动适配
适用场景:智能电视/车机信息娱乐系统(RAM > 1GB, 多核高频 SoC)。
# 1.3.6 Flutter Embedded:跨平台新势力
架构原理:
┌──────────────────────────────────────────────────────────┐
│ Flutter Embedded 架构 │
│ │
│ Dart 代码 → Widget Tree → Element Tree → RenderObject │
│ ↓ │
│ Skia 渲染引擎(C++, 类似 Chrome 的 2D 渲染库) │
│ ↓ │
│ OpenGL ES / Vulkan → Framebuffer │
│ │
│ 嵌入式后端:flutter-elinux / flutter-pi │
│ • 通过 DRM/KMS 直接输出 │
│ • Dart VM 约 20~30MB │
└──────────────────────────────────────────────────────────┘
现状评估(2025年):
- ✅ 渲染性能优秀(Skia GPU 加速)
- ✅ 开发效率高(热重载/声明式)
- ⚠️ 嵌入式支持仍不成熟(社区驱动,非官方)
- ❌ 硬件通信生态弱(串口/CAN/GPIO 缺少稳定插件)
- ❌ Dart VM 内存占用偏高
- ❌ 缺少工业级案例验证
# 1.4 选型决策与量化评估
# 1.4.1 六维评估模型
渲染性能
▲
/|\
/ | \
/ | \
开发效率 ←──┼──→ 内存占用
\ | /
\ | /
\ | /
\|/
生态成熟度 ←→ 启动速度
↓
维护成本
量化评分表(满分 10 分,针对 ARM Cortex-A53 + 512MB RAM 场景):
| 方案 | 渲染性能 | 内存占用 | 启动速度 | 开发效率 | 生态成熟度 | 维护成本 | 综合 |
|---|---|---|---|---|---|---|---|
| 裸机 FB | 3 | 10 | 10 | 1 | 1 | 2 | 4.5 |
| LVGL | 5 | 9 | 9 | 5 | 6 | 5 | 6.5 |
| Qt Widgets | 5 | 6 | 6 | 7 | 9 | 7 | 6.7 |
| Qt QML | 9 | 7 | 7 | 9 | 9 | 8 | 8.2 |
| HTML5 | 7 | 3 | 3 | 8 | 8 | 6 | 5.8 |
| Flutter | 8 | 5 | 5 | 8 | 4 | 5 | 5.8 |
# 1.4.2 选型决策树
你的设备有 GPU 吗?
├── 是 → 你的 RAM ≥ 128MB 吗?
│ ├── 是 → 需要复杂动画/多页面吗?
│ │ ├── 是 → 【Qt QML】 ← 最优解
│ │ └── 否 → 【Qt Widgets】也能胜任
│ └── 否 → 【LVGL + GPU 加速(可选)】
└── 否 → 你的 RAM ≥ 32MB 吗?
├── 是 → 界面复杂度如何?
│ ├── 高 → 【Qt Widgets (软件渲染)】
│ └── 低 → 【LVGL】
└── 否 → 【LVGL】或【裸机 FB】
# 1.4.3 典型场景推荐
| 产品类型 | 典型硬件 | 推荐方案 | 理由 |
|---|---|---|---|
| 工业 HMI | Cortex-A53 + Mali | Qt QML | 动画流畅、串口/CAN 生态好 |
| 智能家电 | Cortex-A7 无 GPU | LVGL | 资源受限、界面简单 |
| 智能座舱 | 高通 8155 | Qt QML + HTML5(混合) | 多屏/多模态/生态丰富 |
| 医疗设备 | i.MX6 + Vivante | Qt QML | 长期维护/认证需要稳定框架 |
| 充电桩 | STM32MP1 | LVGL 或 Qt QML | 看界面复杂度 |
| 智能电视 | 4核A55 + Mali | HTML5 | 应用生态/内容分发需要 |
# 1.5 Qt/QML 技术栈全景
# 1.5.1 Qt 框架模块家族
┌──────────────────────────────────────────────────────────────────┐
│ Qt 模块全景图 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 核心模块(Core Modules) │ │
│ │ ┌─────────┬─────────┬──────────┬──────────┬─────────┐ │ │
│ │ │ Qt Core │ Qt GUI │ Qt QML │ Qt Quick │ Qt Net │ │ │
│ │ │ 核心库 │ 图形基础│ QML引擎 │ Quick控件│ 网络 │ │ │
│ │ └─────────┴─────────┴──────────┴──────────┴─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 扩展模块(Add-on Modules) │ │
│ │ ┌──────────┬──────────┬──────────┬──────────────────┐ │ │
│ │ │ Qt Serial│ Qt SQL │ Qt Multi │ Qt Bluetooth │ │ │
│ │ │ Port/Bus │ 数据库 │ media │ 蓝牙 │ │ │
│ │ ├──────────┼──────────┼──────────┼──────────────────┤ │ │
│ │ │ Qt Charts│ Qt 3D │ Qt WebSk │ Qt Virtual KB │ │ │
│ │ │ 图表 │ 3D渲染 │ WebSocket│ 虚拟键盘 │ │ │
│ │ └──────────┴──────────┴──────────┴──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 工具链 │ │
│ │ Qt Creator · qmake/CMake · qmlcachegen · MOC · RCC │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
嵌入式常用模块清单:
| 模块 | 用途 | 嵌入式场景 |
|---|---|---|
Qt Core | 事件循环/信号槽/容器/IO | 所有项目必须 |
Qt GUI | 图形基础设施/字体/图片 | 所有 GUI 项目 |
Qt QML | QML 引擎/JavaScript 解析 | QML 项目必须 |
Qt Quick | QML 可视化控件库 | 界面开发 |
Qt Quick Controls | 高级控件(按钮/滑块/表格) | 快速 UI 搭建 |
Qt SerialPort | 串口通信 | 传感器/PLC 通信 |
Qt SerialBus | CAN/Modbus 协议 | 工业总线 |
Qt Multimedia | 摄像头/音视频播放 | 监控/娱乐 |
Qt Network | TCP/UDP/HTTP | 远程通信/OTA |
Qt SQL | 数据库访问 | 本地数据存储 |
Qt Charts | 数据图表 | 监控仪表盘 |
Qt Virtual Keyboard | 虚拟键盘 | 触摸屏输入 |
# 1.5.2 Qt5 vs Qt6 核心差异
| 维度 | Qt5 | Qt6 |
|---|---|---|
| 图形后端 | OpenGL 为主 | RHI 抽象层(支持 Vulkan/Metal/D3D/OpenGL) |
| QML 引擎 | V4 引擎 | 改进 V4 + QML 类型编译器(性能提升 30%) |
| CMake | qmake 为主,CMake 可选 | CMake 为唯一官方构建系统 |
| C++ 标准 | C++11 | C++17 |
| 属性绑定 | 仅 QML 中可用 | C++ 中也可用(QProperty<T>) |
| 模块变动 | Qt Widgets/Qt Quick 并重 | 重心完全转向 Qt Quick |
| 嵌入式支持 | Boot2Qt (商业) | 开源 + Boot2Qt |
| 最低 OpenGL | ES 2.0 | ES 2.0(通过 RHI 适配) |
选型建议:
- 新项目优先 Qt6(长期支持到 2029+)
- 已有 Qt5 项目迁移成本中等(主要是 CMake + API 微调)
- 极低端硬件(OpenGL ES 2.0 勉强):Qt5 更稳定
# 1.5.3 QML 的设计哲学
┌──────────────────────────────────────────────────────────┐
│ QML 设计三原则 │
│ │
│ ① 声明式 > 命令式 │
│ 描述"是什么"而非"怎么做" │
│ Rectangle { width: 100; color: "red" } │
│ 而非 painter.fillRect(0,0,100,100, Qt::red) │
│ │
│ ② 属性绑定 = 响应式 │
│ width: parent.width * 0.5 ← 自动跟随父元素变化 │
│ 类似 Excel 公式:A1 = B1 + C1(B1/C1 变时 A1 自动更新)│
│ │
│ ③ UI 与逻辑分离 │
│ QML 负责"长什么样"(设计师也能修改) │
│ C++ 负责"干什么活"(性能关键路径) │
│ 通过 Property/Signal 自动桥接 │
└──────────────────────────────────────────────────────────┘
一个直观的 QML 示例:
// Dashboard.qml —— 一个实时速度表
import QtQuick 2.15
import QtQuick.Controls 2.15
Rectangle {
width: 400; height: 400
color: "#1a1a2e"
// 表盘
Canvas {
id: gauge
anchors.fill: parent
property real speed: backend.currentSpeed // ← 绑定 C++ 后端数据
onSpeedChanged: requestPaint() // speed 变化时自动重绘
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)
// 绘制表盘刻度...
drawNeedle(ctx, speed)
}
}
// 数字显示
Text {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: Math.round(backend.currentSpeed) + " km/h"
color: "white"
font.pixelSize: 32
}
}
# 1.6 QML 渲染管线概览
# 1.6.1 从 .qml 文件到屏幕像素
┌──────────────────────────────────────────────────────────────────┐
│ QML 渲染管线(完整流程) │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 阶段 1:编译/加载 │ │
│ │ .qml 文件 → QML 编译器 → 字节码 (.qmlc) │ │
│ │ 或运行时:.qml → 解析器 → AST → 字节码 │ │
│ └────────────────────────────────┬───────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 阶段 2:对象实例化(主线程) │ │
│ │ 字节码 → QObject 对象树(QQuickItem 层级) │ │
│ │ 属性绑定引擎启动 → 依赖追踪图建立 │ │
│ └────────────────────────────────┬───────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 阶段 3:同步(主线程 → 渲染线程) │ │
│ │ QQuickItem 属性变更 → 同步到 QSGNode(Scene Graph 节点) │ │
│ │ 这是主线程和渲染线程的唯一交汇点 │ │
│ └────────────────────────────────┬───────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 阶段 4:批处理与优化(渲染线程) │ │
│ │ 遍历 Scene Graph → 合并相同材质的节点 → 减少 Draw Call │ │
│ │ 构建渲染列表 → 排序(不透明先画,半透明后画) │ │
│ └────────────────────────────────┬───────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 阶段 5:GPU 渲染 │ │
│ │ 提交 OpenGL ES / Vulkan 指令 → GPU 执行 │ │
│ │ → 结果写入 Framebuffer → LCD 控制器扫描输出 │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
# 1.6.2 Scene Graph 批处理原理
为什么 Scene Graph 能高效渲染?
传统"逐控件绘制"模式(Qt Widgets):
绘制按钮A → 切换纹理 → 绘制按钮B → 切换纹理 → 绘制文本C → ...
每个控件 1 次 Draw Call → N 个控件 = N 次 Draw Call
GPU 状态切换代价高 → 性能瓶颈
Scene Graph 批处理模式(Qt QML):
收集所有节点 → 按材质/纹理分组 → 合并同组几何体 → 一次 Draw Call 绘制
100 个相同材质的矩形 → 合并为 1 次 Draw Call
Draw Call 数量: O(材质种类) 而非 O(元素数量)
具体示例:
界面上有 50 个 Rectangle + 20 个 Image + 30 个 Text
传统方式:100 次 Draw Call(每个元素一次)
Scene Graph 批处理:
- 50 个纯色 Rectangle → 合并 → 1 次 Draw Call
- 20 个 Image(假设来自同一纹理图集)→ 1 次 Draw Call
- 30 个 Text(距离场字体渲染)→ 1 次 Draw Call
- 总计:3 次 Draw Call(减少 97%!)
# 1.6.3 QML vs HTML5 渲染管线对比
| 阶段 | QML | HTML5 (Chromium) |
|---|---|---|
| 解析 | .qml → AST → QObject(轻量) | HTML→DOM + CSS→CSSOM(重量级) |
| 布局 | anchors/positioners(O(1) 增量) | CSS Box Model + Reflow(O(n) 最坏) |
| 样式 | 属性直接绑定 | Style Recalculation(级联计算) |
| 绘制 | Scene Graph 批处理 | Paint → Compositing Layers |
| GPU 提交 | 直接 GL 调用 | 需经合成器中转 |
| 动画 | 属性动画(GPU 直接插值) | CSS Animation/JS rAF(需回到主线程) |
结论:QML 少了 DOM、CSSOM、Style Recalc、Reflow 四个重量级阶段,渲染路径短 2~3 层。
# 1.7 嵌入式运行环境
# 1.7.1 QPA 平台抽象层
┌──────────────────────────────────────────────────────────┐
│ QPA (Qt Platform Abstraction) 架构 │
│ │
│ Qt 应用代码(与平台无关) │
│ ↓ │
│ QPlatformIntegration(平台集成接口) │
│ ↓ 根据 -platform 参数选择不同实现 │
│ ┌─────────┬──────────┬────────────┬───────────────────┐ │
│ │ xcb │ eglfs │ linuxfb │ wayland │ │
│ │ X11窗口 │ EGL直接 │ FB纯软件 │ Wayland客户端 │ │
│ │ 桌面用 │ 嵌入式首选│ 无GPU备选 │ 多窗口嵌入式 │ │
│ └─────────┴──────────┴────────────┴───────────────────┘ │
└──────────────────────────────────────────────────────────┘
使用方式:
./myapp -platform eglfs # GPU 加速直接渲染
./myapp -platform linuxfb # CPU 软件渲染
./myapp -platform wayland # Wayland 合成器
export QT_QPA_PLATFORM=eglfs # 环境变量方式
# 1.7.2 EGLFS:无窗口系统的 GPU 渲染
EGLFS 是什么?
- EGL = 嵌入式图形库接口(连接 OpenGL ES 和本地窗口系统)
- FS = Full Screen(全屏,无窗口管理器)
- 本质:Qt 直接通过 EGL/DRM/KMS 获取 GPU 上下文,输出到 LCD
EGLFS 启动流程:
┌──────────────────────────────────────────────────────────┐
│ EGLFS 初始化流程 │
│ │
│ ① Qt 加载 libqeglfs.so 插件 │
│ ↓ │
│ ② 打开 DRM 设备 (/dev/dri/card0) │
│ ↓ │
│ ③ 查询 Connector → CRTC → Encoder → 屏幕模式 │
│ ↓ │
│ ④ 创建 GBM (Generic Buffer Manager) Surface │
│ ↓ │
│ ⑤ 初始化 EGL → 创建 EGLContext (OpenGL ES 2.0) │
│ ↓ │
│ ⑥ 创建 EGLSurface → 绑定到 GBM Surface │
│ ↓ │
│ ⑦ Scene Graph 开始渲染 │
│ ↓ │
│ ⑧ eglSwapBuffers() → DRM Page Flip → LCD 显示 │
└──────────────────────────────────────────────────────────┘
环境变量配置:
# 指定 DRM 设备
export QT_QPA_EGLFS_KMS_CONFIG=/etc/qt-eglfs-kms.json
# KMS 配置文件示例
{
"device": "/dev/dri/card0",
"outputs": [
{
"name": "HDMI-A-1",
"mode": "1024x600@60",
"size": { "width": 1024, "height": 600 }
}
]
}
# 其他常用变量
export QT_QPA_EGLFS_PHYSICAL_WIDTH=154 # 物理宽度mm(用于 DPI 计算)
export QT_QPA_EGLFS_PHYSICAL_HEIGHT=86 # 物理高度mm
export QT_QPA_EGLFS_ROTATION=90 # 屏幕旋转
export QT_LOGGING_RULES="qt.qpa.*=true" # 调试:打印 QPA 日志
# 1.7.3 LinuxFB:纯软件渲染退路
当设备没有 GPU 或 GPU 驱动不可用时,使用 LinuxFB:
┌──────────────────────────────────────────────────────────┐
│ LinuxFB 渲染路径 │
│ │
│ Qt Scene Graph → QSG Software Renderer(CPU) │
│ ↓ │
│ QImage(内存中的位图) │
│ ↓ │
│ mmap(/dev/fb0) → 直接写入 Framebuffer │
│ ↓ │
│ LCD 控制器扫描输出 │
│ │
│ 特点: │
│ • 无需 GPU 驱动 │
│ • CPU 占用高(所有渲染在 CPU 完成) │
│ • 适合简单界面 / 调试阶段 │
│ • 动画帧率受限(复杂界面 15~25fps) │
└──────────────────────────────────────────────────────────┘
使用方式:
./myapp -platform linuxfb:fb=/dev/fb0:size=1024x600
# 1.7.4 Wayland:现代合成器方案
┌──────────────────────────────────────────────────────────┐
│ Wayland 在嵌入式中的角色 │
│ │
│ 适用场景:需要多个 GUI 进程共享屏幕 │
│ 例如:仪表盘(全屏) + 弹窗通知(叠加) + 虚拟键盘(浮层) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 仪表盘App │ │ 通知App │ │ 键盘App │ │
│ │ (Client) │ │ (Client) │ │ (Client) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ wl_surface │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ Wayland Compositor (如 Weston/QtWayland)│ │
│ │ 负责窗口合成、输入分发 │ │
│ └──────────────────┬─────────────────────┘ │
│ ↓ │
│ DRM/KMS → LCD │
└──────────────────────────────────────────────────────────┘
相比 EGLFS:
• EGLFS = 单应用独占屏幕(更简单、更高效)
• Wayland = 多应用共享屏幕(更灵活、多一层合成开销)
# 1.8 QML 嵌入式生态能力
# 1.8.1 硬件通信(串口/CAN/GPIO)
// 串口通信 —— Qt SerialPort
#include <QSerialPort>
#include <QSerialPortInfo>
class SensorReader : public QObject {
Q_OBJECT
Q_PROPERTY(double temperature READ temperature NOTIFY temperatureChanged)
public:
explicit SensorReader(QObject *parent = nullptr) : QObject(parent) {
m_serial.setPortName("/dev/ttyS1");
m_serial.setBaudRate(115200);
m_serial.open(QIODevice::ReadOnly);
connect(&m_serial, &QSerialPort::readyRead, this, &SensorReader::onData);
}
double temperature() const { return m_temp; }
signals:
void temperatureChanged();
private slots:
void onData() {
QByteArray data = m_serial.readAll();
m_temp = parseTemperature(data);
emit temperatureChanged(); // QML 中绑定此属性的 UI 自动更新
}
private:
QSerialPort m_serial;
double m_temp = 0.0;
};
// CAN 总线 —— Qt SerialBus
#include <QCanBus>
#include <QCanBusDevice>
class CanController : public QObject {
Q_OBJECT
public:
void init() {
QString errorString;
m_device = QCanBus::instance()->createDevice("socketcan", "can0", &errorString);
if (m_device) {
m_device->connectDevice();
connect(m_device, &QCanBusDevice::framesReceived, this, &CanController::onFrames);
}
}
private slots:
void onFrames() {
while (m_device->framesAvailable()) {
QCanBusFrame frame = m_device->readFrame();
// frame.frameId(), frame.payload()
processCanFrame(frame);
}
}
private:
QCanBusDevice *m_device = nullptr;
};
# 1.8.2 多媒体(摄像头/音视频)
// QML 中直接使用摄像头
import QtMultimedia 5.15
Rectangle {
width: 640; height: 480
Camera {
id: camera
deviceId: "/dev/video0"
}
VideoOutput {
anchors.fill: parent
source: camera
}
// 拍照
MouseArea {
anchors.fill: parent
onClicked: camera.imageCapture.capture()
}
}
# 1.8.3 网络与 OTA 升级
// OTA 升级框架示例
class OtaManager : public QObject {
Q_OBJECT
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
public:
Q_INVOKABLE void checkUpdate() {
QNetworkRequest req(QUrl("https://ota.example.com/api/check"));
auto *reply = m_nam.get(req);
connect(reply, &QNetworkReply::finished, this, &OtaManager::onCheckDone);
}
Q_INVOKABLE void startDownload(const QString &url) {
auto *reply = m_nam.get(QNetworkRequest(QUrl(url)));
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 recv, qint64 total) {
m_progress = total > 0 ? (recv * 100 / total) : 0;
emit progressChanged();
});
}
signals:
void progressChanged();
void updateAvailable(const QString &version, const QString &url);
private:
QNetworkAccessManager m_nam;
int m_progress = 0;
};
# 1.8.4 数据库与本地存储
// SQLite 本地存储
#include <QSqlDatabase>
#include <QSqlQuery>
class DataLogger : public QObject {
Q_OBJECT
public:
void init() {
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("/data/sensor_log.db");
db.open();
QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS readings ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,"
"sensor_id INTEGER,"
"value REAL)");
}
Q_INVOKABLE void logReading(int sensorId, double value) {
QSqlQuery query;
query.prepare("INSERT INTO readings (sensor_id, value) VALUES (?, ?)");
query.addBindValue(sensorId);
query.addBindValue(value);
query.exec();
}
};
# 1.9 完整项目示例
# 1.9.1 项目结构
embedded-hmi/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── sensorbackend.h
├── qml/
│ ├── main.qml
│ └── GaugeWidget.qml
└── deploy/
├── run.sh
└── qt-eglfs-kms.json
# 1.9.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(EmbeddedHMI VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt6 REQUIRED COMPONENTS Core Quick SerialPort)
qt_add_executable(embedded-hmi
src/main.cpp
)
qt_add_qml_module(embedded-hmi
URI EmbeddedHMI
VERSION 1.0
QML_FILES
qml/main.qml
qml/GaugeWidget.qml
)
target_link_libraries(embedded-hmi PRIVATE
Qt6::Core
Qt6::Quick
Qt6::SerialPort
)
# 交叉编译时设置 sysroot
if(CMAKE_CROSSCOMPILING)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
endif()
# 1.9.3 main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "sensorbackend.h"
int main(int argc, char *argv[])
{
// 嵌入式优化:禁用不必要的特性
qputenv("QT_IM_MODULE", "none"); // 无输入法
qputenv("QT_QPA_FONTDIR", "/usr/share/fonts");
QGuiApplication app(argc, argv);
// 创建后端对象
SensorBackend backend;
backend.init("/dev/ttyS1");
QQmlApplicationEngine engine;
// 将 C++ 后端暴露给 QML
engine.rootContext()->setContextProperty("sensorBackend", &backend);
engine.loadFromModule("EmbeddedHMI", "Main");
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
# 1.9.4 QML 界面
// qml/main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
id: root
visible: true
width: 1024; height: 600
title: "Industrial HMI"
color: "#0d1117"
// 顶部状态栏
Rectangle {
id: statusBar
anchors.top: parent.top
width: parent.width; height: 40
color: "#161b22"
RowLayout {
anchors.fill: parent
anchors.margins: 8
Text {
text: "工业监控系统 v1.0"
color: "#c9d1d9"
font.pixelSize: 16
}
Item { Layout.fillWidth: true }
Text {
text: Qt.formatDateTime(new Date(), "yyyy-MM-dd hh:mm:ss")
color: "#8b949e"
font.pixelSize: 14
Timer {
interval: 1000; running: true; repeat: true
onTriggered: parent.text = Qt.formatDateTime(new Date(), "yyyy-MM-dd hh:mm:ss")
}
}
}
}
// 仪表盘网格
GridLayout {
anchors.top: statusBar.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 16
columns: 4
rowSpacing: 12
columnSpacing: 12
Repeater {
model: 8
GaugeWidget {
Layout.fillWidth: true
Layout.fillHeight: true
sensorName: "传感器 " + (index + 1)
currentValue: sensorBackend.values[index] || 0
maxValue: 100
unit: "°C"
}
}
}
}
// qml/GaugeWidget.qml
import QtQuick 2.15
Rectangle {
id: gauge
radius: 8
color: "#21262d"
border.color: "#30363d"
border.width: 1
property string sensorName: "Sensor"
property real currentValue: 0
property real maxValue: 100
property string unit: ""
Column {
anchors.centerIn: parent
spacing: 8
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: gauge.sensorName
color: "#8b949e"
font.pixelSize: 12
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: currentValue.toFixed(1) + " " + unit
color: currentValue > maxValue * 0.8 ? "#f85149" : "#58a6ff"
font.pixelSize: 24
font.bold: true
Behavior on color { ColorAnimation { duration: 300 } }
}
// 进度条
Rectangle {
width: gauge.width * 0.7
height: 6
radius: 3
color: "#30363d"
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: parent.width * Math.min(currentValue / maxValue, 1.0)
height: parent.height
radius: 3
color: currentValue > maxValue * 0.8 ? "#f85149" : "#58a6ff"
Behavior on width { NumberAnimation { duration: 200 } }
Behavior on color { ColorAnimation { duration: 300 } }
}
}
}
}
# 1.9.5 交叉编译与运行
# === 交叉编译(在 x86 开发机上) ===
# 1. 设置交叉编译工具链
export CROSS_COMPILE=aarch64-linux-gnu-
export SYSROOT=/opt/aarch64-sysroot
# 2. 使用 CMake 工具链文件
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=cmake/aarch64-toolchain.cmake \
-DCMAKE_SYSROOT=$SYSROOT \
-DQt6_DIR=$SYSROOT/usr/lib/cmake/Qt6
cmake --build build -j$(nproc)
# === 目标板运行 ===
# 3. 传输到目标板
scp build/embedded-hmi root@192.168.1.100:/opt/app/
# 4. 在目标板上运行
ssh root@192.168.1.100
export QT_QPA_PLATFORM=eglfs
export QT_QPA_EGLFS_KMS_CONFIG=/opt/app/qt-eglfs-kms.json
/opt/app/embedded-hmi
# deploy/run.sh —— 目标板启动脚本
#!/bin/bash
export QT_QPA_PLATFORM=eglfs
export QT_QPA_EGLFS_KMS_CONFIG=/opt/app/qt-eglfs-kms.json
export QT_QPA_EGLFS_PHYSICAL_WIDTH=154
export QT_QPA_EGLFS_PHYSICAL_HEIGHT=86
export QT_IM_MODULE=none
export QT_LOGGING_RULES="qt.qpa.*=false"
# 关闭光标
echo 0 > /sys/class/graphics/fbcon/cursor_blink
exec /opt/app/embedded-hmi "$@"
# 1.10 学习路径与速查表
# 1.10.1 从 0 到 1 的十周路径
┌──────────────────────────────────────────────────────────────────┐
│ 嵌入式 Qt/QML 开发十周学习路径 │
│ │
│ 第 1 周:环境搭建 │
│ • 安装 Qt Creator + Qt 6.x SDK │
│ • 桌面运行第一个 QML 程序 │
│ • 理解 qmake/CMake 基本用法 │
│ │
│ 第 2 周:QML 语法基础 │
│ • 基本类型(int/real/string/color/date) │
│ • 属性声明与绑定 │
│ • 信号与处理器(onClicked/onTriggered) │
│ │
│ 第 3 周:可视元素与布局 │
│ • Rectangle/Text/Image/MouseArea │
│ • anchors 锚定布局 │
│ • Row/Column/Grid/Flow 定位器 │
│ • RowLayout/ColumnLayout/GridLayout │
│ │
│ 第 4 周:组件与复用 │
│ • 自定义 QML 组件 │
│ • Loader/Component 动态加载 │
│ • StackView 页面导航 │
│ │
│ 第 5 周:动画与状态机 │
│ • PropertyAnimation/NumberAnimation/ColorAnimation │
│ • State + Transition │
│ • Behavior(属性行为) │
│ │
│ 第 6 周:Model-View │
│ • ListModel/ObjectModel │
│ • ListView/GridView/PathView │
│ • delegate 代理组件设计 │
│ │
│ 第 7 周:C++ 集成 │
│ • Q_PROPERTY 暴露属性到 QML │
│ • Q_INVOKABLE 暴露方法 │
│ • 注册自定义 QObject 类型 │
│ │
│ 第 8 周:硬件通信 │
│ • Qt SerialPort 串口通信 │
│ • Qt SerialBus (CAN/Modbus) │
│ • QProcess 执行系统命令 │
│ │
│ 第 9 周:嵌入式部署 │
│ • 交叉编译工具链配置 │
│ • EGLFS 配置与调试 │
│ • 启动速度优化 │
│ │
│ 第 10 周:综合项目 │
│ • 完成一个工业 HMI 项目 │
│ • 性能分析(QML Profiler) │
│ • 打包发布 + 开机自启 │
└──────────────────────────────────────────────────────────────────┘
# 1.10.2 技术选型速查表
| 决策因素 | 选 QML | 选 LVGL | 选 HTML5 |
|---|---|---|---|
| GPU 可用 | ✅ 必须 | 可选 | ✅ 需要 |
| RAM | ≥ 128MB | ≥ 32KB | ≥ 512MB |
| 动画需求 | 复杂/60fps | 简单/30fps | 中等 |
| 开发语言 | QML + C++ | C | HTML/CSS/JS |
| 人才获取 | C++/Qt 工程师 | 嵌入式 C 工程师 | 前端工程师 |
| 硬件通信 | 丰富(串口/CAN/蓝牙) | 需自实现 | 需 Native 桥接 |
| 长期维护 | Qt 商业支持 | 社区 | Chromium 跟进困难 |
| 认证需求 | 有工业认证案例 | 少 | 少 |
# 1.10.3 本专栏路线图
本专栏 14 篇文章路线图:
① 嵌入式GUI技术全景 ← 你在这里
↓
②③④ 原理篇:引擎/语法/绑定
↓
⑤⑥ 界面篇:布局/事件
↓
⑦⑧⑨ 高级篇:Model-View/动画/Canvas
↓
⑩⑪ 集成篇:C++/SceneGraph
↓
⑫⑬⑭ 嵌入式篇:交叉编译/渲染后端/性能优化
下一篇:02.QML引擎与渲染原理 —— 深入 QML 引擎内部:从 .qml 文件解析到 Scene Graph 渲染的完整链路。