Sleepy Pico - ein Raspberry Pi Pico geht mit C/C++ schlafen

Wer einen Microcontroller wie den RP2040 des Raspberry Pi Pico nutzt, interessiert sich früher oder später für dessen Energieverbrauch. Zum Beispiel bei einer Anwendung für mobile Messdatenerfassung oder Monitoring. Ohne Beachtung der Energiebilanz ist jede Batterie nach wenigen Stunden völlig erschöpft. Dieser Artikel erläutert deshalb, wie Entwickler höhere Energieeffizienz erreichen können.

In Pocket speichern vorlesen Druckansicht 2 Kommentare lesen
Lesezeit: 21 Min.
Von
  • Dr. Michael Stal
Inhaltsverzeichnis

Wer einen Microcontroller wie den RP2040 des Raspberry Pi Pico nutzt, interessiert sich früher oder später für dessen Energieverbrauch. Zum Beispiel bei einer Anwendung für mobile Messdatenerfassung oder Monitoring. Ohne Beachtung der Energiebilanz ist jede Batterie nach wenigen Stunden völlig erschöpft. Dieser Artikel erläutert deshalb, wie Entwickler höhere Energieeffizienz erreichen können.

Laut Datenblatt gibt es beim Pico zwei mögliche Schlafmodi, den normalen zeitgesteuerten Schlafmodus (sleep mode) und den von externer Hardware abhängigen Schlafmodus (dormant mode).

Letztere Variante erfordert ein Aufwecken durch einen externen Trigger, der an einem der GPIO-Pins angeschlossen ist. Das könnte zum Beispiel ein Taster sein, ein PIR-Sensor, ein externer Taktgeber, eine NE555-Schaltung, oder ein anderer Microcontroller. Der Dormant-Modus erlaubt den geringstmöglichen Energieverbrauch, benötigt dafür aber einen externen „Wecker“. Während des Dormant-Modus sind alle internen Oszillatoren deaktiviert, ganz im Gegensatz zum Sleep-Modus.

Auf der anderen Seite ist der zeitgesteuerte Schlafmodus nicht ganz so energieeffizient, hat aber den Vorteil, dass der RP2040 in der Lage ist, sich selbst über die eingebaute Echtzeituhr RTC (Real-Time Clock) zu wecken. Der Schlafmodus hält zu diesem Zweck einen Oszillator aktiv, der die Echtzeituhr taktet.

Laut Spezifikation verbraucht der RP2040 bei 25°C im Mittel 0,8 mA im Dormant-Mode und 1.3 mA im Sleep-Mode. Das muss man im Verhältnis zum Normalverbrauch sehen. Unter Volllast können es durchaus 95 mA sein.

Gemäß der Formel P = U*I (P = Leistung, U = Spannung, I = Stromstärke) ergeben sich bei 3.3V Versorgungsspannung theoretische Werte von 313.5 mW (unter Volllast) bis zu sehr niedrigen 4.3 mW (im Sleep Mode) beziehungsweise 2.7 mW (im Dormant Mode).
Nähere Informationen dazu finden sich im Datenblatt des Raspberry Pi Pico.
Offensichtlich bieten die beiden Schlafmodi also deutliche Einsparmöglichkeiten. Doch wie lassen sich diese Potenziale programmatisch nutzen?

Mehr Infos

All you need is code

Der frei verfügbare und unter GPLv3-Lizenz stehende Quellcode für das Beispielsprogramm dieses Beitrags ist auf GitHub abgelegt. Sie finden ihn unter diesem Link.

Verwendete Programmiersprachen: C und C++.

Für MicroPython hat die Raspberry Pi Foundation leider noch keine API veröffentlicht, mit der sich der Pico über die integrierte Echtzeituhr in Tiefschlaf versetzen ließe. Anders verhält sich die Situation auf der C/C++-Seite.

