# 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 0–255. 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 0–255 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.