![](https://ramona.gloetter.de/wp-content/uploads/2023/02/ws2812b-1-1140x733.jpg)
LED-Ring WS2812b mit einem ESP32 und MicroPython programmieren
Es wäre doch cool, wenn man mit einem 12 Lichter LED-Ring eine Prozentanzeige darstellen könnte. Wie das geht zeige ich hier.
Entwicklungsumgebung
Für MicroPython bietet sich die Entwicklungsumgebung Thonny an. In diesem Tutorial von randomnerdtutorials.com wird die Installation beschrieben. Damit kann man einfach den ESP über USB mit dem Laptop oder Rechner verbinden und direkt den Code dort speichern.
Weil allerdings Thonny seine Eigenheiten hat und bei der Korrektur und Ergänzung von Python nicht so viele Möglichkeiten stellt, bietet es sich an parallel Visual Studio Code zu verwenden. VS Code kann Python-Code ergänzen, formatieren und die Semantik prüfen, kennt jedoch MicroPython an sich nicht.
Und dann – leider sehr stupide – einfach mit Strg+A, Strg+C und Strg+V in Thonny rein.
Verkabelung
Bei dem LED-Ring, den ich verwendet habe, handelt es sich um einen WS2812b, hier erhältlich. Der LED-Ring wird folgendermaßen angeschlossen:
LED-Ring | ESP32 |
DI | Pin 10 (SD3) |
5V | 3.3V (reicht locker) oder 5V |
GND | GND |
DO | – |
Der Code
Der Gedanke ist, eine LED-Ring-Klasse mit allen Attributen des Rings und allen gewünschten Funktionen zu schreiben. So können später beliebig viele LED-Ring-Objekte erzeugt werden, also auch beliebig viele LED-Ringe an den ESP angeschlossen werden. Außerdem müssen so nur die Objekte in der main.py erzeugt und verwendet werden und der Code ist ordentlicher.
LED-Ring als Klasse
Zuerst wie immer die Imports:
import time
import sys
import random
import os
import machine
import neopixel
Anschließend die eigentliche Klasse mit Konstruktor. Hier werden die Anzahl der LEDs und der Pin standardmäßig festgelegt, wenn keine anderen Werte übergeben werden. Diese also gegebenenfalls bearbeiten.
class color_wheel:
def __init__(self, pixel_count=12, pin=10):
self.pixel_count = pixel_count
self.pin = pin
self.np = neopixel.NeoPixel(machine.Pin(self.pin), self.pixel_count)
Jetzt zur eigentlichen Funktion, dem Darstellen einer Prozentzahl. Dementsprechend wird der Funktion eine Zahl übergeben, die dann gleich auf ihre Richtigkeit (liegt zwischen 0 und 100) geprüft wird.
Da der LED-Ring 12 LEDs hat sollen bei 0% keine und bei 100% alle Lichter leuchten. Dementsprechend veranschaulicht eine LED 8,3% bzw. mit ein bisschen Toleranz (bei der bereits 96% als 100% akzeptiert wird) 8%. So kann die Anzahl der zu erleuchtenden LEDs berechnet werden.
Da ich wollte, dass alle Lichter im Uhrzeigersinn angeschaltet und gegen den Uhrzeigersinn ausgeschaltet werden, benötigt es hier 2 for-Schleifen. Die erste for-Schleife zählt von 0 bis zur Anzahl der zu erleuchtenden LEDs, während die zweite Rückwärts von der totalen Anzahl der LEDs des Rings bis zur Anzahl der zu erleuchtenden LEDs minus 1 zählt, um alle anderen Lichter auszuschalten.
Bei der vorhin erstellten Variable np
handelt es sich um ein Array an LEDs. Demnach können RGB-Werte an np[i]
übergeben werden. Beim Anschalten der Lichter kann man natürlich alle mit self.np[i] = (0, 255, 0)
auf Grün setzen. Allerdings habe ich mich für einen Farbverlauf von Rot nach Grün entschieden, schließlich soll mein LED-Ring einen Ladestatus anzeigen. Da ich hier definitiv zu faul war alle Werte von Hand zu berechnen und viel zu viele if-Abfragen einzubauen – was den Code nebenbei bemerkt total unübersichtlich macht – kam es zur folgenden kleinen Rechnung. Die erste LED soll rot leuchten, die 12. grün und der Rest in den jeweiligen Zwischenstufen. Demnach muss R linear von 255 bis 0 abnehmen, während G von 0 bis 255 linear zunimmt. Das bedeutet, dass R = 255 - (i * (100 / self.pixel_count))
und G = i * (100 / self.pixel_count)
. So nehmen R und G linear in Abhängigkeit der gegebenen Anzahl an LEDs ab bzw. zu.
def display_percentage(self, percent):
if (percent < 0 or percent > 100):
print("Error: Percentage not correct " + str(percent))
self.show_error()
else:
# 12 leds = 100% -> 1 led = 8.3%
# tolerance: 12 leds = 96% -> 1 led = 8%
#num_leds = round(percent/8.3)
#num_leds = round(percent/8)
num_leds = round(percent/(96/pixel_count))
print(num_leds)
for i in range(num_leds):
time.sleep(0.5)
#self.np[i] = (0, 255, 0)
self.np[i] = (round(255-(i*(100/self.pixel_count))), round(i*(100/self.pixel_count)), 0)
self.np.write()
for i in range(self.np.n-1, num_leds-1, -1):
time.sleep(0.5)
self.np[i] = (0, 0, 0)
self.np.write()
Wenn ein übergebener Wert keine gültige Prozentzahl ist, wird die Funktion show_error( )
aufgerufen. Diese Funktion lässt den ganzen LED-Ring zweimal rot aufleuchten, bevor sie zur aufrufenden Funktion zurückkehrt. Das heißt, dass verschachtelte for-Schleifen benötigt werden. Die äußere zählt bis zwei, der Inhalt wird also zweimal aufgerufen, während die for-Schleifen im Inneren über das ganze Array an LEDs laufen und diese entweder ein- oder ausschalten.
def show_error(self):
for i in range(2):
for j in range(self.np.n):
self.np[j] = (255, 0, 0)
self.np.write()
time.sleep(0.5)
for j in range(self.np.n):
self.np[j] = (0, 0, 0)
self.np.write()
time.sleep(0.5)
Hier nochmal der gesamte Code der Klasse:
import time
import sys
import random
import os
import machine
import neopixel
class color_wheel:
def __init__(self, pixel_count=12, pin=10):
self.pixel_count = pixel_count
self.pin = pin
self.np = neopixel.NeoPixel(machine.Pin(self.pin), self.pixel_count)
def show_error(self):
for i in range(2):
for j in range(self.np.n):
self.np[j] = (255, 0, 0)
self.np.write()
time.sleep(0.5)
for j in range(self.np.n):
self.np[j] = (0, 0, 0)
self.np.write()
time.sleep(0.5)
def display_percentage(self, percent):
if (percent < 0 or percent > 100):
print("Error: Percentage not correct " + str(percent))
self.show_error()
else:
# 12 leds = 100% -> 1 led = 8.3%
# tolerance: 12 leds = 96% -> 1 led = 8%
#num_leds = round(percent/8.3)
num_leds = round(percent/8)
print(num_leds)
for i in range(num_leds):
time.sleep(0.5)
self.np[i] = (round(255-(I*(100/self.pixel_count))), round(I*(100/self.pixel_count)), 0)
self.np.write()
for i in range(self.np.n-1, num_leds-1, -1):
time.sleep(0.5)
self.np[i] = (0, 0, 0)
self.np.write()
main.py
Wieder zuerst die Importe. Besonders wichtig ist hier der Import der LED-Ring-Klasse.
import time
import machine
from class_color_wheel import color_wheel
Anschließend wird in der Methode ein LED-Ring erstellt sowie erste Probewerte an die Funktion display_percentage
übergeben; gültige Werte sowie ungültige Werte, einfach um den Code zu prüfen.
An dieser Stelle können andere Werte, die beispielsweise von anderen Geräten via MQTT empfangen wurden verwendet werden.
if name == "__main__":
led_ring = color_wheel(12, 10)
led_ring.display_percentage(30)
time.sleep(2)
led_ring.display_percentage(-10)
time.sleep(2)
led_ring.display_percentage(100)
time.sleep(2)
led_ring.display_percentage(50)
time.sleep(2)
led_ring.display_percentage(120)
Die gesamte main.py:
import time
import machine
from class_color_wheel import color_wheel
if name == "__main__":
led_ring = color_wheel(12, 10)
led_ring.display_percentage(30)
time.sleep(2)
led_ring.display_percentage(-10)
time.sleep(2)
led_ring.display_percentage(100)
time.sleep(2)
led_ring.display_percentage(50)
time.sleep(2)
led_ring.display_percentage(120)
Der vollständige Code
import time
import sys
import random
import os
import machine
import neopixel
class color_wheel:
def __init__(self, pixel_count=12, pin=10):
self.pixel_count = pixel_count
self.pin = pin
self.np = neopixel.NeoPixel(machine.Pin(self.pin), self.pixel_count)
def show_error(self):
for i in range(2):
for j in range(self.np.n):
self.np[j] = (255, 0, 0)
self.np.write()
time.sleep(0.5)
for j in range(self.np.n):
self.np[j] = (0, 0, 0)
self.np.write()
time.sleep(0.5)
def display_percentage(self, percent):
if (percent < 0 or percent > 100):
print("Error: Percentage not correct " + str(percent))
self.show_error()
else:
# 12 leds = 100% -> 1 led = 8.3%
# tolerance: 12 leds = 96% -> 1 led = 8%
#num_leds = round(percent/8.3)
num_leds = round(percent/8)
print(num_leds)
for i in range(num_leds):
time.sleep(0.5)
self.np[i] = (round(255-(I*(100/self.pixel_count))), round(I*(100/self.pixel_count)), 0)
self.np.write()
for i in range(self.np.n-1, num_leds-1, -1):
time.sleep(0.5)
self.np[i] = (0, 0, 0)
self.np.write()
import time
import machine
from class_color_wheel import color_wheel
if name == "__main__":
led_ring = color_wheel(12, 10)
led_ring.display_percentage(30)
time.sleep(2)
led_ring.display_percentage(-10)
time.sleep(2)
led_ring.display_percentage(100)
time.sleep(2)
led_ring.display_percentage(50)
time.sleep(2)
led_ring.display_percentage(120)
![](https://ramona.gloetter.de/wp-content/uploads/2023/03/Bildschirmfoto-2023-03-11-um-16.34.27-75x75.png)
![](https://ramona.gloetter.de/wp-content/uploads/2022/03/effizient-lernen-75x75.jpg)