Der Pico Playground bietet C-Beispiele zur Veranschaulichung, die den Einsatz dieser Modi illustrieren. Alle Beispiele des Playgrounds basieren auf den Pico-Extras, bei denen es sich größtenteils um noch unfertige oder unvollständig dokumentierte Bibliotheken und Beispielsanwendungen handelt. Daher sollte man neben dem Pico-Examples-Verzeichnis auch eines für Pico-Extras einrichten, um die dortigen Beispiele zu testen.

Wer gerne einen Einblick in die aktuellen Taktfrequenzen des Pico haben möchte, die für den Betrieb der verschiedenen Subsysteme verantwortlich sind, kann hierfür die API-Funktion frequency_count_khz() nutzen. Diese ermittelt die aktuellen Taktfrequenzen, etwa pll_usb für die Steuerung der USB-Komponenten oder den Takt des Ringoszillators, der die Echtzeituhr antreibt:

void measure_freqs(void) {
uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);
uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY);
uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC);
uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS);
uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI);
uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB);
uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC);
uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC);

printf("pll_sys = %dkHz\n", f_pll_sys);
printf("pll_usb = %dkHz\n", f_pll_usb);
printf("rosc = %dkHz\n", f_rosc);
printf("clk_sys = %dkHz\n", f_clk_sys);
printf("clk_peri = %dkHz\n", f_clk_peri);
printf("clk_usb = %dkHz\n", f_clk_usb);
printf("clk_adc = %dkHz\n", f_clk_adc);
printf("clk_rtc = %dkHz\n", f_clk_rtc);

uart_default_tx_wait_blocking(); // wait blocking for UART output
}

Ein erster Schritt zum reduzierten Energieverbrauch besteht darin, die System-Taktfrequenz zu reduzieren, wofür eine weitere SDK-Funktion existiert.

Der Aufruf von

set_sys_clock_khz(60000, true);

verändert die Systemfrequenz von 125 MHz auf 60 MHz. Mit Hilfe dieser Funktion ließe sich der RP2040 grundsätzlich auch übertakten, was dementsprechend zu höherem Energieverbrauch führen würde. Aber das ist eine andere Geschichte.

Alles gut, möchte man meinen. Dummerweise legen die Schlafmodi die meisten Taktgeber schlafen. Das Programm kehrt also nach der Schlafphase nicht in den Zustand zurück, der vorher geherrscht hat. Würden Entwickler in dem Code nach Beenden des Schlafmodus eine Methode wie etwa sleep_ms() aufrufen, die zum Funktionieren einen der internen Taktgeber benötigt, bliebe das Programm an dieser Instruktion hängen. Folglich sollten Entwickler alle (benötigten) Oszillatoren nach dem Schlafmodus neu starten beziehungsweise die zugehörigen Register wieder auf die Werte einstellen, die sie vor der Schlafphase aufwiesen. Die Programmbeispiele auf Pico-Playground lassen das notwendige Sichern der Register übrigens unerwähnt.

Um das leisten zu können, muss ein Programm die Registerwerte vor jeder Schlafphase speichern. Das lässt sich durch folgenden Code bewerkstelligen:

// save current values for clocks
scb_orig = scb_hw->scr;
en0_orig = clocks_hw->sleep_en0;
en1_orig = clocks_hw->sleep_en1;

Für den zeitgesteuerten Schlafmodus ist die RTC (Real Time Clock) erforderlich, die mit Datum und Zeit belegt sein sollte:

// Initialize real-time-clock
rtc_init();
rtc_set_datetime(&now);

Schließlich ist der Sleep-Modus von der Echtzeituhr anhängig, die für die Erweckung aus dem Tiefschlaf zu einem vorgegebenen Zeitpunkt sorgen muss.

Natürlich existieren dazu komplementäre Anweisungen für die Zeit unmittelbar nach dem Aufwachen. Diese sorgen für die Inbetriebnahme der im Schlafmodus deaktivierten Komponenten. Der Aufruf von rosc_write() reaktiviert den Ringoszillator ROSC (Ring Oscillator), der dadurch das Regime auf dem Pico wieder übernehmen kann. Dazu gleich mehr. Die gesicherten Register werden ebenfalls wieder hergestellt:

// Re-enable Ring Oscillator control
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);

// restore clock registers
scb_hw->scr = scb_orig;
clocks_hw->sleep_en0 = en0_orig;
clocks_hw->sleep_en1 = en1_orig;

Die Taktung der Echtzeituhr kann im Tiefschlaf nicht der Ringoszillator leisten, der sich selbst schlafen legt. Stattdessen soll der Kristalloszillator XOSC das Kommando übernehmen. In Software geschieht dies über den Aufruf von

sleep_run_from_xosc()

Es gibt, wie oben erwähnt, zwei grundsätzliche Varianten des Tiefschlafs. Im Falle des Sleep-Modus gibt der Entwickler einen Weckzeitpunkt vor. Das geschieht mittels des Aufrufs von sleep_goto_sleep_until(). Beim Erreichen dieses Zeitpunkts ruft das System eine vom Programmierer zur Verfügung gestellte Callback-Funktion auf. Diese könnte wie folgt aussehen:

static void onWakeUp(void) {
// put wake up actions here
}

Die notwendigen Anweisungen für das Einschlafen des Raspberry Pi Pico gestalten sich also im Detail folgendermaßen.

  • Der Crystal Oscillator XOSC muss bekanntlich wach bleiben, weil er die Echtzeituhr antreibt.
  • Es muss die Festlegung eines Zeitpunkts (RTC_alarm) erfolgen, an dem die Echtzeituhr den Raspberry Pi Pico aufwecken soll.
  • In den Schlaf versetzen lässt sich der Microcontroller über den Aufruf von sleep_goto_sleep_until(…):

datetime_t RTC_alarm = {
.year = 2021,
.month = 05,
.day = 01,
.dotw = 6, // 0 is Sunday, so 5 is Friday
.hour = 00,
.min = 08,
.sec = 00
}

sleep_goto_sleep_until(&RTC_alarm, &onWakeUp);

Nach dem "Wiedererwecken" ruft das System die benutzerdefinierte Rückrufroutine onWakeUp() auf.

Für die zweite Schlaf-Variante, den Dormant Modus, ist der entsprechenden Pico-SDK-Funktion sleep_goto_dormant_until_edge_high() ein GPIO-Pin zu übergeben. Im Code unten ist dies der WAKEUP_PIN. Sobald der Pico an diesem Pin eine aufsteigende Signalflanke bzw. ein High-Signal erkennt, erwacht er aus seinem Tiefschlaf. Wie eingangs erläutert, könnte z.B. ein Taster oder ein PIR-Sensor der Auslöser sein:

// Go to sleep until we see a high edge on WAKEUP_PIN
sleep_goto_dormant_until_edge_high(WAKEUP_PIN);

Alternativ gibt es die Methode sleep_goto_dormant_until_pin(), der man neben dem GPIO-Pin auch die boole'schen Variablen edge und active übergibt. Erstere spezifiziert, ob das Aufwachen nach einer steigenden (edge == true) oder fallenden Signalflanke (edge == false) erfolgen soll. Letztere, ob der Pin als Active HIGH (active == true) oder Active LOW (active == false) arbeitet.

Die im vorhergehenden Abschnitt gezeigten Aktionen für den Sleep mode bleiben bis auf die Aufrufe von rtc_init() und rtc_set_datetime() identisch. Auch die Rückrufmethode onWakeUp kann entfallen.

Es würde nur wenig nützen, wenn der Raspberry Pi Pico im Tiefschlaf weilt, die angeschlossenen Verbraucher wie Sensoren oder Aktuatoren aber viel Strom konsumieren. Eine Lösung könnte darin bestehen, die entsprechenden Komponenten über ein GPIO zu versorgen und dieses während des Tiefschlafs auf LOW zu setzen. Das hat mehrere Haken: Zum einen erlauben die GPIOs des Pico nur sehr beschränkte Stromstärken, die für viele Anwendungsfälle nicht genügen - alle GPIOs zusammen dürfen nicht mehr als 50 mA ziehen. Zum anderen benötigen einige Komponenten etwas Zeit für Start und Initialisierung beziehungsweise Kalibrierung.

