# Quadratur-Encoder **Dateien:** `hal/encoder.h`, `hal/encoder.cpp`, `config/pins.h` ## Hardware 4 mechanische Quadratur-Encoder mit je 2 Phasen-Pins (A, B). Pins sind INPUT_PULLUP. Encoder-SW-Tasten laufen **nicht** durch diesen HAL, sondern durch den Matrix-Scan (COL_0). ## Dekodierung 4-State-Lookup-Table über `(prev_state << 2) | cur_state`: ``` Zustand = (A << 1) | B → 4 Bits: 00 / 01 / 10 / 11 LUT[prev<<2 | cur] → Roh-Vorzeichen, 0 (ungueltig/Prellen) ``` Mechanische Encoder erzeugen 4 Flanken pro Raste → Akkumulator zählt Halbschritte. Ein Event wird erst gefeuert wenn `|accum| >= 4` (= ein vollständiger Klick). Die VersaPad-PCB-Verdrahtung liefert das Quadratur-Vorzeichen gegenueber der sichtbaren Drehrichtung invertiert. Deshalb bleibt die LUT konventionell, aber der HAL dreht das Vorzeichen vor dem Callback mit `ENCODER_DIRECTION_SIGN = -1`. Nach aussen gilt weiterhin: - `direction = +1` -> `ENC_CW` - `direction = -1` -> `ENC_CCW` ## ISR-Aufbau 8 ISR-Wrapper (je einer pro Pin, da `attachInterrupt` keinen Parameter unterstützt): ```cpp static void isr_enc0_a() { handle_encoder(0); } static void isr_enc0_b() { handle_encoder(0); } // ... analog für Encoder 1–3 ``` `attachInterrupt(..., CHANGE)` auf beiden Pins jedes Encoders. `handle_encoder()` → `encoder_cb(enc_id, direction)` → `CEventQueue::push(ENC_CW / ENC_CCW)` ## 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 ## Initialisierung Initialer Zustand von A/B wird beim `encoder_init()` gelesen damit der erste Interrupt korrekt ausgewertet wird (kein "Phantom-Step" beim Einschalten).