Stromausfall-Monitor mit dem Pico

Eine vereiste Tiefkühltruhe, Lücken in den Server-Logs, abgestürzte Rechner: Gab es einen Stromausfall während der Abwesenheit?

vorlesen Druckansicht
Lesezeit: 16 Min.
Von
  • Hans-Martin Hilbig
Inhaltsverzeichnis

Eine vereiste Tiefkühltruhe, Lücken in den Server-Logs, abgestürzte Rechner: Gab es einen Stromausfall während der Abwesenheit? Eine simple Schaltung mit einem Raspberry Pi Pico und ausgefeiltem Python-Code kann einen Stromausfall feststellen und dokumentieren und so vor Ungemach schützen oder als Basis für eigene Entwicklungen ähnlicher Anwendungen dienen.

Vielleicht kennen Sie das: Man kommt nach einer mehrwöchigen Reise wieder nach Hause. Die Rückfahrt war lang, anstrengend und man freut sich auf die Tiefkühl-Pizza im heimischen Gefrierschrank, um schnell etwas im Magen zu haben. Jedoch ist die Pizza im Tiefkühlfach von einer Eisschicht überzogen. Gab es einen Stromausfall, während ich nicht zu Hause war? Wenn ja, wann war er und wie lange dauerte er? Die Antworten auf diese Fragen entscheiden dann darüber, ob man die Pizza besser gleich in den Mülleimer entsorgt oder sie doch noch im Ofen aufbackt. Wie auch die Stiftung Warentest regelmäßig in ihren Kühlschrank-Tests notiert, fehlt ein solcher Netzausfallmonitor in den meisten Geräten. Also greifen wir zur Selbsthilfe.

Dieser Online-Artikel zum Artikel in Make 4/22 auf Seite 62 gibt ergänzende Informationen, die wir Ihnen nicht vorenthalten möchten, aber den Rahmen des gedruckten Heftes gesprengt hätten.

Make 4/22
Cover der Make 4/22 mit Energie erzeugen und sparen, Gratis-CAD fĂĽr Maker und Super8-Kamera mit Raspberry Pi Zero

Mehr zum Thema gibt es in Ausgabe 4/22 der Make.

Viele Python-Programmierer, die nicht mit dem Umgang mit einem Mikrocontroller vertraut sind, würden in einer Endlos-Schleife das Vorhandensein der Netzspannung abfragen und Ausfälle entsprechend protokollieren. Eine elegantere Methode ist jedoch das Nutzen von Interrupt-Funktionen. Sobald ein Stromausfall auftritt, wird ein Interrupt im Mikrocontroller ausgelöst. Unmittelbar danach protokolliert Python-Code diesen Vorfall, bevor das eigentliche Skript wieder an der Stelle fortgesetzt wird, wo es gerade beim Auftreten eines Interrupts gewesen war. Im Make-Heft-Artikel wird bereits ausführlich auf die Funktionsweise eines Interrupts eingegangen, wie man Interrupts in Python programmiert und was hinsichtlich der Interrupt Service Routine (ISR) zu beachten ist.

Eine weitere Kenngröße von Interrupts in Mikrocontrollern ist die sogenannte Interrupt-Latenz-Zeit, also der Zeit, die vom Auftreten eines Interrupts bis zur Abarbeitung des ersten Befehls der ISR verstreicht. Je kürzer diese Zeit ist, desto höher ist die mögliche Interrupt-Frequenz, also die Wiederholrate, mit der aufeinander folgende Interrupts auftreten können. Der Autor hat beim Pi Pico unter MicroPython eine Interrupt-Latenzzeit von 20-30µs gemessen. Für die Anwendung als Stromausfall-Monitor ist diese Performance mehr als ausreichend (siehe auch Systembetrachtung unter „Stresstest“ weiter unten). Unter C++ oder Arduino sollte die Latenzzeit noch deutlich kürzer sein, da dort kompilierter Objektcode abgearbeitet wird.

