Raspberry Pico und Multicore-Programmierung mit PlatformIO (Visual Studio Code)

  • Im Gegensatz zu den sehr häufig verwendeten Arduino Nano/Uno µControllern ist der Raspberry Pico (RP2040) nicht nur schneller und hat mehr Speicher, er hat auch zwei Prozessorkerne. Neben der PIO-Programmierung ermöglicht das auch, Prozesse parallel laufen zu lassen.

    Es wird bei der Multicore-Programmierung auch gerne von Multithreading gesprochen. Allerdings ist das beim RP2040 nicht ganz richtig, weil jeder Prozessorkern nur einen Thread laufen lassen kann.


    Diese (Thread)Programmierung ist nicht ganz unproblematisch. Bei gleichzeitig laufenden Prozessen, muss der Zugriff auf Ressourcen die nur durch einen Prozess gleichzeitig verwendet werden können, geregelt werden.


    Ein Beispielprogramm soll zeigen, wie man beide Kerne nutzen kann und wie man Daten zwischen den beiden Prozessor-Kernen austauscht.



    Aufbau zum Programm:


    rp2040-multicore3.png


    Standardmäßig, wird ein Programm beim RP2040 immer in den Core0 geladen. Mit mit der Funktion "multicore_launch_core1(<funktionsname>)" wird Programmcode in den Core1 geladen und gestartet.


    Der Code für den Core1 ist in einer void Funktion verpackt. Es können keine Argumente übergeben werden. In obigem Beispiel läuft die main() Funktion im Core0 ab. Diese macht nichts anderes, als permanent Text auf die serielle Konsole zu schreiben, eine Counter-Variable hochzählen und wenn die Variable einen bestimmten Wert erreicht hat, eine Indexvariable für einen Array mit GPIO-Nummern von 0 bis 2 rotieren zu lassen.


    Bei jeder Änderung der Indexvariable wird der Inhalt der entsprechenden Arrayposition mit Hilfe der Funktion "multicore_fifo_push_blocking(pins[pinIndex])" an die im Core1 laufende "blinky()" Funktion gesendet. Diese Funktion lässt eine LED blinken. Wird durch das Programm von Core0 eine neue GPIO-Nummer an das Programm in Core1 geschickt, so blinkt immer die LED, die an der gesendeten GPIO-Nummer angeschlossen ist.


    Beide Programmteile laufen unabhängig voneinander parallel. In der blinky() Funktion (Core1) wird durch die Abfrage "multicore_fifo_rvalid()" geprüft, ob Daten zum lesen anliegen. Liefert diese Funktion "true" zurück, so wird mit "ledPin = multicore_fifo_pop_blocking();" der Wert gelesen. Hier erfolgt eine Synchronisation mit dem Programm in Core0.


    Äquivalent dazu wird im Core0 Programm mit "multicore_fifo_wready()" geprüft, ob Platz zum Schreiben in der FIFO-Queue

    ist. Sollte das nicht der Fall sein, wird solange nichts in die FIFO-Queue geschrieben bis vom Core1 Thread ein Wert gelesen wurde.

    Bei diesem Programm spielt das aber keine Rolle, weil der Thread im Core1 nicht so beschäftigt ist, als dass er die Daten nicht schnell genug lesen könnte.


    Der lesende Thread hält solange an, bis der Wert gelesen ist. Umgekehrt hält der schreibende Thread solange an, bis der Wert geschrieben ist. Darum die Zeichenfolge "blocking" in FIFO-Befehlen. Der Zeitraum, in dem Daten von Core0 an Core1 gesendet werden, hat unmittelbaren Einfluss auf das Laufverhalten des Programms in Core1


    Würde im Blinky-Thread nicht abgefragt ob überhaupt Daten anliegen, würde der Thread so lange angehalten bleiben, bis der Thread in Core0 Daten schreibt. Die LEDs würden im Wechsel dauerleuchten und nicht blinken. In die FIFO-Queue zum Datenaustausch zwischen den Cores können maximal acht Einträge zu je 32Bit hineingeschrieben werden.


    Ein weiteres Programm, bei dem die beiden Prozessorkerne gegeneinander Würfeln, zeigt weitere Mechanismen zum Datenaustausch zwischen den Threads und die Regelung des Zugriffs auf die serielle Konsole mit Hilfe von sog. Mutex Variablen. Auch die Nutzung von Semaphoren wäre möglich.

    Hier gibt es keine externe Beschaltung am Pico. Nur Terminalausgaben.


    Hier wird gezeigt, wie auch strukturierte Daten zwischen den Threads ausgetauscht werden können. Der "sleep_ms (100);" Befehl am Anfang der Funktion playerTwo() sorgt dafür, dass Spieler 1 als Erster würfelt. Damit es bei der Textausgabe auf die serielle Konsole keinen Buchstabensalat gibt, wird mit Hilfe einer Mutex-Variable (mx) die Ausgabe synchronisiert.


    Ohne Mutex:

    rp2040-multicore1.png


    Mit Mutex:

    rp2040-multicore2.png


    Weitere Informationen zum Multicore Programmieren: Des Prozessor Kern...