VersaMCU/README.md
Julian Appel 01c5e0930e Guard env:versapad's upload against overwriting the bootloader
pio run -e versapad --target upload writes the app starting at
0x0000 and silently destroyed the installed UF2 bootloader twice
today during testing -- no warning, no error, just a board that
stopped responding to the boot-key hold.

upload_openocd.py now checks for the bootloader (verify_image against
the locally built bootloader/.pio/build/versapad_bootloader/firmware.bin)
before an env:versapad upload and refuses if one is present, pointing
at env:versapad_usb instead. Fails closed: an inconclusive check (e.g.
bootloader not built locally, SWD not responding) blocks rather than
proceeding on a guess -- confirmed necessary the hard way, since a
"fail open" first attempt let the destructive upload through silently.
Scoped to PIOENV == "versapad" only, since bootloader/platformio.ini's
own upload reuses this same script and must always be allowed to write
0x0000. A new erase-bootloader-and-flash custom target remains as the
explicit, deliberate override.

Documented the workflow (bootloader is its own PlatformIO project,
flashed once via SWD; versapad_usb is the normal path afterward;
versapad's upload is now guarded) in README.md and
doc/10_usb_bootloader.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 23:27:02 +02:00

186 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# VersaMCU
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 |
| Firmware-Update | SWD (Atmel-ICE) oder USB (UF2-Bootloader, `bootloader/`) |
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 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, oder USB über den UF2-Bootloader |
## 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 -e versapad
pio run -e versapad --target upload
```
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.
Alternativ lässt sich die App-Firmware nach einem einmaligen
SWD-Bootloader-Flash auch über USB aktualisieren, ganz ohne Atmel-ICE:
```bash
cd bootloader && pio run -e versapad_bootloader --target upload # einmalig
pio run -e versapad_usb --target upload # danach jedes App-Update
```
`bootloader/` ist ein eigenständiges PlatformIO-Projekt (eigene
`platformio.ini`, kein Arduino-Framework). Für die PlatformIO-IDE-Buttons in
VS Code müsste der Ordner separat als eigener Workspace geöffnet werden; über
die Kommandozeile reicht `cd bootloader && pio run ...` im selben Fenster.
Sobald der Bootloader installiert ist, **verweigert `versapad --target
upload` den normalen Upload** — dieser Weg schreibt ab `0x0000` und würde den
Bootloader sonst kommentarlos überschreiben (`upload_openocd.py` prüft das
vorher automatisch). Bauen und Debuggen über `versapad` bleiben uneingeschränkt
möglich, nur der Upload ist betroffen. Für App-Updates danach `versapad_usb`
verwenden; bewusst zurück zu reinem SWD-Betrieb geht über
`pio run -e versapad -t erase-bootloader-and-flash`.
Details, Speicherlayout und die Bootloader-Aktivierung (kein physischer
Reset-Taster auf dieser Platine) stehen in
[10_usb_bootloader.md](doc/10_usb_bootloader.md).
## Laufzeitmodell
`main.cpp` besitzt genau einen `CMainController`. Nach einem roten Startsignal
initialisiert er NVM, USB, Matrix und Encoder. Die Hauptschleife ist:
```text
matrix_scan()
poll_vendor()
processEvents()
check_factory_reset()
updateLEDs()
```
- 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.
## Wichtige Datenverträge
### Config v3
- Magic `0x56503203`
- `SDeviceConfig`: 740 Byte
- CRC16-CCITT über Bytes `7..739`
- 3 Profile
- globale und LED-spezifische Helligkeit
- 124 Chunks mit je 6 Nutzbytes beim CDC-Transfer
### Makros
- `SMacroTable`: 512 Byte
- 32 Slots × 8 Schritte × 2 Byte
- 86 Chunks mit je 6 Nutzbytes beim CDC-Transfer
### Flashzugriffe
| Bereich | Adresse | Größe |
|---|---|---|
| Makros | `0x1FB00..0x1FCFF` | 512 B |
| Config | `0x1FD00..0x1FFFF` | 768 B, davon 740 B genutzt |
Das aktive Linker-Skript und `boards/versapad_nobl.json` begrenzen das
Firmware-Image auf `0x00000..0x1FAFF`. Damit sind alle fünf NVM-Rows gegen
Firmwarewachstum geschützt.
## Werksreset
Unteren linken und unteren rechten MX-Button gleichzeitig fünf Sekunden
halten:
- 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.
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
|-- upload_openocd.py # SWD-Upload-Hook (env:versapad)
|-- uf2conv.py # .bin -> .uf2 Konverter (App-Firmware)
|-- upload_uf2.py # USB-Upload-Hook (env:versapad_usb)
|-- boards/ # PlatformIO-Boarddefinitionen
|-- variants/versapad/ # Pinmapping und Linker-Skripte
|-- doc/ # Architektur- und Protokolldokumentation
|-- bootloader/ # UF2-Bootloader (eigenes PlatformIO-Projekt)
`-- src/
|-- main.cpp
|-- CMainController.* # Orchestrierung
|-- CButton.* # Actions und LED-Zustand
|-- CEventQueue.* # feste Event-Queue
|-- config/ # Binärformate, NVM und Pins
`-- hal/ # Matrix, Encoder, HID, CDC, WS2812
```
## Einstieg für Entwickler und LLMs
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)
- [USB-Bootloader](doc/10_usb_bootloader.md)