随着汽车座舱智能化的不断演进,车内显示设备的数量显著增加,从传统的仪表盘和中控屏扩展至空调控制、扶手、副驾驶区域以及抬头显示(HUD)等多样化的显示单元。为了有效支持这些功能单元,同时控制整车成本,越来越多的汽车制造商开始采用微控制器单元(MCU)芯片。MCU以其低功耗、高集成度和经济性,成为降低成本的优选方案。
但由于平台的计算能力和存储空间受限,如何有效利用有限的资源来实现功能丰富、页面切换平滑的HMI应用变得至关重要。资源是否被合理的使用将直接影响应用的性能和用户体验。如果计算能力或存储空间使用不当,程序可能在PC上运行流畅,但在MCU上会遇到卡顿甚至崩溃的情况,无法满足设计目标。为应对此挑战,专为资源受限设备量身定制的工具——Qt for MCUs应运而生。凭借其极小内存占用和高度优化的库,Qt for MCUs旨在为MCU和低端MPU提供高性能HMI应用程序开发工具。采用Qt for MCUs在MCU平台上可以获得极佳的开发体验以及更好性能的HMI应用。
Qt for MCUs极大地减轻了开发人员在移植和调优方面的成本,但在资源受限的MCU平台上,性能优化始终是一项不可回避的挑战。
本文将基于Qt for MCUs工具,探讨如何通过精简代码、提升运行效率以及优化内存和资源管理等策略,来提高MCU平台下的HMI开发效率和应用性能。
一、降低代码量
减少代码量不仅能够降低程序对存储空间的需求,还能简化程序结构,使代码更加清晰。这有助于系统承载更多功能,同时还可以避免因内存不足导致的系统崩溃。简化的代码逻辑减少了CPU的分支判断,从而减轻了处理器的负担。此外,减少的运行时间还能降低能耗,并且有助于编译器更有效地优化代码,生成更高效的机器码。
在Qt for MCUs平台,我们推荐从以下三个方面对代码量进行优化:
1、减少锚点的使用
锚点是一种布局方式,用于将一个元素相对于另一个元素进行定位。
通过设置元素的anchors属性,可以将元素的边缘与其父元素或其他元素的边缘对齐。锚点可以在不同大小的屏幕上自适应,并且可以根据不同的屏幕方向进行动态调整。每个项目都可以被认为有一组 6 条不可见的“锚线”:
左 | 水平中心 | 右 | 顶部 | 垂直中心 | 底部 |
使用锚点进行开发虽然提供了布局的灵活性,但过度依赖它们可能会带来一些弊端。特别是在界面尺寸固定的情况下,锚点的动态计算过程不仅增加了代码的复杂度,还会带来不必要的性能开销。
通过直接指定坐标位置来替代部分锚点,可以简化代码结构,降低代码行数,从而提高代码的可读性和可维护性。
让我们通过一个简单的图形示例来比较 锚点定位 和 绝对定位 两种实现方式的不同。
下表左侧代码为绝对布局实现方式,右侧代码是锚点布局实现方式
import QtQuick Item { id: root width: 500 height: 200 Rectangle { id: rect x: 50 y: 50 width: 200 height: 100 color: "#ED8E00" } } | import QtQuick Item { id: root width: 500 height: 200 Rectangle { id: rect anchors.left: parent.left anchors.top: parent.top anchors.leftMargin: 50 anchors.topMargin: 50 width: 200 height: 100 color: "#ED8E00" } } |
对比可以看出锚点布局的实现方式相较于绝对布局方式代码行数要多,且从代码逻辑上看,计算逻辑要更为复杂。
- 使用Repeater
Repeater是Qt for MCUs提供的一个自动重复的功能组件。通过使用Repeater可以方便的创建多个相似的控件,而无需手动编写重复的布局和逻辑代码。这不仅可以减少代码量,还可以提高界面的一致性和可维护性。
import QtQuick Item { ......... //Column布局矩形元素竖向排列 Column { spacing: 10 // 列元素之间的间距为10 Repeater { // 用于重复生成元素 model: 3 // 模型为3,生成三个矩形 //delegate 定义了每个重复元素的模板,这里每个元素都是矩形 delegate: Rectangle { id: rect width: 100 height: 100 color: getColor(index) // 使用预定义的函数获取矩形填充颜色 } } } .......... } |
通过上图的例子可以看出,创建三个矩形,我们可以通过将编辑好的矩形样式应用到Repeater内的组件中来实现,无需三次重复编写矩形控件代码和使用布局来设置它们的位置。同时还可以通过函数调用的方式为每个矩形设置不同的属性。这种方法不仅减少了代码量,还提高了代码的可维护性。
3、增加可复用组件
在UI界面开发中,经常会遇到默认的组件样式无法完全满足设计需求的情况。为了解决这一问题,我们可以将基础组件进行样式定制,以满足特定的设计要求。然后,将这些定制的组件封装到一个独立的文件中,创建为自定义组件。这样,我们就可以像使用官方组件一样,通过几行简单的代码来调用这些封装好的自定义组件。这种方法不仅提高了代码的复用性,减少了冗余,也提升了整体的代码质量,如下图所示。
除了提取重复使用的组件样式代码外,我们还可以通过将多处出现的逻辑代码抽取并封装到单独的文件中,实现代码的集中管理和统一维护。
二、提高运行效率
提升运行效率能够显著加快系统的响应速度,从而增强用户的使用体验。我们可以通过以下四种策略来提高运行效率:
1、减少动态绑定依赖次数
如图所示,当属性与其他属性绑定并形成依赖关系时,若属性值发生变化,依赖属性会被标记为“脏”状态。随后,系统会触发并处理所有标记为“脏”的事件,确保依赖属性能够及时更新其值。这种动态更新机制虽然有效,但其复杂的依赖管理和事件处理过程可能会导致性能下降。通过减少不必要的动态绑定和依赖次数,我们可以降低属性刷新的频率,进而提升系统的运行效率。
2、修改控件源代码
修改Qt for MCUs的控件源代码,可以暴露更多的控制接口,从而能够更直接地操作控件的属性和行为,减少中间层的开销,提高运行效率。
3、错峰加载与预加载
Loader是Qt for MCUs中用于加载UI资源的组件。修改Loader的加载策略可以有效平衡性能消耗,减少资源加载期间的卡顿现象。
错峰加载策略:分散资源或任务的加载时间
系统启动时可能会因资源争用而遇到性能下降或崩溃的问题。通过实施错峰加载机制,我们不是同时加载和初始化所有模块或任务,而是按计划的顺序和时间间隔分批进行。这种方法可以缓解启动时的资源竞争和高负载,确保系统的平稳运行。
预加载策略:提前加载资源或执行任务
提前加载资源
预加载策略指的是在页面实际显示给用户之前,就预先加载未显示的页面。这样做可以防止在页面加载过程中出现空白屏幕,从而提升用户体验。
为了实现系统性能的优化和用户体验的提升,我们需要根据具体状况,选择最合适的加载策略。
4、局部数据刷新
全局数据刷新会触发整个界面的重绘,造成不必要的性能损耗。通过改进刷新策略,采用局部配置数据刷新代替全局刷新,可以只更新界面中需要变化的部分,减少不必要的渲染工作,从而提高运行效率。
全局刷新
局部刷新
如上图所示,当数据发生变化时,首先进行数据对比,有数据变化的部分重新加载刷新数据,无数据变化的部分保持不变,无需再次加载,减少了渲染操作,提高了运行效率。
三、内存管理
高效的内存管理对于提升HMI的应用性能和稳定性至关重要。它有助于减少内存碎片、加快访问速度、降低分配成本、防止内存泄漏,并优化内存使用。在资源受限的MCU环境中,可以确保HMI应用的流畅运行。我们提供三种关键的内存管理策略,旨在优化内存使用。
1、单例模式
单例模式是一种设计模式,它保证一个类在整个应用中只有一个实例,并且提供了一个统一的访问点。这种模式通过一个特定的类来实现,这个类负责自己的实例化,并且保证只实例化一次。它允许我们直接访问这个唯一的实例,无需通过常规的实例化过程。这有助于降低系统复杂性,提高性能,并节省宝贵的资源。
2、内部申请机制
我们采用统一且预定义的内存管理机制,有效预防出现内存泄漏、碎片化和过度分配等问题。
上图展示了FreeRTOS中pvPortMalloc的内存分配流程,这是FreeRTOS为动态内存分配特别设计的函数,与标准C库中的函数类似,但专为FreeRTOS的任务调度和内存管理机制优化。FreeRTOS提供了五种内存管理方案:
- heap_1:最基础的实现,不支持内存释放。
- heap_2:支持内存释放,采用简单的连续内存分配算法,可能产生内存碎片。
- heap_3:使用标准库的malloc()和free()函数,依赖于标准库的实现。
- heap_4:提供最佳平衡,采用首次适应算法,并通过合并空闲块减少碎片化。
- heap_5:最灵活,支持多个独立堆区域,适合复杂内存管理场景。
开发者可以根据设备条件和需求选择最合适的内存管理方案。
3、内存池技术
内存碎片是嵌入式系统中普遍存在的问题,尤其是在频繁申请和释放小块内存时更为明显。内存池技术通过预先分配一定数量的小块内存,并在程序运行期间循环使用这些内存块来解决这一问题。需要内存时,直接从内存池中获取;不再需要时,则将其返还给内存池,而不是释放回系统。这种方法减少了内存的频繁分配与释放,有效防止了内存碎片的产生。此外,内存池还有助于优化内存对齐,进一步提升内存的使用效率。
如上图所示,程序启动时,我们预先分配一定数量的固定大小内存块。在申请内存时,直接从这些预分配的块中获取;释放内存时,则将其放回预分配的块中,以便再次使用。每个内存池针对特定的使用场景设计,与传统的malloc/free相比,它减少了复杂的逻辑处理,可以显著提高性能。
四、资源管理
资源管理的核心在于优化图片资源的性能,我们采用以下三种策略来应对系统对图片资源的各种需求,目的是不影响用户体验的前提下,降低图片存储的开销。
1、BorderImage
BorderImage属性允许在用户界面中绘制可伸缩的边框图像,非常适合创建大小可变且边缘不失真的UI元素,例如按钮、面板和背景。当处理图像内容简单(如纯色图片)时,BorderImage可以替代传统的Image属性。它能够无损放大图像边缘,防止拉伸时的失真,同时减少了存储空间中不必要的像素数据。在某些特殊场景下,这种方法几乎可以避免图像质量损失,具体效果如下方图示。
2、ColorizedImage
ColorizedImage组件用于展示图片并应用颜色遮罩,能够将指定颜色应用于原始图像,从而生成具有不同色彩效果的图像。当面对内容相同但颜色各异的大量图像时,ColorizedImage提供了一种高效的解决方案:只需存储一张基础图片并进行动态上色,这样可以大幅减少因颜色不同而需存储的多张图片所占用的存储资源。然而,这种方法在执行效率上可能略低于直接使用Image,因此更适合于小型图标以及显示数量较少的场景,具体效果可以参考以下图示。
3、降低图片编码渲染格式
Qt for MCUs针对图片资源提供了多种格式选项,允许开发者根据不同场景和图片特性选择最合适的格式,以优化存储效率。每种图片格式都有其特点和占用空间大小,合理选择格式有助于在图片资源的存储优化和MCU的计算能力之间找到最佳平衡点。
图像格式 | 格式描述 | 清晰度描述 | 像素占用 |
ARGB8888 | 拥有透明度、红、绿、蓝四个通道,每个通道占用8位 | 可最大化保证图片质量和原图一致 | 32 |
ARGB4444 | 拥有透明度、红、绿、蓝四个通道,每个通道占用4位 | 将会损失绝大部分的色彩细节,在复杂图像肉眼可明显感受到失真 | 16 |
RGB888 | 拥有红、绿、蓝三个通道,每个通道占用8位 | 可最大化保证图片质量和原图一致,但无法实现显示透明像素的效果 | 24 |
RGB565 | 拥有红、绿、蓝三个通道,其中,红色和蓝色通道占用5位,绿色通道占用6位 | 可在图像质量和图像大小之间取得较好的平衡,对于复杂色彩的图像可能失真,但肉眼感知不明显 | 16 |
RGB444 | 拥有红、绿、蓝三个通道,每个通道占用4位 | 将会损失绝大部分的色彩细节,在复杂图像肉眼可明显感受到失真且无法显示透明像素效果 | 12 |
通过在合适的场景下使用适合的图片存储格式可平衡显示效果和存储、加载的空间占用,如下图所示,上方图片为在RGB444下的显示效果,下方图片为在RGB888下的显示效果,在色彩丰富的情况下可明显看出两者的区别,但同大小图RGB888的存储占用是RGB444的2倍,但在图像颜色简单的情况下者区别肉眼
本文深入探讨了使用Qt for MCUs进行HMI应用开发时,通过优化代码、提升运行效率、管理内存以及合理使用资源来提高应用性能的策略。这些优化措施使开发者能够在资源有限的MCU平台上构建高效且功能丰富的HMI应用,确保用户获得流畅、稳定的操作体验。随着技术的不断发展,我们将探索和分享更多的优化方法,进一步提升MCU平台上的HMI应用性能。
下一章,我们将结合实际案例,深入开展Qt for MCUs的实战演练,通过具体示例详细讲解HMI开发过程中的各项优化策略的应用及其效果,帮助开发者全面掌握通过Qt for MCUs来进行HMI开发的实战开发技巧。