Sogenanntes bit banging kann jeder Mikrocontroller, aber PIOs gibt es vorläufig nur im RP2040. Daher ist es Ehrensache, serielle Impulsfolgen, wie sie für den LED-Morsecode beim Stromausfallmonitor benötigt werden, mit der PIO zu erzeugen. Im RP2040 gibt es tatsächlich acht solcher State Machines, verteilt auf zwei sogenannte PIO (Programable I/O) Module. Das Modul pio_led_msg.py enthält den dafür notwendigen Code. Die State Machine wird in Assemblercode programmiert. Der PIO Assembler (pioasm) kommt mit nur neun Instruktionen aus, die es aber in sich haben.

Die PIO morst einen Stromausfall – ein Logic Analyzer hilft beim Debuggen.

Die Syntax der PIO-Assembler unterscheiden sich nur geringfügig in den Implementierungen in C++, Arduino und MicroPython. Daher ist zum Einarbeiten in die Magie der PIO sowohl das Datenblatt des RP2040 wie auch das C++ SDK, das Python SDK und auch das Pi-Pico-Datenblatt zu empfehlen. In allen Dokumenten bekommt man wertvolle Hinweise und Beispiel-PIO-Code. Die Portierung von PIO Assemblercode von einer Programmiersprache zu einer anderen ist also im Wesentlichen nur eine Fleißübung.

Allerdings gibt es deutliche Unterschiede bei der Initialisierung der State Machines. In MicroPython erfolgt dies naturgemäß abstrakter als unter C++. Als Schnittstelle zwischen PIO-Code und MicroPython-Code gibt es die Klasse rp2.StateMachine() im Modul rp2. Die put()- und get()-Methoden dieser Klasse exportieren die Werte aus Python-Variablen in den Tx-FiFo-Speicher der State Machine oder importieren Daten aus dem Rx-FiFo der PIO in die Python-Welt. Die State Machine läuft unabhängig von den eigentlichen CPU-Kernen und ist beschäftigt, solange genug Daten und Platz in den FiFo-Speichern sind. Während die PIO über die LED des Pi Pico den Betriebszustand des Stromüberwachungsmonitors und die Anzahl der bisherigen Stromausfälle morst, kann sich die CPU des RP2040 mit der Überwachung und Protokollierung der Netzspannung beschäftigen oder einfach in den lightsleep()-Zustand gehen.

So schön und vielseitig das Programmieren des Pi Pico unter MicroPython auch ist, so gibt es leider auch eine Schattenseite: Der Stromverbrauch. Im Normalbetrieb läuft der RP2040 mit 125MHz Taktfrequenz. Dementsprechend zeigt das mA-Messgerät dann auch satte 31mA Stromverbrauch im Batteriebetrieb an. Bei einer durchschnittlichen Kapazität einer preiswerten Supermarkt-AA-Zink-Kohle-Batterie von ca. 1200mAh würde der Stromausfall-Monitor knapp 39 Stunden bei Stromausfall über seine Batterien laufen, bis die Entladespannung der Zellen unter je 1,2V gesunken ist und die Batteriewarnung anspringt. Das ist nun doch ein bisschen kurz, also muss an der Stromverbrauchsschraube gedreht werden.

Im Kapitel 3.1 der Pi-Pico-Spezifikation gibt es dafür einige Hinweise – siehe DORMANT und SLEEP Mode. Der Beispiel-Code ist in C geschrieben und man sieht, dass einige Register-Operationen im RP2040 nötig sind, zum Beispiel Umschaltung der Quelle für die System-Clock von PLL auf Ring-Oszillator(ROSC) oder Quarz-Oszillator (XOSC). Diese low-level-Befehle werden in MicroPython nicht unterstützt und man muss sich daher mit folgenden MicroPython-Befehlen auseinandersetzen: machine.deepsleep(), machine.lightsleep() und machine.freq(). Bei machine.deepsleep() scheint der RP2040 in den DORMANT Mode zu gehen, jedenfalls wird dabei die interne RTC angehalten und sämtliche Programme im RAM (also der Stromausfallmonitor-Code) gehen verloren.

