news 2026/9/26 8:27:21

IronOS 用户界面(UI)架构解析:logic/drawing 双层模式机、屏幕类型与渲染管线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IronOS 用户界面(UI)架构解析:logic/drawing 双层模式机、屏幕类型与渲染管线
  • 嵌入式
  • 固件
  • 硬件开发
  • 智能硬件

【免费下载链接】IronOS

Open Source Soldering Iron firmware

项目地址:https://gitcode.com/gh_mirrors/ir/IronOS
点击查看免费下载

导读:本文以 UI 目录 README 为骨架,深入剖析 IronOS(开源焊台固件)的用户界面如何被拆分为logic(模式逻辑)与drawing(屏幕绘制)两个半区,并进一步按屏幕类型(128×32、96×16)细分绘制实现。读者将理解每个 UI 模式的状态机组织方式、guiContext上下文与 scratch 状态的设计意图、渲染循环与转场动画的工作机制,以及各模式对应的逻辑文件与绘制文件如何协作,为二次开发、移植新焊台或调试 UI 行为提供可直接对照源码的实战指引。

一、整体架构:UI 的两个半区

IronOS 的用户界面位于 source/Core/Threads/UI,整个 UI 被刻意拆分为两个半区(half):

  • logic文件夹:存放实现每个模式逻辑的.cpp文件,负责处理按键事件(button events)与任何业务逻辑。它回答"这个模式该做什么、按键按下后去哪个模式"。
  • drawing文件夹:存放仅负责屏幕绘制的.cpp文件,回答"这个模式在屏幕上画什么"。绘制文件进一步按**屏幕类型(screen types)**细分。

对应地,drawing下存在两个屏幕类型子目录:

  • mono_128x32:面向 128×32 单色 OLED(如 Miniware TS100/TS80 系列、Sequre 部分机型);
  • mono_96x16:面向 96×16 单色 OLED(如 Pinecil、MHP30 等)。

两个子目录拥有同名同签名的绘制函数(例如draw_homescreen_detailed.cpp、draw_soldering_power_status.cpp、pre_render_assets.cpp等),通过编译期宏(OLED_128x32/OLED_96x16)选择实现。屏幕类型宏在各自 BSP 的 configuration.h 中定义,例如:

  • Miniware/configuration.h:#define OLED_128x32 1、#define OLED_96x16 1;
  • Pinecil/configuration.h:#define OLED_96x16 1;
  • MHP30/configuration.h:#define OLED_96x16 1;
  • Sequre/configuration.h:#define OLED_128x32 1。

所有绘制函数通过统一的头文件 ui_drawing.hpp 暴露接口,逻辑层只依赖该接口,不关心实际屏幕尺寸——这是两层解耦的关键。

二、逻辑层:模式机与按键状态

2.1 所有模式共用一个OperatingMode状态枚举

逻辑层的心脏是 OperatingModes.h 中定义的OperatingMode枚举,它覆盖了 IronOS 的全部 UI 状态:

枚举值含义
StartupLogo显示启动 Logo
CJCCalibration冷端(CJC)校准
StartupWarnings启动检查与警告
InitialisationDone首次启动进入主界面前的特殊过渡态,允许跳转到其他启动态
HomeScreen主界面,作为进入其他模式的"发射台"
Soldering主焊接模式
SolderingProfile跟随温度曲线焊接(如回流焊 reflow)
Sleeping睡眠态,保持较低睡眠温度
Hibernating休眠态,加热器完全关闭直至唤醒
SettingsMenu设置菜单
DebugMenuReadout调试信息
TemperatureAdjust目标温度调整
UsbPDDebugUSB-PD 调试信息
ThermalRunaway热失控警告

这些模式分别由逻辑文件实现:主界面在 HomeScreen.cpp、焊接在 Soldering.cpp、曲线焊接在 SolderingProfile.cpp、睡眠在 Sleep.cpp、设置菜单在 SettingsMenu.cpp、调试菜单在 DebugMenu.cpp、温度调整在 TemperatureAdjust.cpp、启动警告在 ShowStartupWarnings.cpp,另有 CJC 校准 CJC.cpp 与三个 USB-PD 调试实现(USBPDDebug_FUSB.cpp、USBPDDebug_FS2711.cpp、USBPDDebug_HUSB238.cpp)。

2.2 按键状态机:ButtonState

按键输入由 Buttons.hpp 中的ButtonState枚举描述:

BUTTON_NONE = 0, /* 无按键 / 低于滤波时间 */ BUTTON_F_SHORT = 1, /* 前键(front)短按 */ BUTTON_B_SHORT = 2, /* 后键(back)短按 */ BUTTON_F_LONG = 4, /* 前键长按(持续按住) */ BUTTON_B_LONG = 8, /* 后键长按 */ BUTTON_BOTH = 16, /* 同时按下两键(按下并释放) */ BUTTON_BOTH_LONG = 32, /* 两键同时长按 */

注意其中注释的语义:"Pressed" 表示完整的按下+释放脉冲(__/),"holding" 表示按键保持低电平超过滤波时间。所有逻辑文件都围绕这 7 种按键状态编写switch分支。例如主界面 HomeScreen.cpp 中:

  • BUTTON_F_SHORT→ 进入Soldering(若烙铁头未断开);
  • BUTTON_B_SHORT→ 进入SettingsMenu;
  • BUTTON_F_LONG→ 进入SolderingProfile(开启PROFILE_SUPPORT时);
  • BUTTON_B_LONG→ 进入DebugMenuReadout。

2.3 上下文与 scratch 状态:guiContext

每个模式函数都以(const ButtonState buttons, guiContext *cxt)为签名,返回下一个OperatingMode。guiContext(定义于 OperatingModes.h)承担跨渲染帧的状态保持:

struct guiContext { TickType_t viewEnterTime; // 进入该视图的 tick 时间 OperatingMode previousMode; // 上一个模式 TransitionAnimation transitionMode;// 转场动画方向 struct scratch { // 跨重绘保留、模式切换时清空的草稿状态 uint16_t state1, state2, state5, state6; uint32_t state3, state4, state7; } scratch_state; };

设计意图非常明确(与 GUIRendering.md 中"类即时模式渲染"理念一致):函数应尽量把状态收敛到 context 结构里,保持状态使用扁平化。这样外部事件可以改变状态,状态也可经 BLE 等外部控制接口读写。各模式把scratch_state的不同字段当作"局部变量"使用,例如:

  • 焊接模式 Soldering.cpp 中:state1= 按键锁定状态(0 未锁定+已释放,1 未锁定,2 已锁定,3 已锁定+已释放),state2= 升压(boost)模式,state3= 蜂鸣器定时器;
  • 设置菜单 SettingsMenu.cpp 中:state1= 根菜单条目、state2= 子菜单条目、state3/state4= 自动重复加速定时、state5= 当前菜单长度缓存、state6= 是否正在渲染帮助文本;
  • 温度调整 TemperatureAdjust.cpp 中:state1= 等待释放标志、state2/state3= 自动重复加速。

scratch_state在模式切换时会被整体清零(见下文渲染循环),因此它天然不适合承载需要跨模式保留的数据——跨模式数据应放全局变量或设置项。

三、绘制层:按屏幕类型细分的绘制函数

3.1 统一接口

绘制层通过 ui_drawing.hpp 暴露以下核心接口(均为void或bool返回,只画屏不改状态):

void ui_draw_warning_undervoltage(void); void ui_draw_power_source_icon(void); void ui_draw_tip_temperature(bool symbol, const FontStyle font); bool warnUser(const char *warning, const ButtonState buttons); void ui_draw_cjc_sampling(const uint8_t num_dots); void ui_draw_debug_menu(const uint8_t item_number); void ui_draw_homescreen_detailed(TemperatureType_t tipTemp); void ui_draw_homescreen_simplified(TemperatureType_t tipTemp); void ui_pre_render_assets(void); void ui_draw_soldering_power_status(bool boost_mode_on); void ui_draw_soldering_basic_status(bool boostModeOn); void ui_draw_soldering_detailed_sleep(TemperatureType_t tipTemp); void ui_draw_soldering_basic_sleep(TemperatureType_t tipTemp); void ui_draw_soldering_profile_advanced(...); void ui_draw_temperature_change(void); void ui_draw_usb_pd_debug_state(...); void ui_draw_usb_pd_debug_pdo(...); void printVoltage(void);

从函数命名可看出绘制层遵循两条轴线:详细/简化视图(detailed/simplified,由DetailedIDLE、DetailedSoldering设置项控制)与模式(主界面/焊接/睡眠/温度调整/调试等)。

3.2 以主界面为例:同一函数,两种屏幕类型

ui_draw_homescreen_detailed在 mono_128x32/draw_homescreen_detailed.cpp 与 mono_96x16/draw_homescreen_detailed.cpp 各有一份实现,都通过#ifdef OLED_128x32/#ifdef OLED_96x16保护。两者逻辑一致,差异在布局常量:

