diff --git a/APP/app_log.c b/APP/app_log.c index f61da20..9ac8080 100644 --- a/APP/app_log.c +++ b/APP/app_log.c @@ -14,11 +14,95 @@ static char s_debug_level = 'D'; static char s_cmd_buf[48]; static uint8_t s_cmd_len; +/* RTT 二进制烧录:将 ui0902_res.bin 写到 W25Q128 @ UI0902_RES_BASE */ +static uint8_t s_flash_mode; +static uint32_t s_flash_addr; +static uint32_t s_flash_remain; +static uint32_t s_flash_last_sector; +static uint16_t s_flash_page_len; +static uint8_t s_flash_page[256]; + +uint8_t app_log_flash_busy(void) +{ + return s_flash_mode; +} + static uint32_t app_log_ms(void) { return (uint32_t)(rt_tick_get() * 1000U / RT_TICK_PER_SECOND); } +static void app_log_flash_flush_page(void) +{ + uint32_t sector; + char ack[40]; + + if (s_flash_page_len == 0U) { + return; + } + + sector = s_flash_addr & ~0xFFFU; + if (sector != s_flash_last_sector) { + W25Q128_Erase_Sector(sector); + s_flash_last_sector = sector; + } + W25Q128_Write(s_flash_page, s_flash_addr, s_flash_page_len); + s_flash_addr += s_flash_page_len; + 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); + } +} + +static void app_log_flash_feed(const uint8_t *data, unsigned len) +{ + unsigned i; + + for (i = 0; i < len && s_flash_remain > 0U; i++) { + if (s_flash_page_len == 0U && s_flash_addr == UI0902_RES_BASE && i == 0U) { + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "FLASH_RX\n"); + } + s_flash_page[s_flash_page_len++] = data[i]; + if (s_flash_page_len >= sizeof(s_flash_page) || + s_flash_page_len >= s_flash_remain) { + app_log_flash_flush_page(); + } + } + + if (s_flash_remain == 0U && s_flash_mode) { + s_flash_mode = 0U; + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "FLASH_UI0902_OK\n"); + UI_DrawModeSelectScreen(); + } +} + +static void app_log_flash_start(uint32_t size) +{ + char msg[64]; + + if (size == 0U || size > (2U * 1024U * 1024U)) { + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "FLASH_UI0902_ERR size\n"); + return; + } + + s_flash_mode = 1U; + s_flash_addr = UI0902_RES_BASE; + s_flash_remain = size; + s_flash_last_sector = 0xFFFFFFFFUL; + s_flash_page_len = 0U; + s_cmd_len = 0U; + s_cmd_buf[0] = '\0'; + + snprintf(msg, sizeof(msg), + "FLASH_UI0902_GO base=0x%08lX size=%lu\n", + (unsigned long)UI0902_RES_BASE, (unsigned long)size); + SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, msg); +} + static void app_log_slot_store(const char *line) { uint16_t idx = s_head; @@ -177,6 +261,11 @@ uint8_t app_log_try_command(const char *cmd) app_log_sys_reset(); return 1U; } + if (strncmp(cmd, "flash ui0902 ", 13) == 0) { + uint32_t size = (uint32_t)strtoul(cmd + 13, NULL, 10); + app_log_flash_start(size); + return 1U; + } return 0U; } @@ -207,7 +296,7 @@ static void app_log_feed_char(char c) void app_log_poll(void) { - char rx[16]; + uint8_t rx[64]; unsigned read_len; unsigned i; @@ -216,8 +305,13 @@ void app_log_poll(void) return; } + if (s_flash_mode) { + app_log_flash_feed(rx, read_len); + return; + } + for (i = 0; i < read_len; i++) { - app_log_feed_char(rx[i]); + app_log_feed_char((char)rx[i]); } } @@ -234,5 +328,6 @@ void app_log_write(char level, const char *cat, const char *fmt, ...) { (void)le void app_log_poll(void) {} uint8_t app_log_try_command(const char *cmd) { (void)cmd; return 0U; } void app_log_set_ui_page(const char *name) { (void)name; } +uint8_t app_log_flash_busy(void) { return 0U; } #endif /* APP_LOG_ENABLE */ diff --git a/APP/app_log.h b/APP/app_log.h index e34f9de..29062e5 100644 --- a/APP/app_log.h +++ b/APP/app_log.h @@ -34,6 +34,7 @@ void app_log_write(char level, const char *cat, const char *fmt, ...); void app_log_poll(void); uint8_t app_log_try_command(const char *cmd); void app_log_set_ui_page(const char *name); +uint8_t app_log_flash_busy(void); #if APP_LOG_ENABLE diff --git a/UI/UI_Idle.c b/UI/UI_Idle.c index 29eb314..754c55c 100644 --- a/UI/UI_Idle.c +++ b/UI/UI_Idle.c @@ -71,6 +71,16 @@ static void UI_ModeSelect_DrawButtons(void) UI_ModeSelect_DrawButtonAt(2); } +void UI_ReturnToModeSelect(void) +{ + StartFlag = 0; + InModeFlag = false; + AutoBandTop1_Stop(); + s_mode_select_focus = 0; + CallUI_Idle(); + UI_DrawModeSelectScreen(); +} + void UI_DrawModeSelectScreen(void) { UI_ClearFocus(); @@ -80,7 +90,7 @@ void UI_DrawModeSelectScreen(void) label_top(); UI_ModeSelect_DrawButtons(); Draw_Bottom_Bar(UI0902_NAV_MODE, 0); -} +} const Touch_AreaTypeDef Touch_ModeSelect_Areas[] = { /* Flag=按钮序号(0万能/1普通/2专业);ID=进入 TAB */ @@ -230,8 +240,6 @@ void IdleProcess(DisplayTaskMessage_Type msg) LOG_I("UI", "mode_select hit area=%u row=%u focus=%u x=%u y=%u", (unsigned)k, (unsigned)row, (unsigned)s_mode_select_focus, (unsigned)msg.HiByte, (unsigned)msg.LoByte); - /* 调试十字:确认触点是否落在按钮上 */ - TP_Drow_Touch_Point(msg.HiByte, msg.LoByte, RED); /* 已聚焦 → 进入;未聚焦 → 切蓝焦点,再点一次进入 */ if (row == s_mode_select_focus) { if (now - s_last_enter_ms < 300U) @@ -248,7 +256,6 @@ void IdleProcess(DisplayTaskMessage_Type msg) if (!hit) { LOG_I("UI", "mode_select miss x=%u y=%u", (unsigned)msg.HiByte, (unsigned)msg.LoByte); - TP_Drow_Touch_Point(msg.HiByte, msg.LoByte, YELLOW); } } break; diff --git a/device/LCD_ILI9341/Drv_ILI9341_Lcd_App.c b/device/LCD_ILI9341/Drv_ILI9341_Lcd_App.c index 548aab4..0e345f0 100644 --- a/device/LCD_ILI9341/Drv_ILI9341_Lcd_App.c +++ b/device/LCD_ILI9341/Drv_ILI9341_Lcd_App.c @@ -236,24 +236,26 @@ void LCD_WR_PIC_ClipX(uint16_t x, uint16_t y, uint16_t full_w, uint16_t h, } } -/* 从W25Q128读小图到RAM后透明显示(跳过近黑像素),仅用于 <=4KB 小图 */ -#define PIC_TRANS_BUF_SIZE 4096 +/* 从 W25Q128 按行读取并透明显示(跳过近黑像素),复用 pic_batch_buf,无大块静态 RAM */ void LCD_WR_PIC_FROM_FLASH_Trans(uint16_t x, uint16_t y, uint16_t length, uint16_t width, uint32_t flash_addr) { - static uint8_t s_trans_buf[PIC_TRANS_BUF_SIZE]; - uint32_t total_bytes = (uint32_t)length * width * 2; - uint32_t i; - if (total_bytes == 0 || total_bytes > PIC_TRANS_BUF_SIZE) return; - W25Q128_Read(s_trans_buf, flash_addr, (uint16_t)total_bytes); - for (i = 0; i < total_bytes / 2; i++) + uint16_t row, col; + uint32_t row_bytes = (uint32_t)length * 2U; + if (length == 0 || width == 0 || row_bytes > PIC_BATCH_SIZE) return; + + for (row = 0; row < width; row++) { - uint16_t c = (uint16_t)((s_trans_buf[i * 2] << 8) | s_trans_buf[i * 2 + 1]); - uint8_t r5 = (uint8_t)((c >> 11) & 0x1F); - uint8_t g6 = (uint8_t)((c >> 5) & 0x3F); - uint8_t b5 = (uint8_t)(c & 0x1F); - if (r5 <= 1 && g6 <= 2 && b5 <= 2) - continue; - LCD_DrawPoint((uint16_t)(x + (i % length)), (uint16_t)(y + (i / length)), c); + W25Q128_Read(pic_batch_buf, flash_addr + row * row_bytes, (uint16_t)row_bytes); + for (col = 0; col < length; col++) + { + uint16_t c = (uint16_t)((pic_batch_buf[col * 2U] << 8) | pic_batch_buf[col * 2U + 1U]); + uint8_t r5 = (uint8_t)((c >> 11) & 0x1F); + uint8_t g6 = (uint8_t)((c >> 5) & 0x3F); + uint8_t b5 = (uint8_t)(c & 0x1F); + if (r5 <= 1 && g6 <= 2 && b5 <= 2) + continue; + LCD_DrawPoint((uint16_t)(x + col), (uint16_t)(y + row), c); + } } } diff --git a/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c b/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c index 4472cf3..8bd63c3 100644 --- a/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c +++ b/device/LCD_ILI9341/Drv_ILI9341_Lcd_Dump.c @@ -190,6 +190,11 @@ static uint8_t LCD_Dump_CommandPending(void) unsigned read_len; unsigned i; + /* 烧录外部 UI 资源时占用 RTT0 down,禁止此处吞字节 */ + if (app_log_flash_busy()) { + return 0U; + } + read_len = SEGGER_RTT_Read(LCD_DUMP_RTT_DOWN_CH, rx, sizeof(rx)); if (read_len == 0U) { return 0U; diff --git a/project/IAR_V7.4/AT32F403AxC.icf b/project/IAR_V7.4/AT32F403AxC.icf index 218abde..603d715 100644 --- a/project/IAR_V7.4/AT32F403AxC.icf +++ b/project/IAR_V7.4/AT32F403AxC.icf @@ -21,7 +21,7 @@ define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFED define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { }; define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { }; -initialize by copy { readwrite }; +initialize by copy with packing = none { readwrite }; do not initialize { section .noinit }; place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }; diff --git a/tools/flash_ui0902_res.py b/tools/flash_ui0902_res.py new file mode 100644 index 0000000..6172c5c --- /dev/null +++ b/tools/flash_ui0902_res.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Flash tools/out/ui0902_res.bin to W25Q128 @ 0x00100000 via RTT.""" + +from __future__ import annotations + +import argparse +import os +import sys +import time + +DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A") +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +DEFAULT_BIN = os.path.join(ROOT, "tools", "out", "ui0902_res.bin") + + +def connect_jlink(device: str): + import pylink + + jlink = pylink.JLink() + jlink.open() + try: + jlink.exec_command("HideDeviceSelection = 1") + except Exception: + pass + jlink.set_tif(pylink.enums.JLinkInterfaces.SWD) + + last_error = None + for candidate in (device, *DEFAULT_DEVICES): + try: + try: + jlink.exec_command(f"Device = {candidate}") + except Exception: + pass + jlink.connect(candidate) + print(f"Connected as {candidate}") + return jlink + except Exception as exc: + last_error = exc + jlink.close() + raise SystemExit(f"Failed to connect J-Link: {last_error}") + + +def find_rtt_control_block(jlink, ram_base: int = 0x20000000, ram_size: int = 0x18000): + needle = b"SEGGER RTT" + chunk = 0x1000 + was_halted = jlink.halted() + if not was_halted: + jlink.halt() + try: + for off in range(0, ram_size, chunk): + data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off))) + idx = data.find(needle) + if idx >= 0: + return ram_base + off + idx + finally: + if not was_halted: + if hasattr(jlink, "restart"): + jlink.restart() + else: + jlink.go() + time.sleep(0.1) + return None + + +def rtt_read_text(jlink, timeout_s: float = 0.2) -> str: + deadline = time.time() + timeout_s + chunks: list[bytes] = [] + while time.time() < deadline: + try: + data = bytes(jlink.rtt_read(0, 512) or []) + except Exception: + data = b"" + if data: + chunks.append(data) + deadline = time.time() + timeout_s + else: + time.sleep(0.02) + return b"".join(chunks).decode("ascii", errors="replace") + + +def wait_for(jlink, marker: str, timeout_s: float = 30.0) -> str: + deadline = time.time() + timeout_s + buf = "" + while time.time() < deadline: + buf += rtt_read_text(jlink, 0.15) + if marker in buf: + return buf + raise SystemExit(f"Timeout waiting for {marker!r}\n--- RTT ---\n{buf[-800:]}") + + +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) + continue + off += n + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--bin", default=DEFAULT_BIN) + parser.add_argument("--device", default="Cortex-M4") + args = parser.parse_args() + + if not os.path.isfile(args.bin): + raise SystemExit(f"missing bin: {args.bin}") + + data = open(args.bin, "rb").read() + size = len(data) + print(f"bin={args.bin} size={size}") + + jlink = connect_jlink(args.device) + try: + cb = find_rtt_control_block(jlink) + if cb is None: + raise SystemExit("RTT control block not found") + print(f"RTT CB @ 0x{cb:08X}") + jlink.rtt_start(cb) + time.sleep(0.3) + _ = rtt_read_text(jlink, 0.3) + + # 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: + pass + cb = find_rtt_control_block(jlink) + if cb is None: + raise SystemExit("RTT CB missing after reset") + print(f"RTT CB @ 0x{cb:08X}") + jlink.rtt_start(cb) + time.sleep(0.5) + _ = rtt_read_text(jlink, 0.5) + + cmd = f"flash ui0902 {size}\n".encode("ascii") + send_bytes(jlink, cmd) + print("sent flash command, waiting GO...") + log = wait_for(jlink, "FLASH_UI0902_GO", 15.0) + print(log.strip().splitlines()[-1]) + time.sleep(0.2) + + # wait for first byte confirmation after starting transfer + chunk = 256 + sent = 0 + t0 = time.time() + # prime first page then expect FLASH_RX + send_bytes(jlink, data[0:256]) + sent = 256 + wait_for(jlink, "FLASH_RX", 15.0) + print("device receiving...") + while sent < size: + end = min(sent + chunk, size) + send_bytes(jlink, data[sent:end]) + sent = end + if sent % 4096 == 0 or sent == size: + log = wait_for(jlink, "FLASH_ACK", 30.0) + 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") + + log = wait_for(jlink, "FLASH_UI0902_OK", 30.0) + print(log.strip().splitlines()[-1]) + print("Done. Mode-select should redraw with new assets.") + finally: + try: + jlink.rtt_stop() + except Exception: + pass + jlink.close() + + +if __name__ == "__main__": + main() diff --git a/tools/gen_ui0902_assets.py b/tools/gen_ui0902_assets.py index e52745f..479cfcc 100644 --- a/tools/gen_ui0902_assets.py +++ b/tools/gen_ui0902_assets.py @@ -103,11 +103,12 @@ INTERNAL = [ # ---------------- external flash (big images / text strips) ---------------- EXTERNAL = [ + # Art numbering: 16/19=universal, 17/20=normal, 18/21=expert (not consecutive pairs) ("ROW_UNIVERSAL_NOT", "04_模式图标/完整版/模式界面栏背景-16.png"), - ("ROW_UNIVERSAL_SEL", "04_模式图标/完整版/模式界面栏背景-17.png"), - ("ROW_NORMAL_NOT", "04_模式图标/完整版/模式界面栏背景-18.png"), - ("ROW_NORMAL_SEL", "04_模式图标/完整版/模式界面栏背景-19.png"), - ("ROW_EXPERT_NOT", "04_模式图标/完整版/模式界面栏背景-20.png"), + ("ROW_UNIVERSAL_SEL", "04_模式图标/完整版/模式界面栏背景-19.png"), + ("ROW_NORMAL_NOT", "04_模式图标/完整版/模式界面栏背景-17.png"), + ("ROW_NORMAL_SEL", "04_模式图标/完整版/模式界面栏背景-20.png"), + ("ROW_EXPERT_NOT", "04_模式图标/完整版/模式界面栏背景-18.png"), ("ROW_EXPERT_SEL", "04_模式图标/完整版/模式界面栏背景-21.png"), ("ICON_UNIVERSAL_SEL", "04_模式图标/万能模式图标-1.png"), ("ICON_UNIVERSAL_NOT", "04_模式图标/万能模式图标-2.png"),