diff --git a/README.md b/README.md index b210fa1..0d4cc4a 100644 --- a/README.md +++ b/README.md @@ -401,6 +401,28 @@ slot and resets. Both halves are reachable from the page. - `tools/uvk5_slots.py` does the same offline: write a slot into a flash image, and print what each slot holds. +### Which build renders correctly, and how that is decided + +The page draws the display controller's **own memory**, not the firmware's framebuffer, so it +does not need to know where a given build keeps its screen -- and `tools/panel_dump.py` shows +the same thing from a shell, one character per pixel, so two builds can be diffed: + + tools/panel_dump.py --qmp 127.0.0.1:4444 # ASCII + tools/panel_dump.py --qmp 127.0.0.1:4444 --png shot.png # scaled PNG + +That path is faithful for the builds measured here. Against its own framebuffer the 5.9.0.CN +image agrees 8188 of 8192 pixels, and the fetched 6.0.0 build renders byte-identical to it. +Both program the same panel registers -- `0xA1` segment reverse, `0xC0`, `0xA6`, columns +0..127, start line 0 -- and that is the point: **the registers do not decide the mapping, the +driver does**, because one driver compensates for the panel's segment order in software and +another may not. So the mapping cannot be derived from the controller's settings; it has to be +measured (`--mapping` exists for that). + +Two things the panel model does not yet honour, either of which will misplace pixels in a +build that uses them: the **display start line** (`0x40|n`, a vertical scroll), and the +controller's **132 columns** -- the model stores pixels at `col - 4` and wraps the column +counter at 128, so a driver that addresses 4..131 loses its first four pixels and shifts the +row. Both are on the list; neither affects the builds measured above. ### Moto/DFU flashing, and the flag this build does not set The factory bootloader is on the machine and it does speak the flashing protocol: a real diff --git a/README.zh-CN.md b/README.zh-CN.md index 00f9aea..7417568 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -356,6 +356,23 @@ v6.0.0 版把开机菜单和四个固件槽放在外部 flash 里:开机按住 决定(默认 8 秒:开机路径可能花 20 秒把当前固件"采纳"进槽 0,之后才会去采样键盘)。 - `tools/uvk5_slots.py` 做同样的事但离线:把槽写进 flash 镜像,并打印每个槽的内容。 +### 哪份构建渲染正确,这件事怎么判定 + +网页画的是显示控制器**自己的显存**,不是固件的 framebuffer,所以它不需要知道某份固件把画面 +放在哪 —— `tools/panel_dump.py` 在命令行里做同一件事,一个字符一个像素,方便把两份构建 diff: + + tools/panel_dump.py --qmp 127.0.0.1:4444 # ASCII + tools/panel_dump.py --qmp 127.0.0.1:4444 --png shot.png # 放大后的 PNG + +对这里量过的构建,这条路是忠实的:CN 5.9.0 的面板与它自己的 framebuffer **8192 个像素里吻合 +8188 个**,而取回的 6.0.0 构建渲染出来**逐字节相同**。两者把面板寄存器配成**一样**的值 —— +`0xA1` 段反序、`0xC0`、`0xA6`、列 0..127、起始行 0 —— 这正是关键:**映射不是寄存器决定的, +而是驱动决定的**,因为有的驱动在软件里补偿面板的段顺序,有的不补偿。所以映射无法从控制器设置里 +推导,只能**量**出来(`--mapping` 就是干这个的)。 + +面板模型目前还有两处没落实,任何用到它们的构建都会像素错位:**显示起始行**(`0x40|n`,纵向 +滚动),以及控制器的 **132 列** —— 模型把像素存在 `col - 4`,并把列计数器在 128 处回绕,所以 +按 4..131 寻址的驱动会丢掉前 4 个像素、整行左移。两条都在待办上;都不影响上面量过的构建。 ### MOTO/DFU 刷机,以及这份构建没有打开的开关 出厂引导就在机器上,而且它**确实会说刷机协议**:真正的 `0x0518` / `0x0530` / `0x0519` 交互, diff --git a/qemu/py32f071.c b/qemu/py32f071.c index e6c36fe..51f19c4 100644 --- a/qemu/py32f071.c +++ b/qemu/py32f071.c @@ -807,6 +807,23 @@ static void st7565_set_cs(void *opaque, int line, int level) static uint8_t st7565_xfer(void *opaque, uint8_t out) { ST7565State *s = opaque; + /* Diagnostic probe (UVK5_PANEL_PROBE): what the driver actually tells the + * controller, bounded to the first few hundred bytes. Two firmware builds that + * disagree about the column offset or the scan direction render differently, and + * this is how that is measured rather than guessed. */ + { + const char *panel_probe = g_getenv("UVK5_PANEL_PROBE"); + static unsigned panel_probe_n; + if (panel_probe && panel_probe_n < 600) { + FILE *f = fopen(panel_probe, "a"); + if (f) { + fprintf(f, "PANEL a0=%d cs=%d page=%d col=%d byte=%02x\n", + s->a0, s->selected, s->page, s->col, out); + fclose(f); + } + panel_probe_n++; + } + } if (!s->selected) { return 0xff; diff --git a/tools/panel_dump.py b/tools/panel_dump.py new file mode 100644 index 0000000..5897805 --- /dev/null +++ b/tools/panel_dump.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Render the display controller's own memory, for any firmware, without a browser. + +The web page already does this; this is the same thing from a shell, which is what you +want when the question is "does *this* build render correctly" and the answer has to be +compared rather than glanced at. It prints ASCII at one character per LCD pixel, so two +builds can be diffed, and can also write a scaled PNG. + + tools/panel_dump.py --qmp 127.0.0.1:4444 # ASCII, as the panel shows it + tools/panel_dump.py --qmp 127.0.0.1:4444 --mapping mirror-cols + tools/panel_dump.py --qmp 127.0.0.1:4444 --png work/screen.png + +The --mapping option exists because which mapping is right is a property of the *driver*, +not of the controller. Two builds can program identical panel registers -- 0xA1 segment +reverse, 0xC0, 0xA6 -- and still arrive in different orders, because one compensates in +software and the other does not. Measured on the 5.9.0.CN build against its own +framebuffer: 8188 of 8192 pixels agree with the panel data untouched, 6954 with the +columns mirrored. +""" +import argparse +import sys + +import uvk5_lcd + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--qmp", default="127.0.0.1:4444", + help="the emulator's QMP endpoint (host:port or unix:path)") + ap.add_argument("--mapping", default="identity", + choices=["identity", "mirror-cols", "mirror-rows", "both"]) + ap.add_argument("--png", help="write a scaled PNG here instead of ASCII") + ap.add_argument("--scale", type=int, default=4) + args = ap.parse_args(argv) + + from uvk5_qmp import QmpClient + # The frame addresses are unused on this path: the controller has its own memory. + grab = uvk5_lcd.FrameGrabber(QmpClient(args.qmp, timeout=20), 0, 0) + pixels = grab.panel_pixels() + if args.mapping in ("mirror-cols", "both"): + pixels = [list(reversed(row)) for row in pixels] + if args.mapping in ("mirror-rows", "both"): + pixels = list(reversed(pixels)) + + if args.png: + with open(args.png, "wb") as fh: + fh.write(uvk5_lcd.encode_png(pixels, args.scale)) + print("wrote %s (%d x %d)" % (args.png, len(pixels[0]), len(pixels))) + return 0 + + print("panel %d x %d, %d pixels lit, mapping %s" + % (len(pixels[0]), len(pixels), sum(sum(r) for r in pixels), args.mapping)) + for row in pixels: + print("".join("#" if value else "." for value in row)) + return 0 + + +if __name__ == "__main__": + sys.exit(main())