VersaMCU/doc/07_serial_protocol.md
cjjohn 13c3e41c91 Add lightweight READ_STATUS command to avoid blocking LED updates during polling
CONFIG_READ was being (ab)used by the Windows viewer's Live-Sync feature
to poll just the active profile every 1.5s, but the handler sends the
full 740-byte config as ~124 blocking chunk packets from inside
poll_vendor() -- which runs before updateLEDs() in the same loop
iteration (see the loop-order comment at the top of
CMainController.cpp). Every poll cycle stalled updateLEDs() long enough
that running Pulse/Blink animations visibly stuttered, since their
brightness is computed from an absolute millis() timestamp and jumps
forward once the stall clears instead of catching up smoothly.

Added USB_CMD_READ_STATUS (0x06) / USB_EVT_STATUS (0x86): a single NVM
read (no serial I/O) and one 8-byte reply packet with the active
profile in Data[1], no chunking. Documented in
doc/07_serial_protocol.md alongside why CONFIG_READ is unsuitable for
polling. CONFIG_READ stays as-is for actual full-dump use (e.g. "Vom
Board laden").

Verified on hardware after flashing via env:versapad_usb: READ_STATUS
returns the correct profile in ~well under CONFIG_READ's dump time,
Live-Sync no longer visibly disturbs LED animations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 15:37:21 +02:00

5.6 KiB
Raw Permalink Blame History

Serial-Protokoll (CDC USB)

Dateien:

  • hal/usb_serial.h
  • hal/usb_serial.cpp
  • CMainController.cpp

Paketformat

Alle Pakete sind exakt 8 Byte lang:

Byte 0: command / event id
Byte 1: key_id oder chunk-index oder chunk-count
Byte 2..7: kommandospezifische Daten

LED-Kommandos verwenden Byte 2..4 für RGB. Config- und Makro-DATA-Pakete verwenden alle sechs Bytes 2..7 als Nutzlast. Einfache Events aus usb_serial_send() nutzen höchstens Byte 2..3 und füllen den Rest mit null.

Es gibt kein Framing, keinen Längenheader und keine Prüfsumme auf Paketebene.

Richtungen

Richtung IDs Verarbeitung
PC -> Board 0x01..0x7F poll_vendor()
Board -> PC 0x81..0xFF usb_serial_send()

Commands

ID Name Zweck
0x01 SET_LED_OVERRIDE temporaere LED-Override setzen
0x02 CLEAR_LED_OVERRIDE Override entfernen
0x03 SET_LED_BASE Base-Farbe im RAM setzen
0x05 PING Antwort: PONG
0x06 READ_STATUS Antwort: STATUS (Byte 1 = aktives Profil 0-2)
0x10 CONFIG_BEGIN Config-Transfer starten
0x11 CONFIG_DATA 6 Byte Config-Nutzdaten
0x12 CONFIG_COMMIT Config pruefen und speichern
0x13 CONFIG_READ Config-Dump an Host senden
0x20 MACRO_BEGIN Makro-Transfer starten
0x21 MACRO_DATA 6 Byte Makro-Nutzdaten
0x22 MACRO_COMMIT Makros speichern
0x23 MACRO_READ Makro-Dump an Host senden

Events

ID Name Zweck
0x81 KEY_DOWN Host-Button gedrückt
0x82 KEY_UP Host-Button losgelassen
0x83 ENC_CW Encoder-Host-Action im Uhrzeigersinn
0x84 ENC_CCW Encoder-Host-Action gegen Uhrzeigersinn
0x85 PONG Antwort auf Ping
0x86 STATUS Antwort auf READ_STATUS, Byte 1 = aktives Profil 0-2
0x90 CONFIG_ACK Config erfolgreich gespeichert
0x91 CONFIG_NACK Config ungueltig oder NVM-Timeout
0x92 CONFIG_BEGIN Config-Dump beginnt
0x93 CONFIG_DATA 6 Byte Config-Dump
0x94 CONFIG_END Config-Dump fertig
0x95 MACRO_ACK Makros erfolgreich gespeichert
0x96 MACRO_BEGIN Makro-Dump beginnt
0x97 MACRO_DATA 6 Byte Makro-Dump
0x98 MACRO_END Makro-Dump fertig
0x99 MACRO_NACK Makro-Speichern fehlgeschlagen

Bei ActionType::HOST_COMMAND enthält Byte 1 die Matrix-Key-ID oder Encoder-ID. Die 16-Bit-Command-ID aus SAction.data steht little-endian in Byte 2 und 3. Encoder-Actions verwenden die richtungsspezifischen IDs 0x83/0x84.

Chunk-Zahlen

Aktuelle Blob-Groessen:

  • Config: 740 Byte
  • Makros: 512 Byte

Bei 6 Nutzbytes pro Paket ergibt das:

Config: ceil(740 / 6) = 124 Chunks
Makros: ceil(512 / 6) = 86 Chunks

Wichtig fuer Implementierungen: Der Byte-Offset eines Chunks muss mindestens 16 Bit breit sein. Bei der Config liegt der Offset ab Chunk 43 ueber 255 Byte; ein 8-Bit-Offset wuerde ueberlaufen und spaetere Profilbereiche falsch dumpen.

Transferablauf

PC -> Board

BEGIN(chunk_count)
DATA 0
DATA 1
...
COMMIT

Board -> PC

BEGIN(chunk_count)
DATA 0
DATA 1
...
END

Validierung

CONFIG_COMMIT prueft:

  • Magic
  • Version
  • CRC

Nur bei erfolgreicher Pruefung wird in NVM geschrieben.

MACRO_COMMIT schreibt ohne CRC direkt nach NVM und signalisiert nur Erfolg oder Fehler.

Beide Empfangspfade erwarten exakt die berechnete Chunkzahl, markieren jeden Index einmalig und akzeptieren COMMIT nur nach einem vollständigen Transfer. Doppelte oder außerhalb des Bereichs liegende Chunks machen den Transfer ungültig und führen beim Commit zu NACK.

Config-Commit prüft zusätzlich Magic, Version, CRC, Profilindex, Actiontypen/-daten, LED-Enums und kritische Animationsperioden. Makro-Commit prüft die Vollständigkeit und alle HID-Keycodes. Die Makrotabelle besitzt weiterhin keine eigene persistente CRC.

READ_STATUS vs. CONFIG_READ fuer Polling

CONFIG_READ sendet den kompletten 740-Byte-Dump synchron und blockierend aus poll_vendor(), bevor updateLEDs() im selben Loop-Durchlauf drankommt (siehe Kommentar am Dateianfang von CMainController.cpp) bei wiederholtem Polling (z.B. Live-Sync in der Windows-App, die nur das aktive Profil braucht) fuehrt das sichtbar zu ins Stocken geratenden LED-Pulse- Animationen, weil updateLEDs() bei jedem Poll um die Dump-Dauer verzoegert wird. READ_STATUS liest nur active_profile aus dem NVM-Config und schickt ein einzelnes Paket zurueck kein Chunking, keine mehrfachen SerialUSB.write()-Aufrufe, kein spuerbarer Einfluss auf die LED-Animation. Für periodisches Profil-Polling immer READ_STATUS verwenden, CONFIG_READ nur fuer den tatsaechlichen vollen Dump (z.B. "Vom Board laden" im Programmiermodus).

Praktische Hinweise fuer die GUI

  • nach CONFIG_COMMIT auf CONFIG_ACK oder CONFIG_NACK warten
  • danach erst MACRO_* senden
  • Dumps besser sequenziell lesen: zuerst Config, danach Makros
  • DtrEnable muss aktiv sein, sonst verwirft das Board CDC-Ausgaben
  • ausschließlich vollständige 8-Byte-Pakete schreiben; schon ein verlorenes Byte verschiebt die Paketgrenzen für alle folgenden Daten
  • Host-Command-ID little-endian aus Byte 2/3 lesen
  • Config- und Makro-Dumps ebenfalls auf Chunkzahl, eindeutige Indizes und Vollständigkeit prüfen

Implementierungsdetails

  • RX-Ringbuffer: 256 Byte = 32 volle Pakete; er ist ein laufender Zwischenpuffer und fasst keinen kompletten Configtransfer
  • feste 8-Byte-Pakete vereinfachen Firmware und GUI
  • nach einem reinen SWD-Reflash kann ein physischer USB-Reconnect noetig sein