Files
VersaMCU/doc/05_led_system.md
T

107 lines
4.1 KiB
Markdown
Raw 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.
# LED-System (WS2812)
**Dateien:** `hal/ws2812.h`, `hal/ws2812.cpp`, `CButton.h`, `CButton.cpp`
## Hardware-Treiber (ws2812 HAL)
Dünner Wrapper um **Adafruit NeoPixel** (bit-bang, kein DMA, kein SERCOM).
| Funktion | Bedeutung |
|---|---|
| `ws2812_init()` | `begin()` + `clear()` + `show()` |
| `ws2812_set(idx, r, g, b)` | `setPixelColor()` schreibt in RAM-Puffer |
| `ws2812_fill(r, g, b)` | Alle LEDs gleiche Farbe |
| `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 oder eine Animation läuft. Bei einer endlos
laufenden Animation wird daher in jedem Loop ein Frame übertragen.
Der Treiber nutzt `Adafruit NeoPixel` per Bit-Banging; es gibt keinen
DMA-/SERCOM-Ausgabepfad.
## 2-Schicht-Modell (CButton)
Jeder MX-Button hat zwei LED-Schichten:
```
override (aktiv wenn set_override() aufgerufen) ← temporär (GUI-Benachrichtigungen)
base (Idle-Farbe aus NVM) ← dauerhaft
```
Aktive Farbe = `override` wenn aktiv, sonst `base`. `clear_override()` kehrt sofort zu `base` zurück, ohne base zu verändern.
## Animationen
| Animation | Typ | Verhalten | Endbedingung |
|---|---|---|---|
| `STATIC` | — | Feste Farbe | — |
| `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) |
| `COLOR_CYCLE` | Farbe | Hue-Sweep, ignoriert base/override | endlos |
| `COLOR_FADE` | Farbe | Crossfade from→to | → STATIC (base=to) |
**Helligkeits-Animationen** (`compute_scale`): Multiplizieren die aktive Farbe mit einem Skalierungsfaktor 0255. Formel: `(channel * scale) / 255`.
**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 0255 aufgeteilt in 6 Segmente à 43 Einheiten. Innerhalb jedes Segments steigt/fällt ein Kanal linear:
```
Seg 0: R=255, G steigt (Rot → Gelb)
Seg 1: R fällt, G=255 (Gelb → Grün)
Seg 2: G=255, B steigt (Grün → Cyan)
Seg 3: G fällt, B=255 (Cyan → Blau)
Seg 4: B=255, R steigt (Blau → Magenta)
Seg 5: R=255, B fällt (Magenta → Rot)
```
Ausgabe wird auf 40 % Helligkeit skaliert (Faktor 102/255) damit die LEDs nicht blenden.
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. Configvalidierung lehnt kleinere Werte ab; `set_anim()` klemmt direkte
interne Aufrufe zusätzlich auf mindestens 2 ms.
`COLOR_FADE` benötigt `set_color_fade(to, period_ms)`. Beim Laden aus der
Config interpretiert der Controller die gespeicherte Base-Farbe als Ziel und
startet einen einmaligen Fade von Schwarz zu dieser Farbe.
### Phasenversatz (Regenbogen-Wellen)
`set_anim(COLOR_CYCLE, period, phase_offset_ms)`: `m_anim_start_ms = millis() - phase_offset_ms`. Die 20 MX-Buttons werden in `init_buttons()` gleichmäßig phasenverschoben initialisiert:
```cpp
uint16_t phase = (mx_idx * period) / 20;
```
## Render-Pipeline
```
CMainController::updateLEDs()
für jeden CButton:
CButton::render_led()
if !dirty && !is_animating(): return false
rgb = compute_rgb()
ws2812_set(led_index, rgb.r, rgb.g, rgb.b)
dirty = false
return true
if any returned true:
ws2812_show()
```
`dirty` wird gesetzt bei: `init()`, `set_base()`, `set_override()`, `clear_override()`, `set_anim()`, `clear_anim()`, Animations-Abschluss.