Fix mode-row asset pairing and add RTT external UI flash path.

- Map 0902 rows as 16/19 universal, 17/20 normal, 18/21 expert
- Add flash ui0902 RTT writer; avoid LCD dump stealing down bytes
- Restore UI_ReturnToModeSelect; shrink flash-trans RAM use
- Disable mode-select debug touch crosses

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yuquanjun 2026-09-02 15:24:49 +08:00
parent f8675e3529
commit e62c2270af
8 changed files with 320 additions and 26 deletions

View File

@ -14,11 +14,95 @@ static char s_debug_level = 'D';
static char s_cmd_buf[48]; static char s_cmd_buf[48];
static uint8_t s_cmd_len; 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) static uint32_t app_log_ms(void)
{ {
return (uint32_t)(rt_tick_get() * 1000U / RT_TICK_PER_SECOND); 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) static void app_log_slot_store(const char *line)
{ {
uint16_t idx = s_head; uint16_t idx = s_head;
@ -177,6 +261,11 @@ uint8_t app_log_try_command(const char *cmd)
app_log_sys_reset(); app_log_sys_reset();
return 1U; 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; return 0U;
} }
@ -207,7 +296,7 @@ static void app_log_feed_char(char c)
void app_log_poll(void) void app_log_poll(void)
{ {
char rx[16]; uint8_t rx[64];
unsigned read_len; unsigned read_len;
unsigned i; unsigned i;
@ -216,8 +305,13 @@ void app_log_poll(void)
return; return;
} }
if (s_flash_mode) {
app_log_flash_feed(rx, read_len);
return;
}
for (i = 0; i < read_len; i++) { 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) {} void app_log_poll(void) {}
uint8_t app_log_try_command(const char *cmd) { (void)cmd; return 0U; } uint8_t app_log_try_command(const char *cmd) { (void)cmd; return 0U; }
void app_log_set_ui_page(const char *name) { (void)name; } void app_log_set_ui_page(const char *name) { (void)name; }
uint8_t app_log_flash_busy(void) { return 0U; }
#endif /* APP_LOG_ENABLE */ #endif /* APP_LOG_ENABLE */

View File

@ -34,6 +34,7 @@ void app_log_write(char level, const char *cat, const char *fmt, ...);
void app_log_poll(void); void app_log_poll(void);
uint8_t app_log_try_command(const char *cmd); uint8_t app_log_try_command(const char *cmd);
void app_log_set_ui_page(const char *name); void app_log_set_ui_page(const char *name);
uint8_t app_log_flash_busy(void);
#if APP_LOG_ENABLE #if APP_LOG_ENABLE

View File

@ -71,6 +71,16 @@ static void UI_ModeSelect_DrawButtons(void)
UI_ModeSelect_DrawButtonAt(2); 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) void UI_DrawModeSelectScreen(void)
{ {
UI_ClearFocus(); UI_ClearFocus();
@ -80,7 +90,7 @@ void UI_DrawModeSelectScreen(void)
label_top(); label_top();
UI_ModeSelect_DrawButtons(); UI_ModeSelect_DrawButtons();
Draw_Bottom_Bar(UI0902_NAV_MODE, 0); Draw_Bottom_Bar(UI0902_NAV_MODE, 0);
} }
const Touch_AreaTypeDef Touch_ModeSelect_Areas[] = { const Touch_AreaTypeDef Touch_ModeSelect_Areas[] = {
/* Flag=按钮序号(0万能/1普通/2专业)ID=进入 TAB */ /* 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", 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)k, (unsigned)row, (unsigned)s_mode_select_focus,
(unsigned)msg.HiByte, (unsigned)msg.LoByte); (unsigned)msg.HiByte, (unsigned)msg.LoByte);
/* 调试十字:确认触点是否落在按钮上 */
TP_Drow_Touch_Point(msg.HiByte, msg.LoByte, RED);
/* 已聚焦 → 进入;未聚焦 → 切蓝焦点,再点一次进入 */ /* 已聚焦 → 进入;未聚焦 → 切蓝焦点,再点一次进入 */
if (row == s_mode_select_focus) { if (row == s_mode_select_focus) {
if (now - s_last_enter_ms < 300U) if (now - s_last_enter_ms < 300U)
@ -248,7 +256,6 @@ void IdleProcess(DisplayTaskMessage_Type msg)
if (!hit) { if (!hit) {
LOG_I("UI", "mode_select miss x=%u y=%u", LOG_I("UI", "mode_select miss x=%u y=%u",
(unsigned)msg.HiByte, (unsigned)msg.LoByte); (unsigned)msg.HiByte, (unsigned)msg.LoByte);
TP_Drow_Touch_Point(msg.HiByte, msg.LoByte, YELLOW);
} }
} }
break; break;

View File

@ -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 小图 */ /* 从 W25Q128 按行读取并透明显示(跳过近黑像素),复用 pic_batch_buf无大块静态 RAM */
#define PIC_TRANS_BUF_SIZE 4096
void LCD_WR_PIC_FROM_FLASH_Trans(uint16_t x, uint16_t y, uint16_t length, uint16_t width, uint32_t flash_addr) 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]; uint16_t row, col;
uint32_t total_bytes = (uint32_t)length * width * 2; uint32_t row_bytes = (uint32_t)length * 2U;
uint32_t i; if (length == 0 || width == 0 || row_bytes > PIC_BATCH_SIZE) return;
if (total_bytes == 0 || total_bytes > PIC_TRANS_BUF_SIZE) return;
W25Q128_Read(s_trans_buf, flash_addr, (uint16_t)total_bytes); for (row = 0; row < width; row++)
for (i = 0; i < total_bytes / 2; i++)
{ {
uint16_t c = (uint16_t)((s_trans_buf[i * 2] << 8) | s_trans_buf[i * 2 + 1]); W25Q128_Read(pic_batch_buf, flash_addr + row * row_bytes, (uint16_t)row_bytes);
uint8_t r5 = (uint8_t)((c >> 11) & 0x1F); for (col = 0; col < length; col++)
uint8_t g6 = (uint8_t)((c >> 5) & 0x3F); {
uint8_t b5 = (uint8_t)(c & 0x1F); uint16_t c = (uint16_t)((pic_batch_buf[col * 2U] << 8) | pic_batch_buf[col * 2U + 1U]);
if (r5 <= 1 && g6 <= 2 && b5 <= 2) uint8_t r5 = (uint8_t)((c >> 11) & 0x1F);
continue; uint8_t g6 = (uint8_t)((c >> 5) & 0x3F);
LCD_DrawPoint((uint16_t)(x + (i % length)), (uint16_t)(y + (i / length)), c); 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);
}
} }
} }