machine.lightsleep() ist da schon Code-freundlicher. Die RTC läuft weiter, während der RP2040 im lightsleep()-Modus ist und der Pico setzt auch willig sein Python-Programm nach Ablauf der lightsleep()-Zeit fort. Allerdings sinkt der Stromverbrauch im lightsleep()-Modus nicht nennenswert. Bleibt als letzte Rettung nur das Senken der CPU Clock-Frequenz. Und – siehe da: Bei 25MHz Clock-Frequenz zeigt das mA-Meter nur noch etwa 6mA an.

Allerdings hat die Sache zwei Haken: Belässt man die Clock Frequenz permanent auf Werten unter 48MHz, funktioniert die USB-Kommunikation mit dem Pi Pico nicht mehr. Sollte dies in einem main.py-Programm passieren, also einem Programm, das automatisch nach Einschalten der Stromversorgung startet, hilft dann nur noch ein Löschen des kompletten Pi Pico Flash per flash_nuke.uf2, um wieder Zugriff auf den Pico zu bekommen. Abhilfe würde ein Hochdrehen der System-Clock im Python Skript auf 125MHz nach einem Stromausfall bringen, sobald der Pico wieder an der USB Versorgung hängt. Dies würde aber bedeuten, dass die State Machine der PIO angehalten und ihre Frequenz für die Dauer der Stromausfallzeiten ebenfalls neu programmiert werden müsste, da die PIO Taktfrequenz vom System-Clock abgeleitet wird. Also verwenden wir hier als Kompromiss eine permanente System-Frequenz von 50MHz. Die Batterielebensdauer wird durch diese Änderung mehr als verdoppelt und man kann den main.py-Code zuverlässig mit Thonny stoppen, wenn der Pico wieder am PC hängt, um zum Beispiel die Stromausfall-Daten auszulesen.

Damit und mit dem Heft-Artikel wären alle wichtigen Teile der Hard- und Software des Stromausfall-Monitors beschrieben. Allerdings bleibt noch eine Frage offen: Wie sicher funktioniert eigentlich der Stromausfall-Monitor?

Um diese Frage zu beantworten, sollte man sich zunächst die möglichen Betriebsfälle des Stromausfall-Monitors näher anschauen. Der Pi Pico wird im Normalfall von einem USB-Steckernetzteil versorgt. Je nach Ausführung wird dieses Netzteil kurze Stromausfälle bis in den Sekundenbereich klaglos wegbügeln, zumal der Pi Pico ja nur gut 30mA verbraucht und nicht mehrere 100mA wie ein zu ladender Akku.

Wenn jedoch die Pufferkapazitäten des USB-Laders aufgebraucht sind, wird die Batterie die Versorgung des Pi Pico übernehmen und der Stromausfall-Monitor einen Interrupt erzeugen. Ist die Netzversorgung über USB nach einer weiteren Millisekunde wieder da, dann würde auch dies protokolliert werden und man hätte damit einen worst case von zwei Interrupts innerhalb einer sehr kurzen Zeit. Da diese beiden Events an jeder beliebigen Stelle des Python-Programms auftreten können, wird der Beginn eines Stromausfalls nicht sofort in den Flash geschrieben, sondern in einer Variablen im RAM zwischengespeichert.

Ist der Stromausfall beendet, kann der nächste Stromausfall für den Pi Pico allerdings erst knapp eine Sekunde später sichtbar werden, da die Pufferkapazitäten des USB-Laders gerade wieder frisch aufgeladen wurden. Wir nutzen diese interruptfreie Zeit zum sicheren Schreiben beider Stromausfalldaten (Beginn, Ende) ins Flash.

Um die oben beschriebenen Anwendungsfälle zu testen, baut man sich einen künstlichen Stromausfall-Generator, der einen Low-Pegel von zufälliger Dauer im Abstand von etwa 800ms erzeugt. Diese Impulse werden dem laufenden Stromausfall-Monitor zugeführt. Der Test ist erfolgreich, wenn er alle künstlichen Stromausfälle erkennt, protokolliert und sein Programm am Ende dieses Tests auch immer noch einwandfrei läuft und sich nicht aufgehängt hat. Die USB-Stromversorgung muss dafür natürlich nicht ein- und ausgeschaltet werden, sondern ein beliebiger anderer GPIO-Pin wird für diesen Test vorübergehend als Stromausfall-Erkennungs-Eingang benutzt.