Zum Glück integrieren komplexere Sensoren und Aktuatoren oft eigene Funktionalität, um sie auf Bedarf ebenfalls in den Tiefschlaf zu versetzen. Wie das funktioniert, hängt natürlich von den speziellen Eigenschaften dieser Bausteine ab.

Da die beschriebenen Aktionen immer wieder neu geschrieben werden müssen, sobald eine energieeffiziente Schaltung mit dem Raspberry Pi Pico geplant ist, liegt die Idee nahe, ein entsprechendes Mini-Framework mit der notwendigen Funktionalität zu realisieren.

Zu diesem Zweck ist die Implementierung einer C++-Klasse empfehlenswert. Diese implementiert das Singleton-Entwurfsmuster. Während der Einsatz von Singletons in vielen Fällen eher kontraproduktiv erscheint, ist es bei der Klasse Sleep ausnahmsweise sinnvoll, zumal hier wirklich nur eine zentrale Instanz vorliegen soll beziehungsweise darf. Die nachfolgend dargestellte Klasse Sleep enthält Datenelemente zum Speichern und Restaurieren der Oszillator-Register vor beziehungsweise nach dem Schlafen, zudem die für den Dormant-Modus benötigte Information bezüglich des GPIO-Pins, an dem der Pico im Dormant-mode ein Weck-Signal registrieren soll, um seinen Tiefschlaf zu unterbrechen. An diesem GPIO-Eingang (wakeup_pin) lassen sich unterschiedliche Signalquellen anschließen.

Für die durch die RTC gesteuerte Schlafenszeit (Sleep-mode) benötigt die Klasse Information über Anfangs- und Alarmzeitpunkt (_init_time, _alarm_time).

Die globale Singleton-Instanz lässt sich entsprechend über die Methoden XXXconfigure() konfigurieren oder rekonfigurieren:

void Sleep::configureDormant(void (*setupfnc)(), void (*loopfnc)(), 
uint8_t WAKEUP_PIN, bool edge, bool active) {
_mode = Sleep:MODE::DORMANT;
_loop = loopfnc;
_setup = setupfnc;
_wakeup_pin = WAKEUP_PIN; _edge = edge; _active = active;
}

Dabei legt der Aufrufer den Modus durch die aufgerufene Konfigurationsmethode fest (configureDormant(), configureSleep(), configureNormal), unter dem die Anwendung ablaufen soll. Das entspricht den beschriebenen Schlafmodi, wobei Normal den Ablauf ohne Schlafenszeiten realisiert. Das ist zum Beispiel bei der Fehlersuche nützlich oder bei der Verbrauchsmessung der unterschiedlichen Modi. Die Methoden before_sleep(), start_sleep() und after_sleep() kapseln die weiter oben erläuterte Funktionalität zum Vorbereiten des Schlafs, Durchführen des Schlafs und Wiederaufsetzen nach der Schlafphase:

class Sleep {
public:
enum MODE { NORMAL = 1, SLEEP = 2, DORMANT = 4 };
// class implemented using the
// Singleton design pattern.
static Sleep& instance() {
static Sleep _instance;
return _instance;
}

// No copy constructor or assignment operator =
Sleep(Sleep const&) = delete;
void operator=(Sleep const&) = delete;

// used to (re-)configure
void configureDormant(void (*setupfnc)(), void (*loopfnc)(),
uint8_t WAKEUP_PIN, bool edge, bool active);
...

// get current mode
MODE get_mode();

// display current frequencies
void measure_freqs();

// saves clock registers
// and initializes RTC for sleep mode
void before_sleep();

// function responsible for sleep
// sleep ends with high edge (DORMANT) or when alarm time is reached (SLEEP)
void start_sleep();

// sleep recovery
void after_sleep();

// kind of run shell: calls _setup once, and
// implements infinite loop where sleep phases
// are initiated and _loop is being called
// in each iteration.
// Actually, this function implements an
// event loop:
void run();

private:
uint _scb_orig; // clock registers saved before DORMANT or SLEEP mode
uint _en0_orig; // ""
uint _en1_orig; // ""
MODE _mode; // can be SLEEP, DORMANT or NORMAL

uint _wakeup_pin; bool _edge; bool _active;
datetime_t _init_time; // initial time set
datetime_t _alarm_time; // alarm time
void (* _setup)(); // user-defined setup function - called once
void (* _loop) (); // user-defined loop function: called in each // iteration
Sleep(){}; // private constructor
};

