Skip to content

Latest commit

 

History

History
183 lines (141 loc) · 5.82 KB

File metadata and controls

183 lines (141 loc) · 5.82 KB

RT82 Display - Agent Instructions

Instructions for AI agents working on this project.

Quick Start

cd rt82display
source ../.venv/bin/activate  # or use uv
rt82display upload <file.qgif>

Troubleshooting

Device Not Detected

Symptom: Could not find keyboard interface (0x36B0) or LCD interface (0x1919) did not appear after init

Cause: The RT82 has TWO USB devices:

  • 0x36B0:0x30A3 - Always visible (keyboard)
  • 0x1919:0x1919 - Only appears AFTER init commands sent to 0x36B0

Cross-OS note: On macOS, hid.enumerate() reports the usage_page field correctly (e.g. 0xFF60 for the keyboard interface). On Linux with the libusb backend (common default), usage_page is returned as 0 for all interfaces. The 0x36B0 device exposes three interfaces:

  • IF=0: Keyboard (usage page 0x0001)
  • IF=1: Raw HID (usage page 0xFF60) -- this is the one needed for init
  • IF=2: Mouse/consumer (usage page 0x0001)

The CLI handles this by: (1) preferring the correct usage_page when available, (2) falling back to interface_number == 1 (the standard QMK raw HID interface), (3) falling back to the first match as last resort.

Additionally, USB re-enumeration is slower on Linux (~1-2 seconds) than macOS (~300ms). The CLI polls for the 0x1919 device with retries rather than a single fixed sleep.

Solution: The CLI should automatically handle this, but if it fails:

  1. Check devices are visible:
rt82display list
  1. (Linux only) Install udev rules so non-root users can access the HID device:
sudo cp udev/99-rt82.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger

Then unplug and replug the keyboard.

  1. Manually activate LCD interface:
import hid
import time

# Send init to 0x36B0 — prefer usage_page 0xFF60, then IF=1, then first
devices = list(hid.enumerate(0x36B0, 0x30A3))
target = None
for d in devices:
    if d.get('usage_page') == 0xFF60:
        target = d
        break
if target is None:
    for d in devices:
        if d.get('interface_number') == 1:
            target = d
            break
if target is None and devices:
    target = devices[0]

if target:
    dev = hid.device()
    dev.open_path(target['path'])
    dev.set_nonblocking(True)
    dev.write(bytes([0xAA, 0xE2] + [0]*62))
    time.sleep(0.05)
    for _ in range(5):
        dev.write(bytes([0xAA, 0xE0] + [0]*62))
        time.sleep(0.05)
    dev.close()

# Poll for 0x1919 (may take 1-2s on Linux)
for _ in range(10):
    time.sleep(0.3)
    found = list(hid.enumerate(0x1919, 0x1919))
    if found:
        for d in found:
            print(f"Found: IF={d.get('interface_number')}  UP=0x{d.get('usage_page', 0):04X}")
        break
else:
    print("0x1919 did not appear")
  1. If still not working: Unplug and replug the keyboard

Screen Stuck in "Downloading"

Cause: Transfer started but didn't complete properly.

Solution:

  1. Unplug keyboard
  2. Wait 5 seconds
  3. Replug keyboard
  4. Try upload again

Garbled/Wrong Colors

Cause: Data format issue

Possible fixes:

  • Ensure using QGIF format (not raw RGB565)
  • Check byte order (should be Little Endian)
  • Verify dimensions are 240×135

Upload Succeeds But No Image

Cause: QGIF format might not match what firmware expects

Debug steps:

  1. Capture a working QGIF from web tool using browser console
  2. Compare header bytes with generated QGIF
  3. Try the captured QGIF to verify protocol works

Device IDs

Device VID PID Usage Page Interface Purpose
Keyboard HID 0x36B0 0x30A3 0x0001 0 Standard keyboard
Raw HID 0x36B0 0x30A3 0xFF60 1 Init commands
Mouse/Consumer 0x36B0 0x30A3 0x0001 2 Media keys etc.
LCD 0x1919 0x1919 0xFF 0,1 Data transfer

Note: On Linux (libusb backend), usage_page is reported as 0x0000 for all interfaces. Use interface_number to distinguish them.

Protocol Summary

  1. Init (on 0x36B0:0x30A3, IF=1, UP=0xFF60):

    • Send AA E2 + AA E0 ×5
    • Poll for 0x1919 device (appears in ~300ms on macOS, ~1-2s on Linux)
    • On Linux, usage_page may be 0; select interface by interface_number == 1
  2. Download Mode (on 0x1919:0x1919, UP=0xFF):

    • Query commands (AA 10, AA 17, etc.)
    • Trigger download: AA E3 00 00 00 01 00 00 01
    • Screen shows "Downloading"
  3. Data Transfer:

    • Setup packets (AA 15, AA 16, AA 18)
    • Data packets: AA 19 [offset_L] [offset_H] 00 38 00 00 [56 bytes]
  4. Finalize:

    • Status: AA 1C
    • End: AA 1A

File Formats

QGIF (Recommended)

  • Compressed RLE format
  • 32-byte header + frame blocks
  • Generated by rt82display.qgif.encode_qgif()

Raw RGB565 (Experimental)

  • Uncompressed 16-bit pixels
  • May not work on all firmware versions
  • Use --raw flag

Key Files

File Purpose
cli.py Main CLI application
qgif.py QGIF encoder
hid_device.py USB HID communication
protocol.py Packet builders
udev/99-rt82.rules Linux udev rules for non-root HID access
PROTOCOL.md Full protocol documentation
QGIF.md QGIF format specification

Common Issues for AI Agents

  1. Don't prepend Report ID - Just send 64-byte packets directly
  2. Two-step connection - Must init 0x36B0 before 0x1919 appears
  3. Correct interface on 0x36B0 - Init must go to IF=1 (raw HID, UP=0xFF60), not IF=0 (keyboard) or IF=2 (mouse). On Linux, usage_page is 0 so select by interface_number == 1
  4. Poll for 0x1919 - USB re-enumeration takes ~1-2s on Linux; use a retry loop, not a fixed sleep
  5. Little Endian - All multi-byte values are LE
  6. 56-byte chunks - Data packets have 8-byte header + 56 data
  7. QGIF required - Raw RGB565 usually doesn't display correctly