Slint 打印机 Demo 在 Zephyr 上的构建与移植实战:从 native_sim 模拟器到 MIMXRT1170 / EK-RZ/A3M 真机
【免费下载链接】slintSlint is an open-source declarative GUI toolkit to build native user interfaces for Rust, C++, JavaScript, or Python apps.项目地址: https://gitcode.com/GitHub_Trending/sl/slint
本篇技术指南以 demos/printerdemo/zephyr/README.md 为核心骨架,完整讲解 Slint Printer Demo 在 Zephyr RTOS 上的环境搭建、模拟器构建运行、两块真实开发板的构建烧录流程,并结合仓库中 zephyr-common 平台集成的源码实现,深入剖析显示驱动接入、RGB565 像素字节序处理、面板旋转、触摸事件映射与事件循环等底层原理。读完本文,你不仅能亲手在 Zephyr 上跑起 Slint 示例,还能理解如何将 Slint 移植到自己的 Zephyr 板卡。
一、示例概览:一个跑在 MCU 上的打印机触屏界面
Printer Demo 是 Slint 官方示例中模拟打印机触摸屏界面的演示程序,UI 定义位于 demos/printerdemo/ui/printerdemo.slint,由MainWindow组件组织HomePage、SettingsPage、InkPage等页面,并通过PrinterState、PrinterQueue、InkLevel等全局结构对外暴露数据与回调,供 C++ 侧代码驱动。
Zephyr 移植版本位于 demos/printerdemo/zephyr,目录结构如下:
main.cpp:应用入口,负责初始化显示设备、创建 UI、用 C++ Model 填充墨量/打印队列数据、注册定时器驱动打印进度动画;CMakeLists.txt:按 BOARD 区分平台,配置 Rust target、软件渲染器特性、面板旋转与字节序等编译选项;VERSION:Zephyr 应用版本元数据(1.0.0);README.md/README_NXP.md:构建说明(本文主体即基于前者展开)。
与 Espressif 集成不同,Slint 的 Zephyr 平台集成不作为 Slint C++ API 的一部分提供,而是以可复用的共享源码形式放在 demos/zephyr-common 目录,由slint-zephyr.cpp/slint-zephyr.h实现。这样设计的原因文档中也有说明:Zephyr 使用 Device Tree 向设备驱动模型描述硬件,要注册输入事件回调,既需要从 device tree 节点拿到struct device *指针,也需要理解具体驱动行为才能编写回调函数。
二、已确认的已知问题
在动手之前,先了解该示例当前的两个已知限制(来自原文档,均可从源码得到印证):
- 平台集成不随 C++ API 分发:如前所述,当前实现是通用方案,足以覆盖 native_sim 模拟器与带显示 shield 的驱动;更通用的抽象方案留待未来研究。
- 不支持双缓冲(Double Buffering):在 slint-zephyr.cpp 中可以看到,
ZephyrWindowAdapter::init_from里双缓冲相关代码被注释掉,固定使用RepaintBufferType::ReusedBuffer,因为测试所用的模拟器与硬件均未上报支持SCREEN_INFO_DOUBLE_BUFFER。日志中会打印Double buffering: 0之类的信息可供确认。
三、环境准备(Prerequisites)
在运行本示例前,需要完成以下七步准备:
1. 安装 Rust 并启用 nightly 通道
# 通过 rustup 安装(官方安装方式) rustup toolchain install nightly rustup default nightlynightly 通道是必需的:从 CMakeLists.txt 可以看到,native_sim 与 rza3m_ek 目标都使用-Zbuild-std=core,alloc这类仅 nightly 可用的 cargo 标志。
2. 安装 Zephyr 依赖:按 Zephyr 官方 Getting Started 文档安装系统级编译依赖。
3. 在虚拟环境中安装 West
# 如果已检出 Slint 源码: python3 -m venv ../.venv source ../.venv/bin/activate # 如果还没有 Slint 源码: mkdir slint-zephyr python3 -m venv slint-zephyr/.venv source slint-zephyr/.venv/bin/activate # 安装 west pip install west4. 获取 Zephyr 源码(通过 West manifest)
# 如果已检出 Slint 源码(会在 Slint 源码旁边拉取 Zephyr 源码): cd .. west init -l --mf demos/zephyr-common/west.yaml ./slint # 如果还没有 Slint 源码(会把 Slint 与 Zephyr 源码都检出到 slint-zephyr): west init -m https://github.com/slint-ui/slint --mr zephyr --mf demos/zephyr-common/west.yaml slint-zephyr cd slint-zephyr # 检出所有仓库: west update这里用到的 manifest 文件 demos/zephyr-common/west.yaml 中,Zephyr 被固定到v4.4.1版本。注意 manifest 的路径是以仓库根目录为基准的相对路径,位于demos/zephyr-common/west.yaml。
5. 导出 Zephyr CMake 包
west zephyr-export这能让 CMake 自动加载构建 Zephyr 应用所需的样板代码。
6. 安装 Zephyr 的 Python 依赖
pip install -r ~/zephyrproject/zephyr/scripts/requirements.txt7. 安装 Zephyr SDK:使用v1.0.1版本(按原文档要求)。
四、在模拟器中构建与运行
环境就绪后,进入本目录执行:
# 构建 west build -b native_sim/native/64 -p always slint/demos/printerdemo/zephyr # 运行 ./build/zephyr/zephyr.exe-p always强制进行一次 pristine 构建(干净构建),Zephyr 官方文档推荐新用户使用此选项。
模拟器方案的配套配置位于 demos/zephyr-common/boards/native_sim_64.conf(开启CONFIG_INPUT_SDL_TOUCH=y,通过 SDL 模拟触摸输入)与 demos/zephyr-common/boards/native_sim_64.overlay(将 SDL 显示控制器设为 720x1280,即竖屏面板尺寸)。
对应地,CMakeLists.txt 为native_sim/native/64指定了x86_64-unknown-linux-gnu的 Rust 目标以及-Zbuild-std=core,alloc标志。模拟器下 Zephyr 的 POSIX 架构无法抢占忙碌线程,因此 slint-zephyr.cpp 在存在活跃动画时会主动k_sem_take睡眠 10ms,避免事件循环死循环。
五、在真实设备上构建、烧录与运行
5.1 NXP MIMXRT1170-EVKB(RK055HDMIPI4MA0 MIPI 显示屏)
该示例已在 NXP MIMXRT1170-EVKB 开发板配合 RK055HDMIPI4MA0 MIPI 显示屏上完成测试。板载调试探针可能需要按 Zephyr 文档说明进行配置。
# 构建 west build -b mimxrt1170_evk@B/mimxrt1176/cm7 -p always slint/demos/printerdemo/zephyr -- -DSHIELD=rk055hdmipi4ma0 -DCMAKE_BUILD_TYPE=Release # 烧录 west flash对应板级配置在 demos/zephyr-common/boards/mimxrt1170_evk_mimxrt1176_cm7.conf:
CONFIG_MAIN_STACK_SIZE=32768:加大主线程栈,避免 "MPU FAULT: Stacking error";CONFIG_FPU=y+CONFIG_FP_HARDABI=y:启用硬件 FPU 与硬浮点 ABI(Rustthumbv7em-none-eabihf目标必需);CONFIG_DMA=y+CONFIG_MCUX_ELCDIF_PXP=y+CONFIG_MCUX_ELCDIF_PXP_ROTATE_90=y:启用 PXP 外设及其 90 度旋转。
PXP 硬件旋转的接入逻辑在 slint-zephyr.cpp:当使能CONFIG_MCUX_ELCDIF_PXP_ROTATE_90时,rotation.hardware = Rotate270——因为 Kconfig 命名的是"面板看到的旋转角度",与帧缓冲实际经历的旋转方向相反。由于 PXP 走 DMA API 时显示驱动不支持局部刷新,slint-zephyr.cpp 在CONFIG_MCUX_ELCDIF_PXP定义下改为整帧display_write。
此外,由于 RK055HDMIPI4MA0 盾板的 MIPI 显示屏也消费原生字节序的 RGB565 像素数据,CMakeLists.txt 会为所有使用该 shield 的构建统一设置SLINT_ZEPHYR_RGB565_NATIVE_ENDIAN=1。
5.2 Renesas EK-RZ/A3M(5 英寸 720x1280 MIPI 屏)
该示例也可以在 Renesas EK-RZ/A3M 评估套件及其 5 英寸 720x1280 MIPI 显示屏上运行。
注意:EK-RZ/A3M 必须使用 Renesas 的 Zephyr fork。将前置步骤第 4 步替换为:
# 如果已检出 Slint 源码(使用 west-rza3m.yaml 而非 west.yaml): cd .. west init -l --mf demos/zephyr-common/west-rza3m.yaml ./slint # 如果还没有 Slint 源码: west init -m https://github.com/slint-ui/slint --mr zephyr --mf demos/zephyr-common/west-rza3m.yaml slint-zephyr cd slint-zephyr # 检出所有仓库: west update专用 manifest demos/zephyr-common/west-rza3m.yaml 将 Zephyr 指向 Renesas 仓库的wip_rz分支,因为该板卡与显示支持目前只存在于这个 fork 上。
随后构建与烧录:
# 构建 west build -b rza3m_ek -p always slint/demos/printerdemo/zephyr -- -DSHIELD=rtkapplcdms02001be -DCMAKE_BUILD_TYPE=Release # 烧录(使用 J-Link) west flash对应的板级配置在 demos/zephyr-common/boards/rza3m_ek.conf,其中CONFIG_MAIN_STACK_SIZE=65536、CONFIG_HEAP_MEM_POOL_SIZE=16777216提供了更充裕的栈与堆空间,并启用了CONFIG_INPUT_GT911(触摸控制器驱动);rza3m_ek.overlay 则通过 chosen 节点将zephyr,touch指向 GT911 触摸设备。
针对该板卡的特殊处理集中在 CMakeLists.txt:
- Rust 目标为
aarch64-unknown-none,并同样使用-Zbuild-std=core,alloc; - 由于面板以竖屏方式安装,
SLINT_ZEPHYR_PANEL_ROTATION=90,由软件渲染器将 UI 旋转四分之一圈后再绘制; - 该显示驱动消费原生字节序 RGB565 像素,设置
SLINT_ZEPHYR_RGB565_NATIVE_ENDIAN=1。
另外,slint-zephyr.cpp 在CONFIG_BOARD_RZA3M_EK下把事件循环最长睡眠时间截断为 10 秒,避免唤醒间隔过长。
六、深入源码:Zephyr 平台集成是如何工作的
6.1 CMake 构建要点
CMakeLists.txt 是整个移植的构建枢纽,几个关键点:
- 以
BOARD变量区分三套目标,并为每套目标设置对应的 RustCARGO_TARGET、SLINT_LIBRARY_CARGO_FLAGS、板级配置名(BOARD_CONF_NAME); - 共享配置来自 demos/zephyr-common/prj.conf,其中开启了 C++20(
CONFIG_CPP=y、CONFIG_STD_CPP20=y、CONFIG_REQUIRES_FULL_LIBCPP=y)、显示与输入子系统,并通过CONFIG_INPUT_THREAD_PRIORITY_OVERRIDE=y+CONFIG_INPUT_THREAD_PRIORITY=-1把输入线程优先级提到帧渲染之上,避免渲染期间触摸事件无法及时出队; - 强制启用 Slint 的 freestanding(无标准库宿主环境)与软件渲染器特性:
SLINT_FEATURE_FREESTANDING、SLINT_FEATURE_RENDERER_SOFTWARE、SLINT_FEATURE_SDF_FONTS,资源以embed-for-software-renderer-with-sdf方式内嵌; - 通过
slint_target_sources(app ../ui/printerdemo.slint)将 UI 文件接入 Slint 构建系统,自动生成 C++ 头文件printerdemo.h; - 通过
SLINT_ZEPHYR_PANEL_ROTATION与SLINT_ZEPHYR_RGB565_NATIVE_ENDIAN两个编译期宏向slint-zephyr.cpp传递面板安装方向与像素字节序信息。
6.2 显示适配与软件渲染
ZephyrPlatform与ZephyrWindowAdapter分别继承 Slint 的slint::platform::Platform与slint::platform::WindowAdapter。初始化时:
slint_zephyr_init(display)关闭背光(display_blanking_off)并注册平台;ZephyrWindowAdapter::init_from通过display_get_capabilities查询屏幕分辨率、像素格式与双缓冲能力,并打印到日志(如Screen size: ...、Pixel format: RGB_565);- 若当前像素格式不受支持,则在
RGB_565与RGB_565X(Zephyr 4.4 前称BGR_565,见 slint-zephyr.cpp 的版本兼容宏)之间切换,必要时调用display_set_pixel_format; - 之后为软件渲染器分配 RGB565 帧缓冲,并调用
m_renderer.set_rendering_rotation(...)应用渲染旋转。
绘制流程在maybe_redraw()中:软件渲染器只重绘脏区域,得到矩形列表后逐块写入显示驱动;当需要字节交换时,会把每个 16 位像素的高低字节对调((*px << 8) | (*px >> 8)),用于解决驱动声明字节序与实际消费字节序不一致的问题(对应 Zephyr 的 issue #53642 场景)。日志会输出slint渲染耗时与write写屏耗时,便于性能分析。
6.3 旋转模型与触摸坐标映射
源码用DisplayRotation结构描述面板、帧缓冲与 UI 三者之间的旋转关系,区分两种旋转来源:显示硬件(如 PXP)在送往面板途中对帧缓冲的旋转(hardware),以及软件渲染器绘制时对 UI 的旋转(rendering)。由此派生出帧缓冲尺寸、逻辑尺寸与触摸坐标转换(touch_rotation),并在map_touch_position中通过rotated()函数把面板坐标系下的触摸位置换算到逻辑坐标。这套模型正是文档中"通用实现足以覆盖模拟器与显示 shield 驱动"的源码体现。
6.4 输入事件回调与事件循环
触摸接入采用 Zephyr 4.x 的输入子系统:
- 当 device tree 中存在
zephyr,touchchosen 节点时,INPUT_CALLBACK_DEFINE注册zephyr_process_input_event回调(slint-zephyr.cpp); - 回调解析
INPUT_ABS_X/INPUT_ABS_Y累积坐标,在sync事件时根据按下/移动/抬起状态,通过slint::invoke_from_event_loop把dispatch_pointer_press_event/dispatch_pointer_move_event/dispatch_pointer_release_event投递到事件循环线程执行,保证触摸事件与渲染线程串行安全。
事件循环run_event_loop的主体是:更新定时器与动画 → 消费run_in_event_loop投递的任务队列 → 触发重绘 → 依据"是否存在活跃动画 / 下一个定时器更新时间"决定睡眠时长(k_sem_take),空闲时永久睡眠等待信号量唤醒,实现零功耗轮询。
6.5 应用侧数据驱动
main.cpp 展示了 Slint 的 C++ 数据绑定用法:
- 自定义
InkLevelModel继承slint::Model<InkLevel>,提供四种墨水的颜色与余量,通过global<PrinterState>().set_ink_levels(...)注入 UI; - 用
slint::VectorModel<PrinterQueueItem>接管打印队列,注册on_start_job(添加新任务、附带时间戳)与on_cancel_job(按下标取消)回调; - 用
slint::Timer每秒推进队首任务的打印进度,超过 100% 即出队,从而驱动 UI 上的队列动画。
七、小结与延伸阅读
至此,你应当能够独立完成 Printer Demo 在 Zephyr 上的三套目标构建(native_sim 模拟器、NXP MIMXRT1170-EVKB、Renesas EK-RZ/A3M),并理解 Slint 平台集成在 Zephyr 下的核心机制:Device Tree 驱动的显示与触摸接入、RGB565 字节序适配、硬件/软件双层旋转模型、信号量驱动的事件循环,以及通过Model与回调实现的数据绑定。
进一步了解与本主题相关的仓库资源:
- 平台集成源码:demos/zephyr-common/slint-zephyr.cpp、demos/zephyr-common/slint-zephyr.h
- 共享 Kconfig:demos/zephyr-common/prj.conf
- West manifest:demos/zephyr-common/west.yaml(Zephyr v4.4.1)、demos/zephyr-common/west-rza3m.yaml(Renesas
wip_rz分支) - 板级配置与 overlay:demos/zephyr-common/boards
- 应用入口与构建脚本:demos/printerdemo/zephyr/main.cpp、demos/printerdemo/zephyr/CMakeLists.txt
- UI 定义:demos/printerdemo/ui/printerdemo.slint
如果你计划把 Slint 带到自己的 Zephyr 板卡,可以从zephyr-common这套共享实现出发,为你的 BOARD 在CMakeLists.txt中补充 Rust 目标与板级配置,并在boards/下添加对应的.conf与.overlay,重点核对显示驱动的像素格式、字节序与面板安装方向三项参数。
【免费下载链接】slintSlint is an open-source declarative GUI toolkit to build native user interfaces for Rust, C++, JavaScript, or Python apps.项目地址: https://gitcode.com/GitHub_Trending/sl/slint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考