2. 嵌入式UI基础

在桌面或移动端开发中,UI 工程师通常默认拥有充足的内存、强大的 GPU、统一的窗口系统和丰富的输入事件模型。但嵌入式 UI 面对的是另一套现实:MCU 可能只有几十 KB RAM,屏幕通过 SPI 逐行刷新,触摸事件需要和电机控制、通信任务共享同一个 CPU。理解这些差异,是做好组件复用设计的前提。

一、嵌入式 UI 的四个本质特点

嵌入式 UI 不是“缩小版的手机界面”,它有几个本质特征:

  • 确定性优先于丰富性:界面必须在规定时间内完成刷新和响应,动画可以少,但不能卡顿或丢帧。
  • 资源强约束:RAM、Flash、CPU 主频、总线带宽、功耗预算都是硬上限,组件不能随意申请堆内存。
  • 软硬件耦合深:UI 往往直接操作显示控制器、触摸芯片、GPIO 背光,甚至与 RTOS 任务优先级绑定。
  • 生命周期长:工业设备、医疗仪器、车载终端的 UI 可能持续维护 5~10 年,组件接口必须稳定可复用。
核心判断:一个嵌入式 UI 组件是否合格,不只看“画得好不好看”,更要看它在 64KB RAM、无 MMU、无文件系统的环境下能否稳定运行。

二、资源约束:组件复用的第一道门槛

资源约束直接决定组件的设计方式。下面这张表列出了典型嵌入式 UI 场景中的资源边界与对应设计策略:

资源维度 典型约束 对组件设计的影响
RAM 32KB~512KB 避免动态分配,使用静态缓冲或内存池;控件实例尽量小
Flash 256KB~4MB 字库、图标、图片需压缩或按需加载;代码体积敏感
CPU 48MHz~400MHz 避免全屏重绘,采用局部刷新和脏矩形机制
显示总线 SPI 10~80MHz / RGB / MIPI SPI 屏需 DMA 传输,组件应支持分块渲染
功耗 电池供电,mA 级预算 支持休眠、降帧、背光调节等状态管理

因此,嵌入式 UI 组件复用的核心原则是:接口统一、实现轻量、内存可控、渲染可裁剪。一个按钮组件在 PC 上可以随意 new,在 MCU 上则必须支持静态实例化。

三、实时性要求:UI 与任务调度的协同

嵌入式 UI 的实时性通常体现在两个层面:输入响应延迟和画面刷新周期。在 RTOS 环境下,UI 任务往往不是最高优先级,但必须保证在可接受的时间内完成事件处理。

常见的做法是把 UI 拆成“事件采集—状态更新—渲染输出”三段,并分别放入不同优先级或不同触发时机:

/* 典型 RTOS UI 任务划分 */
TouchTask   (优先级 4) : 读取触摸坐标 -> 投递事件队列
UITask      (优先级 3) : 从队列取事件 -> 更新控件状态 -> 标记脏区
RenderTask  (优先级 2) : 合并脏区 -> 调用组件 draw() -> DMA 刷屏

如果所有工作都塞进一个任务,触摸采样会被渲染阻塞,出现“按了没反应”或“拖动跳点”。组件复用时要特别注意:draw() 必须是可重入且无阻塞的,不能在里面等待信号量或做长耗时计算。

经验值:对于 60Hz 以下的小屏,单帧渲染预算通常控制在 10~16ms;触摸到视觉反馈的端到端延迟建议小于 100ms。

四、常见显示与输入设备

嵌入式 UI 的显示设备种类繁多,组件设计需要屏蔽差异。常见组合如下:

  • 单色 LCD / OLED:128×64、192×64,通过 I2C/SPI 驱动,适合状态栏、菜单。
  • SPI TFT 彩屏:240×320、320×480,成本低,刷新带宽有限,需 DMA 和局部刷新。
  • RGB 并口屏:480×272、800×480,带 LCD 控制器,适合中高端 HMI。
  • MIPI DSI 屏:高分辨率,常见于带 Linux/Android 的嵌入式设备。
  • 段码屏 / 字符屏:家电、仪表常用,UI 组件以“段”或“字符位”为最小单位。

输入设备同样多样:电阻触摸、电容触摸、按键矩阵、旋转编码器、红外遥控。组件复用层应把输入抽象为统一事件,例如 PRESS、RELEASE、MOVE、KEY,而不是让每个控件直接读 GPIO。

五、开发环境概览与选型

嵌入式 UI 的开发环境通常由“芯片厂商 SDK + RTOS + GUI 库 + 构建工具”组成。常见组合包括:

  • STM32CubeMX + FreeRTOS + LVGL:入门友好,组件生态丰富。
  • ESP-IDF + LVGL:Wi-Fi/蓝牙场景常用,适合物联网 HMI。
  • NXP MCUXpresso + emWin:工业与汽车领域成熟方案。
  • Qt for MCUs / Embedded Wizard:适合复杂界面和跨平台复用。
  • Linux + Framebuffer/Wayland + Qt:高性能嵌入式设备的主流选择。

选择环境时,重点看三件事:组件是否可静态实例化、是否支持局部刷新、是否有稳定的输入抽象层。这三点直接决定后续组件复用工程的成本。

六、案例与误区复盘

某工业温控器项目最初在 320×240 SPI 屏上使用全屏刷新,UI 任务每 30ms 重绘一次,结果 CPU 占用率超过 70%,触摸响应明显迟滞。复盘后发现两个问题:一是按钮组件每次 draw 都重画整个背景;二是触摸事件在渲染任务中同步处理。

改造方案:

  1. 引入脏矩形机制,按钮只重绘自身区域。
  2. 触摸事件改为队列投递,UI 任务独立处理。
  3. 按钮组件增加 set_dirty() 接口,由状态变化触发重绘。

改造后 CPU 占用降到 25% 左右,触摸延迟从约 180ms 降到 60ms 以内。这个案例说明:嵌入式 UI 的性能问题,往往不是“画得太多”,而是“画得没有边界”。组件复用如果只复用外观、不复用刷新策略,就会把性能隐患带到每一个页面。

常见误区:直接把 PC 端 GUI 库的组件移植到 MCU,忽略动态内存、浮点运算和全屏重绘。结果往往是“能跑但不可用”,在低端芯片上尤其明显。
嵌入式 UI 系统分层与资源约束关系 应用组件层 按钮 / 列表 / 图表 / 弹窗 / 状态栏 —— 可复用组件,接口统一,静态实例化 UI 框架层 事件分发 / 脏矩形管理 / 局部刷新 / 控件树 / 样式与主题 驱动抽象层 显示驱动 / 触摸驱动 / 按键扫描 / DMA 传输 / 背光与功耗控制 硬件层 MCU / SPI·RGB·MIPI 屏 / 触摸芯片 / 按键矩阵 / 编码器 / 电源管理 资源约束:RAM / Flash / CPU / 功耗 实时性:响应延迟 / 刷新周期 / 任务优先级 组件复用发生在应用组件层与 UI 框架层之间;向下必须适配驱动抽象,向上必须满足实时性与资源约束

总结来看,嵌入式 UI 基础可以浓缩为一句话:在有限资源下,用确定的实时行为,把多样的显示与输入设备抽象成稳定可复用的组件接口。只有先理解资源、实时性和硬件差异,后续的组件复用工程才有坚实的落脚点。