Stock firmware & disassembly¶
⚙️ The two official bins¶
- The user's "ripple" firmware is YUNZII official v1.21 (USB bcdDevice 0x0121), 66,780 bytes. Compared against V0119 ("10-min sleep", bcdDevice 0x0119, 58,956 bytes). Ripple is the newer build, not a stripped one. VID 0x28E9 / PID 0x30AF; HID descriptors identical.
- The LCD command dispatch is byte-identical between the two firmwares (just relocated). The
announce-type dispatch is a
cmpladder for 9/10/11 (time / date / homepage) plus a TBB jump table (ripple @ flash 0x08005f2e, V0119 @ 0x08004852) covering types 0..16: types 9–15 route to a shared handler that writes the0x55ACK and calls a subroutine; type 16 (image) has its own handler. Picture/GIF pages render via the upload-TYPE handlers, not via standalone view-switch commands. - Sleep timeout is a tunable little-endian u32 milliseconds: ripple = 240000 (4 min) at flash 0x08005838; V0119 = 600000 (10 min) at 0x08004158.
Base-address note: RIPPLE.bin links at flash 0x08002000 (an 8 KB stm32duino bootloader sits
below).
📟 The MCU does not render pixels¶
The stock raw-HID 0x40/0x41/0x42 handler is a dumb USART3 passthrough (@0x08007FE8 /
@0x08005fe0): it forwards &buff[7] for buff[3] bytes to SD3 (USART3) and acks with
0x55/0x0F ready/busy bytes. It does not parse A5 5A or RGB565. The separate display
module does. This is the foundation of the whole PK_* commit protocol.
⚠️ Custom-firmware image SHEAR = UART TX jitter, not geometry (2026-07-05)¶
Custom vial-QMK firmware rendered images sheared (a vertical line → diagonal) while stock rendered clean. Chased wrongly for hours as a width/format/pacing problem. It was none of those.
Proof (RIPPLE.bin disasm): the stock raw-HID handler @ 0x08005fe0 forwards &buff[7] for
buff[3] bytes — byte-identical to the custom fw's sdWrite(&SD3,&data[7],data[3]). So the wire
data was correct and the mode-2 96×64 main-page format was right all along.
Actual cause: the custom fw writes USART3 interrupt-driven while running rgb_matrix (aw20216s
SPI flush = 2 transactions/refresh) + Vial. Those preempt/mask the serial TX interrupt → gaps in
the byte stream → the self-parsing module re-syncs at a gap → progressive shift → diagonal on
patterns, invisible on solids.
Fix (confirmed on-device 2026-07-05) — two parts, both required
- Firmware (
AL80_CUSTOM_QMK_v14_rgbpause.bin): gateaw20216s_flush()on ag_screen_busyflag (set on0x40, cleared on0x42; watchdog inmatrix_scan_kb). Kills the UART TX jitter. - Host (al80-studio): send the MAIN page (a banked mode-2 transfer) through the
per-bank-paced path (
sendGifWithProgress, ~30 ms/bank), not the blast. Once jitter is gone, blasting makes banks overwrite → white; pacing lets each bank commit → clean.
Result: clean split + gradient + text on the main page with the clock. Picture page also works (same jitter fix; it is flat/ack-gated so needed no pacing change). Byte order = big-endian, no swap. Baud: stock actual 460800; custom needs the 921600 setting to hit it (clock/divisor difference) — expected quirk.
Lesson
For "works on stock, fails on custom", disassemble RIPPLE.bin first — the byte-identical forward would have killed the entire geometry hunt in one look.