Eigentlich wollte der Autor den Stromausfallgenerator-Code auf dem zweiten M0 Kern (Core1) des RP2040 laufen lassen, während der Stromausfall-Monitor-Code auf dem ersten Kern (Core0) läuft. Dafür gibt es in MicroPython ein _thread-Modul. Mit der Methode _thread.start_new_thread() lässt sich relativ einfach eine Python-Funktion starten, die komplett auf dem zweiten Rechnerkern des RP2040 läuft. Doch leider scheinen beide Rechnerkerne im Parallel-Betrieb nach einer Weile bei Benutzung von Interrupts und Schreiben ins Flash ins Stolpern zu geraten. Jedenfalls hing sich das Dual-Core-Programm nach 30-70 Stromausfall-Zyklen auf und konnte nur mit einem Reset wieder zum Leben erweckt werden.

Captain Resetti – nützliches Zubehör zum Code debuggen auf dem Pi Pico

Daher wurde doch auf die gute alte Zwei-Board-Lösung zurückgegriffen: Auf dem einen Pi Pico läuft das Stromausfall-Generator-Skript, auf dem zweiten Pico der eigentliche Stromausfall-Monitor-Code. Ein GPIO-Ausgang vom Generator wird mit einem GPIO-Eingang vom Monitor verbunden, die GND-Pins beider Boards zusammengeschlossen und los geht’s mit einhundert zufälligen Stromausfällen, die so in der Realität hoffentlich nie vorkommen werden. Der Stromausfall-Monitor hat mit diesem Setup jedenfalls alle Stromausfälle erkannt und protokolliert. Somit kann man von einem robusten Code ausgehen.

Stromausfallgenerator (RP2040Tiny, hinten im Bild) schickt Stromausfälle zum Pi Pico. Beide Boards (hier COM5 und COM13) können abwechselnd über Thonny bedient werden.

Der Pi Pico blinkt bereits, sobald ich die Batterien an Vsys anschlieĂźe.

Zunächst sollte Thonny auf dem PC geöffnet werden. Dann wird der Pi Pico über den USB-Port an den PC angeschlossen. Wenn die Stromausfall-Skripts bereits vorher im Pi Pico geladen waren, muss der Pi Pico zeitnah mit der Stopp-Taste in Thonny gestoppt werden, da das main.py-Skript sofort nach Anlegen einer Spannung im Pi Pico gestartet wird. Erst dann sollten als letztes die Batterien an Vsys des Pi Pico angeschlossen werden.

Der Pi Pico blinkt zweimal alle zwei Sekunden.

Dann hat die Ăśbernahme der Systemzeit von Thonny in die RTC vom Pi Pico nicht geklappt. Auch hier gilt: Als erstes Thonny starten, danach den Pi Pico ĂĽber USB an den PC anschlieĂźen, als drittes den Stopp-Button in Thonny drĂĽcken. Erst dann das main.py-Skript neu starten und die Systemzeit wird in den Pi Pico ĂĽbertragen.

Im powerlog.txt file stimmt die Stunde des Stromausfalls nicht.

Speziell beim Umstellen auf Sommerzeit und wieder zurück auf Winterzeit kann es zu falschen Berechnungen der Uhrzeit-Stunde in Thonny kommen. Abhilfe schafft hier folgendes Vorgehen: Im REPL-Fenster from time import localtime eingeben. Dann die localtime()-Funktion im REPL aufrufen. Man bekommt die aktuelle Uhrzeit im Format YYYY,MM,DD,hh,mm,ss usw. angezeigt. Vergleichen Sie die angezeigte Uhrzeit mit der tatsächlichen Zeit und ändern Sie den Korrekturwert in Zeile 18 des brownout.py-Skripts entsprechend. Übrigens: Die RTC des Pi Pico besitzt keine automatische Umschaltfunktion zwischen Sommer- und Winterzeit. Wenn Sie Ihren Stromausfallmonitor über die Sommer/Winterzeit-Grenzen hinaus am Netz betreiben, dann müssen sie mit einer falschen Stunden-Anzeige rechnen oder dem Pi Pico eine neue Systemzeit über Thonny und Neustart des main.py-Programm in die RTC schreiben lassen.

