From 2f84c3bb9bdffa40276971f786cbae952956f4e7 Mon Sep 17 00:00:00 2001 From: yuquanjun Date: Wed, 2 Sep 2026 16:57:36 +0800 Subject: [PATCH] Add 0902 full-screen boot logo to external UI resource pack. Draw POWER_ON from UI0902_BOOT_LOGO, add ui boot RTT preview command, and harden RTT flash ACK pacing. Co-authored-by: Cursor --- APP/app_log.c | 15 ++++--- UI/UI_Idle.c | 4 +- project/inc/UI0902_Res_Addr.h | 6 ++- .../assets_0902/07_开机/开机界面-20.png | Bin 0 -> 4953 bytes tools/flash_ui0902_res.py | 37 +++++++++--------- tools/gen_ui0902_assets.py | 35 +++++++++++++++-- 6 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 tools/assets_0902/07_开机/开机界面-20.png diff --git a/APP/app_log.c b/APP/app_log.c index 9ac8080..348715d 100644 --- a/APP/app_log.c +++ b/APP/app_log.c @@ -51,11 +51,9 @@ static void app_log_flash_flush_page(void) s_flash_remain -= s_flash_page_len; s_flash_page_len = 0U; - /* 扇区边界或结束时 ACK,主机据此节流 */ - if ((s_flash_addr & 0xFFFU) == 0U || s_flash_remain == 0U) { - snprintf(ack, sizeof(ack), "FLASH_ACK %lu\n", (unsigned long)s_flash_addr); - SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, ack); - } + /* 每页 ACK,避免主机打爆 RTT 下行缓冲 */ + snprintf(ack, sizeof(ack), "FLASH_ACK %lu\n", (unsigned long)s_flash_addr); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, ack); } static void app_log_flash_feed(const uint8_t *data, unsigned len) @@ -266,6 +264,13 @@ uint8_t app_log_try_command(const char *cmd) app_log_flash_start(size); return 1U; } + if (strncmp(cmd, "ui boot", 7) == 0) { + LCD_BLK_Set(); + LCD_WR_PIC_FROM_FLASH(0, 0, UI0902_BOOT_LOGO_W, UI0902_BOOT_LOGO_H, + UI0902_BOOT_LOGO_ADDR); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "UI_BOOT_OK\n"); + return 1U; + } return 0U; } diff --git a/UI/UI_Idle.c b/UI/UI_Idle.c index cd05f8d..fbd3a97 100644 --- a/UI/UI_Idle.c +++ b/UI/UI_Idle.c @@ -181,8 +181,8 @@ void IdleProcess(DisplayTaskMessage_Type msg) { case MSG_ID_POWER_ON : LCD_BLK_Set(); - LCD_FillByColor(0, 0, 240, 320, WHITE); - LCD_WR_PIC_FROM_FLASH(40, 80, 168, 155, 0x00000000); + LCD_WR_PIC_FROM_FLASH(0, 0, UI0902_BOOT_LOGO_W, UI0902_BOOT_LOGO_H, + UI0902_BOOT_LOGO_ADDR); BSP_SelectAdcChannel(6); /* 让出 CPU,避免忙等卡住触摸/主循环线程 */ rt_thread_mdelay(1000); diff --git a/project/inc/UI0902_Res_Addr.h b/project/inc/UI0902_Res_Addr.h index 61bd7da..3029131 100644 --- a/project/inc/UI0902_Res_Addr.h +++ b/project/inc/UI0902_Res_Addr.h @@ -276,7 +276,11 @@ #define UI0902_TXT_RESTORE_CONFIRM_W 71 #define UI0902_TXT_RESTORE_CONFIRM_H 14 -#define UI0902_BTN_CANCEL_BIG_ADDR (UI0902_RES_BASE + 0x086000UL) /* 11872 bytes */ +#define UI0902_BOOT_LOGO_ADDR (UI0902_RES_BASE + 0x086000UL) /* 153600 bytes */ +#define UI0902_BOOT_LOGO_W 240 +#define UI0902_BOOT_LOGO_H 320 + +#define UI0902_BTN_CANCEL_BIG_ADDR (UI0902_RES_BASE + 0x0AC000UL) /* 11872 bytes */ #define UI0902_BTN_CANCEL_BIG_W 106 #define UI0902_BTN_CANCEL_BIG_H 56 diff --git a/tools/assets_0902/07_开机/开机界面-20.png b/tools/assets_0902/07_开机/开机界面-20.png new file mode 100644 index 0000000000000000000000000000000000000000..a7dd0ac46f51cc819546f195c6e1630549a4943e GIT binary patch literal 4953 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 BYbO8z literal 0 HcmV?d00001 diff --git a/tools/flash_ui0902_res.py b/tools/flash_ui0902_res.py index d8294f7..0d88d16 100644 --- a/tools/flash_ui0902_res.py +++ b/tools/flash_ui0902_res.py @@ -91,9 +91,13 @@ def wait_for(jlink, marker: str, timeout_s: float = 30.0) -> str: def send_bytes(jlink, payload: bytes) -> None: off = 0 while off < len(payload): - n = jlink.rtt_write(0, list(payload[off : off + 128])) - if n <= 0: - time.sleep(0.01) + try: + n = jlink.rtt_write(0, list(payload[off : off + 64])) + except Exception: + time.sleep(0.05) + continue + if n is None or n <= 0: + time.sleep(0.02) continue off += n @@ -124,11 +128,9 @@ def main() -> None: _ = rtt_read_text(jlink, 0.3) if not args.no_reset: - # Hardware reset clears any stuck flash_mode from a previous attempt print("hardware reset...") jlink.reset(halt=False) time.sleep(3.5) - # RTT CB may move after reboot — re-find try: jlink.rtt_stop() except Exception: @@ -148,26 +150,23 @@ def main() -> None: print(log.strip().splitlines()[-1]) time.sleep(0.2) - # Smaller chunks + brief pause so erase/write can keep up with RTT down-buffer - chunk = 128 + chunk = 256 sent = 0 t0 = time.time() - send_bytes(jlink, data[0:256]) - sent = 256 - wait_for(jlink, "FLASH_RX", 20.0) - print("device receiving...") + receiving = False while sent < size: end = min(sent + chunk, size) send_bytes(jlink, data[sent:end]) sent = end - if sent % 256 == 0: - time.sleep(0.002) - if sent % 4096 == 0 or sent == size: - log = wait_for(jlink, "FLASH_ACK", 60.0) - if "FLASH_UI0902_OK" in log: - print(log.strip().splitlines()[-1]) - print("Done. Mode-select should redraw with new assets.") - return + # 一页对应一次 ACK;FLASH_RX 可能夹在同一次读缓冲里,勿分两次 wait + log = wait_for(jlink, "FLASH_ACK", 60.0) + if (not receiving) and ("FLASH_RX" in log or "FLASH_ACK" in log): + receiving = True + print("device receiving...") + if "FLASH_UI0902_OK" in log: + print(log.strip().splitlines()[-1]) + print("Done. Mode-select should redraw with new assets.") + return if sent % (64 * 1024) == 0 or sent == size: elapsed = time.time() - t0 print(f" {sent}/{size} ({100.0 * sent / size:.1f}%) {elapsed:.1f}s") diff --git a/tools/gen_ui0902_assets.py b/tools/gen_ui0902_assets.py index 21ecaee..544d63b 100644 --- a/tools/gen_ui0902_assets.py +++ b/tools/gen_ui0902_assets.py @@ -48,9 +48,26 @@ def rgba_to_rgb565(im): r = (r * a) // 255 g = (g * a) // 255 b = (b * a) // 255 - c = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3) - out[i] = (c >> 8) & 0xFF - out[i + 1] = c & 0xFF + v = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) + out[i] = (v >> 8) & 0xFF + out[i + 1] = v & 0xFF + i += 2 + return bytes(out), w, h + + +def rgba_to_rgb565_opaque(im): + """Full-screen boot/charge: keep dark background pixels (no near-black punch-through).""" + im = im.convert("RGB") + px = im.load() + w, h = im.size + out = bytearray(w * h * 2) + i = 0 + for y in range(h): + for x in range(w): + r, g, b = px[x, y] + v = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) + out[i] = (v >> 8) & 0xFF + out[i + 1] = v & 0xFF i += 2 return bytes(out), w, h @@ -172,6 +189,8 @@ EXTERNAL = [ ("TXT_RESTORE_V2", "06_控件/文字/文字-25.png"), ("TXT_RESTORE_H", "06_控件/文字/文字-26.png"), ("TXT_RESTORE_CONFIRM", "06_控件/文字/文字-27.png"), + # 整屏开机 Logo(避开旧 0x0 / 充电 0xCB70 区,挂在 ui0902 资源包末尾) + ("BOOT_LOGO", "07_开机/开机界面-20.png"), ] @@ -209,7 +228,15 @@ def main(): im = Image.open(SRC / rel) if name in ("ROW_UNIVERSAL_NOT", "ROW_UNIVERSAL_SEL", "ROW_NORMAL_NOT", "ROW_NORMAL_SEL", "ROW_EXPERT_NOT", "ROW_EXPERT_SEL"): im = im.resize(ROW_RESIZE, Image.LANCZOS) - data, w, h = rgba_to_rgb565(im) + data, w, h = rgba_to_rgb565(im) + elif name == "BOOT_LOGO": + if im.size[0] >= 241 and im.size[1] >= 321: + im = im.crop((0, 0, 240, 320)) + elif im.size != (240, 320): + im = im.resize((240, 320), Image.LANCZOS) + data, w, h = rgba_to_rgb565_opaque(im) + else: + data, w, h = rgba_to_rgb565(im) entries[name] = (offset, w, h, len(data)) blob += data pad = (-len(blob)) % ALIGN