Update firmware documentation and agent guidance
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Arbeitsanweisungen für Coding-Agents
|
||||
|
||||
Diese Datei gilt für das gesamte Repository. Sie ist zugleich der kompakte
|
||||
Einstiegskontext für LLM-basierte Entwicklungswerkzeuge.
|
||||
|
||||
## Ziel und Plattform
|
||||
|
||||
VersaMCU ist die Firmware des VersaPad-v2-Makropads. Das aktive und unterstützte
|
||||
PlatformIO-Ziel ist `env:versapad`:
|
||||
|
||||
- ATSAMD21G17D, Cortex-M0+, 48 MHz
|
||||
- 128 KiB Flash, 16 KiB RAM
|
||||
- Arduino-SAMD-Core über PlatformIO
|
||||
- Upload per Atmel-ICE/CMSIS-DAP und OpenOCD, ohne Bootloader
|
||||
- USB Composite Device: Keyboard-HID, Consumer-HID und CDC Serial
|
||||
|
||||
Das auskommentierte Bootloader-Ziel in `platformio.ini` ist kein verifiziertes
|
||||
Produktionsziel.
|
||||
|
||||
## Vor dem Ändern lesen
|
||||
|
||||
In dieser Reihenfolge:
|
||||
|
||||
1. `README.md` für Scope, Build und Einstieg
|
||||
2. `doc/INDEX.md` für die thematische Navigation
|
||||
3. `doc/00_architecture.md` für Datenfluss und Laufzeitmodell
|
||||
4. `doc/09_known_limitations.md` für bewusst noch nicht gelöste Risiken
|
||||
5. die zum Task gehörende Fachdokumentation und anschließend den Quellcode
|
||||
|
||||
Bei Widersprüchen ist der kompilierte Code die Quelle für das aktuelle
|
||||
Verhalten. Hardwarekonstanten stehen in `src/config/pins.h` und
|
||||
`variants/versapad/`; binäre Formate stehen in den Structs unter `src/config/`
|
||||
und in `src/hal/usb_serial.h`. Widersprüche zwischen Code und Dokumentation
|
||||
müssen im selben Change behoben oder ausdrücklich als bekannte Einschränkung
|
||||
festgehalten werden.
|
||||
|
||||
## Unverzichtbare Verträge
|
||||
|
||||
- `SAction`, `SDeviceProfile`, `SDeviceConfig`, `SMacroStep` und
|
||||
`SMacroTable` sind persistente beziehungsweise hostseitige Binärverträge.
|
||||
- Änderungen an Feldreihenfolge, Enum-Werten, Packing, Größen, Magic, Version,
|
||||
CRC-Bereich oder Chunking benötigen gleichzeitig:
|
||||
Firmware-Migration/Versionswechsel, Anpassung der externen GUI und
|
||||
Aktualisierung von `doc/06_nvm_config.md` sowie
|
||||
`doc/07_serial_protocol.md`.
|
||||
- Die Windows-GUI gehört nicht zu diesem Repository. Keine Kompatibilität mit
|
||||
ihr behaupten, wenn nur die Firmware geändert oder geprüft wurde.
|
||||
- Die NVM-Adressen liegen am oberen Ende des 128-KiB-Flash. Vor Änderungen an
|
||||
Linker-Skripten, Boardgrößen oder NVM-Layouts immer
|
||||
`doc/09_known_limitations.md` lesen.
|
||||
- Der Code läuft auf 16 KiB RAM. Keine unnötige dynamische Allokation, keine
|
||||
großen Stackpuffer und keine Float-Arithmetik in Loop-/ISR-Pfaden einführen.
|
||||
- ISR-Code muss kurz und nicht blockierend bleiben. Niemals USB, NVM,
|
||||
WS2812-Ausgabe oder `delay()` aus einer ISR aufrufen.
|
||||
- Das feste CDC-Protokoll besteht aus 8-Byte-Paketen. Es besitzt aktuell weder
|
||||
Framing noch Sequenz-/Vollständigkeitsprüfung.
|
||||
|
||||
## Änderungsleitfaden
|
||||
|
||||
- Kleine, lokale Änderungen bevorzugen; HAL, Controller und persistente Config
|
||||
nicht ohne Grund vermischen.
|
||||
- Neue Hardwarezugriffe gehören unter `src/hal/`.
|
||||
- Neue konfigurierbare Werte benötigen definierte Defaults und eine
|
||||
Validierungsstrategie für Daten vom Host.
|
||||
- Neue Action- oder Eventtypen müssen in Firmware, Protokolldoku und externer
|
||||
GUI gemeinsam geplant werden.
|
||||
- Bei Matrix- oder Encoderänderungen die ISR-/Queue-Interaktion prüfen.
|
||||
- Bei LED-Animationen nur ganzzahlige Arithmetik verwenden und Randwerte wie
|
||||
`period_ms <= 1` behandeln.
|
||||
- Bestehende deutsch- und englischsprachige Kommentare dürfen vereinheitlicht
|
||||
werden; Dateien als UTF-8 speichern.
|
||||
- Keine generierten Inhalte aus `.pio/` committen.
|
||||
|
||||
## Verifikation
|
||||
|
||||
Mindestens:
|
||||
|
||||
```bash
|
||||
pio run -e versapad
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Für Hardware-, USB-, NVM- oder Timingänderungen zusätzlich einen passenden
|
||||
Gerätetest beschreiben. Es gibt derzeit keine automatisierten Unit- oder
|
||||
Integrationstests. Ein erfolgreicher Build beweist daher weder elektrische
|
||||
Funktion noch GUI-Kompatibilität.
|
||||
|
||||
Vor Abschluss prüfen:
|
||||
|
||||
- Stimmen README und betroffene `doc/*.md` noch?
|
||||
- Wurden Binärgrößen und Offsets neu berechnet statt übernommen?
|
||||
- Bleiben Firmware und NVM-Bereiche kollisionsfrei?
|
||||
- Ist klar getrennt, was verifiziert, nur aus Code abgeleitet oder noch offen
|
||||
ist?
|
||||
@@ -1,33 +1,59 @@
|
||||
# VersaMCU
|
||||
|
||||
Firmware fuer das VersaPad v2 Macro-Pad.
|
||||
Laeuft auf einem ATSAMD21G17D mit PlatformIO und Arduino-Framework.
|
||||
Firmware für das VersaPad-v2-Makropad. Das Projekt läuft auf einem
|
||||
ATSAMD21G17D mit PlatformIO und dem Arduino-SAMD-Framework.
|
||||
|
||||
## Aktueller Funktionsumfang
|
||||
|
||||
| Bereich | Stand |
|
||||
|---|---|
|
||||
| Eingaben | 20 MX-Tasten, 4 Encoder-Taster und 4 Quadratur-Encoder |
|
||||
| USB | Keyboard-HID, Consumer-HID und CDC Serial |
|
||||
| Aktionen | HID-Key, Consumer-Key, Host-Event, Makro, Profilwechsel |
|
||||
| Makros | 32 Slots mit je bis zu 8 HID-Schritten |
|
||||
| Profile | 3 Profile in Config v3 |
|
||||
| LEDs | 20 WS2812B mit Base-/Override-Farbe und 7 Animationsmodi |
|
||||
| Persistenz | Config und Makros im internen Flash |
|
||||
| Recovery | Werksreset über zwei Tasten |
|
||||
|
||||
Die drei Fader-Pins sind im Board-Variant definiert, werden von der aktuellen
|
||||
Firmware aber noch nicht eingelesen.
|
||||
|
||||
## Hardware
|
||||
|
||||
| Eigenschaft | Detail |
|
||||
|---|---|
|
||||
| MCU | ATSAMD21G17D, Cortex-M0+, 48 MHz |
|
||||
| Flash / RAM | 128 KB / 16 KB |
|
||||
| USB | Composite: HID Keyboard + Consumer + CDC Serial |
|
||||
| Matrix | 5x5 logisch, davon 20 MX-Buttons + 4 Encoder-SW + 1 unbelegt |
|
||||
| Encoder | 4x Rotary Encoder mit Quadratur via EIC-Interrupt |
|
||||
| LEDs | 20x WS2812B an `PB22` |
|
||||
| Programmer | Atmel-ICE via SWD, kein Bootloader |
|
||||
| Flash / RAM | 128 KiB / 16 KiB |
|
||||
| Matrix | logisch 5×5: 20 MX, 4 Encoder-SW, 1 unbelegt |
|
||||
| Encoder | 4× Quadratur über EIC-Interrupts |
|
||||
| LEDs | 20× WS2812B an `PB22` |
|
||||
| USB | Native USB als HID + CDC Composite Device |
|
||||
| Programmer | Atmel-ICE/CMSIS-DAP über SWD, standardmäßig ohne Bootloader |
|
||||
|
||||
## Build und Flash
|
||||
## Schnellstart
|
||||
|
||||
Voraussetzungen sind PlatformIO Core oder die PlatformIO IDE sowie für den
|
||||
Upload ein angeschlossener Atmel-ICE beziehungsweise kompatibler
|
||||
CMSIS-DAP-Adapter.
|
||||
|
||||
```bash
|
||||
pio run
|
||||
pio run --target upload
|
||||
pio run -e versapad
|
||||
pio run -e versapad --target upload
|
||||
```
|
||||
|
||||
Der Upload laeuft per OpenOCD ueber SWD.
|
||||
Das Standard-Environment `versapad` baut für `boards/versapad_nobl.json`. Der
|
||||
Upload wird durch `upload_openocd.py` über das von PlatformIO installierte
|
||||
OpenOCD ausgeführt.
|
||||
|
||||
Das in `platformio.ini` auskommentierte Bootloader-Environment ist derzeit
|
||||
nicht als Produktionsziel unterstützt. Details stehen unter
|
||||
[bekannte Einschränkungen](doc/09_known_limitations.md).
|
||||
|
||||
## Laufzeitmodell
|
||||
|
||||
`main.cpp` startet genau einen `CMainController`.
|
||||
Die Hauptschleife in `work()` ist:
|
||||
`main.cpp` besitzt genau einen `CMainController`. Nach einem roten Startsignal
|
||||
initialisiert er NVM, USB, Matrix und Encoder. Die Hauptschleife ist:
|
||||
|
||||
```text
|
||||
matrix_scan()
|
||||
@@ -37,133 +63,98 @@ check_factory_reset()
|
||||
updateLEDs()
|
||||
```
|
||||
|
||||
Dabei gilt:
|
||||
- Matrix und Encoder legen `SEvent`s in eine feste Queue.
|
||||
- Der Controller setzt Events in HID-Aktionen, Makros, Host-Events oder
|
||||
Profilwechsel um.
|
||||
- `poll_vendor()` verarbeitet feste 8-Byte-Pakete über CDC.
|
||||
- LEDs werden nur neu übertragen, wenn ein Zustand dirty ist oder eine
|
||||
Animation läuft.
|
||||
- Makros, Encoder-Taps, NVM-Zugriffe und visuelles Reset-Feedback blockieren
|
||||
den Loop kurzzeitig; es gibt keinen Scheduler.
|
||||
|
||||
- Matrix und Encoder erzeugen `SEvent`s.
|
||||
- `processEvents()` fuehrt daraus HID, Makros, Host-Commands oder Profilwechsel aus.
|
||||
- `poll_vendor()` verarbeitet das 8-Byte-CDC-Protokoll mit Config- und Makro-Transfers.
|
||||
- `updateLEDs()` rendert nur dann zu den WS2812, wenn sich etwas geaendert hat.
|
||||
## Wichtige Datenverträge
|
||||
|
||||
## Action-System
|
||||
### Config v3
|
||||
|
||||
Unterstuetzte `ActionType`s:
|
||||
|
||||
| Typ | Verhalten |
|
||||
|---|---|
|
||||
| `NONE` | keine Aktion |
|
||||
| `HID_KEY` | Keyboard-Hold ueber USB HID |
|
||||
| `HID_CONSUMER` | Media/Consumer-Hold ueber USB HID |
|
||||
| `HOST_COMMAND` | Event an die GUI per CDC Serial |
|
||||
| `MACRO` | Firmware spielt Makro-Slot komplett ab |
|
||||
| `PROFILE_SWITCH` | aktives Profil in NVM wechseln |
|
||||
|
||||
Wichtige Semantik:
|
||||
|
||||
- normale Keys und Consumer folgen dem Hold-Modell
|
||||
- Encoder `CW` / `CCW` sind immer Tap-Events
|
||||
- Makros laufen komplett in der Firmware, ohne laufende App
|
||||
|
||||
## LED-System
|
||||
|
||||
Jeder MX-Button hat:
|
||||
|
||||
- eine Base-Farbe
|
||||
- optional eine temporaere Override-Farbe
|
||||
- eine Animation
|
||||
|
||||
Aktuelle Animationsmodi:
|
||||
|
||||
- `STATIC`
|
||||
- `BLINK`
|
||||
- `PULSE`
|
||||
- `FADE_IN`
|
||||
- `FADE_OUT`
|
||||
- `COLOR_CYCLE`
|
||||
- `COLOR_FADE`
|
||||
|
||||
Die GUI nutzt derzeit vor allem `STATIC`, `BLINK`, `PULSE` und `COLOR_CYCLE`.
|
||||
|
||||
## Aktuelles NVM-Layout
|
||||
|
||||
### DeviceConfig
|
||||
|
||||
- Version: `3`
|
||||
- Magic: `0x56503203`
|
||||
- Groesse: `740` Byte
|
||||
- CRC16-CCITT ueber Bytes `7..739`
|
||||
- Magic `0x56503203`
|
||||
- `SDeviceConfig`: 740 Byte
|
||||
- CRC16-CCITT über Bytes `7..739`
|
||||
- 3 Profile
|
||||
- globale Helligkeit
|
||||
- per-LED-Helligkeit
|
||||
- globale und LED-spezifische Helligkeit
|
||||
- 124 Chunks mit je 6 Nutzbytes beim CDC-Transfer
|
||||
|
||||
### MacroTable
|
||||
### Makros
|
||||
|
||||
- 32 Slots
|
||||
- 8 Steps pro Slot
|
||||
- 512 Byte gesamt
|
||||
- `SMacroTable`: 512 Byte
|
||||
- 32 Slots × 8 Schritte × 2 Byte
|
||||
- 86 Chunks mit je 6 Nutzbytes beim CDC-Transfer
|
||||
|
||||
### Flash-Bereich
|
||||
### Flashzugriffe
|
||||
|
||||
| Bereich | Adresse | Groesse |
|
||||
| Bereich | Adresse | Größe |
|
||||
|---|---|---|
|
||||
| Makros | `0x1FB00-0x1FCFF` | 512 B |
|
||||
| Config | `0x1FD00-0x1FFFF` | 768 B, davon 740 B genutzt |
|
||||
| Makros | `0x1FB00..0x1FCFF` | 512 B |
|
||||
| Config | `0x1FD00..0x1FFFF` | 768 B, davon 740 B genutzt |
|
||||
|
||||
Config und Makros liegen in getrennten reservierten NVM-Bereichen.
|
||||
|
||||
Beim Serial-Dump der Config werden 124 Chunks zu je 6 Nutzbytes uebertragen. Implementierungen muessen den daraus berechneten Byte-Offset mindestens 16 Bit breit halten, weil Profil 2 und 3 hinter Byte 255 liegen.
|
||||
Wichtig: Das aktive Linker-Skript reserviert aktuell nur die letzten 512 Byte
|
||||
explizit. Das derzeit kleine Firmware-Image überschneidet sich nicht mit den
|
||||
NVM-Daten, zukünftiges Wachstum ist aber nicht vollständig abgesichert. Siehe
|
||||
[bekannte Einschränkungen](doc/09_known_limitations.md).
|
||||
|
||||
## Werksreset
|
||||
|
||||
Die Firmware hat einen eingebauten Recovery-Pfad:
|
||||
Unteren linken und unteren rechten MX-Button gleichzeitig fünf Sekunden
|
||||
halten:
|
||||
|
||||
- unteren linken und unteren rechten MX-Button gleichzeitig 5 Sekunden halten
|
||||
- waehrend des Holds leuchten diese beiden Tasten rot
|
||||
- ihre normalen HID-Aktionen werden waehrenddessen unterdrueckt
|
||||
- bei Erfolg blinken alle LEDs kurz rot
|
||||
- danach werden Config und Makros auf Werkseinstellungen zurueckgesetzt und neu geladen
|
||||
- die Tasten werden während des Haltens rot markiert,
|
||||
- ihre normalen Aktionen werden unterdrückt,
|
||||
- bei Erfolg blinken alle LEDs kurz rot,
|
||||
- Config und Makrotabelle werden auf Defaults zurückgesetzt.
|
||||
|
||||
Reset-Inhalt:
|
||||
|
||||
- alle Aktionen `NONE`
|
||||
- alle Makro-Slots leer
|
||||
- Base-LEDs auf Defaultwerte
|
||||
- sichtbarer Idle-Zustand wieder Regenbogen
|
||||
|
||||
Wichtig:
|
||||
|
||||
- ein SWD-Reflash loescht diese NVM-Daten nicht automatisch
|
||||
- der Werksreset ist der vorgesehene Weg, um eine kaputte Konfiguration zu bereinigen
|
||||
Ein normaler SWD-Reflash löscht diese NVM-Daten nicht automatisch.
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```text
|
||||
VersaMCU/
|
||||
|-- AGENTS.md # Kontext und Richtlinien für Coding-LLMs
|
||||
|-- README.md
|
||||
|-- platformio.ini
|
||||
|-- boards/
|
||||
|-- variants/versapad/
|
||||
|-- boards/ # PlatformIO-Boarddefinitionen
|
||||
|-- variants/versapad/ # Pinmapping und Linker-Skripte
|
||||
|-- doc/ # Architektur- und Protokolldokumentation
|
||||
`-- src/
|
||||
|-- main.cpp
|
||||
|-- CMainController.h/.cpp
|
||||
|-- CButton.h/.cpp
|
||||
|-- CEventQueue.h/.cpp
|
||||
|-- SEvent.h
|
||||
|-- config/
|
||||
| |-- action.h
|
||||
| |-- macro_config.h/.cpp
|
||||
| `-- nvm_config.h/.cpp
|
||||
`-- hal/
|
||||
|-- encoder.h/.cpp
|
||||
|-- matrix.h/.cpp
|
||||
|-- usb_hid.h/.cpp
|
||||
|-- usb_serial.h/.cpp
|
||||
`-- ws2812.h/.cpp
|
||||
|-- CMainController.* # Orchestrierung
|
||||
|-- CButton.* # Actions und LED-Zustand
|
||||
|-- CEventQueue.* # feste Event-Queue
|
||||
|-- config/ # Binärformate, NVM und Pins
|
||||
`-- hal/ # Matrix, Encoder, HID, CDC, WS2812
|
||||
```
|
||||
|
||||
## Weiterfuehrende Doku
|
||||
## Einstieg für Entwickler und LLMs
|
||||
|
||||
- [doc/INDEX.md](doc/INDEX.md)
|
||||
- [doc/00_architecture.md](doc/00_architecture.md)
|
||||
- [doc/03_action_engine.md](doc/03_action_engine.md)
|
||||
- [doc/04_macro_system.md](doc/04_macro_system.md)
|
||||
- [doc/06_nvm_config.md](doc/06_nvm_config.md)
|
||||
- [doc/07_serial_protocol.md](doc/07_serial_protocol.md)
|
||||
Für einen neuen Kollegen:
|
||||
|
||||
1. [Entwicklung und Einstieg](doc/08_development.md)
|
||||
2. [Architektur](doc/00_architecture.md)
|
||||
3. die zum Task passende Fachdokumentation im [Dokumentationsindex](doc/INDEX.md)
|
||||
4. [bekannte Einschränkungen](doc/09_known_limitations.md)
|
||||
|
||||
Für einen Coding-Agent zusätzlich [`AGENTS.md`](AGENTS.md) als
|
||||
Repository-Anweisung mitgeben. Die Datei enthält Quellenhierarchie,
|
||||
Binärverträge, Änderungsregeln und die minimale Verifikation.
|
||||
|
||||
## Dokumentation
|
||||
|
||||
- [Dokumentationsindex](doc/INDEX.md)
|
||||
- [Architektur](doc/00_architecture.md)
|
||||
- [Matrix](doc/01_matrix.md)
|
||||
- [Encoder](doc/02_encoder.md)
|
||||
- [Action-Engine](doc/03_action_engine.md)
|
||||
- [Makros](doc/04_macro_system.md)
|
||||
- [LED-System](doc/05_led_system.md)
|
||||
- [NVM-Config](doc/06_nvm_config.md)
|
||||
- [CDC-Protokoll](doc/07_serial_protocol.md)
|
||||
- [Entwicklung und Einstieg](doc/08_development.md)
|
||||
- [Bekannte Einschränkungen](doc/09_known_limitations.md)
|
||||
|
||||
+28
-9
@@ -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()
|
||||
Arduino setup()
|
||||
delay(500)
|
||||
ws2812_init()
|
||||
rotes Startsignal für 1 s
|
||||
CMainController::setup()
|
||||
macro_config_load()
|
||||
nvm_config_load()
|
||||
init_buttons()
|
||||
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)
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
; VersaPad v2 – PlatformIO Configuration
|
||||
; Custom SAMD21G18A board (custom PCB)
|
||||
; Custom SAMD21G17D board (custom PCB)
|
||||
; Programmer: Atmel-ICE via SWD (kein Bootloader nötig)
|
||||
|
||||
[common]
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ struct RGB
|
||||
enum class LEDAnim : uint8_t
|
||||
{
|
||||
STATIC = 0, // Sofort, keine Animation (Standardzustand)
|
||||
BLINK, // Binäres An/Aus – period_ms = Halbperiode (An-Zeit = Aus-Zeit)
|
||||
BLINK, // Binäres An/Aus – period_ms = Vollperiode (50 % an, 50 % aus)
|
||||
PULSE, // Lineares Fade-In/Fade-Out in Schleife – period_ms = Vollperiode
|
||||
FADE_IN, // Einmalig: schwarz → volle Helligkeit über period_ms
|
||||
FADE_OUT, // Einmalig: volle Helligkeit → schwarz über period_ms
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
|
||||
// ── LED-Animation ─────────────────────────────────────────────────────────
|
||||
// set_anim(): für STATIC, BLINK, PULSE, FADE_IN, FADE_OUT, COLOR_CYCLE.
|
||||
// period_ms: Halbperiode (BLINK), Vollperiode (PULSE/COLOR_CYCLE), Dauer (FADE_*).
|
||||
// period_ms: Vollperiode (BLINK/PULSE/COLOR_CYCLE), Dauer (FADE_*).
|
||||
// phase_offset_ms: Zeitversatz in die Vergangenheit – verschiebt den Startpunkt der
|
||||
// Animation. Nützlich für COLOR_CYCLE um LEDs versetzt starten zu
|
||||
// lassen (Regenbogen-Wellen-Effekt über mehrere Buttons).
|
||||
|
||||
+4
-5
@@ -7,11 +7,10 @@
|
||||
// Leer: m_head == m_tail
|
||||
// Voll: (m_tail + 1) % SIZE == m_head → ein Slot bleibt immer frei
|
||||
//
|
||||
// Interrupt-Sicherheit (Cortex-M0+):
|
||||
// push() wird aus Encoder-ISR aufgerufen, pop() aus dem Loop.
|
||||
// Auf M0+ sind uint8_t-Lese/Schreibzugriffe atomar (single-cycle LDR/STR) –
|
||||
// solange nur ein Producer (ISR) und ein Consumer (Loop) existieren, ist kein
|
||||
// Mutex nötig. Bei mehreren Producern müsste noInterrupts() verwendet werden.
|
||||
// Nebenläufigkeit:
|
||||
// Encoder-ISRs und der Matrixcallback im Loop können beide push() aufrufen.
|
||||
// Dieser gemischte Producerfall ist aktuell nicht durch eine Critical
|
||||
// Section geschützt; siehe doc/09_known_limitations.md.
|
||||
|
||||
#include "CEventQueue.h"
|
||||
|
||||
|
||||
+4
-5
@@ -7,11 +7,10 @@
|
||||
// Kapazität: QUEUE_SIZE - 1 = 16 Events (ein Slot bleibt leer damit
|
||||
// is_full() und is_empty() ohne extra Zähler unterscheidbar sind).
|
||||
//
|
||||
// Thread-Sicherheit:
|
||||
// push() wird aus ISR-Kontext aufgerufen (encoder_cb).
|
||||
// pop() wird aus Loop-Kontext aufgerufen (processEvents).
|
||||
// Auf Cortex-M0+ sind 8-Bit-Lese/Schreibzugriffe atomar → kein Mutex nötig
|
||||
// solange nur ein Producer (ISR) und ein Consumer (Loop) existieren.
|
||||
// Nebenläufigkeit:
|
||||
// push() wird aus Encoder-ISRs und aus dem Matrixcallback im Loop aufgerufen.
|
||||
// pop() läuft ebenfalls im Loop. Es gibt aktuell keine Critical Section für
|
||||
// den Fall, dass ein Encoderinterrupt einen Matrix-Push unterbricht.
|
||||
|
||||
#pragma once
|
||||
#include "SEvent.h"
|
||||
|
||||
@@ -58,7 +58,8 @@ static void matrix_cb(uint8_t key, bool pressed)
|
||||
}
|
||||
|
||||
// Wird von handle_encoder() aufgerufen – läuft im ISR-Kontext (EIC-Interrupt).
|
||||
// CEventQueue::push() ist interrupt-sicher (kein Heap, atomare Indizes auf M0+).
|
||||
// Die Queue vermeidet den Heap, schützt den gemischten Matrix-/ISR-Producerfall
|
||||
// aber aktuell nicht mit einer Critical Section.
|
||||
static void encoder_cb(uint8_t enc, int8_t dir)
|
||||
{
|
||||
if (!s_queue) return;
|
||||
@@ -168,7 +169,7 @@ void CMainController::work()
|
||||
poll_vendor(); // 2. Eingehende Serial-Pakete (PC→Board) verarbeiten
|
||||
processEvents(); // 3. Queue leeren, Aktionen ausführen
|
||||
check_factory_reset();// 4. Long-Press-Kombination für Werksreset prüfen
|
||||
updateLEDs(); // 4. Geänderte LED-Zustände in WS2812-Buffer schreiben + show()
|
||||
updateLEDs(); // 5. Geänderte LED-Zustände in WS2812-Buffer schreiben + show()
|
||||
}
|
||||
|
||||
// ─── Vendor-Kommunikation (PC → Board) ───────────────────────────────────────
|
||||
@@ -525,7 +526,7 @@ void CMainController::show_factory_reset_feedback()
|
||||
// execute_action_up(): Taste wird losgelassen (Hold-Ende).
|
||||
// HID_KEY: sendet Key-Up.
|
||||
// HID_CONSUMER: sendet Consumer-Up.
|
||||
// HOST_COMMAND: kann USB_EVT_KEY_UP senden.
|
||||
// HOST_COMMAND: aktuell keine Ausgabe auf Release.
|
||||
// MACRO/NONE: keine Aktion.
|
||||
|
||||
void CMainController::execute_action_down(SAction action, uint8_t key_id)
|
||||
@@ -606,7 +607,7 @@ void CMainController::execute_action_up(SAction action, uint8_t key_id)
|
||||
break;
|
||||
|
||||
case ActionType::HOST_COMMAND:
|
||||
// Optional: USB_EVT_KEY_UP senden (aktuell nicht implementiert)
|
||||
// USB_EVT_KEY_UP ist definiert, wird aktuell aber nicht gesendet.
|
||||
break;
|
||||
|
||||
case ActionType::MACRO:
|
||||
|
||||
+5
-4
@@ -6,15 +6,16 @@ enum class ActionType : uint8_t
|
||||
NONE, // Keine Aktion
|
||||
HID_KEY, // Standard-Keyboard-Keycode (direkt in Firmware gesendet)
|
||||
HID_CONSUMER, // Consumer-Control-Keycode (Volume, Media, …)
|
||||
HOST_COMMAND, // Command-ID → Windows-App führt aus (URL, Programm, …)
|
||||
HOST_COMMAND, // Host-Event; data wird vom aktuellen Controller nicht übertragen
|
||||
MACRO, // Makro-Slot (data = Slot-Index 0–31) → bis zu 8 HID-Keys sequenziell
|
||||
PROFILE_SWITCH, // Profil wechseln (data = Profil-Index 0–2); speichert in NVM
|
||||
PROFILE_SWITCH, // Profil 0–2 oder 0xFF = nächstes Profil; speichert in NVM
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) SAction
|
||||
{
|
||||
ActionType type;
|
||||
uint16_t data; // Keycode (HID_KEY / HID_CONSUMER) oder Command-ID (HOST_COMMAND)
|
||||
uint16_t data; // Typabhängige Nutzdaten; für HOST_COMMAND aktuell ungenutzt
|
||||
// packed: 1B type + 2B data = 3B (kein Alignment-Padding)
|
||||
// Muss packed sein damit sizeof(SDeviceConfig)==163 == C#-Serialisierung
|
||||
// Muss packed sein, damit sizeof(SDeviceConfig)==740 und die
|
||||
// hostseitige Serialisierung bytegenau übereinstimmen.
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// Slot-Zuweisung (vom Windows-App vergeben, Board speichert blind):
|
||||
// Slot 0–19 : MX-Buttons (mx_idx)
|
||||
// Slot 20–31 : Encoder-Aktionen (enc*3 + act_idx, 0=SW/1=CW/2=CCW)
|
||||
// Slot 20–31 : Encoder-Aktionen (20 + enc*3 + act_idx, 0=SW/1=CW/2=CCW)
|
||||
//
|
||||
// Ein Step mit keycode=0 gilt als leer → Ausführung stoppt dort.
|
||||
// Delay zwischen Steps: 20 ms (hardcoded).
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@
|
||||
#define BTN_COL_COUNT 5
|
||||
#define BTN_ROW_COUNT 5
|
||||
|
||||
// Column pins: driven OUTPUT LOW during scan, otherwise INPUT (high-Z or HIGH)
|
||||
// Column pins: INPUT; external 10k pull-ups hold them HIGH.
|
||||
static const uint8_t BTN_COLS[BTN_COL_COUNT] = {
|
||||
PIN_COL0, // PB10 – encoder SW column
|
||||
PIN_COL1, // PA11 – Cherry MX col 1 (leftmost)
|
||||
@@ -28,7 +28,7 @@ static const uint8_t BTN_COLS[BTN_COL_COUNT] = {
|
||||
PIN_COL4, // PA08 – Cherry MX col 4 (rightmost)
|
||||
};
|
||||
|
||||
// Row pins: INPUT_PULLUP, read LOW when button pressed
|
||||
// Row pins: idle INPUT (high-Z), driven OUTPUT LOW one at a time during scan.
|
||||
static const uint8_t BTN_ROWS[BTN_ROW_COUNT] = {
|
||||
PIN_ROW0, // PB11
|
||||
PIN_ROW1, // PA12
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
#include <HID.h>
|
||||
|
||||
// ── HID Report Descriptor: Keyboard + Consumer Control ───────────────────────
|
||||
// Vendor-Kommunikation läuft über CVendorHID (eigenes PluggableUSBModule).
|
||||
// Host-Kommunikation außerhalb von HID läuft separat über USB CDC (SerialUSB).
|
||||
|
||||
static const uint8_t k_hid_descriptor[] = {
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// alle verfügbaren Bytes in einen internen Ring-Buffer und gibt ein vollständiges
|
||||
// 8-Byte-Paket zurück sobald genug Bytes akkumuliert sind.
|
||||
// Der Ring-Buffer (256 Bytes = 32 Pakete) verhindert Datenverlust wenn mehrere
|
||||
// Pakete auf einmal ankommen (Config-Transfer: 30 Pakete).
|
||||
// Pakete auf einmal ankommen. Größere Transfers werden fortlaufend geleert.
|
||||
//
|
||||
// Senden (Board → PC):
|
||||
// Direkt via SerialUSB.write() – kein eigener Puffer nötig, da der Arduino-CDC-
|
||||
@@ -16,8 +16,9 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
// Ring-Buffer für eingehende Bytes – CDC kann jederzeit Bytes liefern.
|
||||
// Größe: 32 Pakete × 8 Bytes = 256 Bytes – reicht für eine vollständige
|
||||
// Config-Übertragung (30 Pakete) ohne Überlauf.
|
||||
// Größe: 32 Pakete × 8 Bytes = 256 Bytes. Der Puffer ist nur ein
|
||||
// Zwischenpuffer; ein vollständiger Config-Transfer umfasst 126 Pakete
|
||||
// einschließlich BEGIN und COMMIT.
|
||||
static uint8_t s_buf[SERIAL_PKT_SIZE * 32];
|
||||
static uint16_t s_head = 0;
|
||||
static uint16_t s_count = 0;
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
// Byte-Layout aller Pakete:
|
||||
// [0] Command/Event-ID
|
||||
// [1] key_id (Button 0–24 oder Encoder 0–3)
|
||||
// [2] r / Daten-Byte A
|
||||
// [3] g / Daten-Byte B
|
||||
// [4] b
|
||||
// [5..7] reserviert (0x00)
|
||||
// [2..7] kommandospezifische Daten
|
||||
// LED-Kommandos nutzen [2..4] als RGB; Config-/Makro-DATA nutzt [2..7].
|
||||
//
|
||||
// Richtungen:
|
||||
// PC → Board (Commands, 0x01–0x7F): poll_vendor() in CMainController
|
||||
@@ -44,9 +42,9 @@
|
||||
|
||||
// ── Events: Board → PC ────────────────────────────────────────────────────────
|
||||
#define USB_EVT_KEY_DOWN 0x81 // key_id → HOST_COMMAND-Button gedrückt
|
||||
#define USB_EVT_KEY_UP 0x82 // key_id → HOST_COMMAND-Button losgelassen
|
||||
#define USB_EVT_ENC_CW 0x83 // enc_id → Encoder Schritt CW (HOST_COMMAND)
|
||||
#define USB_EVT_ENC_CCW 0x84 // enc_id → Encoder Schritt CCW (HOST_COMMAND)
|
||||
#define USB_EVT_KEY_UP 0x82 // Reserviert; vom Controller aktuell nicht gesendet
|
||||
#define USB_EVT_ENC_CW 0x83 // Reserviert; vom Controller aktuell nicht gesendet
|
||||
#define USB_EVT_ENC_CCW 0x84 // Reserviert; vom Controller aktuell nicht gesendet
|
||||
#define USB_EVT_PONG 0x85 // Antwort auf USB_CMD_PING
|
||||
#define USB_EVT_CONFIG_ACK 0x90 // Config erfolgreich in NVM geschrieben
|
||||
#define USB_EVT_CONFIG_NACK 0x91 // Config CRC/Magic ungültig – nicht geschrieben
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// VersaPad v2 – SAMD21G18A Custom Variant
|
||||
// VersaPad v2 – SAMD21G17D Custom Variant
|
||||
// Pin descriptions and peripheral object definitions
|
||||
|
||||
#include "variant.h"
|
||||
@@ -15,7 +15,7 @@
|
||||
const PinDescription g_APinDescription[] = {
|
||||
|
||||
// ── Button Matrix: Columns (D0–D4) ────────────────────────────────────────
|
||||
// Driven LOW one at a time during scanning; idle = INPUT_PULLUP or OUTPUT HIGH
|
||||
// Inputs with external pull-ups; read LOW through a pressed switch.
|
||||
|
||||
// D0 – PA08 – COL_4
|
||||
{ PORTA, 8, PIO_DIGITAL, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NMI },
|
||||
@@ -29,7 +29,7 @@ const PinDescription g_APinDescription[] = {
|
||||
{ PORTB, 10, PIO_DIGITAL, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_10 },
|
||||
|
||||
// ── Button Matrix: Rows (D5–D9) ───────────────────────────────────────────
|
||||
// Read as INPUT_PULLUP; go LOW when a button in the active column is pressed
|
||||
// Idle high-Z; driven LOW one at a time during scanning.
|
||||
|
||||
// D5 – PB11 – ROW_0
|
||||
{ PORTB, 11, PIO_DIGITAL, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_11 },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
// VersaPad v2 – SAMD21G18A Custom Variant
|
||||
// VersaPad v2 – SAMD21G17D Custom Variant
|
||||
// Arduino pin assignments for the custom PCB
|
||||
|
||||
#define ARDUINO_SAMD_VARIANT_COMPLIANCE 10610
|
||||
@@ -24,14 +24,14 @@
|
||||
// attachInterrupt() then internally looks up ulExtInt via g_APinDescription.
|
||||
|
||||
// ─── Button Matrix ────────────────────────────────────────────────────────────
|
||||
// Columns (driven LOW one at a time)
|
||||
// Columns (inputs with external pull-ups; read LOW for a pressed switch)
|
||||
#define PIN_COL0 (4u) // PB10 – also encoder SW column
|
||||
#define PIN_COL1 (3u) // PA11
|
||||
#define PIN_COL2 (2u) // PA10
|
||||
#define PIN_COL3 (1u) // PA09
|
||||
#define PIN_COL4 (0u) // PA08
|
||||
|
||||
// Rows (read with internal pull-up)
|
||||
// Rows (idle high-Z; driven LOW one at a time during scanning)
|
||||
#define PIN_ROW0 (5u) // PB11
|
||||
#define PIN_ROW1 (6u) // PA12
|
||||
#define PIN_ROW2 (7u) // PA13
|
||||
|
||||
Reference in New Issue
Block a user