OpenHarmony 小鸿 AI 开发实战 07:用 LVGL 构建 240×240 语音伙伴界面
240×240 小屏不是把手机页面缩小。当前小鸿界面要同时容纳状态栏、语音焦点、状态提示、对话文本、音量反馈和配网/升级页面还要在 WS63 的 SRAM 与 LiteOS-M 任务模型下稳定刷新。界面代码因此被拆成布局常量、对象创建、视觉状态、事件消费和长文本分页五个部分而不是在一个循环里不断清屏重画。本文依据当前lcd_st7789_driver.h、lvgl_ui_layout.h/.c、lvgl_task.c和显示事件枚举整理。所有尺寸、缓冲区大小、颜色、页面时长和状态文案都来自实际源码。最新 UI 固件包已存在并记录哈希但本轮没有重新烧录后拍摄实机屏幕所以文中使用的是根据代码确定性渲染的结构图不把设计示意图冒充设备照片源码验证与实机视觉验收仍然分开。显示尺寸由 ST7789 驱动向布局层传递面板驱动头文件定义 240×240lvgl_ui_layout.h再把LCD_PANEL_PIXEL_X/Y映射成DISP_WIDTH/HEIGHT。如果驱动没有给出尺寸预处理直接报错不允许布局悄悄使用另一个默认值。这样布局常量和 LVGL display 创建共享同一尺寸来源。#if defined(LCD_PANEL_PIXEL_X) #define DISP_WIDTH LCD_PANEL_PIXEL_X #else #error LCD_PANEL_PIXEL_X unknown #endif #if defined(LCD_PANEL_PIXEL_Y) #define DISP_HEIGHT LCD_PANEL_PIXEL_Y #else #error LCD_PANEL_PIXEL_Y unknown #endif当前 UI 留出 8 像素安全边界状态栏从 y4 开始高 22焦点环位于 64、36尺寸 112中心核心为 90×90。提示文字位于 y149对话摘要从 y179 开始。这些位置围绕 240 像素高度手工组织目标是优先让用户看清“设备现在在做什么”。常态页面由状态栏、焦点区和两段文字组成常态布局上方是 Wi-Fi、音量、亮度和电池状态中间 112×112 区域包含 12 段环形状态点、90×90 核心、咕咕嘎嘎图像或 Emoji以及监听时使用的五根波形条下方两段文字分别承担状态提示和操作/对话摘要。#define UI_STATUS_POS_Y (4) #define UI_STATUS_HEIGHT (22) #define UI_FOCUS_POS_X (64) #define UI_FOCUS_POS_Y (36) #define UI_FOCUS_SIZE (112) #define UI_FOCUS_CORE_SIZE (90) #define UI_PROMPT_POS_Y (149) #define UI_CHAT_TXT_POS_Y (179) #define UI_CHAT_TXT_HEIGHT (49)这种布局不会在回答长文本时继续强行保留大焦点环。进入回答态后中心视觉让位给阅读区域只保留顶部状态和“正在回答”提示。小屏界面的优先级必须随任务改变而不是让所有组件永久同时可见。对象只创建一次状态变化通过隐藏与样式完成disp_create_ui在 LVGL 任务初始化时创建状态栏、焦点环、12 个环段、核心区、图像、5 个波形条、互动文字、回答页装饰、全屏文字和音量覆盖层。运行时不为每个 Agent 状态重复 new 一套对象而是使用LV_OBJ_FLAG_HIDDEN切换可见性并更新文本、颜色、尺寸。status_bar_obj lv_label_create(lv_scr_act()); s_status_divider_obj lv_obj_create(lv_scr_act()); s_focus_ring_obj lv_obj_create(lv_scr_act()); for (uint8_t i 0U; i ASSISTANT_RING_SEGMENT_COUNT; i) { s_focus_ring_segments[i] lv_obj_create(s_focus_ring_obj); } s_focus_core_obj lv_obj_create(lv_scr_act()); s_penguin_image_obj lv_image_create(s_focus_core_obj); for (uint8_t i 0U; i 5U; i) { s_wave_bars[i] lv_obj_create(s_focus_core_obj); }固定对象树减少频繁分配和销毁也让 LVGL 样式与定时器更容易管理。创建完成后代码默认隐藏互动区和焦点区先显示全屏启动文字“咕咕嘎嘎 / 正在醒来”待系统事件到达后再切入常态页面。五种视觉模式与 Agent 状态对齐当前视觉枚举包含空闲、连接、倾听、思考和回答五态。它们不是网络协议状态的完整复制而是 UI 需要表达的最小集合。监听态和思考态都可能来自AGENT_STATE_LISTENING再通过agent_is_waiting_response()区分语音采集尚在进行还是语音已上传、正在等待服务端。typedef enum { ASSISTANT_VISUAL_IDLE 0, ASSISTANT_VISUAL_CONNECTING, ASSISTANT_VISUAL_LISTENING, ASSISTANT_VISUAL_THINKING, ASSISTANT_VISUAL_SPEAKING, ASSISTANT_VISUAL_INVALID } assistant_visual_mode_t;每种模式设置不同的焦点背景、边框和前景色。空闲使用暖黄色连接偏紫倾听使用青色思考使用琥珀色回答使用绿色。12 段焦点环可以固定点亮或旋转五根波形条则按预设高度序列动画帮助用户不读文字也能分辨设备状态。状态文案来自真实分支而不是设计稿显示任务处理eDisp_Prompt_Update时读取 Agent 状态。空闲文案为“咕咕嘎嘎 / 按中键和我说话长按5秒进入配网”连接态显示“正在连接 / 网络连接中请稍候”监听态显示“我在听 / 请开始说话”等待返回时显示“让我想一想 / 语音已上传请稍候”回答态提示“正在回答”。assistant_visual_set_mode(waiting_response ? ASSISTANT_VISUAL_THINKING : ASSISTANT_VISUAL_LISTENING); lv_label_set_text( interact_txt_prompt, waiting_response ? 让我想一想 : 我在听); lv_label_set_text( interact_txt_chat, waiting_response ? 语音已上传请稍候 : 请开始说话);这段逻辑解决了“已经说完但界面还显示我在听”的语义错误。状态文案与颜色必须由同一个状态判断驱动否则用户会看到环在思考、文字却提示继续说话。OpenHarmony 构建只使用 16 行局部缓冲240×240 RGB565 全帧缓冲需要 115,200 字节。当前 OpenHarmony 路径把局部刷新缓冲设为 16 行即240 × 16 × 2 7,680字节非 OpenHarmony 分支使用 80 行、38,400 字节。代码在注释中明确说明缓冲越大刷新可能更快但 SRAM 压力也更高。#if SUPPORT_OHOS #define LVGL_PARTIAL_BUF_LINES (16) #else #define LVGL_PARTIAL_BUF_LINES (80) #endif static uint8_t disp_buffer[ DISP_WIDTH * LVGL_PARTIAL_BUF_LINES * 2];LVGL display 使用LV_DISPLAY_RENDER_MODE_PARTIAL注册该缓冲。局部缓冲不是“屏幕只有 16 行”而是 LVGL 分块绘制后通过 flush 回调送到 ST7789。文章给出精确字节数避免把帧缓冲、对象内存和外部 Flash 混在一起。LVGL tick 与对象更新必须留在显示任务当前代码专门注明不能在硬件定时器 ISR 中调用lv_tick_inc()否则会与LvglTask并发并破坏 LVGL 内部状态。实际循环在同一任务中根据内核 tick 推进 LVGL再调用lv_timer_handler()然后以它返回的下一定时器间隔等待显示消息。等待时间被限制在 120 ms。过大时动画和滚动发涩过小时任务空转、发热增加。显示事件通过g_disp_event_qid进入同一线程调用disp_update_ui因此其他任务只发枚举不直接操作 LVGL 对象。lv_tick_inc(xh_lvgl_elapsed_ms_since(lv_prev_kernel_tick)); uint32_t next_ms lv_timer_handler(); uint32_t qwait next_ms; if (qwait 1U) { qwait 1U; } if (qwait 20U) { qwait 20U; } ret osMessageQueueGet( g_disp_event_qid, (void *)msg_recv, NULL, qwait);回答页用 UTF-8 安全分页而不是按字节硬切动态回答先写入 512 字节 pending buffer。单页最多复制 144 字节页面推进步长为 108 字节相邻页因此保留一部分上下文每页停留 3.2 秒。这里的 144 和 108 是字节数不等于固定中文字数。UTF-8 中文通常占 3 字节Emoji 可能占 4 字节所以切页必须回退到字符起点。#define DISP_LVGL_TEXT_PENDING_MAX (512) #define DISP_SHOW_TEXT_LINE_LEN (108) #define DISP_SHOW_TEXT_MAX_LEN (144) #define DISP_SHOW_TEXT_DELAY_MS (3200)utf8_safe_rollback从目标位置向前跳过所有10xxxxxxcontinuation byte找到一个合法字符起点。第一页显示后创建 LVGL timer后续每次向前移动 108 字节再安全截取最多 144 字节并更新右下角当前页/总页数。最后一页还会完整停留一个周期再清理播放状态避免回答刚显示就返回首页。回答页把 144 像素高度让给正文回答布局的正文从 x16、y64 开始宽度为DISP_WIDTH - 28高度 144左侧有 3×144 的绿色强调条页码放在 y213。相较常态页 49 像素的摘要区回答页可以显示约五到六行 18 px 中文。顶部仍保留状态栏用户不会因为进入长文本页面失去网络、电量等信息。内置lv_font_Chinese_18_UI是兜底字体若 LittleFS 中的外部font.bin可用互动文字可切到流式字体。完整中文字库下载与替换属于第 08 篇范围本文只确认当前 UI 对字体缺失有回退路径不声称所有 Unicode 字形都在内置字体中。音量反馈使用覆盖层不挤压主状态收到eDisp_Volume_Update后显示任务先刷新状态栏再显示位于屏幕下部的音量覆盖层。覆盖层包含音量图标、116×11 的轨道、按百分比计算宽度的填充条和数值。它由定时器自动恢复原界面不需要重建整个对象树。填充最大宽度为 112当前值按112 × volume / 100计算音量为零时隐藏填充条。这个细节比只改状态栏图标更容易给用户及时反馈也不会永久占用回答区。从源码到实机还需要三类验收源码层已经确认 240×240 尺寸、对象层次、五种视觉模式、16 行局部缓冲、单任务 tick、120 ms 等待、512 字节 pending buffer、144/108 字节分页和 3.2 秒停留。最新 UI 包000_BURN_THIS_42_GUGUGAGA_DETAILED_UI_WS63_20260723_2040.fwpkg的大小和 SHA-256 也已在第 04 篇记录。但界面验收还应分三类静态布局检查边缘裁切、字体和页码动态检查焦点环、波形、音量覆盖层和分页节奏长时间运行检查任务占用、发热、队列堵塞和 LVGL fault。本轮没有重新烧录和拍摄设备因此本文结论是“当前 240×240 UI 实现已按源码复核”不是“今天完成了新包的全部实机视觉验收”。