Einige haben sich wahrscheinlich über die Funktionszeiger _setup und _loop am Ende der Klassendefinition gewundert, die auf benutzerdefinierte Funktionen verweisen.

Wie bei der Arduino-Entwicklung erwartet die Klasse Sleep einen Zeiger auf eine setup()-Funktion und einen auf eine loop()-Funktion.

Die Methode run() ruft zunächst die konfigurierte setup-Funktion auf, die Code für die Initialisierung von Komponenten, benötigte Deklarationen und weitere Teile enthalten kann, welche zu Start der Anwendung einmalig ablaufen sollen.

Danach startet eine Endlosschleife, die in jeder Schleifeni-Ieration zunächst die Methoden before_sleep(), start_sleep(), after_sleep() aufruft, um den gewünschten Schlafmodus, sleep oder dormant, implementieren.
Nach jeder Schlafphase führt run() die benutzerdefinierte Methode loop() aus, in der sich zum Beispiel Messungen vornehmen, Daten verarbeiten, oder Geräte ansteuern lassen. Hier passiert also die eigentliche Routinearbeit:

void Sleep::run() {
_setup(); // called once
while(true) {
if (_mode != MODE::NORMAL) {
before_sleep();
start_sleep();
after_sleep(); // calls _loop in each iteration
}
else {
_loop(); // NORMAL mode =>
// must explicitly call _loop
}
}
}

Das war alles sehr theoretisch. Den Einsatz in der Praxis soll ein konkretes Anwendungsbeispiel illustrieren.

Eine typische Problemstellung ist das Erfassen von Sensorwerten, etwa bei der Messung von Wetter- oder Umweltdaten. Diese findet normalerweise in längeren zeitlichen Abständen statt. Während dieser untätigen Warteperioden lässt sich der RP2040 in den Schlaf versetzen, um die Batterielaufzeit zu verlängern.
Als allseits beliebtes Beispiel soll also wieder einmal eine Wetterstation fungieren, die folgende Schaltung realisiert:

Prototypischer Aufbau der Wetterstation auf einem Steckbrett

Als Sensor nutzt die Schaltung den bekannten BME280 von Bosch, der sich über SPI (Serial Peripheral Interface) anschließen lässt. Dieser Sensor misst Temperatur, Luftfeuchtigkeit und Luftdruck, woraus sich per barometrischer Formel die ungefähre Höhe berechnen lässt. Das Datenblatt des Sensors findet sich auf der Webseite von Bosch-Sensortec.

Breakout-Board mit dem BME280 von Bosch-Sensortec


Ein BME280 benötigt zwischen 1.8V und 3.6V Eingangsspannung. Er verbraucht im Schlafmodus in der Regel 0.1 uA (maximal 0.3 uA), während einer Messung 340 uA (Feuchtigkeit), 350 uA (Temperatur) und 740 uA (Druck).

Für die Programmierung der Schaltung kommt Code von der Raspberry-Pi-Organisation zum Einsatz. Die C-Routinen hat der Autor in eine C++-Wrapperklasse BME280 eingebettet, Fehlerkorrekturen vorgenommen, und weitere Funktionalität hinzugefügt. So etwa die Berechnung der Höhe und die Implementierung des sogenannten Forced Mode, bei dem der Sensor nur dann aktiv ist, sobald eine Messung erfolgt. Dadurch lässt sich sein Stromhunger auf ein Minimum reduzieren.

