Update firmware documentation and agent guidance
This commit is contained in:
+33
-14
@@ -1,4 +1,4 @@
|
||||
# VersaMCU - Architekturuebersicht
|
||||
# VersaMCU – Architekturübersicht
|
||||
|
||||
## Zielplattform
|
||||
|
||||
@@ -7,23 +7,27 @@
|
||||
| MCU | ATSAMD21G17D, Cortex-M0+, 48 MHz |
|
||||
| Flash | 128 KB |
|
||||
| RAM | 16 KB |
|
||||
| FPU | keine, deshalb Integer-Arithmetik |
|
||||
| FPU | keine; LED-/Timingpfade verwenden Integer-Arithmetik |
|
||||
| USB | HID Keyboard + Consumer + CDC Serial |
|
||||
| Toolchain | PlatformIO + Arduino Core |
|
||||
|
||||
## Setup und Loop
|
||||
|
||||
```text
|
||||
setup()
|
||||
macro_config_load()
|
||||
nvm_config_load()
|
||||
init_buttons()
|
||||
usb_hid_init()
|
||||
usb_serial_init()
|
||||
matrix_init(cb)
|
||||
encoder_init(cb)
|
||||
Arduino setup()
|
||||
delay(500)
|
||||
ws2812_init()
|
||||
rotes Startsignal für 1 s
|
||||
CMainController::setup()
|
||||
macro_config_load()
|
||||
init_buttons() -> nvm_config_load()
|
||||
Queue-Bridge setzen
|
||||
usb_hid_init()
|
||||
usb_serial_init()
|
||||
matrix_init(cb)
|
||||
encoder_init(cb)
|
||||
|
||||
loop()
|
||||
Arduino loop()
|
||||
matrix_scan()
|
||||
poll_vendor()
|
||||
processEvents()
|
||||
@@ -39,6 +43,9 @@ Die Reihenfolge ist absichtlich simpel:
|
||||
- Sonderlogik fuer den Werksreset pruefen
|
||||
- LED-Frame nur bei Bedarf rendern
|
||||
|
||||
Makros, Encoder-Taps, NVM-Schreiben sowie Start- und Reset-Feedback verwenden
|
||||
blockierende Delays. Währenddessen werden Matrix und CDC nicht bearbeitet.
|
||||
|
||||
## Datenfluss
|
||||
|
||||
```text
|
||||
@@ -67,7 +74,7 @@ LED-Render
|
||||
| `main.cpp` | startet den Controller |
|
||||
| `CMainController.*` | Orchestrator fuer Inputs, Actions, Serial, LEDs |
|
||||
| `CButton.*` | LED-Zustand, Animationen, Action-Referenz |
|
||||
| `CEventQueue.*` | ISR-sicherer Ringbuffer |
|
||||
| `CEventQueue.*` | fester Ringbuffer mit 16 nutzbaren Slots |
|
||||
| `config/nvm_config.*` | Config v3 laden, speichern, Defaults |
|
||||
| `config/macro_config.*` | Makros laden, speichern |
|
||||
| `hal/matrix.*` | 5x5-Matrixscan mit Debounce |
|
||||
@@ -76,6 +83,9 @@ LED-Render
|
||||
| `hal/usb_serial.*` | CDC-Paketpfad |
|
||||
| `hal/ws2812.*` | WS2812-Treiber |
|
||||
|
||||
Die drei Fader sind nur im Variant und in `config/pins.h` definiert. Es gibt
|
||||
aktuell keinen Fader-HAL und keine Verarbeitung im Controller.
|
||||
|
||||
## Key-ID-Schema
|
||||
|
||||
```text
|
||||
@@ -100,8 +110,17 @@ Der Werksreset ist keine PC-Funktion, sondern Teil der Firmware:
|
||||
|
||||
## Invarianten
|
||||
|
||||
- kein Heap
|
||||
- keine Floats
|
||||
- Projektcode vermeidet dynamische Allokation
|
||||
- Integer-Arithmetik in zeitkritischen LED-/ISR-Pfaden
|
||||
- `packed` fuer serielle und NVM-relevante Structs
|
||||
- NVM-Schreibpuffer muessen 4-Byte-aligned sein
|
||||
- `usb_serial_send()` sendet nur bei aktiver CDC-Verbindung
|
||||
|
||||
## Nebenläufigkeit
|
||||
|
||||
Encoder-Callbacks laufen im EIC-Interrupt, Matrixcallbacks im Loop. Beide
|
||||
schreiben in dieselbe `CEventQueue`; `processEvents()` liest im Loop. Die Queue
|
||||
besitzt aktuell keinen Interruptschutz für den Matrix-Push und verwirft Events
|
||||
bei Überlauf. Das ist eine bekannte Einschränkung, keine garantierte
|
||||
Multi-Producer-Sicherheit; siehe
|
||||
[09_known_limitations.md](09_known_limitations.md).
|
||||
|
||||
+4
-1
@@ -41,5 +41,8 @@ key_id = col * MATRIX_ROWS + row
|
||||
## Kontext
|
||||
|
||||
- Läuft im Loop-Kontext (kein ISR)
|
||||
- Encoder-SW-Tasten gehen durch denselben Matrix-Pfad (COL_0)
|
||||
- Encoder-SW-Tasten gehen durch denselben Matrix-Pfad (`COL_0`)
|
||||
- `matrix_scan()` wird einmal pro `loop()` aufgerufen
|
||||
- Der Callback schreibt in dieselbe Queue wie die Encoder-ISRs. Ein
|
||||
Encoderinterrupt kann den Matrix-Push unterbrechen; die aktuelle Queue
|
||||
schützt diesen Multi-Producer-Fall nicht ausdrücklich.
|
||||
|
||||
+8
-1
@@ -41,8 +41,15 @@ static void isr_enc0_b() { handle_encoder(0); }
|
||||
## ISR-Sicherheit
|
||||
|
||||
- `s_state[]` und `s_accum[]` sind `volatile`
|
||||
- `CEventQueue::push()` ist ISR-sicher (atomare Index-Inkremente auf Single-Core-M0+, kein Heap)
|
||||
- Der Callback-Pointer `s_cb` wird einmalig in `setup()` gesetzt, bevor Interrupts aktiviert werden
|
||||
- ISR-Wrapper führen nur Dekodierung und Queue-Push aus; sie rufen kein USB,
|
||||
NVM, LED-Rendering oder `delay()` auf
|
||||
|
||||
Wichtig: Die Queue wird zusätzlich vom Matrixcallback im Loop beschrieben.
|
||||
Ein Encoderinterrupt kann diesen Push unterbrechen. Die Implementierung ist
|
||||
damit nicht streng Single-Producer und besitzt für diesen Fall aktuell keinen
|
||||
Interruptschutz. Details:
|
||||
[09_known_limitations.md](09_known_limitations.md).
|
||||
|
||||
## Initialisierung
|
||||
|
||||
|
||||
+23
-3
@@ -25,7 +25,7 @@ Das `packed` ist zwingend, weil Config v3 bytegenau zwischen Firmware und GUI ue
|
||||
| `NONE` | keine Aktion | - |
|
||||
| `HID_KEY` | Tastaturtaste ueber USB HID | low byte = keycode, high byte = modifier |
|
||||
| `HID_CONSUMER` | Media/Consumer-HID | usage id |
|
||||
| `HOST_COMMAND` | Event an die GUI | command id |
|
||||
| `HOST_COMMAND` | Event an die GUI | aktuell nicht ausgewertet |
|
||||
| `MACRO` | Makro aus `SMacroTable` | slot 0..31 |
|
||||
| `PROFILE_SWITCH` | Profilwechsel | 0..2 oder `0xFF` fuer naechstes Profil |
|
||||
|
||||
@@ -35,7 +35,7 @@ Das `packed` ist zwingend, weil Config v3 bytegenau zwischen Firmware und GUI ue
|
||||
|---|---|
|
||||
| `HID_KEY` | `usb_hid_send_key()` |
|
||||
| `HID_CONSUMER` | `usb_hid_send_consumer()` |
|
||||
| `HOST_COMMAND` | `usb_serial_send(KEY_DOWN/ENC_*)` |
|
||||
| `HOST_COMMAND` | `USB_EVT_KEY_DOWN (0x81)` mit `key_id` senden |
|
||||
| `MACRO` | komplette Sequenz sofort abspielen |
|
||||
| `PROFILE_SWITCH` | Config aus NVM laden, Profil aendern, CRC neu berechnen, speichern, Buttons neu initialisieren |
|
||||
| `NONE` | nichts |
|
||||
@@ -46,7 +46,7 @@ Das `packed` ist zwingend, weil Config v3 bytegenau zwischen Firmware und GUI ue
|
||||
|---|---|
|
||||
| `HID_KEY` | `usb_hid_release_key()` |
|
||||
| `HID_CONSUMER` | `usb_hid_release_consumer()` |
|
||||
| `HOST_COMMAND` | optionaler Up-Pfad, derzeit praktisch ohne Nutzlast |
|
||||
| `HOST_COMMAND` | keine Ausgabe; `USB_EVT_KEY_UP` ist nur definiert |
|
||||
| `MACRO` | nichts |
|
||||
| `PROFILE_SWITCH` | nichts |
|
||||
| `NONE` | nichts |
|
||||
@@ -62,6 +62,26 @@ down -> delay(10 ms) -> up
|
||||
|
||||
- Makros laufen komplett synchron in der Firmware.
|
||||
|
||||
Das Hold-Modell verwaltet keine Menge gleichzeitig gedrückter Tasten. Ein
|
||||
neuer Keyboard-Down ersetzt den vorherigen Report und jeder Keyboard-Up leert
|
||||
den gesamten Report. Consumer-HID hat dieselbe Einschränkung mit genau einem
|
||||
Usage-Wert.
|
||||
|
||||
## Host-Commands
|
||||
|
||||
Der aktuelle Code sendet für jede `HOST_COMMAND`-Action nur:
|
||||
|
||||
```text
|
||||
Byte 0 = USB_EVT_KEY_DOWN (0x81)
|
||||
Byte 1 = Matrix-Key-ID oder Encoder-ID
|
||||
```
|
||||
|
||||
`SAction.data` wird dabei nicht übertragen. Auch Encoder-Actions verwenden
|
||||
aktuell `0x81`; die definierten Events `ENC_CW (0x83)` und `ENC_CCW (0x84)`
|
||||
werden nicht emittiert. Ein Release erzeugt kein `KEY_UP`-Paket. Das ist
|
||||
aktuelles Verhalten und als Einschränkung in
|
||||
[09_known_limitations.md](09_known_limitations.md) festgehalten.
|
||||
|
||||
## Makro-Ausfuehrung
|
||||
|
||||
Bei `ActionType::MACRO` wird `action.data` als Slot interpretiert.
|
||||
|
||||
@@ -41,7 +41,10 @@ Das Board speichert die Slots blind, die GUI verwendet dabei diese Zuordnung:
|
||||
| Slots | Bedeutung |
|
||||
|---|---|
|
||||
| `0..19` | MX-Buttons |
|
||||
| `20..31` | Encoder-Aktionen (`enc * 3 + act_idx`) |
|
||||
| `20..31` | Encoder-Aktionen (`20 + enc * 3 + act_idx`) |
|
||||
|
||||
Diese Zuordnung ist eine Konvention der externen GUI. Die Firmware erzwingt
|
||||
sie nicht: `SAction.data` wird direkt als Slotindex `0..31` verwendet.
|
||||
|
||||
## Laden
|
||||
|
||||
|
||||
+22
-4
@@ -14,9 +14,12 @@ Dünner Wrapper um **Adafruit NeoPixel** (bit-bang, kein DMA, kein SERCOM).
|
||||
| `ws2812_show()` | Bit-Bang-Übertragung (~600 µs, Interrupts gesperrt) |
|
||||
| `ws2812_clear()` | `clear()` + `show()` |
|
||||
|
||||
`ws2812_show()` wird in `CMainController::updateLEDs()` **nur** aufgerufen wenn mindestens ein Button dirty war – 600 µs Blockzeit werden so vermieden wenn keine Änderung nötig ist.
|
||||
`ws2812_show()` wird in `CMainController::updateLEDs()` nur aufgerufen, wenn
|
||||
mindestens ein Button dirty war oder eine Animation läuft. Bei einer endlos
|
||||
laufenden Animation wird daher in jedem Loop ein Frame übertragen.
|
||||
|
||||
**Warum kein DMA?** DMA + SERCOM-SPI würde ~1,5 KB extra RAM (1440 Byte Kodier-Puffer) und erhebliche Implementierungskomplexität erfordern. Bei 20 LEDs und ~20 ms Loop-Rate sind 600 µs gesperrte Interrupts (= 3 % der Loop-Zeit) unkritisch.
|
||||
Der Treiber nutzt `Adafruit NeoPixel` per Bit-Banging; es gibt keinen
|
||||
DMA-/SERCOM-Ausgabepfad.
|
||||
|
||||
## 2-Schicht-Modell (CButton)
|
||||
|
||||
@@ -34,7 +37,7 @@ Aktive Farbe = `override` wenn aktiv, sonst `base`. `clear_override()` kehrt sof
|
||||
| Animation | Typ | Verhalten | Endbedingung |
|
||||
|---|---|---|---|
|
||||
| `STATIC` | — | Feste Farbe | — |
|
||||
| `BLINK` | Helligkeit | An/Aus, `period_ms` = Halbperiode | endlos |
|
||||
| `BLINK` | Helligkeit | erste Hälfte an, zweite Hälfte aus; `period_ms` = Vollperiode | endlos |
|
||||
| `PULSE` | Helligkeit | Lineares Dreieck 0→255→0 | endlos |
|
||||
| `FADE_IN` | Helligkeit | Einmalig schwarz → voll | → STATIC (voll) |
|
||||
| `FADE_OUT` | Helligkeit | Einmalig voll → schwarz | → STATIC (base=schwarz) |
|
||||
@@ -45,6 +48,12 @@ Aktive Farbe = `override` wenn aktiv, sonst `base`. `clear_override()` kehrt sof
|
||||
|
||||
**Farb-Animationen** (`compute_rgb`): Berechnen RGB direkt; base/override werden nicht verändert (außer bei Abschluss).
|
||||
|
||||
Die globale und LED-spezifische Helligkeit werden beim Initialisieren in die
|
||||
Base-Farbe eingerechnet. `COLOR_CYCLE` ignoriert diese Base-Farbe und rendert
|
||||
mit einem festen Faktor von 40 %. Auch CDC-Overrides werden von
|
||||
`COLOR_CYCLE`/`COLOR_FADE` visuell überdeckt, solange die Farbanimation aktiv
|
||||
ist.
|
||||
|
||||
### COLOR_CYCLE – Hue-Arithmetik (kein Float)
|
||||
|
||||
Hue 0–255 aufgeteilt in 6 Segmente à 43 Einheiten. Innerhalb jedes Segments steigt/fällt ein Kanal linear:
|
||||
@@ -60,7 +69,16 @@ Seg 5: R=255, B fällt (Magenta → Rot)
|
||||
|
||||
Ausgabe wird auf 40 % Helligkeit skaliert (Faktor 102/255) damit die LEDs nicht blenden.
|
||||
|
||||
`Adafruit_NeoPixel::ColorHSV()` ist nicht nutzbar: verwendet intern float (kein FPU auf M0+).
|
||||
Die Firmware verwendet eine eigene ganzzahlige Hue-Umrechnung und ruft
|
||||
`Adafruit_NeoPixel::ColorHSV()` nicht auf.
|
||||
|
||||
Für `PULSE` muss `period_ms >= 2` gelten, da der Code durch die halbe Periode
|
||||
teilt. Eingehende Configs validieren diesen Grenzwert aktuell nicht.
|
||||
|
||||
`COLOR_FADE` benötigt `set_color_fade(to, period_ms)`, weil dort Start- und
|
||||
Zielfarbe gesetzt werden. Ein bloßes `set_anim(COLOR_FADE, ...)`, wie es beim
|
||||
direkten Laden dieses Enum-Werts aus der Config geschieht, initialisiert diese
|
||||
Farben nicht aus den Configdaten.
|
||||
|
||||
### Phasenversatz (Regenbogen-Wellen)
|
||||
|
||||
|
||||
+20
-1
@@ -17,6 +17,13 @@ Dateien:
|
||||
|
||||
Makros und Config sind komplett getrennt.
|
||||
|
||||
Diese Tabelle beschreibt die Adressen, auf die der Laufzeitcode zugreift. Das
|
||||
aktive Linker-Skript reserviert davon aktuell nur `0x1FE00..0x1FFFF`
|
||||
ausdrücklich; Makrobereich und erste Config-Row liegen noch im zulässigen
|
||||
Firmware-ROM. Das aktuelle Binary ist klein genug, aber die Bereiche sind
|
||||
nicht vollständig gegen Firmwarewachstum geschützt. Siehe
|
||||
[09_known_limitations.md](09_known_limitations.md).
|
||||
|
||||
## `SDeviceConfig`
|
||||
|
||||
Aktueller Stand:
|
||||
@@ -38,6 +45,9 @@ Aktueller Stand:
|
||||
| `9` | 4 | `enc_sensitivity[4]` |
|
||||
| `13` | 19 | Reserve |
|
||||
|
||||
`enc_sensitivity` ist im Binärformat vorhanden und hat Default `1`, wird von
|
||||
der Encoderdekodierung derzeit aber nicht verwendet.
|
||||
|
||||
### Pro Profil
|
||||
|
||||
Jedes Profil belegt 236 Byte:
|
||||
@@ -97,12 +107,19 @@ Praktisch sichtbares Ergebnis:
|
||||
5. bei Fehlern Defaults laden und `false` zurueckgeben
|
||||
|
||||
Die Firmware faellt also immer auf einen gueltigen Zustand zurueck.
|
||||
Die Defaults werden bei diesem Fallback nur in das übergebene RAM-Struct
|
||||
geschrieben und nicht automatisch in Flash persistiert.
|
||||
|
||||
Nach erfolgreicher CRC-Prüfung sichert `load()` einen zu großen
|
||||
`active_profile` im RAM auf Profil 0 ab. Andere Felder und Enum-Werte werden
|
||||
nicht auf gültige Bereiche geprüft.
|
||||
|
||||
## Speichern
|
||||
|
||||
`nvm_config_save()`:
|
||||
|
||||
1. 740-Byte-Config in einen 768-Byte-Row-Puffer kopieren
|
||||
1. die vom Aufrufer bereits vorbereitete 740-Byte-Config in einen
|
||||
768-Byte-Row-Puffer kopieren
|
||||
2. Rest mit `0xFF` fuellen
|
||||
3. `MANW = 1`
|
||||
4. 3 Rows loeschen
|
||||
@@ -117,6 +134,8 @@ Wichtig:
|
||||
|
||||
- der Schreibpuffer muss 4-Byte-aligned sein
|
||||
- `packed` allein reicht dafuer nicht
|
||||
- `nvm_config_save()` berechnet die CRC nicht selbst; der Aufrufer muss
|
||||
`cfg.crc = nvm_config_crc(cfg)` vorher setzen
|
||||
|
||||
## Zusammenhang mit Werksreset
|
||||
|
||||
|
||||
+30
-10
@@ -13,13 +13,14 @@ Alle Pakete sind exakt 8 Byte lang:
|
||||
```text
|
||||
Byte 0: command / event id
|
||||
Byte 1: key_id oder chunk-index oder chunk-count
|
||||
Byte 2: daten a
|
||||
Byte 3: daten b
|
||||
Byte 4: daten c
|
||||
Byte 5..7: reserviert
|
||||
Byte 2..7: kommandospezifische Daten
|
||||
```
|
||||
|
||||
Es gibt kein Framing und keinen Laengenheader.
|
||||
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
|
||||
|
||||
@@ -49,10 +50,10 @@ Es gibt kein Framing und keinen Laengenheader.
|
||||
|
||||
| ID | Name | Zweck |
|
||||
|---|---|---|
|
||||
| `0x81` | `KEY_DOWN` | Host-Command-Button gedrueckt |
|
||||
| `0x82` | `KEY_UP` | Host-Command-Button losgelassen |
|
||||
| `0x83` | `ENC_CW` | Encoder Host-Command im Uhrzeigersinn |
|
||||
| `0x84` | `ENC_CCW` | Encoder Host-Command gegen Uhrzeigersinn |
|
||||
| `0x81` | `KEY_DOWN` | wird für jede aktuelle Host-Action gesendet |
|
||||
| `0x82` | `KEY_UP` | definiert, aktuell nicht gesendet |
|
||||
| `0x83` | `ENC_CW` | definiert, aktuell nicht gesendet |
|
||||
| `0x84` | `ENC_CCW` | definiert, aktuell nicht gesendet |
|
||||
| `0x85` | `PONG` | Antwort auf Ping |
|
||||
| `0x90` | `CONFIG_ACK` | Config erfolgreich gespeichert |
|
||||
| `0x91` | `CONFIG_NACK` | Config ungueltig oder NVM-Timeout |
|
||||
@@ -65,6 +66,10 @@ Es gibt kein Framing und keinen Laengenheader.
|
||||
| `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 die
|
||||
Encoder-ID. `SAction.data` wird nicht übertragen. Auch CW- und CCW-Actions
|
||||
senden aktuell `0x81`; die Drehrichtung ist im Paket nicht enthalten.
|
||||
|
||||
## Chunk-Zahlen
|
||||
|
||||
Aktuelle Blob-Groessen:
|
||||
@@ -115,15 +120,30 @@ Nur bei erfolgreicher Pruefung wird in NVM geschrieben.
|
||||
|
||||
`MACRO_COMMIT` schreibt ohne CRC direkt nach NVM und signalisiert nur Erfolg oder Fehler.
|
||||
|
||||
Die in `BEGIN` angekündigte Chunkzahl wird zwar gespeichert, aber beim Commit
|
||||
nicht ausgewertet. Die Firmware verfolgt nicht, welche Chunkindizes tatsächlich
|
||||
eingetroffen sind. Doppelte, fehlende und ungeordnete Chunks werden deshalb
|
||||
nicht als solche erkannt. Bei Configdaten schlägt ein unvollständiger Transfer
|
||||
typischerweise an der CRC fehl; bei Makrodaten kann ein unvollständiger
|
||||
Null-gefüllter Puffer gespeichert werden.
|
||||
|
||||
Die Configvalidierung prüft keine Feldwerte oder Enum-Bereiche. Insbesondere
|
||||
müssen Hostimplementierungen gültige Profilindizes, Actiontypen, LED-Enums und
|
||||
Animationsperioden liefern.
|
||||
|
||||
## 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
|
||||
- Encoder-Host-Actions derzeit nicht anhand von `0x83/0x84` erwarten
|
||||
|
||||
## Implementierungsdetails
|
||||
|
||||
- RX-Ringbuffer: 256 Byte = 32 volle Pakete
|
||||
- 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
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Entwicklung und Einstieg
|
||||
|
||||
Diese Seite ist der praktische Einstieg für neue Entwickler. Für einen
|
||||
LLM-basierten Coding-Agent zusätzlich die Anweisungen in
|
||||
[`../AGENTS.md`](../AGENTS.md) bereitstellen.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- PlatformIO Core oder PlatformIO IDE
|
||||
- USB-Kabel für Laufzeittests
|
||||
- Atmel-ICE beziehungsweise kompatibler CMSIS-DAP-Adapter für den Upload
|
||||
- Zugriff auf die separat gepflegte Windows-GUI, wenn das CDC-Protokoll oder
|
||||
persistente Formate geändert werden
|
||||
|
||||
PlatformIO lädt den Arduino-SAMD-Core, OpenOCD und `Adafruit NeoPixel` über
|
||||
`platformio.ini`. Das aktive Standardziel ist `versapad_nobl` im Environment
|
||||
`versapad`.
|
||||
|
||||
## Build und Upload
|
||||
|
||||
```bash
|
||||
pio run -e versapad
|
||||
pio run -e versapad --target upload
|
||||
```
|
||||
|
||||
Der Upload nutzt `upload_openocd.py`, das das von PlatformIO installierte
|
||||
OpenOCD mit `interface/cmsis-dap.cfg` und `target/at91samdXX.cfg` startet.
|
||||
|
||||
Das in `platformio.ini` nur als Beispiel enthaltene Environment
|
||||
`versapad_usb` ist auskommentiert und mit dem aktuellen NVM-/Linker-Layout
|
||||
nicht als unterstützt anzusehen.
|
||||
|
||||
## Was beim Start passiert
|
||||
|
||||
```text
|
||||
Arduino setup()
|
||||
500 ms warten
|
||||
WS2812 initialisieren
|
||||
1 s rotes Startsignal
|
||||
Makros aus NVM laden
|
||||
Config laden und Buttons initialisieren
|
||||
USB-HID/CDC, Matrix und Encoder initialisieren
|
||||
|
||||
Arduino loop()
|
||||
Matrix scannen
|
||||
CDC-Pakete verarbeiten
|
||||
Event-Queue leeren
|
||||
Werksreset prüfen
|
||||
LEDs rendern
|
||||
```
|
||||
|
||||
Der Controller blockiert während Makros, Encoder-Taps, Start-/Reset-Feedback
|
||||
und NVM-Schreibvorgängen. Es gibt keinen Scheduler und keine Threads.
|
||||
|
||||
## Einstieg nach Änderungstyp
|
||||
|
||||
| Änderung | Zuerst lesen | Typische Dateien |
|
||||
|---|---|---|
|
||||
| Matrix/Key-Mapping | `01_matrix.md` | `hal/matrix.*`, `config/pins.h`, Variant |
|
||||
| Encoder | `02_encoder.md` | `hal/encoder.*`, `CMainController.cpp` |
|
||||
| Actions/HID | `03_action_engine.md` | `config/action.h`, Controller, `hal/usb_hid.*` |
|
||||
| Makros | `04_macro_system.md` | `config/macro_config.*`, Controller |
|
||||
| LEDs | `05_led_system.md` | `CButton.*`, `hal/ws2812.*` |
|
||||
| Persistente Config | `06_nvm_config.md` | `config/nvm_config.*`, Linker-Skripte |
|
||||
| Host-Protokoll | `07_serial_protocol.md` | `hal/usb_serial.*`, Controller |
|
||||
|
||||
## Verifikation
|
||||
|
||||
Es gibt derzeit keine automatisierten Tests. Der minimale lokale Check ist:
|
||||
|
||||
```bash
|
||||
pio run -e versapad
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Je nach Änderung folgen Hardwaretests:
|
||||
|
||||
- Matrix: jede Taste einzeln, Mehrfachtasten und beide Reset-Tasten
|
||||
- Encoder: beide Richtungen und schneller Richtungswechsel
|
||||
- HID: Down/Up sowie Modifier und Consumer Usage
|
||||
- CDC: Ping, vollständiger Config-/Makro-Transfer und Readback
|
||||
- NVM: Power-Cycle, ungültige CRC und Werksreset
|
||||
- LEDs: alle Animationen, Helligkeit und temporäre Overrides
|
||||
|
||||
Die GUI ist ein externer Vertrag. Änderungen an `SDeviceConfig`,
|
||||
`SMacroTable`, Action-Werten oder USB-IDs sind erst vollständig verifiziert,
|
||||
wenn Firmware und GUI dieselben Bytes senden und interpretieren.
|
||||
|
||||
## Dokumentation mitpflegen
|
||||
|
||||
Bei jedem Change die betroffene Fachdokumentation aktualisieren. Zahlen wie
|
||||
Structgrößen, Offsets, Chunk-Anzahlen und Flashgrenzen immer aus dem neuen Code
|
||||
neu ableiten. Offene oder absichtlich nicht behobene Punkte gehören nach
|
||||
[`09_known_limitations.md`](09_known_limitations.md).
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Bekannte Einschränkungen und Risiken
|
||||
|
||||
Diese Liste beschreibt den aktuellen Implementierungsstand. Sie ist keine
|
||||
Liste bereits umgesetzter Features.
|
||||
|
||||
## Flash-Reservierung stimmt nicht vollständig mit dem NVM-Zugriff überein
|
||||
|
||||
Die Firmware liest und schreibt:
|
||||
|
||||
```text
|
||||
0x1FB00..0x1FCFF Makros (512 Byte)
|
||||
0x1FD00..0x1FFFF Config (768 Byte, davon 740 Byte genutzt)
|
||||
```
|
||||
|
||||
Das aktive Linker-Skript `flash_without_bootloader.ld` erlaubt Firmware jedoch
|
||||
bis einschließlich `0x1FDFF` und reserviert nur `0x1FE00..0x1FFFF`. Damit sind
|
||||
`0x1FB00..0x1FDFF` nicht gegen ein zukünftig wachsendes Firmware-Image
|
||||
geschützt. Das aktuelle Image liegt deutlich darunter, aber der Build prüft
|
||||
diese NVM-Grenze nicht.
|
||||
|
||||
Das Bootloader-Linker-Skript reserviert aktuell gar keinen separaten
|
||||
NVM-Bereich. Das auskommentierte USB-Bootloader-Environment ist daher kein
|
||||
unterstütztes Ziel.
|
||||
|
||||
Die aktive Boarddatei benennt die MCU als `samd21g17d`, setzt für den
|
||||
Arduino-Core aber weiterhin das Kompatibilitätsmakro `__SAMD21G18A__`. Der
|
||||
PlatformIO-Build meldet korrekt 128 KiB Flash und 16 KiB RAM; vor
|
||||
device-spezifischen Änderungen sollte diese historische Makro-Abweichung
|
||||
trotzdem geprüft werden.
|
||||
|
||||
## Event-Queue hat gemischte Producer
|
||||
|
||||
Matrixevents werden im Loop erzeugt, Encoderevents in EIC-Interrupts. Beide
|
||||
rufen `CEventQueue::push()` auf und verändern denselben Tail-Index ohne
|
||||
Interruptschutz. Ein Encoderinterrupt kann einen Matrix-Push unterbrechen.
|
||||
Die bisherige Annahme eines reinen Single-Producer/Single-Consumer-Ringbuffers
|
||||
ist deshalb nicht vollständig erfüllt; seltene verlorene oder überschriebene
|
||||
Events sind theoretisch möglich.
|
||||
|
||||
Zusätzlich werden Events bei voller Queue still verworfen.
|
||||
|
||||
## `HOST_COMMAND` nutzt seine `data` nicht
|
||||
|
||||
`SAction.data` ist für `ActionType::HOST_COMMAND` vorhanden, wird in
|
||||
`execute_action_down()` aber nicht übertragen. Gesendet wird nur
|
||||
`USB_EVT_KEY_DOWN (0x81)` mit der Matrix-Key-ID beziehungsweise Encoder-ID.
|
||||
|
||||
`USB_EVT_KEY_UP (0x82)`, `USB_EVT_ENC_CW (0x83)` und
|
||||
`USB_EVT_ENC_CCW (0x84)` sind definiert, werden vom aktuellen Controller aber
|
||||
nicht gesendet. Bei Encoder-Host-Actions geht dadurch die Richtung im
|
||||
gesendeten Paket verloren, sofern die Host-Anwendung sie nicht anderweitig aus
|
||||
der konfigurierten Action ableitet.
|
||||
|
||||
## HID-Holds sind global
|
||||
|
||||
Der Keyboard-Report enthält zwar sechs Keycode-Felder, die Implementierung
|
||||
setzt aber nur das erste. Jeder neue `HID_KEY`-Down ersetzt den vorherigen
|
||||
Report; jedes Release sendet einen komplett leeren Report. Analog existiert
|
||||
nur ein globaler Consumer-Usage-Wert. Gleichzeitige unabhängige Holds werden
|
||||
daher nicht korrekt verwaltet.
|
||||
|
||||
## CDC-Transfers sind nur schwach validiert
|
||||
|
||||
- `BEGIN` speichert die angekündigte Chunkzahl, `COMMIT` vergleicht sie aber
|
||||
nicht mit empfangenen Chunks.
|
||||
- Doppelte, fehlende oder ungeordnete Chunks werden nicht verfolgt.
|
||||
- Config-Commit prüft Magic, Version und CRC, aber keine Feldwerte oder
|
||||
Enum-Bereiche.
|
||||
- Makro-Commit hat weder CRC noch Vollständigkeitsprüfung.
|
||||
- Das 8-Byte-Protokoll besitzt kein Framing. Ein verlorenes oder zusätzliches
|
||||
Byte desynchronisiert alle folgenden Pakete.
|
||||
|
||||
Insbesondere kann ein formal CRC-korrektes Profil ungültige Animationswerte
|
||||
enthalten. `PULSE` benötigt in der aktuellen Arithmetik eine Periode von
|
||||
mindestens 2 ms; dieser Mindestwert wird nicht validiert.
|
||||
|
||||
## Farbanimationen umgehen Teile der Helligkeits-/Override-Logik
|
||||
|
||||
Globale und LED-spezifische Helligkeit werden beim Laden in die Base-Farbe
|
||||
eingerechnet. `COLOR_CYCLE` berechnet RGB dagegen direkt mit festen 40 %
|
||||
Helligkeit und ignoriert Base-Farbe sowie Override. `COLOR_FADE` benötigt den
|
||||
separaten Aufruf `set_color_fade()`; das reine Laden des Enum-Werts aus einer
|
||||
Config setzt keine Start- und Zielfarbe.
|
||||
|
||||
## Reservierte beziehungsweise noch ungenutzte Hardware und Felder
|
||||
|
||||
- Die drei Fader-Pins sind im Variant und in `config/pins.h` definiert, werden
|
||||
von der Firmware aber nicht eingelesen.
|
||||
- `enc_sensitivity[4]` wird gespeichert und mit Default `1` befüllt, beeinflusst
|
||||
die Encoderdekodierung derzeit aber nicht.
|
||||
- `SET_LED_BASE` verändert nur den RAM-Zustand und wird nicht in NVM
|
||||
persistiert.
|
||||
|
||||
## Zeitverhalten
|
||||
|
||||
Makros und Encoder-Taps verwenden blockierende `delay()`-Aufrufe. Startsignal,
|
||||
Werksreset-Feedback und NVM-Operationen blockieren ebenfalls den Loop. Während
|
||||
dessen werden Matrix und CDC nicht bearbeitet; Encoder-ISRs können weiter
|
||||
Events erzeugen, bis die Queue voll ist.
|
||||
+11
-2
@@ -1,6 +1,9 @@
|
||||
# VersaMCU - Dokumentationsindex
|
||||
# VersaMCU – Dokumentationsindex
|
||||
|
||||
Die Dateien hier beschreiben den aktuellen Firmware-Stand von Config v3, 3 Profilen und 32x8 Makros.
|
||||
Die Dateien beschreiben den aktuellen Firmware-Stand von Config v3, drei
|
||||
Profilen und 32×8 Makros. Bekannte Abweichungen oder noch nicht abgesicherte
|
||||
Bereiche stehen ausdrücklich in
|
||||
[09_known_limitations.md](09_known_limitations.md).
|
||||
|
||||
| Datei | Inhalt |
|
||||
|---|---|
|
||||
@@ -12,6 +15,11 @@ Die Dateien hier beschreiben den aktuellen Firmware-Stand von Config v3, 3 Profi
|
||||
| [05_led_system.md](05_led_system.md) | LED-Schichten, Animationen, Render-Pipeline |
|
||||
| [06_nvm_config.md](06_nvm_config.md) | Config v3, 3 Profile, CRC16, Defaults, Werksreset-Bezug |
|
||||
| [07_serial_protocol.md](07_serial_protocol.md) | 8-Byte-Protokoll, Config-/Makro-Transfer, ACK/NACK |
|
||||
| [08_development.md](08_development.md) | Setup, Build, Einstieg nach Änderungstyp, Verifikation |
|
||||
| [09_known_limitations.md](09_known_limitations.md) | Aktuelle technische Einschränkungen und Risiken |
|
||||
|
||||
Die Repository-weiten Richtlinien und der kompakte LLM-Kontext stehen in
|
||||
[`../AGENTS.md`](../AGENTS.md).
|
||||
|
||||
## Schnellreferenz
|
||||
|
||||
@@ -20,3 +28,4 @@ Die Dateien hier beschreiben den aktuellen Firmware-Stand von Config v3, 3 Profi
|
||||
- Work-Loop inkl. Werksreset: [00_architecture.md](00_architecture.md)
|
||||
- Action-Semantik und HID-Hold: [03_action_engine.md](03_action_engine.md)
|
||||
- CDC-Protokoll und Chunk-Zahlen: [07_serial_protocol.md](07_serial_protocol.md)
|
||||
- bekannte Risiken vor strukturellen Änderungen: [09_known_limitations.md](09_known_limitations.md)
|
||||
|
||||
Reference in New Issue
Block a user