相关新闻

Rust 的安全编码在企业合规中的价值:CWE Top 25 在 Rust 中的天然消解分析

Rust 的安全编码在企业合规中的价值:CWE Top 25 在 Rust 中的天然消解分析

Rust 的安全编码在企业合规中的价值:CWE Top 25 在 Rust 中的天然消解分析 一、合规审计中的安全编码困境 企业合规审计中,代码安全问题被量化为 CWE(Common Weakness Enumeration)弱点的检出数量。CWE Top 25 汇集了当前最危险的…

2026/9/24 16:28:18 阅读更多 →
Kratos(SneakyLog)AiTM 钓鱼套件技术机理、黑产运营与全域防御研究

Kratos(SneakyLog)AiTM 钓鱼套件技术机理、黑产运营与全域防御研究

摘要 针对 2026 年欧美联合执法行动关停的 Kratos(微软威胁情报命名为 SneakyLog)钓鱼即服务套件展开系统性实证研究。该工具依托对抗中间人(AiTM)反向代理架构实现 Microsoft 365 会话 Cookie 劫持,可完整绕过多因素认…

2026/10/15 17:50:03 阅读更多 →
Unity过场动画制作:Timeline与Cinemachine高效电影感运镜指南

Unity过场动画制作:Timeline与Cinemachine高效电影感运镜指南