Zur Ausgabe der Messwerte dient ein über I2C angeschlossenes monochromes OLED-Display des Typs SSD1306 (siehe Datenblatt) mit einer Auflösung von 128 x 64 Pixeln. Die Eingangsspannung sollte zwischen 1.65 V und 3.3 V betragen. Während des Schlafs verbraucht die Anzeige etwa 10 uA. Im Betrieb können es hingegen durchschnittlich zwischen 50 uA und 430 uA sein.

Das preisgünstige SSD1306-Display bietet 128 x 64 Pixel

Als Treiber nutzt das Beispiel eine Bibliothek von Larry Bank.

Zur Stromreduktion gibt es die Möglichkeit, die Anzeige programmatisch ein- und auszuschalten. Dadurch lassen sich die aktiven Zeiten des Displays und damit dessen Stromverbrauch begrenzen.

Ein mit GPIO-Pin 15 (WAKEUP_PIN) und der Versorgungsspannung verbundener Taster dient im Dormant Mode des Raspberry Pi Pico als externer Trigger. Drückt man ihn, erwacht der Pico, führt eine Messung durch, deren Messwerte am OLED-Display für ein paar Sekunden erscheinen, worauf das Ausschalten der Anzeige erfolgt. Den Beginn der Messung zeigt die eingebaute LED an, die zu diesem Zweck kurz blinkt.

Zur Stromversorgung nutzt der Autor einen Batteriehalter für eine Batterie des Typs 18650. Grundsätzlich wäre natürlich jedes andere Setup möglich, etwa der Einsatz von LiPos oder die Verwendung einer Solarzelle.

Für die Schaltung sind folgende Komponenten notwendig, die insgesamt für unter 20 Euro zu haben sind.Die Preise für Batteriehalter plus Batterie oder ein LiPo sind ebenso wie das USB-Kabel zum Programmieren des Raspberry Pi Pico nicht eingerechnet.

SDSD1306                  ca.  5,00 Euro
BME280 (SPI)              ca.  4,60 Euro 
Raspberry Pi Pico         ca.  4,10 Euro
Breadboard                ca.  3,00 Euro
Dupontkabel, Taster       ca.  1,00 Euro
GESAMT:                   ca. 19,70 Euro

Gleich vorab. Der gesamte Code liegt auf Github (siehe die GitHub-Seite) bereit, um Tipparbeit zu sparen und damit Energieverbrauch zu reduzieren.

Die Funktion setup() enthält den notwendigen Code für die Initialisierung von Sensor und Anzeige. Zudem nutzt die Software die eingebaute LED an GPIO25, um Beginn und Ende einer Sensormessung zu signalisieren. Aufgabe der Funktion welcome() ist einzig die Ausgabe eines Start-Bildschirms:

void setup() {
gpio_init(LED_PIN); // Use built-in LED to signal wake time
gpio_set_dir(LED_PIN, GPIO_OUT);

// ssd1306 OLED is initialized
oled_rc = myOled.init();
myOled.set_back_buffer(ucBuffer);
myOled.fill(0,1);

// Welcome screen
welcome(myOled);

// empty read as a warm-up
myBME280.measure();
sleep_ms(100);
}

In der Funktion loop() findet die Messung und die Ausgabe der
Messergebnisse am SSD1306 statt:

void loop() { 
// get measurement from BME280
gpio_put(LED_PIN, 1);
result = myBME280.measure();
gpio_put(LED_PIN, 0);
draw_on_oled(myOled, result);
}

Aufgabe des Hauptprogrammes (main()) ist im wesentlichen das Konfigurieren der Sleep-Instanz, unter anderem mit Zeigern auf die setup()- & loop()-Funktionen sowie der im Dormant-Modus benötigten Angabe des gewünschten GPIO-Eingangs. Zusätzlich reduziert main() gleich zu Beginn die Systemfrequenz des Pico von 125 MHz auf 60 MHz, um den Energieverbrauch zu minimieren.
Die Wetterstation soll beim Betätigen eines Pushbuttons die Messung starten und deren Ergebnisse anzeigen, weshalb der extern getriggerte Dormant-Modus zum Einsatz kommt. Am Schluss geschieht der Aufruf der run()-Methode, die den gesamten Rest der Verarbeitung steuert.

