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. 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)
`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.