  • 128×32 版:大号(12×24)温度数字垂直居中靠一侧,另一侧两行 SMALL(8×16)状态行(设定温度、输入电压);断头(tip disconnected)时在对应侧绘制disconnectedTip位图并显示电压,xTaskGetTickCount() % 1000 < 300控制CoolingTempBlink的 300ms 灭/700ms 亮闪烁;
  • 96×16 版:单行布局,温度与电压分居左右两侧,同样处理断头与闪烁。

两个版本都考虑OLED::getRotation()来左右翻转布局,适应左右手持握。绘制前会先通过ui_pre_render_assets(见 mono_128x32/pre_render_assets.cpp)把位图预翻转存入 RAM,避免每帧实时镜像。

3.3 焊接状态的两种视图

焊接时根据DetailedSoldering设置选择:

  • 详细视图draw_soldering_power_status.cpp:大号温度 + 功率(取自x10WattHistory.average(),超过 99.9W 时去掉小数位保持 5 格宽度)+ 输入电压;
  • 基础视图draw_soldering_basic_status.cpp:温度、设定值、电源类型图标与电压等更紧凑的信息。

焊接逻辑 Soldering.cpp 在调用绘制前完成全部决策:根据state2(boost)选择目标温度(SolderingTemp或BoostTemp,并按TemperatureInF转换);误差 ±10℃ 内判定收敛,触发蜂鸣器与LED_HOT,否则LED_HEATING;随后依次检查checkExitSoldering()(欠压退出)、shouldBeSleeping()、heaterThermalRunawayCounter > 8(热失控)后,才把剩余按键交给handleSolderingButtons。

四、渲染循环与转场动画

4.1 GUI 线程:类即时模式渲染

UI 在 FreeRTOS 的 GUI 线程中运行,入口是 GUIThread.cpp 的startGUITask(L215-L243)。启动时依次完成翻译准备(prepareTranslations)、OLED 初始化、亮度/反色/旋转设置、ui_pre_render_assets预渲染资源,随后进入for(;;)主循环,以vTaskDelayUntil(&startRender, TICKS_100MS * 4 / 10)维持约 20–25 FPS。

核心渲染函数是guiRenderLoop(L144-L193),每帧流程:

  1. 调用guiHandleDraw()完成一次屏幕绘制;
  2. 若返回的模式与当前模式不同,则记录viewEnterTime、previousMode,清零scratch_state并切换模式;
  3. 若context.transitionMode非None,则切换到 OLED 次级帧缓冲,再渲染一帧新视图,然后按动画类型(transitionScrollDown/transitionSecondaryFramebuffer)在两缓冲间转场;
  4. 最后OLED::refresh()输出。

guiHandleDraw(L44-L143)是模式机的分发中心:先读取按键状态,依据温度/灵敏度决定屏幕亮灭与状态 LED(睡眠判定逻辑见 shouldDeviceSleep.cpp),再以switch(currentOperatingMode)把当前模式分发给对应的逻辑函数(drawHomeScreen、gui_solderingMode、gui_SolderingSleepingMode、gui_solderingTempAdjust、showDebugMenu、performCJCC、gui_SettingsMenu、showPDDebug、showWarnings等)。

4.2 转场动画与方向语义

转场方向定义于 OperatingModes.h 的TransitionAnimation枚举(None/Right/Left/Down/Up)。其"方向感"用于强化菜单导航的空间隐喻。GUI 线程只实现了Left、Right、Down三种动画,Up尚未实现但枚举已预留。

方向约定可参考 GUIRendering.md 中的示意图:

  • 主界面向下 → 调试菜单;
  • 焊接/曲线焊接模式 ← 主界面 ← 设置主菜单 ← 设置子菜单;
  • 设置子菜单之间纵向滚动(Down)。

实际代码中的用法示例:主界面按后键短按进入设置菜单时设TransitionAnimation::Right(HomeScreen.cpp),设置菜单返回时设TransitionAnimation::Left;进入子菜单用Right,退出用Left(SettingsMenu.cpp);主界面进入调试菜单用Down(HomeScreen.cpp),调试菜单返回用Up(DebugMenu.cpp)。在详细视图模式(DetailedIDLE && DetailedSoldering)下部分转场会被抑制为None,因为布局已足够相似、无需动画。

转场得以实现的前提是:逻辑函数先渲染当前屏幕,再返回新状态,保证切换前帧缓冲中有完整的旧视图;随后分发层自动再渲染一帧新视图到次级缓冲并完成过渡(见 GUIThread.cpp)。

五、关键模式逐个拆解

5.1 设置菜单:最复杂的 UI 代码

SettingsMenu.cpp 自述为"最复杂的 GUI 代码",采用两级菜单结构:主菜单(分类)→ 子菜单(设置项)。其数据驱动核心是menuitem结构数组(rootSettingsMenu与subSettingsMenus,定义于 settingsGUI.hpp),每个条目含绘制回调、可见性回调、增量处理回调及多语言短描述/长描述索引。

关键机制:

