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>
5.6 KiB
Serial-Protokoll (CDC USB)
Dateien:
hal/usb_serial.hhal/usb_serial.cppCMainController.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:
740Byte - Makros:
512Byte
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_COMMITaufCONFIG_ACKoderCONFIG_NACKwarten - danach erst
MACRO_*senden - Dumps besser sequenziell lesen: zuerst Config, danach Makros
DtrEnablemuss 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