.. include:: /index.rst :start-after: start_hello_message :end-before: end_hello_message .. _py_hue_knob: 4.10 Farbregler (Hue Knob) ============================== **Einführung** In dieser Lektion bauen Sie einen **Hue Knob** – einen interaktiven Farbregler, der einen Drehencoder verwendet, um den Farbton eines kreisförmigen WS2812-LED-Moduls anzupassen. Dieses WS2812-LED-Modul enthält 12 einzeln adressierbare WS2812-RGB-LEDs, die über die SPI-basierte NeoPixel-Schnittstelle des Fusion HAT+ gesteuert werden. Der externe Drehencoder liefert Echtzeit-Benutzereingaben über standardmäßige GPIO-Pins. Beim Drehen des Encoders durchlaufen die 12 LEDs fließend das gesamte RGB-Farbspektrum. Durch Drücken der integrierten Taste des Encoders wird der Farbton wieder auf den Ausgangswert zurückgesetzt. ---------------------------------------------- **Was Sie benötigen** Für dieses Projekt werden die folgenden Komponenten benötigt: .. list-table:: :widths: 30 20 :header-rows: 1 * - KOMPONENTENBESCHREIBUNG - KAUFLINK * - :ref:`cpn_wires` - |link_wires_buy| * - :ref:`cpn_rotary_encoder` - |link_rotary_encoder_buy| * - :ref:`cpn_circular_ws2812_module` - \- * - :ref:`cpn_fusion_hat` - \- * - Raspberry Pi - \- ---------------------------------------------- **Verdrahtungsdiagramm** Verwenden Sie das folgende Verdrahtungsdiagramm, um die Komponenten korrekt zu verbinden: .. image:: img/fzz/4.10_hub_color_bb.png :width: 80% :align: center ---------------------------------------------- **Einrichtungsschritte** #. Bevor Sie den Code ausführen, müssen Sie die erforderliche Bibliothek installieren: Diese Bibliothek stellt die notwendigen Funktionen bereit, um NeoPixel-LEDs über SPI-Kommunikation zu steuern. .. raw:: html .. code-block:: shell sudo pip3 install adafruit-circuitpython-neopixel-spi --break #. Der gesamte in diesem Tutorial verwendete Beispielcode befindet sich im Verzeichnis ``ai-lab-kit``. Führen Sie die folgenden Schritte aus, um das Beispiel auszuführen: .. raw:: html .. code-block:: shell cd ~/ai-lab-kit/python/ sudo python3 4.10_Hue_Knob.py #. Wenn das Skript ausgeführt wird, reagiert der WS2812-LED-Ring auf den Drehencoder: * Die LEDs starten in Rot (Hue 0°). * Drehen Sie den Encoder, um fließend durch das gesamte RGB-Farbrad zu wechseln. Die Farben ändern sich kontinuierlich (Rot → Gelb → Grün → Blau → Violett → Rot). Im Terminal werden der aktuelle Hue-Wert sowie die RGB-Werte ausgegeben.* * Drücken Sie die Encodertaste, um den Farbton auf 0° (Rot) zurückzusetzen. * Drücken Sie **Ctrl+C**, um das Programm zu beenden. Alle LEDs werden ausgeschaltet, bevor das Programm beendet wird. ---------------------------------------------- **Code** Hier ist das Python-Skript für dieses Projekt: .. raw:: html .. code-block:: python #!/usr/bin/env python3 from fusion_hat.motor import Motor from fusion_hat.pin import Pin, Mode, Pull from fusion_hat.adc import ADC from time import sleep, time import math BtnPin = Pin(22, mode=Mode.IN, pull=Pull.DOWN) motor = Motor("M0") thermistor = ADC("A3") level = 0 currentTemp = None markTemp = None PRINT_INTERVAL = 1.0 _last_print = 0.0 button_event = False # flag: button was pressed def temperature(samples=5, delay=0.01): """Read thermistor multiple times and return averaged Celsius (float) or None.""" vals = [] for _ in range(samples): analogVal = thermistor.read() Vr = 3.3 * float(analogVal) / 4095.0 if (3.3 - Vr) <= 0.1: return None Rt = 10000.0 * Vr / (3.3 - Vr) tempK = 1.0 / (((math.log(Rt / 10000.0)) / 3950.0) + (1.0 / (273.15 + 25.0))) vals.append(tempK - 273.15) sleep(delay) return sum(vals) / len(vals) def motor_run(lv): lv = max(0, min(4, lv)) motor.power(0 if lv == 0 else lv * 25) return lv def changeLevel(): """Button press: cycle level 0~4 and set a flag for main loop to print.""" global level, button_event level = (level + 1) % 5 button_event = True BtnPin.when_activated = changeLevel def main(): global level, currentTemp, markTemp, _last_print, button_event markTemp = temperature() while True: currentTemp = temperature() if currentTemp is None: print("Sensor read failed. Please check the sensor.") sleep(0.5) continue # Handle button event in main loop (stable timing) if button_event: button_event = False markTemp = currentTemp print(f"[Button] Level -> {level} | Temp: {currentTemp:.2f} °C | Mark: {markTemp:.2f} °C") # Periodic temperature print now = time() if now - _last_print >= PRINT_INTERVAL: if markTemp is None: markTemp = currentTemp print(f"Temp: {currentTemp:.2f} °C | Mark: {markTemp:.2f} °C | Level: {level}") _last_print = now # Auto adjust level based on ±5°C if markTemp is None: markTemp = currentTemp if level != 0: diff = currentTemp - markTemp if diff <= -5: level = max(0, level - 1) markTemp = currentTemp print(f"[Auto] Temp down -> Level {level} (Temp: {currentTemp:.2f} °C)") elif diff >= 5: level = min(4, level + 1) markTemp = currentTemp print(f"[Auto] Temp up -> Level {level} (Temp: {currentTemp:.2f} °C)") level = motor_run(level) sleep(0.5) try: main() except KeyboardInterrupt: print("\nExiting...") finally: motor.stop() sleep(0.1) ------------------------- **Code verstehen** 1. **NeoPixel-Initialisierung** - Das Skript verwendet SPI, um einen NeoPixel-LED-Ring oder -Streifen zu steuern (``LED_COUNT = 12``). - ``auto_write=False`` ist aktiviert, sodass die LEDs erst aktualisiert werden, wenn ``strip.show()`` aufgerufen wird. Dadurch werden Flackern reduziert und die Leistung verbessert. - Beim Start werden die LEDs mit ``strip.fill(0)`` und ``strip.show()`` ausgeschaltet. 2. **Hardware-Setup des Drehencoders** - Es werden drei GPIO-Pins verwendet: - ``CLK_PIN`` und ``DT_PIN`` liefern die Quadratur-Signale für Drehrichtung und Schritte. - ``SW_PIN`` ist der Tastereingang des Encoders. - Alle Pins verwenden interne Pull-ups (``Pull.UP``), und der Taster ist **aktiv LOW** (gedrückt = ``0``). 3. **Wichtige Parameter (Verhaltensabstimmung)** - ``DETENTS_PER_CYCLE`` definiert, wie viele physische „Klicks“ erforderlich sind, um eine vollständige Farbtonrotation (0–360°) abzuschließen. - Ein größerer Wert ermöglicht eine feinere Farbsteuerung. - ``TRANSITIONS_PER_DETENT`` wandelt rohe Quadratur-Übergänge in „ein Raster = ein Schritt“ um. - Viele Encoder erzeugen 2 Übergänge pro Raster, einige jedoch 4. Durch Anpassen dieses Wertes wird die Genauigkeit verbessert. 4. ``hue_to_rgb()`` - Wandelt einen Hue-Wert im Bereich ``0.0 ~ 1.0`` in ein RGB-Tupel ``(R, G, B)`` mit Werten von ``0 ~ 255`` um. - Dadurch lassen sich mit dem HSV-Farbmodell leicht sanfte Farbverläufe erzeugen. 5. ``apply_color_from_detent()`` - Ordnet die Rasterzählung des Encoders mithilfe von Modulo einem Farbtonindex zu: - ``hue_idx = detent % DETENTS_PER_CYCLE`` - Wandelt den Farbton in eine RGB-Farbe um und aktualisiert alle LEDs mit ``strip.fill(color)`` gefolgt von ``strip.show()``. - Verwendet ``last_hue_idx``, um unnötige Aktualisierungen zu vermeiden, wenn sich der berechnete Farbton nicht geändert hat. - Gibt den aktuellen Hue-Winkel, den Rasterwert und die RGB-Farbe zur Kontrolle bzw. Fehlersuche im Terminal aus. 6. ``reset_all()`` - Setzt den internen Zählerzustand zurück: - ``raw`` (rohe Übergangszählung) - ``last_detent`` (zuletzt angezeigtes Raster) - ``last_hue_idx`` (zuletzt angezeigter Farbtonindex) - Ruft ``apply_color_from_detent(0)`` auf, um die LEDs sofort auf die Ausgangsfarbe (Hue = 0°) zurückzusetzen. 7. **Hauptschleife (Polling + Richtungsbestimmung)** - Das Programm überwacht kontinuierlich ``CLK`` und prüft auf Änderungen: - Wenn sich ``CLK`` ändert, hat sich der Encoder um einen Schritt in der Quadratur-Sequenz bewegt. - Die Drehrichtung wird durch Vergleich von DT und CLK bestimmt: - ``dt.value() != c`` bedeutet eine Drehrichtung (Erhöhen) - andernfalls wird verringert - Die rohe Übergangszählung wird in Rasterwerte umgerechnet: - ``detent = raw // TRANSITIONS_PER_DETENT`` - Die LED-Farbe wird nur aktualisiert, wenn sich der Rasterwert geändert hat. 8. **Taster-Reset und Entprellung** - Wenn der Taster gedrückt wird (``sw.value() == 0``), ruft das Programm ``reset_all()`` auf. - Eine kurze Entprellverzögerung wird angewendet, und das Skript wartet, bis der Taster wieder losgelassen wird, um mehrere Resets durch einen einzelnen Tastendruck zu verhindern. 9. **Sauberes Beenden** - Durch Drücken von ``Ctrl + C`` wird das Programm beendet. - Im ``finally``-Block werden alle LEDs ausgeschaltet (``strip.fill(0)`` und ``strip.show()``), sodass die Hardware in einem sicheren Zustand verbleibt. **Fehlerbehebung** ------------------- - **LEDs leuchten nicht** - Überprüfen Sie die Verdrahtung des WS2812-LED-Moduls. - Stellen Sie sicher, dass die SPI-NeoPixel-Schnittstelle des Fusion HAT+ aktiviert ist. - Vergewissern Sie sich, dass ein unterstütztes WS2812/WS2812B-LED-Modul verwendet wird. - **Farben ändern sich zu schnell oder zu langsam** - Passen Sie ``STEPS_PER_CYCLE`` an, um die Empfindlichkeit zu erhöhen oder zu verringern. - **Tastendruck setzt den Farbton nicht zurück** - Überprüfen Sie, ob SW mit GPIO27 verbunden ist. - Stellen Sie sicher, dass der Pin mit ``pull=Pin.PULL_UP`` konfiguriert ist. - **Skript beendet sich sofort** - Stellen Sie sicher, dass ``pause()`` aus dem Modul ``signal`` importiert wurde. - Prüfen Sie, ob kein anderer Prozess SPI verwendet. **Probieren Sie es selbst aus** --------------------------------------- Möchten Sie dieses Projekt erweitern? Probieren Sie folgende Ideen: 1. **Helligkeitssteuerung hinzufügen** Verwenden Sie eine weitere Variable (z. B. Encoder drücken + drehen), um die LED-Helligkeit von 0–255 einzustellen. 2. **Mehrere Anzeigemodi hinzufügen** Drücken Sie den Encoder, um zwischen verschiedenen Modi zu wechseln: - Einfarbig (Standard) - Regenbogenanimation - „Breathing“-Effekt - Color-Wipe-Effekt 3. **Ein-/Ausschalter hinzufügen** Halten Sie die Encodertaste länger gedrückt, um den LED-Ring ein- oder auszuschalten. 4. **Sanftere Farbänderung** Erhöhen Sie ``STEPS_PER_CYCLE`` oder fügen Sie Interpolation hinzu, um besonders flüssige Übergänge zu erzielen. 5. **Richtungsanzeige** Lassen Sie eine LED grün leuchten, wenn im Uhrzeigersinn gedreht wird, und rot bei Drehung gegen den Uhrzeigersinn. Diese kleinen Erweiterungen verwandeln den einfachen „Hue Knob“ in eine vielseitige RGB-Steueroberfläche.