MicroPython

Die Entwicklung mit Arduino Studio, Visual Studio Code  ist gängige Praxis. Für einfache Aufgaben oder Schulungszwecke kann es aber auch durchaus interessant sein, den Mikroprozessor mit einem Interpreter auszustatten und einfach per Terminal zu steuern. Genau das verbirgt sich hinter MicroPython und funktioniert auch mit dem ESP8266 und ESP32. Ein ähnlicher Ansatz verfolgt LUA (NodeMCU / LUA).

Hinweis: MicroPython funktioniert auch wunderbar mit ESP8266 und ESP32

MicroPython und pyboard

Die erste Umsetzung von Python auf einem Mikroprozessor erfolgte auf den eigens dafür gebauten pyboards, von denen es Mitte 2019 zwei Serien mit je drei Varianten gibt und auf https://store.micropython.org/ vorgestellt werden:

  • pyboard D-Serie
    Das kleinere kompakte Modell mit WLAN und BT
  • pyboard Serie
    Die Vorgängerversion ohne WLAN/BT und ca. 30€

Natürlich gibt es für die Boards auch entsprechende Gehäuse und weitere Sensoren, die aufgesteckt werden können. Es gibt auch günstigere Nachbauten im Internet, die aber immer noch über den bekannten ESP32 und ESP8266:Boards sind.

ESP32/8266 und Micropython

Interessant ist die MicroPython-Firmware aber dennoch, denn es gibt sie auch mittlerweile für den ESP8266 und den ESP32. Diese Plattform ist natürlich schon allein vom Preis her deutlich interessanter, da es die entsprechenden Module für unter 10€ und in vielen Varianten gibt.

Micropython für ESP8266/ESP32 u.a.
https://micropython.org/download/all/

Lobo MicroPython for ESP32
https://github.com/loboris/MicroPython_ESP32_psRAM_LoBo

Anders als bei Arduino, bei der ein lokal geschriebener Code kompiliert und dann auf den ESP übertragen wird, müssen wir die vorgefertigte Firmware  auf das Modul übertragen.

Wenn Sie den ESP8266 per USB anschließen, müssen Sie natürlich erst einmal den COM-Port ermitteln Ich nutze dazu meist folgende PowerShell

[System.IO.Ports.SerialPort]::getportnames()

Ohne PowerShell geht es auch in einer CMD-Shell

change port /query

Den Port brauche ich später immer wieder.

Ich zeige hier den Upload der MicroPython-Firmware mit dem esptool. Wenn Sie eine Entwicklungsumgebung wie Thonny oder PyCraft nutzen, dann können sie auch dort die Firmware einfach installieren und diesen Teil überspringen. Hier am Beispiel von Thonny:

REM Installation es ESPTOOL auf Windows 
pip install esptool 

REM Wechsel in das Verzeichnis
cd c:\Users\fcarius\AppData\Roaming\Python\Python39\site-packages\

RAM Aktuelle Firmware auf den ESP8266 flashen
esptool.py --port com3 --baud 460800 write_flash --flash_size=detect 0 esp8266-20210202-v1.14.bin

Der Bildschirm gibt dann den Flash-Vorgang aus und startetet den ESP8266. Den Erfolg sehen sie auf ihrem Computer, wenn Sie ein WLAN finden, welches dem Namen "MicroPython-xxxxxx" hat und sie sich mit dem Kennwort "micropythoN" verbinden können. "xxxxxx" sind die letzten 3 Bytes der MAC-Adresse des ESP und er hat die IP-Adresse 192.168.4.1. Ihr Client bekommt dann eine IP-Adresse zugewiesen.

Wichtiger ist aber erst einmal der Zugriff über die serielle Schnittstelle, die das Board auch mit Strom versorgt. Ein einfacher Putty reicht, wenn Sie bei der Verbindung auf "Serial" gehen und den richtigen seriellen Port mit Baudrate angeben

Viel mehr als das "schwarze Fenster" zur interaktiven Eingabe von Python-Code sehen sie hier aber nicht:

Danach brauche ich eigentlich nur noch ein Terminal. zum COM-Port denn die Entwicklung erfolgt direkt auf dem ESP über das Termin oder eine kleine GUI wie z.B. Thonny.

Programmieren