In meinem powerlog.txt file erscheint auch zwischendrin mal eine Kopfzeile.

Das Skript brownout.py erstellt einmal zu Beginn ein File powerlog.txt im Flash des Pi Picos. Sollte diese Datei bereits vorhanden sein, werden weitere Einträge lediglich angehängt, ohne die Datei vorher zu löschen. Wurde main.py also bereits vorher einmal gestartet, werden alle weiteren Starts einfach an die vorhandenen Daten angehängt. Ist dies nicht gewünscht, muss man powerlog.txt über Thonny löschen, bevor man main.py neu startet.

Die LED Impulsfolge ändert sich erst nach einer gewissen Zeit nach Eintreten eines Stromausfalls

Dieses Verhalten ist normal und wird besonders deutlich, wenn sich bereits mehrere Stromausfälle ereignet haben. Der Grund hierfür liegt im Tx-FiFo der PIO State Machine, dessen Inhalt von der State Machine selbständig abgearbeitet wird. Bei z.B. fünf bereits aufgetretenen Stromausfällen ist die State Machine 1 * 500ms + 5 * (50 + 300)ms = 2,25s beschäftigt, bevor sie sich die nächsten aktuellen Morsedaten aus dem FiFo holt.

Der Stromausfall-Monitor blinkt viermal nach dem Starten des main.py Programms, erkennt aber keinen Stromausfall.

ĂśberprĂĽfen Sie die Interrupt-Zuordnungszeile:

self.USBpower = Pin(24, Pin.IN, Pin.PULL_DOWN)

Warum läuft der Strom-Monitor Code nicht auf dem neuen Pico-W?

Leider sind sowohl die onboard LED wie auch die Vsys-Statusabfrage beim Pico-W nicht mehr direkt mit dem RP2040, sondern mit Ein- und Ausgängen des Wireless-Chips verbunden (WL_GPIO0, WL_GPIO2). Man kann zwar auf beide Pins über MicroPython mit Hilfe der Standard-GPIO Befehle mittels der machine.Pin() Funktion zugreifen, aber die direkte Ansteuerung mit der PIO oder direkte Interrupts durch die wireless-GPIOs sind nicht möglich. Damit kommt dann nur bit-banging für die LED-Ansteuerung und Status-Poll für die Vsys-Abfrage in Betracht. Genau das galt es im Strom-Monitor Projekt aber zu vermeiden.

Mögliche Erweiterungen

Brainstorming-Liste der Erweiterungen, Verbesserungen und anderen Einsatzbereiche, schreiben Sie gern an die Redaktion mit weiteren Ideen!

  1. Flash-Speicher-Funktion: Jede Art von Variablen-Speichern (Messdaten, Zähldaten, Ereignisse, periodischer Datenerhalt als Vorsorge-Maßnahme für plötzlichen Versorgungsspannungsausfall des Pi Pico)
  2. Interrupt-Funktionen: Jede Art von unvorhergesehenen digitalen Vorgängen sofort erkennen und entsprechend reagieren (Wild-Kamera, Bewegungsmelder: PIR Sensor löst Interrupt aus, digitaler Wasserstandsfühler löst Interrupt aus). Tastendruck einer Taste oder eines kapazitiven Touch-Sensors.
  3. PIO: Jede Art von Impulsfolgen, die normalerweise mit bit banging erzeugt werden, exotische serielle Protokolle (z.B. TM1637 basierende 7-Segment-Anzeigen (sieht aus wie I2C, ist es aber nicht))
  4. Betriebsstundenzähler: Die Betriebszeit der Heizung oder eines beliebigen anderen Geräts soll erfasst und gespeichert werden. Also Quelle kann sowohl das Vorhandensein einer Spannung oder aber ein Sensor, wie: Licht-Sensor, Magnet-(Hall)-Sensor oder Vibrations-Sensor (Accelerometer) verwendet werden.

Videos by heise

(caw)