  • 帮助文本:render_menu在按键静止超过 3 秒(HELP_TEXT_TIMEOUT_TICKS = TICKS_SECOND * 3)后,从"设置项视图"自动切换为滚动显示该设置的长描述(drawScrollingText);
  • 滚动指示器:getMenuLength遍历菜单计算可见条目数(隐藏条目不计入),indicatorHeight = OLED_HEIGHT / menuLength计算指示条高度,末项或闪烁节拍时隐藏/闪烁(SettingsMenu.cpp);
  • 自动重复与加速:长按前/后键时,autoRepeatAcceleration按PRESS_ACCEL_STEP递增、受PRESS_ACCEL_INTERVAL_MAX/MIN钳制,实现"越按越快"的数值滚动;
  • 按键交换:ReverseButtonSettings开启时在进入分支前交换前后键语义(L200-L219);
  • 保存时机:翻过主菜单末尾(draw == nullptr)或双键退出时调用saveSettings()持久化设置。

5.2 焊接模式:setpoint、蜂鸣与安全退出

焊接主逻辑 Soldering.cpp 的状态机完整注释(L94-L107)概括了交互流程:

  • 短按任意键 → 温度调整屏(TemperatureAdjust);
  • 长按前键 → 升压模式(临时切换 PID 目标温度到BoostTemp);
  • 长按后键 / 双键 → 退出回主界面;
  • 双键长按 → 按键锁定/解锁(锁定逻辑由state1与LockingMode设置项控制)。

温度收敛检测(L124-L140):目标与实际温差落入 ±10℃ 即视为收敛,触发一次 1/3 秒蜂鸣并点亮LED_HOT,否则LED_HEATING。安全出口依次为:欠压退出(checkExitSoldering,内部调用 checkUndervoltage.cpp 的checkForUnderVoltage,在 DC 供电且电压低于lookupVoltageLevel()时把currentTempTargetDegC归零并绘制ui_draw_warning_undervoltage)、关机判定(shouldShutdown,见 shouldDeviceShutdown.cpp,超时或后键长按触发)、睡眠判定与热失控保护(heaterThermalRunawayCounter > 8)。

5.3 温度调整:带约束的增量步进

TemperatureAdjust.cpp 进入时先关闭加热器(currentTempTargetDegC = 0),并等待用户松开按键(waitForRelease)后才响应输入。增量来源:

  • 短按:TempChangeShortStep;
  • 长按:TempChangeLongStep配合自动重复加速;
  • ReverseButtonTempChangeEnabled开启时对delta取反。

新值会按增量取整(newTemp = (newTemp / delta) * delta)并钳制在MIN_TEMP_C ~ MAX_TEMP_C(或华氏MIN_TEMP_F ~ MAX_TEMP_F)范围内,然后写回SolderingTemp设置。3 秒无操作或双键退出,退出前saveSettings()。

5.4 曲线焊接(回流焊):分阶段温度斜坡

SolderingProfile.cpp 实现分段温度曲线:阶段 0 为预热(目标ProfilePreheatTemp、速率ProfilePreheatSpeed),随后依次执行ProfilePhase1..5的 {温度, 持续时间},全部完成后进入冷却段(速率ProfileCooldownSpeed),烙铁头低于 55℃ 时蜂鸣并返回主界面。相位完成条件为"达到目标温度且时间达标"(L71),斜坡目标按"每 tick 温度增量 = 相时长/(温差)"线性插值(L128-L141)。绘制时详细视图调用ui_draw_soldering_profile_advanced显示当前相位、剩余时间与目标温度,并叠加功率状态。

5.5 睡眠/休眠与启动警告

  • Sleep.cpp 统一处理Sleeping与Hibernating:前者把目标温度降到min(SleepTemp, SolderingTemp),后者完全关闭加热(currentTempTargetDegC = 0);shouldBeSleeping()恢复时返回previousMode;
  • ShowStartupWarnings.cpp 用state1作为警告序号,依次检查:设置被重置(settingsWereReset)、设备防伪校验、加速度计缺失(AccelMissingWarningCounter计数小于 2 时提示)、PD 控制器缺失(FUSB/HUB238/FS2711 三种实现按POW_PD_EXT宏选择探测),全部通过后进入StartupLogo。

六、如何定位与扩展 UI 代码

对开发者而言,UI 相关改动遵循清晰的分工:

  1. 改逻辑:进入 logic 对应模式文件(如焊接Soldering.cpp、主界面HomeScreen.cpp),修改按键处理、状态转移、目标温度等决策;
  2. 改画面:进入 drawing/mono_128x32 或 drawing/mono_96x16 修改同名绘制函数,且必须两个屏幕类型同步维护(同名函数、同一签名);
  3. 新增模式:在 OperatingModes.h 增加枚举值,在 GUIThread.cpp 的switch分发中加入分支,并在 ui_drawing.hpp 声明新的绘制接口后到两个 drawing 子目录实现;
  4. 跨状态数据:优先放入guiContext.scratch_state(模式内保留)或全局变量(跨模式),模式切换时scratch_state会被清零;
  5. 转场动画:在返回新模式前设置cxt->transitionMode为Left/Right/Down(Up枚举已预留但动画未实现,见 GUIRendering.md)。

整体架构可用一句话概括:逻辑层决定"去往何方",绘制层决定"画成何样",GUI 线程以类即时模式每帧重绘并驱动转场,guiContext在两者之间传递状态。理解这一分工后,无论是修 bug、加功能还是移植到新屏幕尺寸的焊台,都能在 source/Core/Threads/UI 目录下快速定位到正确文件。

  • 嵌入式
  • 固件
  • 硬件开发
  • 智能硬件

【免费下载链接】IronOS

Open Source Soldering Iron firmware

项目地址:https://gitcode.com/gh_mirrors/ir/IronOS
点击查看免费下载
上一篇:专业级AMD Ryzen硬件调试工具:5大核心功能实战优化指南
下一篇:AlienFX Tools:500KB轻量级工具,彻底取代臃肿的Alienware Command Center

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 8:27:14

Higgsfield实测:前Sora成员打造的高动态AI视频生成工具

Higgsfield这个名字&#xff0c;我第一次是在一个创作者群里看到的&#xff0c;当时有人发了一段雨夜街道里狂奔的镜头&#xff0c;说这是某个前Sora成员做的工具直出的。说实话&#xff0c;AI视频生成我玩得不算少&#xff0c;Runway、可灵、Pika都试过&#xff0c;但Higgsfie…

作者头像 李华
网站建设 2026/9/26 8:26:32

C语言printf格式说明符底层原理与安全实践

1. 为什么刚学C语言的人总在printf里栽跟头&#xff1f;你有没有过这种经历&#xff1a;写完一段代码&#xff0c;编译通过&#xff0c;运行起来却输出一堆莫名其妙的数字、乱码&#xff0c;甚至直接崩溃&#xff1f;我带过的几十个初学者里&#xff0c;八成以上第一次真正“卡…

作者头像 李华
网站建设 2026/9/26 8:25:54

船舶推进系统多体仿真:建模、验证与工程落地

船舶推进系统这块&#xff0c;大家多半都会先想到螺旋桨敞水试验、轴系校中计算&#xff0c;甚至CFD水动力分析这些东西。我在一段时间里参与了一个推进系统多体仿真的评估项目&#xff0c;最初的想法很简单&#xff1a;尝试把从主机飞轮端、中间轴、艉轴到螺旋桨的这一条传动链…

作者头像 李华
网站建设 2026/9/26 8:25:34

docling实战:从PDF到Markdown的文档解析与RAG应用指南

做RAG或者数据处理这块儿&#xff0c;文档解析永远是绕不开的坎。PDF转文本&#xff0c;听着简单&#xff0c;真上手才发现是一个无底洞&#xff1a;文本层和图片混排&#xff0c;表格解析完像一团乱麻&#xff0c;双栏论文读成一条直线&#xff0c;扫描件更是直接劝退。我一开…

作者头像 李华
网站建设 2026/9/26 8:20:42

树莓派与PC间Python+OpenCV实时摄像头数据共享实战

摄像头数据从一块树莓派实时传到 PC 上&#xff0c;这件事听起来简单&#xff0c;真动手做的时候坑一点都不少。我最早做这个需求&#xff0c;是想把树莓派挂在阳台当监控节点&#xff0c;PC 端做画面分析和存档&#xff0c;结果第一版跑起来延迟两秒多、画面还花屏&#xff0c…

作者头像 李华