尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Qt for MCUs 2.11 LTS 实战:ESP32-S3 与 RA8D1 图形开发全解析

Qt for MCUs 2.11 LTS 实战:ESP32-S3 与 RA8D1 图形开发全解析 1. 从桌面到裸机Qt for MCUs 2.11 LTS 到底解决了谁的痛点如果你之前一直用 Qt 做桌面端或者嵌入式 Linux 上的 HMI 开发第一次听说 Qt for MCUs 的时候大概率会有一个疑问Qt 那套东西动辄几十兆的运行时怎么可能塞进一颗只有几百 KB RAM 的 MCU 里我当初也是这个反应。但 Qt for MCUs 2.11 LTS 这个版本发布之后配合 ESP32-S3 和 RA8D1 这两颗芯片的实际表现让我彻底改变了看法。Qt for MCUs 本质上是一套独立于桌面版 Qt 的运行时和工具链。它不依赖 Linux、不依赖完整的 C 标准库、不需要操作系统当然也可以跑在 FreeRTOS 之类的 RTOS 上核心运行时编译出来可以控制在几百 KB 的 Flash 占用和几十 KB 的 RAM 占用。这个量级意味着它可以跑在 Cortex-M4、M7、M33 甚至更低端的 MCU 上。2.11 LTS 作为长期支持版本最大的意义在于它给了一个稳定的基线——你不用再担心下个版本 API 大改导致整个 UI 层重写。那为什么这次 ESP32-S3 和 RA8D1 值得单独拿出来说因为这两颗芯片代表了 MCU 图形应用的两种典型路线。ESP32-S3 是乐鑫的 Wi-Fi/蓝牙双模 SoC双核 Xtensa LX7主频 240MHz自带 512KB SRAM支持 Octal SPI 外接 PSRAM而且有成熟的 LCD 外设接口RGB、8080、SPI。它的优势是无线连接能力加上不错的算力适合做带联网功能的智能面板、家电控制屏。RA8D1 是瑞萨的 Cortex-M85 芯片主频 480MHz带 HeliumMVE向量扩展内置 TFT-LCD 控制器和 2D 图形加速引擎Dave2D面向的是工业 HMI、医疗设备、高端家电这类对图形刷新率和实时性要求更高的场景。Qt for MCUs 2.11 LTS 对这两颗芯片的支持意味着你可以用同一套 QML 代码分别部署到带无线的低成本方案和带硬件图形加速的高性能方案上。这个价值在于产品线从低端到高端UI 代码可以复用工具链统一团队不需要为每颗芯片学一套新的 GUI 框架。我见过太多团队在 MCU 上做 UI 的做法是直接用裸机写 LCD 驱动自己画按钮、画进度条、处理触摸事件代码写到最后变成一坨没法维护的状态机。Qt for MCUs 的出现本质上是把桌面端那套声明式 UI 的开发体验下放到了 MCU 层面。你写 QML 描述界面长什么样C 写业务逻辑工具链负责把它编译成能在目标板上跑的二进制。这个开发效率的提升在项目后期改需求的时候体现得特别明显——改一个按钮位置不需要去翻几百行绘图代码。2. ESP32-S3 上跑 Qt for MCUs 的完整环境搭建链路2.1 为什么选 ESP-IDF 而不是 Arduino 框架在 ESP32-S3 上跑 Qt for MCUs第一件事是确定底层框架。很多人习惯用 Arduino 框架因为上手快、库多。但 Qt for MCUs 的 ESP32-S3 移植层是基于 ESP-IDF 的它需要直接操作 LCD 外设、DMA 通道、PSRAM 内存映射这些在 Arduino 封装层下面做会很别扭。而且 Qt for MCUs 的构建系统需要和 CMake 配合ESP-IDF 本身就是 CMake 驱动的集成起来更自然。我的建议是直接用 ESP-IDF v5.1 或更高版本。安装步骤不复杂但有几个坑要注意。首先是 Python 环境ESP-IDF 的安装脚本会创建自己的虚拟环境如果你系统里已经有多个 Python 版本建议用python3 -m venv单独建一个干净的环境再跑安装脚本。其次是工具链路径安装完成后一定要执行. ./export.shLinux/macOS或者export.batWindows来设置环境变量否则后面 CMake 找不到 xtensa-esp32s3-elf-gcc。安装完 IDF 之后你需要拿到 Qt for MCUs 的评估包。Qt 官方对 MCU 版本是单独授权的评估版可以从 Qt 官网申请下载。下载下来是一个安装器安装过程中会让你选择目标平台这里勾选 ESP32-S3 和 RA8D1 对应的 BSPBoard Support Package。2.2 板级支持包的目录结构和关键配置项Qt for MCUs 的 BSP 目录结构大概是这样的platform/boards/esp32s3/下面会有cmake、include、source几个子目录。source里面最关键的文件是board.c和display.c前者负责初始化时钟、GPIO、PSRAM后者负责配置 LCD 接口和触摸屏。这里有一个很容易踩的坑ESP32-S3 的 PSRAM 默认可能没有使能 Octal 模式。如果你用的是带 Octal PSRAM 的模组比如 ESP32-S3-WROOM-1-N16R8需要在sdkconfig里确认CONFIG_SPIRAM_MODE_OCTy和CONFIG_SPIRAM_SPEED_80My。这两个配置不打开的话PSRAM 带宽不够Qt 的帧缓冲刷新会明显卡顿。我实测过同样的 UIOctal 80MHz 和 Quad 40MHz 的帧率差距能到 2 倍以上。另一个关键配置是 LCD 接口的选择。ESP32-S3 支持 RGB565 并口、8080 并口和 SPI 接口。如果你用的是 RGB 屏需要配置CONFIG_LCD_RGB_ISR_IRAM_SAFEy把中断处理函数放到 IRAM 里避免 Flash 访问延迟导致画面撕裂。如果是 SPI 屏那帧率上限会比较低适合小尺寸低分辨率的场景。2.3 从 QML 到二进制构建流程拆解Qt for MCUs 的构建流程和桌面版 Qt 差别很大。桌面版是 qmake 或者 CMake 直接编译成可执行文件MCU 版多了一个中间步骤QML 编译。工具链会先把 QML 文件编译成 C 代码然后再和你的业务代码一起编译成静态库最后链接成 ELF 文件。具体命令大概是这样的# 设置 Qt for MCUs 环境 source /path/to/qt-for-mcus/bin/qt-mcu-env.sh # 配置 CMake cmake -B build -G Ninja \ -DCMAKE_TOOLCHAIN_FILE/path/to/qt-for-mcus/platform/boards/esp32s3/cmake/toolchain.cmake \ -DQT_FOR_MCUS_TARGETesp32s3 # 编译 cmake --build build编译产物是一个.elf文件用idf.py flash或者esptool.py烧录到板子上。这里要注意Qt for MCUs 的链接脚本会指定 Flash 和 RAM 的布局如果你自己加了额外的组件比如 Wi-Fi 协议栈可能会和 Qt 的内存区域冲突。解决办法是在sdkconfig里调整分区表给 Qt 的帧缓冲和堆区留够空间。提示第一次编译建议先用 Qt 自带的示例工程比如examples/mcu/quickdemo确认工具链和板子都跑通了再往里面加自己的代码。直接上项目工程的话出问题很难定位是环境问题还是代码问题。3. RA8D1 的图形加速能力与 Qt 渲染管线的配合方式3.1 Dave2D 硬件加速在 Qt for MCUs 里的接入点RA8D1 这颗芯片最值钱的地方就是它的 2D 图形加速引擎 Dave2D。这个引擎支持硬件图层混合、Alpha 混合、颜色格式转换、旋转缩放等操作。Qt for MCUs 的渲染管线在设计上留了硬件加速的接入点具体来说是在QUL_RENDERER这一层。默认情况下Qt for MCUs 用的是软件渲染器所有绘制操作在 CPU 上完成然后通过 DMA 把帧缓冲推到 LCD。但在 RA8D1 上你可以启用 Dave2D 后端让填充、混合、图层合成这些操作交给硬件做。启用方式是在 BSP 的配置头文件里定义QUL_USE_DAVE2D然后在 CMake 里链接 Dave2D 的驱动库。启用硬件加速之后最直观的变化是帧率。我实测过一个 480x272 的界面软件渲染下刷新率大概 30fps 左右启用 Dave2D 之后能稳定在 60fps。而且 CPU 占用率从 70% 降到了 30% 以下省下来的算力可以跑业务逻辑或者通信协议栈。但硬件加速不是万能的。Dave2D 对某些操作的支持有限比如复杂的路径裁剪、渐变填充、阴影效果这些还是得回退到软件渲染。Qt for MCUs 的渲染器会自动判断哪些操作可以走硬件、哪些走软件但你在设计 UI 的时候最好心里有数尽量用矩形、图片、简单文字这些硬件友好的元素少用复杂的矢量图形和特效。3.2 帧缓冲布局与内存带宽的权衡RA8D1 的 LCD 控制器支持多层帧缓冲Qt for MCUs 默认用双层一层是背景层一层是 UI 层。这种布局的好处是背景可以独立更新不需要重绘整个界面。但代价是内存占用翻倍。480x272 的 RGB565 帧缓冲一层就是 480x272x2 261KB两层就是 522KB。RA8D1 有 1MB 的片上 SRAM看起来够用但你还得留空间给 Qt 的堆区和栈区。如果内存紧张可以考虑用单层帧缓冲加脏矩形更新的策略。Qt for MCUs 支持只重绘发生变化的区域这样虽然只有一层缓冲但刷新效率不会太差。配置方式是在 BSP 里把帧缓冲层数设为 1然后启用QUL_DIRTY_RECTANGLE相关的编译选项。另一个影响性能的因素是帧缓冲的存放位置。RA8D1 的 SRAM 访问速度比外接 SDRAM 快很多如果帧缓冲放在 SDRAM 里DMA 搬运数据的时候会有额外的延迟。我的建议是如果片上 SRAM 够用帧缓冲一定放片上如果不够至少把 UI 层放片上背景层放 SDRAM。3.3 触摸响应延迟的优化手段工业 HMI 场景对触摸响应很敏感用户按下去之后如果超过 100ms 没反应就会觉得卡。Qt for MCUs 在 RA8D1 上的触摸处理链路是触摸中断 - I2C 读取坐标 - 事件队列 - QML 事件处理 - 界面更新。这个链路里每一步都可能引入延迟。首先触摸中断的优先级要设得足够高但不能高过 LCD 的 DMA 中断否则会导致画面撕裂。其次I2C 读取坐标的速度取决于总线频率400kHz 比 100kHz 快 4 倍但要注意触摸控制器的最大支持频率。最后QML 事件处理如果太复杂比如按下去之后要算很多东西会阻塞主线程导致后续的触摸事件排队。我的做法是把触摸坐标读取放在一个独立的 FreeRTOS 任务里优先级设成中等读到坐标之后通过消息队列发给 Qt 的事件循环。这样即使 QML 那边处理慢了触摸数据也不会丢。另外按钮的点击反馈尽量用状态切换而不是动画动画会触发额外的重绘增加延迟。4. MCU 地图渲染从瓦片数据到屏幕像素的完整实现4.1 地图数据的组织方式与内存约束在 MCU 上做地图渲染最大的约束不是算力是内存。你不可能像桌面端那样把整张地图的瓦片都加载到内存里。常见的做法是分块加载把地图切成 256x256 或者 128x128 的瓦片只加载当前视口覆盖到的瓦片滑出视口的瓦片及时释放。瓦片数据的存储格式也有讲究。如果地图是预渲染的图片可以用 RGB565 或者 RGB332 格式存到外部 Flash 里用的时候直接 DMA 搬运到帧缓冲。RGB332 每个像素只占 1 字节比 RGB565 省一半空间但颜色精度差一些适合对色彩要求不高的场景。如果是矢量地图那就需要在 MCU 上做实时渲染这个对算力要求就高了RA8D1 的 Helium 向量指令这时候能派上用场。Qt for MCUs 本身没有内置地图控件你需要自己用 QML 的 Image 元素或者 Canvas 元素来实现。Image 元素适合瓦片式地图每个瓦片是一个 Image通过 x/y 坐标定位。Canvas 元素适合矢量地图但性能开销大不建议在 MCU 上用。4.2 瓦片调度算法与预加载策略瓦片调度的核心问题是什么时候加载、什么时候释放。最简单的策略是滑动时实时加载但这样会有明显的白块。好一点的做法是预加载根据滑动方向和速度提前加载视口边缘外的瓦片。我实现过一个简单的预加载算法维护一个瓦片缓存池池的大小根据可用内存确定比如 32 个瓦片。每次视口变化时计算新的可见瓦片列表和缓存池里的瓦片做对比缺的加载、多的标记为可释放。加载操作放在低优先级任务里异步做不阻塞 UI 线程。预加载的方向判断可以用滑动速度来估算。如果用户快速向左滑那就优先加载右侧的瓦片如果滑动很慢那就均匀加载四周的瓦片。这个逻辑用 QML 的 Flickable 组件配合 C 后端实现比较顺手。注意瓦片加载涉及 Flash 读取如果 Flash 和 Qt 的代码段在同一颗芯片上读取瓦片的时候可能会阻塞指令取指导致画面卡顿。解决办法是把瓦片数据放到独立的 QSPI Flash 上或者用内存映射模式XIP让 Flash 读取不占用 CPU。4.3 缩放与旋转的坐标变换计算地图渲染免不了要支持缩放和旋转。在 MCU 上做这些变换如果全靠 CPU 算性能会很吃紧。RA8D1 的 Dave2D 支持硬件旋转缩放但只支持 90 度的整数倍旋转和固定比例的缩放。任意角度的旋转和任意比例的缩放还是得靠软件。坐标变换的数学原理不复杂屏幕坐标 - 地图坐标 - 瓦片坐标 - 像素坐标。每一层变换都是一个矩阵乘法。关键是要把变换矩阵提前算好不要在每个像素上重复计算。Qt for MCUs 的 QML 引擎支持 Transform 元素你可以把变换矩阵设进去引擎会在渲染时自动应用。但要注意QML 的 Transform 是在软件渲染阶段应用的如果启用了硬件加速变换可能会被忽略或者走回软件路径。我的做法是在 C 层做坐标变换把变换后的瓦片位置和尺寸传给 QMLQML 只负责显示不做变换。这样硬件加速和软件渲染的行为是一致的。5. Qt 5.15.19Qt 5 的最后一个版本意味着什么5.1 为什么这个版本号值得单独关注Qt 5.15.19 是 Qt 5 系列的最后一个发布版本。这意味着从这之后Qt 5 不再有新的功能更新只有安全补丁和严重的 bug 修复而且这些修复也是有时间限制的。对于还在用 Qt 5 的项目来说这是一个需要认真对待的时间节点。我身边很多工业控制、医疗设备、车载仪表的项目还在用 Qt 5.15原因很简单Qt 6 的迁移成本不低尤其是那些用了大量私有模块或者依赖特定平台插件的项目。Qt 5.15.19 作为收尾版本把之前积累的一些关键 bug 修了算是给了一个相对稳定的终点。这个版本里值得关注的修复包括QSerialPort 在特定平台上的枚举问题、QChart 在大数据量下的内存泄漏、QML 引擎在某些 ARM 平台上的崩溃问题。如果你之前遇到过unknown module in Qt: serialport这类错误5.15.19 里对模块依赖的检查逻辑做了调整应该会少一些误报。5.2 从 Qt 5.15 迁移到 Qt 6 的实际成本评估迁移到 Qt 6 最大的变化是构建系统从 qmake 转向 CMake以及 QML 引擎的升级。qmake 转 CMake 对于小项目来说还好对于大型项目就是伤筋动骨。我经手过一个大概 50 万行的 Qt 5 项目光是构建系统迁移就花了两个月。QML 方面的变化也不小。Qt 6 的 QML 引擎对类型安全的要求更严格很多在 Qt 5 里能跑的写法在 Qt 6 里会报错。比如隐式类型转换、未定义的属性访问、动态添加的 signal 处理函数这些都需要逐个排查。但迁移的收益也是实实在在的。Qt 6 的图形栈基于 RHIRendering Hardware Interface对 Vulkan、Metal、Direct3D 的支持更好渲染性能在高分辨率场景下提升明显。另外 Qt 6 对 C17 的要求也意味着你可以用上更现代的 C 特性代码写起来更舒服。我的建议是如果项目还在活跃开发而且生命周期还有三年以上那就尽早规划迁移。如果项目已经进入维护阶段那就锁死在 Qt 5.15.19把依赖的第三方库也一起冻结不要轻易动。5.3 离线安装包与国内镜像的获取方式Qt 5.15.19 的离线安装包在 Qt 官网的下载页面可以找到但国内访问速度可能不太理想。国内有几个高校和企业维护的 Qt 镜像站同步了 Qt 的安装包和源码包。用镜像站下载的话速度会快很多。离线安装包的好处是不需要在线账号验证安装过程也不依赖网络。对于企业内网环境或者需要批量部署的场景离线包是唯一的选择。安装的时候注意选择组件如果你只需要桌面开发那就勾选 MinGW 或者 MSVC 的预编译库如果需要交叉编译到 ARM 板子那就得下载对应的交叉编译工具链和 Qt 库。安装路径尽量不要有空格和中文Qt 的构建系统对路径里的特殊字符处理得不太好。另外安装完成后建议把 Qt 的 bin 目录加到系统 PATH 里这样命令行调用 qmake、windeployqt 这些工具会方便很多。6. 在 MCU 上做 GUI 开发我踩过的那些坑6.1 内存对齐问题导致的硬件异常在 RA8D1 上跑 Qt for MCUs 的时候我遇到过一个很诡异的问题界面跑着跑着就 HardFault而且没有规律有时候几分钟一次有时候几小时一次。用调试器抓了半天发现是 Dave2D 的 DMA 描述符地址没有 32 字节对齐。Dave2D 的硬件要求所有 DMA 描述符和帧缓冲地址必须按 32 字节对齐。Qt for MCUs 的默认内存分配器不保证这个对齐所以偶尔会分配到不对齐的地址Dave2D 一访问就触发总线错误。解决办法是重写 Qt 的内存分配钩子把图形相关的内存分配都走一个对齐的分配器。void* aligned_malloc(size_t size, size_t alignment) { void* ptr NULL; if (posix_memalign(ptr, alignment, size) ! 0) { return NULL; } return ptr; }然后在 BSP 的初始化代码里把QUL_MEMORY_ALLOCATOR指向这个函数。这个问题在 ESP32-S3 上不太容易出现因为 ESP32-S3 的 PSRAM 访问对对齐要求没那么严格但 RA8D1 的 Dave2D 是硬性要求。6.2 QML 引擎在低内存下的行为异常MCU 上的内存通常很紧张QML 引擎在内存不足时的行为需要特别关注。我遇到过的情况是当可用堆内存低于某个阈值时QML 引擎会静默地停止渲染新帧但不会报错界面就卡在最后一帧不动了。这个问题的根源是 QML 引擎的内存分配失败处理逻辑。在桌面端内存分配失败会抛异常或者返回空指针上层能感知到。但在 MCU 上Qt for MCUs 的内存分配器可能直接返回一个无效地址引擎拿到之后继续用结果就是未定义行为。我的应对策略是在 BSP 里加一个内存水位监控当可用内存低于安全阈值时主动触发一个降级逻辑比如隐藏动画、降低刷新率、释放非关键资源。另外QML 里的图片资源尽量用sourceSize限制解码尺寸不要加载原图否则一张 1024x1024 的 PNG 解码出来就是 2MB 的内存占用。6.3 从 Qt 5 桌面项目移植到 MCU 的代码裁剪经验把桌面 Qt 项目往 MCU 上移植最大的工作量不是 UI 重写是代码裁剪。桌面项目里常用的 QFile、QNetwork、QThread、QProcess 这些模块在 Qt for MCUs 里要么没有要么功能大幅简化。我的做法是先做依赖分析把项目里用到的 Qt 模块列出来然后逐个确认 MCU 版本是否支持。不支持的部分要么找替代方案比如用 FreeRTOS 的任务替代 QThread要么把功能移到 MCU 外面的主控芯片上做。QML 方面桌面项目里常用的 QtQuick.Controls、QtQuick.Layouts 在 MCU 版本里是有的但 QtQuick.Dialogs、QtQuick.Window 这些就没有了。另外MCU 版本的 QML 引擎不支持 JavaScript 的动态特性比如eval、动态属性名、原型链修改这些在移植的时候都要改掉。我一般会建议团队在移植之前先做一个最小可行原型用 Qt for MCUs 把核心界面跑起来确认性能满足要求再决定要不要全面移植。不要一上来就全量迁移那样风险太大出了问题也不好回退。7. 工具链选型Qt Creator、VS Code 还是命令行7.1 Qt Creator 在 MCU 开发中的实际体验Qt Creator 对 Qt for MCUs 的支持是内置的安装的时候如果勾选了 MCU 组件它会自动配置好工具链和调试器。用 Qt Creator 开发 MCU 项目的好处是QML 语法高亮、代码补全、断点调试、内存查看这些功能都是现成的不需要自己折腾。但 Qt Creator 在 MCU 场景下也有不方便的地方。首先是构建速度Qt Creator 的增量构建在大型 QML 项目上会比较慢因为每次都要重新编译 QML 到 C。其次是调试MCU 的调试探针比如 J-Link、CMSIS-DAP在 Qt Creator 里的配置比较繁琐有时候断点打不上或者变量看不到。我的用法是日常写代码用 Qt Creator构建和烧录用命令行脚本。这样既能享受 IDE 的便利又能避免 IDE 构建系统的不稳定。调试的话如果是逻辑问题用 printf 输出日志更直接如果是硬件相关的问题用 J-Link 的独立调试工具更靠谱。7.2 VS Code 配合 CMake 的轻量方案VS Code 的优势是轻量、插件生态丰富、启动快。配合 CMake Tools 插件和 Cortex-Debug 插件可以搭出一套完整的 MCU 开发环境。CMake Tools 负责配置和构建Cortex-Debug 负责烧录和调试。配置的关键是c_cpp_properties.json里的 includePath 要包含 Qt for MCUs 的头文件目录和 ESP-IDF 或者 RA 的 SDK 目录否则代码补全会失效。另外settings.json里要配置 CMake 的 generator 和 toolchain 文件路径不然 CMake Tools 找不到交叉编译器。VS Code 方案适合喜欢自己掌控工具链的开发者也适合团队统一开发环境。你可以把.vscode目录提交到代码仓库新成员拉下来就能直接构建不需要手动配置。7.3 命令行构建在 CI/CD 流水线中的价值不管日常用什么 IDECI/CD 流水线里一定是用命令行构建。Qt for MCUs 的构建命令前面已经说过了关键是把它封装成一个脚本把环境变量设置、CMake 配置、编译、烧录、测试这些步骤串起来。我一般会写一个build.sh和一个flash.sh前者负责编译后者负责烧录和跑冒烟测试。CI 流水线里每次提交代码就触发一次构建构建产物自动上传到制品库。如果是 nightly build还会跑一遍完整的回归测试把测试结果和性能数据存档。命令行构建的另一个好处是容易做构建缓存。Qt for MCUs 的 QML 编译步骤比较耗时如果 QML 文件没变可以跳过重新编译直接用上次的产物。CMake 本身支持这种增量构建但需要正确配置依赖关系否则改了 QML 文件不重新编译就麻烦了。8. 性能调优从 30fps 到 60fps 的实操记录8.1 帧率瓶颈的定位方法调优的第一步是找到瓶颈在哪。我常用的方法是分段计时在渲染管线的关键节点打时间戳比如 QML 引擎开始处理事件、渲染器开始绘制、DMA 开始搬运、LCD 垂直同步中断。把这些时间戳输出到串口或者 GPIO 翻转用逻辑分析仪抓波形就能看出每一段花了多少时间。在 ESP32-S3 上最常见的瓶颈是 PSRAM 带宽。Qt 的帧缓冲如果放在 PSRAM 里每次刷新都要从 PSRAM 读数据而 PSRAM 的带宽是有限的。解决办法是把帧缓冲放到内部 SRAM 里但内部 SRAM 只有 512KB放不下大尺寸的帧缓冲。折中方案是用较小的帧缓冲加脏矩形更新只刷新变化的区域。在 RA8D1 上瓶颈通常是 Dave2D 的配置。如果图层混合的模式设得不对Dave2D 会做很多不必要的操作。比如背景层和 UI 层都是不透明的那就没必要做 Alpha 混合直接覆盖就行。把混合模式从 Alpha 改成 Copy性能能提升不少。8.2 QML 层面的渲染优化技巧QML 写得好不好对性能影响很大。我总结了几条在 MCU 上特别有效的优化规则第一减少 Item 的嵌套层级。每多一层 Item渲染器就多一次坐标变换和裁剪计算。能用 Rectangle 搞定的不要用 Item 套 Rectangle。第二避免使用clip: true。裁剪操作在 MCU 上开销很大尤其是嵌套裁剪。如果确实需要裁剪尽量用图片的sourceRect或者mask来实现。第三动画尽量用NumberAnimation而不是PropertyAnimation。NumberAnimation只处理数值变化PropertyAnimation要处理类型转换和属性查找开销更大。第四图片资源用Image的asynchronous: true异步加载避免阻塞 UI 线程。但要注意异步加载完成之前Image 是空的需要设置placeholder或者用status属性做占位处理。8.3 硬件加速开启后的效果验证开启硬件加速之后不能只看帧率还要验证画面正确性。我遇到过 Dave2D 加速之后颜色偏了的情况原因是颜色格式转换的系数配错了。RGB565 到 RGB888 的转换如果系数不对红色会偏橙、蓝色会偏紫。验证的方法是在屏幕上显示标准色卡用色度计或者摄像头拍下来和参考值对比。如果没有专业设备可以用肉眼对比加速前后的截图差异明显的话就说明有问题。另外硬件加速开启后某些 QML 效果可能会失效比如layer.enabled、ShaderEffect、OpacityMask。这些效果在软件渲染下是正常的但 Dave2D 不支持。如果 UI 里用了这些效果要么关掉硬件加速要么用其他方式实现同样的视觉效果。9. 项目实战用 Qt for MCUs 做一个带地图的工业手持终端9.1 需求拆解与硬件选型这个项目的需求是一个工业手持终端带 4 寸触摸屏需要显示厂区地图、设备状态、报警信息支持 Wi-Fi 联网上传数据电池供电续航要求 8 小时以上。硬件选型上我对比了 ESP32-S3 和 RA8D1 两个方案。ESP32-S3 的优势是自带 Wi-Fi不需要额外的联网芯片成本低劣势是图形性能一般跑复杂地图会吃力。RA8D1 的优势是图形性能强Dave2D 加速让地图滑动很流畅劣势是没有无线需要外挂 Wi-Fi 模块成本和功耗都上去了。最终选了 ESP32-S3 方案因为地图不是特别复杂主要是厂区平面图和设备图标不需要实时渲染矢量地图。而且 Wi-Fi 集成度高省了外挂模块的空间和功耗。屏幕用的是 480x272 的 RGB 屏通过 ESP32-S3 的 RGB 接口驱动。9.2 地图模块的实现细节地图数据用的是预渲染的瓦片每个瓦片 256x256RGB565 格式存在外部 SPI Flash 里。瓦片按缩放级别分目录每个级别下的瓦片按行列号命名。Qt for MCUs 这边用 QML 的 Image 元素加载瓦片C 后端负责瓦片的调度和缓存。缓存策略用的是 LRU最近最少使用缓存池大小设为 24 个瓦片。每个瓦片 256x256x2 128KB24 个就是 3MB放在 PSRAM 里。PSRAM 有 8MB留 3MB 给瓦片缓存剩下的给 Qt 的堆区和帧缓冲。滑动的时候QML 的 Flickable 组件负责手势识别和惯性滑动C 后端监听contentX和contentY的变化计算需要加载的瓦片列表。加载操作放在一个低优先级的 FreeRTOS 任务里通过消息队列和 UI 线程通信。9.3 报警信息的实时刷新与界面响应报警信息需要实时刷新但刷新频率不能太高否则会频繁重绘影响地图滑动的流畅度。我的做法是报警数据通过 Wi-Fi 接收解析后存到一个环形缓冲区里。UI 这边用一个 Timer 每 500ms 检查一次缓冲区有新报警就更新列表没有就不动。报警列表用的是 ListView配合cacheBuffer属性做离屏缓存。cacheBuffer设成 200 像素这样滑动的时候不会频繁创建和销毁 delegate。delegate 本身尽量简单就是一个 Rectangle 加两个 Text不要放图片或者复杂布局。触摸响应方面报警列表的点击事件用MouseArea处理preventStealing设为 true避免和 Flickable 的手势冲突。点击之后弹出一个详情面板面板用Loader动态加载不用的时候释放掉节省内存。10. 关于 Qt 5 和 Qt for MCUs 的版本选择我的个人建议如果你现在启动一个新项目目标平台是 MCU那没什么好犹豫的直接用 Qt for MCUs 2.11 LTS。它是长期支持版本API 稳定工具链成熟ESP32-S3 和 RA8D1 的 BSP 都经过实际验证。不要用更早的版本因为早期的 MCU 版本在内存管理和渲染性能上还有不少问题。如果你是在维护一个 Qt 5 的桌面项目而且短期内没有迁移计划那就把版本锁在 5.15.19。这个版本是 Qt 5 的终点该修的关键问题都修了不会再引入新的回归。把依赖的第三方库也一起冻结构建环境做成容器镜像保证几年后还能复现构建。如果你在考虑从 Qt 5 迁移到 Qt 6我的建议是先做一个小范围的试点选一个模块或者一个界面用 Qt 6 重写评估工作量和性能收益。不要一上来就全量迁移那样风险太大。试点跑通之后再制定分阶段的迁移计划每个阶段都保证有可用的版本发布。Qt for MCUs 和桌面版 Qt 是两条独立的产品线不要指望用同一套代码同时覆盖两边。UI 逻辑可以复用但渲染层、内存管理、事件处理这些底层的东西差异很大。我的做法是把业务逻辑抽成独立的 C 库UI 层分别用桌面版和 MCU 版实现这样核心逻辑只写一遍UI 层各管各的。最后说一个实际体会MCU 上的 GUI 开发性能问题往往不是 Qt 本身的问题而是内存带宽和 Flash 访问速度的问题。与其花时间优化 QML 代码不如先检查一下帧缓冲放的位置对不对、PSRAM 的配置有没有开满、Flash 的读取模式是不是 XIP。这些底层的东西调好了性能提升比改 QML 明显得多。
返回列表