/* * 模块名称:UI Display Port (LVGL 离屏显示端口) * 模块功能:提供 LVGL 1bpp 离屏渲染端口与远程 keypad 输入设备 * 适用平台:STM32F407ZGTx * 作者:王建锋 * 创建日期:2026-08-29 * 修改记录: * 2026-08-29 王建锋 创建初始版本 */ #include "ui_port.h" #include #include "lvgl.h" #define DBG_TAG "[UI_PORT]" #include "dbg_log.h" /* 160x160 1bpp 帧 = 3200 字节 */ static uint8_t g_ui_fbuf[UI_FB_SIZE]; /* 绘制缓冲:I1 索引色布局 = [palette 8B][pixel data],尺寸与取像素偏移的完整说明见 * ui_port.h 中 UI_DRAW_BUF_PALETTE / UI_DRAW_BUF_SIZE 注释(含越界与整帧错位的历史成因)。 */ static uint8_t g_ui_draw_buf[UI_DRAW_BUF_SIZE]; /* 单键缓冲:菜单无需多键队列,仅保存最近一次待处理键 */ static volatile uint8_t g_pending_key = 0; /* 远端键 -> LVGL 键 */ /* * 函数功能:将远端键码映射为 LVGL 按键枚举值 * 入口参数:remo_key - 远端键码 uint8_t UI_KEY_* 取值之一 * 返回值:LVGL 按键值 uint8_t LV_KEY_*;未匹配返回 0 * 限定条件:无 * 函数说明:仅映射方向/确认/返回等导航键,其余返回 0 表示忽略 */ static uint8_t ui_map_key(uint8_t remo_key) { switch (remo_key) { case UI_KEY_UP: return (uint8_t)LV_KEY_UP; case UI_KEY_DOWN: return (uint8_t)LV_KEY_DOWN; case UI_KEY_LEFT: return (uint8_t)LV_KEY_LEFT; case UI_KEY_RIGHT: return (uint8_t)LV_KEY_RIGHT; case UI_KEY_ENTER: return (uint8_t)LV_KEY_ENTER; case UI_KEY_ESC: return (uint8_t)LV_KEY_ESC; default: return 0; } } /* keypad 读取回调,上报待处理键 */ /* * 函数功能:keypad 输入设备读取回调,上报待处理按键 * 入口参数:indev - 输入设备句柄 lv_indev_t * 未使用 * data - 读取结果 lv_indev_data_t * 由本函数填充 * 返回值:无 * 限定条件:无 * 函数说明:1. 若有缓存键则上报 PRESSED 并清除缓存 * 2. 否则上报 RELEASED,等待下次按键 */ static void ui_keypad_read(lv_indev_t * indev, lv_indev_data_t * data) { (void)indev; if (g_pending_key != 0) { data->state = LV_INDEV_STATE_PRESSED; data->key = g_pending_key; g_pending_key = 0; } else { data->state = LV_INDEV_STATE_RELEASED; data->key = 0; } } /* flush 回调:把 LVGL 1bpp 渲染结果拷到内存帧缓冲 */ /* * 函数功能:显示刷新回调,将 LVGL 渲染结果写入内存帧缓冲 * 入口参数:disp - 显示设备 lv_display_t * 未使用 * area - 刷新区域 const lv_area_t * 未使用 * color_p - 1bpp 像素数据首地址 uint8_t * * 返回值:无 * 限定条件:ui_port_init() 已成功调用 * 函数说明:整帧拷贝到 g_ui_fbuf,供 RemoDispBus 推送至远端 */ static void ui_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p) { (void)disp; (void)area; /* color_p = 绘制缓冲首地址(含 palette 头)→ 跳过 palette 取真实像素 */ memcpy(g_ui_fbuf, color_p + UI_DRAW_BUF_PALETTE, UI_FB_SIZE); lv_display_flush_ready(disp); } /* * 函数功能:初始化 LVGL 显示端口与按键输入设备 * 入口参数:无 * 返回值:0 - 成功,其他 - 失败(显示或输入设备创建失败) * 限定条件:无 * 函数说明:1. 初始化 LVGL 并创建 1bpp 离屏显示设备与绘制缓冲 * 2. 创建 keypad 设备及默认输入组,绑定刷新回调 * 3. 后续须周期调用 ui_port_step 驱动刷新 */ int ui_port_init(void) { lv_init(); static lv_display_t *disp = NULL; disp = lv_display_create(UI_SCREEN_W, UI_SCREEN_H); if (disp == NULL) { return -1; } #if LV_USE_THEME_MONO /* mono 主题显式取 dark_bg=true(黑底白字),这是远端画面"纯白"的根因修复: * UI 内容恒以 lv_color_white() 绘制(ui_standby / ui_menu_view / ui_deco / * ui_content_page),若底色是 LVGL 类默认浅灰 F5F5F5,白色内容在白底上完全 * 不可见,整帧近乎全白(实测 99% 像素为亮)。两步缺一不可: * 1) lv_theme_mono_init(disp, true) 重建单例主题样式(create 时已按 false 初始化); * 2) lv_display_set_theme() 只重应用到 screens[0](底层),活动屏幕必须再显式 * lv_theme_apply(),否则活动屏幕仍保持类默认底色(实测 bg_before=F5F5F5)。 */ lv_theme_t *th = lv_theme_mono_init(disp, true, LV_FONT_DEFAULT); lv_display_set_theme(disp, th); lv_theme_apply(lv_display_get_screen_active(disp)); DBG_INFO("theme: mono dark_bg=true applied"); #else DBG_ERROR("theme: LV_USE_THEME_MONO=0 (no mono theme)"); #endif lv_display_set_color_format(disp, LV_COLOR_FORMAT_I1); /* I1 绘制缓冲头 8 字节是调色板(两个 lv_color32_t,内存序 B,G,R,A)。LVGL 不会替 * 调用方初始化它,实测上电后为全 0(两项都是透明黑)。显式写成 [0]=黑 [1]=白: * 远端推流按"位=1 即亮"取用(见 ui_net_sink / DTU-RemoteLCD),语义必须确定。 */ g_ui_draw_buf[0] = 0x00u; g_ui_draw_buf[1] = 0x00u; g_ui_draw_buf[2] = 0x00u; g_ui_draw_buf[3] = 0xFFu; g_ui_draw_buf[4] = 0xFFu; g_ui_draw_buf[5] = 0xFFu; g_ui_draw_buf[6] = 0xFFu; g_ui_draw_buf[7] = 0xFFu; lv_display_set_buffers(disp, g_ui_draw_buf, NULL, UI_DRAW_BUF_SIZE, LV_DISPLAY_RENDER_MODE_FULL); lv_display_set_flush_cb(disp, ui_flush_cb); static lv_indev_t *indev = NULL; indev = lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); lv_indev_set_read_cb(indev, ui_keypad_read); lv_group_t *g = lv_group_create(); if (g == NULL) { return -1; } lv_group_set_default(g); lv_indev_set_group(indev, g); return 0; } /* * 函数功能:获取内存帧缓冲首地址 * 入口参数:无 * 返回值:帧缓冲首地址 uint8_t * 固定长度 UI_FB_SIZE 字节 * 限定条件:ui_port_init() 已成功调用 * 函数说明:缓冲由 flush 回调更新,供网络推流读取 */ uint8_t *ui_port_get_framebuffer(void) { return g_ui_fbuf; } /* * 函数功能:向输入队列喂入一个远端按键值 * 入口参数:remo_key - 远端键码 uint8_t UI_KEY_* 取值之一 * 返回值:无 * 限定条件:无 * 函数说明:映射后缓存,由 keypad 回调在下次轮询上报 */ void ui_port_queue_key(uint8_t remo_key) { uint8_t k = ui_map_key(remo_key); if (k != 0) { g_pending_key = k; } } /* * 函数功能:UI 周期步进(推进节拍并刷新 LVGL) * 入口参数:tick_ms - 距上次调用毫秒数 uint32_t 建议 10 * 返回值:无 * 限定条件:ui_port_init() 已成功调用 * 函数说明:依次调用 lv_tick_inc 与 lv_timer_handler,须由任务周期调用 */ void ui_port_step(uint32_t tick_ms) { lv_tick_inc(tick_ms); lv_timer_handler(); }