1. 项目概述:告别手K动画的繁琐时代 如果你还在用Animator Controller一个状态一个状态地手K关键帧来拼接过场动画,或者用脚本笨拙地控制Camera的Transform和Rotation,那真的有点“原始人钻木取火”的味道了。我经历过那个阶段,为…

2026/10/15 6:13:46 阅读更多 →

最新新闻

Cesium自建地形数据实战:从DEM到量化网格瓦片

Cesium自建地形数据实战:从DEM到量化网格瓦片

搞Cesium开发,最绕不开的一件事就是地形数据。默认的在线地形服务虽然打开就能用,可真到了项目落地,总有让你必须自己生成地形数据的理由——精度不够、网络受限、数据不能离开本机。这篇博客把完整跑过一次的Cesium地形数据生成过程记录下来…

2026/10/15 17:50:05 阅读更多 →
SDL2-devel 2.30.1 MinGW 包:LVGL 模拟器编译与避坑指南

SDL2-devel 2.30.1 MinGW 包:LVGL 模拟器编译与避坑指南

简介:本资源为SDL2-devel-2.30.1-mingw.zip开发依赖包,面向在VSCode中搭建LVGL模拟器的嵌入式与GUI开发者,用于解决Windows环境下编译LVGL仿真工程时缺少SDL2库文件的问题。压缩包共414个文件,约15.29MB,包含160个h头文…

