How-to: render a custom 96×160 frame¶
With both checksums cracked, a full still-image transfer can be forged end-to-end. Any client-side look (brightness, grayscale, etc.) must be baked into the pixels first. There is no device opcode for it (see Still images → attributes are client-side).
🔧 Steps¶
-
Draw your content on a 96×160 canvas. Apply brightness/grayscale/etc. here.
-
Convert each pixel to RGB565 big-endian:
v = ((R>>3)<<11) | ((G>>2)<<5) | (B>>3); bytes = [v>>8, v & 0xFF] -
Concatenate row-major → a 30,720-byte buffer (548 × 56-byte blocks + 1 × 32-byte tail).
-
Announce (0x40):
40 00 00 08 CF 02 00 A5 5A 10 00 01 C5 B1 01type
[9]=0x10(image); CRC[12,13]=C5 B1= CRC16-MODBUS over[0x10,0x00,0x01]. -
Settle ~300 ms (the module must process the announce).
-
Setup / commit (0x41,
PK_ADD_PIC):41 00 00 07 21 03 00 A5 5A 0C 78 00 C3 93 ; 0x7800 = 30,720 = 96×160 -
Settle ~30 ms (the module must arm the ADD_PIC commit).
-
Data blocks (0x41): 548 blocks of 56 bytes + 1 final 32-byte tail block, ACK-gated:
- offset = running byte offset, little-endian in
bytes[1,2](0, 56, 112, … 30,688) byte[3]=0x38(56) for the 548 blocks;0x20(32) for the tailbyte[6]=0x00, then the payload bytes- checksum (
bytes[4,5], 16-bit LE) =(0x41 + offLo + offHi + len + Σpayload) & 0xFFFF— compute after laying down the payload - wait for each block's
byte[6]=0x55ack before sending the next; resend ≤4× if missed
- offset = running byte offset, little-endian in
-
Finish (0x42):
42 00 00 38 7A(rest zero-padded). -
Do NOT send a trailing view switch.
PK_ADD_PICalready displays the committed frame and it stays; a trailing0x0D(PK_TOGGLE_PIC) advances past it.
pad()
OS-level HID libs prepend a 0x00 report-ID byte, then zero-fill to 64 data bytes.
⚠️ Why the settles and ack-gating matter¶
Skip the settles and the frame lands in scratch, acks 549/549, and never displays (old picture stays). Blast the blocks with no flow control and dropped bytes flip pixel alignment → red/blue banding. Both are covered on Display commit (PK_*) and Chunking & pacing. This exact path already renders a live Spotify card — see Now-playing.