int main() {
stdio_init_all();
sleep_ms(3000); // required by some OSses to make Pico visible

// Change frequency of Pico to a lower value
printf("Changing system clock to lower frequency: %d KHz\n",
SYSTEM_FREQUENCY_KHZ);
set_sys_clock_khz(SYSTEM_FREQUENCY_KHZ, true);


// configure Sleep instance
// using Dormant mode
// pointers to loop() and setup() functions
// start and end of alarm period
// WAKEUP_PIN where high edges are detected
Sleep::instance().configureDormant(&setup, &loop, WAKEUP_PIN, true, true);
// show clock frequencies
Sleep::instance().measure_freqs();
// start event loop
Sleep::instance().run();
return 0;
}

Mit der Sleep-Klasse lässt sich eine ganze Menge infrastrukturellen Codes einsparen, weshalb Entwickler sich auf die eigentliche fachliche Logik konzentrieren können.

Übrigens findet sich unter folgendem Link "Sleepy Pico - the Movie": Kurzer Video-Clip zum Betrieb der fertigen Schaltung

Leider funktionieren die Schlafmodi nicht immer problemlos:

  • Nach dem Schlaf ist keine Ausgabe per printf() oder puts() am seriellen Monitor mehr möglich. Zum Debuggen sollten Maker daher die Picoprobe als Hardware-Debugger einsetzen.
  • Es kann passieren, dass der Pico im Schlafmodus „einfriert“.

Das mag an Fehlern in der Pico-SDK oder an der unzureichenden Dokumentation dieser Betriebsarten liegen. Als einfache, aber schmutzige Abhilfe ist es vorläufig ratsam, einen zusätzlichen Taster zwischen RUN-Eingang und einem der GND-Ausgänge des Raspberry Pi Pico anzubringen, um den Microcontroller im Falle des Falles reset-ten zu können. Sobald es weitere sachdienliche Hinweise geben sollte, wie diese Problemzonen zu umschiffen sind, erweitert und ändert der Autor den bereitgestellten Code.

Um den tatsächlichen Stromverbrauch einer Schaltung messen, bietet sich die Serienschaltung eines Multimeters oder Amperemeters zwischen Stromversorgung und Pico an. Einige Labornetzteile ermöglichen ebenfalls die Messung der von dem Verbraucher benötigten Leistung. Leider eignen sich nicht alle Messgeräte für die Erfassung von extrem niedrigen Stromstärken und Verbrauchswerten. Für genaue Aussagen lohnt sich daher der Erwerb eines uCurrent-Boards, das sich für Messungen von Strömen in uA-Bereich eignet.

Um einen Raspberry Pi Pico für eigene Anwendungen, speziell solche mit Batteriebetrieb, energieeffizient zu nutzen, gibt es zwei Schlaf-Modi. Während der Dormant-Mode für das Aufwachen auf eine externe Quelle angewiesen ist, kann sich der RP2040 im Sleep-Mode selbst über die Echtzeituhr wiedererwecken. Dabei ist allerdings darauf zu achten, dass die Anwendung nach dem Wecken alle Oszillatoren wieder auf ihre ursprünglichen Frequenzen einstellt. Der alleinige Fokus auf den Pico reicht in realen Anwendungen indes nicht aus. Auch der Energiehunger angeschlossener Komponenten, die Umgebungstemperatur, und die Taktfrequenz spielen eine entscheidende Rolle. Wer das berücksichtigt, kann die Zeit bis zum erforderlichen Neuladen der Batterie auf erträgliche Werte verlängern.

Auf Basis des Beispiels einer Wetterstation lassen sich natürlich viele Erweiterungen hinzufügen, angefangen von einem e-Ink-Display zur Anzeige der Messergebnisse bis hin zu einer LiPo-Batterie oder Solarzelle für die Energieversorgung. Hauptsache, die Energiebilanz stimmt.

Viel Spaß mit Ihren eigenen Experimenten im Schlaflabor. ()