View File

@ -190,6 +190,11 @@ static uint8_t LCD_Dump_CommandPending(void)
unsigned read_len; unsigned read_len;
unsigned i; 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)); read_len = SEGGER_RTT_Read(LCD_DUMP_RTT_DOWN_CH, rx, sizeof(rx));
if (read_len == 0U) { if (read_len == 0U) {
return 0U; return 0U;

View File

@ -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 CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { }; 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 }; do not initialize { section .noinit };
place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec }; place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };

183
tools/flash_ui0902_res.py Normal file
View File

@ -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()

View File

@ -103,11 +103,12 @@ INTERNAL = [
# ---------------- external flash (big images / text strips) ---------------- # ---------------- external flash (big images / text strips) ----------------
EXTERNAL = [ EXTERNAL = [
# Art numbering: 16/19=universal, 17/20=normal, 18/21=expert (not consecutive pairs)
("ROW_UNIVERSAL_NOT", "04_模式图标/完整版/模式界面栏背景-16.png"), ("ROW_UNIVERSAL_NOT", "04_模式图标/完整版/模式界面栏背景-16.png"),
("ROW_UNIVERSAL_SEL", "04_模式图标/完整版/模式界面栏背景-17.png"), ("ROW_UNIVERSAL_SEL", "04_模式图标/完整版/模式界面栏背景-19.png"),
("ROW_NORMAL_NOT", "04_模式图标/完整版/模式界面栏背景-18.png"), ("ROW_NORMAL_NOT", "04_模式图标/完整版/模式界面栏背景-17.png"),
("ROW_NORMAL_SEL", "04_模式图标/完整版/模式界面栏背景-19.png"), ("ROW_NORMAL_SEL", "04_模式图标/完整版/模式界面栏背景-20.png"),
("ROW_EXPERT_NOT", "04_模式图标/完整版/模式界面栏背景-20.png"), ("ROW_EXPERT_NOT", "04_模式图标/完整版/模式界面栏背景-18.png"),
("ROW_EXPERT_SEL", "04_模式图标/完整版/模式界面栏背景-21.png"), ("ROW_EXPERT_SEL", "04_模式图标/完整版/模式界面栏背景-21.png"),
("ICON_UNIVERSAL_SEL", "04_模式图标/万能模式图标-1.png"), ("ICON_UNIVERSAL_SEL", "04_模式图标/万能模式图标-1.png"),
("ICON_UNIVERSAL_NOT", "04_模式图标/万能模式图标-2.png"), ("ICON_UNIVERSAL_NOT", "04_模式图标/万能模式图标-2.png"),