Sobald die MicroPython-Firmware auf dem Modul ist, können Sie interaktiv über den seriellen Port oder per HTTP über WebREPL (https://github.com/micropython/webrepl) kommunizieren. Wenn Sie MicroPython installiert haben, dann ist der kleine Prozessor auch ein WLAN-AccessPoint und sie können auch ohne seriellen Port z.B. mit einem Smartphone oder Tablet Kontakt aufnehmen. Auf Dauer werden Sie aber nicht per seriellem Terminal" arbeiten, sondern eine IDE nutzen.

Intro to Programming with MicroPython for ESP8266 Boards [Tutorial]
https://www.youtube.com/watch?v=j0hgKkwmSlw

Wenn ich MicroPython mit Arduino vergleiche, dann ist es faszinierend, wie einfach Dinge umzusetzen sind, zumindest wenn es in MicroPython die passenden Module schon gibt. Das sollten Sie natürlich vorher genau prüfen. Taster, SPI, I2C, Neopixel und Servos per PWM sind an Bord. LCD-Displays etc. sind aber kniffliger.

Sicherheit = AP Off

Die MicroPython-Firmware konfiguriert das WLAN eines ESP8266/ESP32 automatische als AccessPoint. Sie finden daher sehr schnell einen "MicroPython_xxxxxx"-AccessPoint mit dem Standardkennwort "micropythoN". Das ist gut, wenn Sie über die WebREPL-Schnittstelle "drahtlos" programmieren wollen. Wenn Sie aber klassische per serieller Schnittstelle arbeiten und erst recht später im produktivbetrieb wollen Sie diese Hintertür nicht offen stehen lassen. Sonst könnte jede Person in der Nähe sich ebenfalls verbinden und nicht nur den Code auslesen sondern auch verändern oder ersetzen. Ich habe daher zur Sicherheit bei all meinen Projekten am Anfang folgende Zeilen addieren

print('Disabled WLAN AP-Mode')
import network  # WLAN Support
wlan = network.WLAN(network.AP_IF)     # get AP-Node
wlan.active(False)                     # diable AP-WLAN

Damit schalte ich nur den AccessPoint aus.

Device-Test

Zuerst habe ich mir ein ESP8266 geschnappt und die Basisfunktionen ausprobiert. Quasi das "Hello World" für Mikroprozessoren. In meinem Fall war es ein "Witty Cloud Modul", welches im Grunde ein ESP8266 mit untergeschnalltem USB2Serial-Adapter ist und einen Taster an GPIO4 und eine RBG-LED an GPIO 12/13/15 hat. Der Helligkeitssensor ist am analog-Port angeschlossen.

Zusätzlich habe ich an GPIO5 einen kleinen Servo angeschlossen und damit Ports erst einmal definiert und getestet.

# ESP8266 Witty Cloud Modul  https://www.schatenseite.de/2016/04/22/esp8266-witty-cloud-modul/
#from machine import Pin, ADC, PWM
ledg     = machine.Pin(12, machine.Pin.OUT)
ledb     = machine.Pin(13, machine.Pin.OUT)
ledr     = machine.Pin(15, machine.Pin.OUT)   # Setzen mit ledr.on() und ledr.off()
tast     = machine.Pin(4, machine.Pin.IN)     # Auslesen mit tast.value()   0 = gerueckt
ldr      = machine.ADC(0)                     # Auslesen mit ldr.read()
servopin = machine.Pin(5)                          # GPIO5,14,16 
sind auch verfuegbar auf dem witty cloud modul
servo    = machine.PWM(servopin,freq=50)  # Servo mit
servo.duty(100)   # Bei meinem Servo nutzbare werte sind zwischen 30 - 
134

Das hat alles schon mal auf Anhieb geklappt.

Tee-Hase

Vielleicht haben Sie das Projekt in der c't gesehen, bei der ein Servo einen Tee-Beutel aus der Tasse zieht, wenn eine gewisse Zeit abgelaufen ist. Die Lösung wurde klassisch mit einem Arduino mit C++ entwickelt. Eine vergleichbare Lösung habe ich als Beispielprojekt mit MicroPython umsetzen wollen.

Meine erste Überlegung war nun eine klassische Programmierung mit einer Endlosschleife, die den Taster ausliest, die Zeit überwacht und den Servo steuert. Klingt einfach aber wenn ich die Tastendrücke erfassen will, muss ich sehr oft die Messung machen. MicroPython und ESP8266 unterstützen aber Interrupts nach Zeiten als auch GPIO-Pins. Daher habe ich den Code so angelegt, dass ein Interrupt beim Drücken des "Buttons" eine Funktion zum Hochzählen bzw. Reset der Laufzet in Sekunden ausführt.

# Teeodor simple
from machine import Pin, ADC, PWM, Timer
import time

print('Program started')

print('Init LED, Button, ldr')
ledg     = Pin(12, machine.Pin.OUT)
ledb     = Pin(13, machine.Pin.OUT)
ledr     = Pin(15, machine.Pin.OUT)        # Setzen mit ledr.on() und ledr.off()
btn1     = Pin(4, machine.Pin.IN)          # Auslesen mit btn1.value()   0 = gerueckt

print('Global Variables')
teesekunden=0       # speichert Zeit zum Ziehen des Tees
servoup=100         # Position des Servo im Ruhezustand
servodown=40        # Position des Servo beim Ziehen
servodelayms=100    # bremse beim heben/senken des Servo

print('Init Servo')

servo    = PWM(Pin(5),freq=50)  # Servo an GPIO5. GPIO5,14,16 sind auch verfuegbar auf dem witty cloud modul
servo.duty(servoup)             # Initialisierung auf Ruhezustand

def handlebutton(btn1):
    global teesekunden
    print("ButtonDown")
    ledb.on()
    time.sleep_ms(500)
    teesekunden+=60
    print("teesekunden",teesekunden)
    ledb.off()
    if btn1.value() == 0:
        print("LongPress")
        teesekunden = 0
        print("teesekunden reset",teesekunden)
        ledg.off()
        for _ in range (1,5):
            ledb.on()
            time.sleep_ms(200)
            ledb.off()
            time.sleep_ms(200)

print("Aktiviere Interrupt fuer Button")
btn1.irq(trigger=Pin.IRQ_FALLING, handler=handlebutton)

# Hauptprogramm
while True:
    print("Teesekunden:",teesekunden)
    teesekunden-=1
    ledr.on()
    time.sleep_ms(100) 
    ledr.off()
    if (teesekunden <= 0):
        print("tee raus")
        servo.duty(servoup)
        teesekunden=0
        ledg.off()
    else: 
        print("tee rein")
        servo.duty(servodown)
        ledg.on()
    time.sleep_ms (1000)    

print('Program ended')

Die Endlosschleife prüft dann einfach immer die Restlaufzeit und lässt die rote LED als Zeichen der Aktivität blinken. Die grüne LED leuchtet, wenn der Teebeutel in der Tasse ist. Denkbar wäre auch eine Version, bei der die Endlosschleife auch eine Interrupt-Funktion ist, die ein Timer jede Sekunde aufruft oder ein Timer, der genau am Ende der Laufzeit den Sensor rausholt.

Eine Weiterentwicklung wäre dann eine NeoPixel-Anzeige der Restlaufzeit oder eine Summer als Hinweise am Ende.

Interrupt mit MicroPython

Die Nutzung von Interrupt ist eine sehr leistungsfähige aber auch tückische Möglichkeit, bestimmte Herausforderungen beim Codieren pfiffig zu lösen. Aber dabei sollte man schon auf Details achten, z.B. habe ich gelernt:

  • Nur ein Interrupt gleichzeitig ausgeführt
    Wenn ein Interrupt passiert, führt Micropython den aktuellen Mikro-Befehl noch aus, sichert dann Variablen und Stack um dann die Funktion auszuführen. Weitere Interrupts finden dann nicht statt. Sie sollten also die eigentlich Funktion "kurz" halten und nur zur Verarbeitung des Events nutzen.
  • Weitere Interrupts werden gequeued.
    Ich habe testweise den Event zur "Tastaturverarbeitung" mit "sleep" angehalten. Weitere Interrupts wurden "gemerkt" und und im Anschluss ausgeführt. Vorsicht, wenn Sie den Status der Taste im Interrupt abfragen, um z.B. die "Drückdauer" zu ermitteln. Es könnte schon der nächste Druck sein. Sie sehen dann keine Zwischenschritte.
  • Variablen-Scopt
    MicroPython unterscheidet globale und lokale Variablen. Eine per Interrupt aufgerufene Funktion kann auf globale Objekte lesend zugreifen. Wenn Sie aber z.B. den Wert einer globalen Variable ändern wollen, muss diese am Anfang mit dem Codewort "global" freigegeben werden. Ein Zugriff auf GPIO-Ports o.a. geht aber ohne vorheriges "Global"

Ich habe mir angewöhnt, die Funktion meines Codes immer erst in einem eigenen Programm zu testen.

Wenn Sie zu viele Interrupts haben und durch die Bildschirmausgabe nicht mehr an die "Konsole" kommen, dann helfen Soft-Resets mittels "CTRL-D", um das MicroPython-Board ohne Interrupts zu starten.

Beispiel: WLAN, NTP, Neopixel

Mit MicroPython auf dem ESP8266 ist es auch sehr einfach, eine NeoPixel-Reihe anzusteuern, sich per WLAN mit dem Internet zu verbinden und per NTP eine aktuelle Uhrzeit zu ermitteln. Einzig die Zeitzone kann man so nicht ermitteln. Im Keller habe ich noch eine Kreisbahn mit 60 NeoPixel-LEDs gefunden. Damit lässt sich sicher schnell eine Uhr bauen, die z.B. einmal in der Minute per WLAN die Uhrzeit holt und jede Sekunde die Anzeige aktualisiert. Das geht auch komplett ohne Interrupts

Die Beschaltung ist für einen Test mit 3,3V ESP8266 trivial: Einfach nur den Neopixel-Kreis mit Vcc, GND und den GPIO5 verbinden. Alles andere ist auf dem ESP8266 schon da. Beachten Sie aber auch die Hinweise auf LEDs und WS2812 bezüglich Vorwiderständen und Stützkondensatoren.

Wer es noch sparsamer machen möchte, kann einfach einen ESP01 einsetzen. Aber hier kommt es nicht auf Stromsparen an, da die LEDs ja eh den meisten Strom brauchen. Eher schlecht können Sie die vier weißen Markierungen bei 3,6,9 und 12 Uhr erkennen. Die blaue "Schlange" stellt die Sekunde dar, der grüne Punkt die Minuten (ca. 15) und die drei rote leuchten kennzeichnen die angefangene Stunde (7/19 Uhr). Die Uhr zeigt also 19:15:49. Bei jeder vollen Stunde springt die Stunde um 5 LEDs weiter. Mein Mustercode finden sie hier:

micropython_neopixeluhr20210419.py.txt 

Sie müssen im Code natürlich noch die WLAN-Daten (SSID und Kennwort) hinterlegt, damit der ESP8266 sich einbuchen und per NTP die Uhrzeit abholen kann.

Bei der Uhr laufen drei Signale im Kreis und überlappen sich auch. Ich habe zwar drei Farben aber muss immer alle drei Farben auch setzen. Daher habe ich mir meine Funktion gebaut, die nur die angegebenen LEDs setzt und zudem noch sicherstellt, dass die Ausgabe immer im Bereich bleibt.

Das der ESP8266 am analogen Port noch einen Helligkeitssensor hat, habe ich den schnell in die Ausgabe mit eingebaut. Das eigentliche "Hauptprogramme "ist eine Endlosschleife mit "while True:", welche am Ende auf die nächste Sekunde wartet.

Einschätzung

Ich bin positiv überrascht, wie schnell der Einstieg mit MicroPython auf einem ESP8266 gelingt. Die Programmierung ist viel direkter und einfacher also der C++ mit Arduino zu schreiben, zu kompilieren und auf den Flashspeicher zu schreiben. Vermutlich ist die Aufführung etwas langsamer aber die "Interaktion" erlaubt auch Anfängern den schnellen Einstieg in verschiedene Sensoren. Speziell für kleine Aufgaben und Einsteiger ist MicroPython super geeignet. Da kann man auch schon mal drüber wegschauen, dass es kleine Unstimmigkeiten gibt, z.B. 0.1 +0.2 ergibt 0.30000000000000004.

Ich werde zukünftig wohl immer erst mal mein Prototyping mit MicroPython machen und nur dann auf C++ wechseln, wenn es keine passenden Module für MicroPython gibt. Ich freue mich schon, meine weiteren ESP-Projekte mit Python zu starten, z.B.

  • WLAN-Scanner/Signalstärke-Überwachung
    Regelmäßige Scans im 2,4 GHz-Band könnten Aussetzer im LAN oder fremde APs erkennen. Vielleicht kann man sogar die Beacon-Suche von Clients nutzen, um die Clients zu erkennen?
  • Bluetooth
    Auch diese Funktechnik finde ich bestechend, um
  • Mini-Sensoren
    Ich bin ja immer noch am Thema "Parkplatz-Überwachung" involviert. Wenn ich aber sehe, wie einfach mit MicroPython sein könnte,

Es bleibt aber schon noch das ein oder andere zu tun. Viele IP-Funktionen wie z.B. MQTT, PING müssen über weitere Libraries nachgerüstet werden.

Weitere Dokumentationen

YouTube Videos

Weitere Links