2026/10/15 17:50:05 阅读更多 →
ADXL357高精度驱动开发:SPI时序控制与温度补偿实战

ADXL357高精度驱动开发:SPI时序控制与温度补偿实战

简介:本资源是面向嵌入式开发工程师与STM32初学者的ADXL357高精度MEMS加速度传感器驱动实现包,专为解决STM32平台下该传感器快速集成与数据采集难题而设计。压缩包共5个文件,含3个头文件(.h)负责寄存器定义、接口声明与…

2026/10/15 17:50:05 阅读更多 →
ADXL357.rar工程包解析:高精度惯性传感器寄存器级调试指南

ADXL357.rar工程包解析:高精度惯性传感器寄存器级调试指南

简介:本资源是面向嵌入式开发工程师与STM32初学者的ADXL357高精度MEMS加速度传感器驱动实现包,专为解决传感器在ARM Cortex-M平台上的快速集成与可靠通信问题而设计。压缩包共5个文件(3个.h头文件、2个.c源文件),总大小…

2026/10/15 17:50:05 阅读更多 →
MT5 与 Python 行情接口对接:历史数据获取及常见报错排查

MT5 与 Python 行情接口对接:历史数据获取及常见报错排查

一、背景在量化研究与策略回测中,行情数据的质量直接决定了结果的可信度。MT5 终端本身提供完整的历史 K 线与实时报价,但研究者往往更习惯在 Python 生态中完成数据分析与建模。通过官方提供的 Python 接口模块,可以把终端里的行情数据以编程…

2026/10/15 17:50:05 阅读更多 →
基于C++的USB通信上位机开发:从枚举到批量传输的完整实现

基于C++的USB通信上位机开发:从枚举到批量传输的完整实现

简介:这是一份面向C开发者与嵌入式方向学习者的USB通信上位机完整工程源码,聚焦于通过C实现与USB设备的数据交互,涵盖设备枚举、驱动初始化、异步数据传输、错误处理及图形界面等核心模块,适合希望深入理解USB协议与底层硬件操作的…

2026/10/15 17:49:04 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →