Raspberry Pi Pico und C/C++ – eine gute Kombination

In der Embedded-Entwicklung spielen noch immer C beziehungsweise C++ eine herausragende Rolle. Der vorliegende Artikel illustriert deshalb, wie sich C/C++-Entwicklung für den Raspberry Pi Pico durchführen lässt.

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

Bislang war in diesem Blog MicroPython das Mittel der Wahl für Projekte mit dem Raspberry Pi Pico. In der Embedded-Entwicklung spielen noch immer C beziehungsweise C++ eine herausragende Rolle. Der vorliegende Artikel illustriert deshalb, wie sich C/C++-Entwicklung für den Pico durchführen lässt.

Nach den Empfehlungen für die Installation der benötigten Werkzeugkette kommt der Beitrag auf ein kleines Beispielprojekt zu sprechen.

Nutzer können unterschiedliche Betriebssysteme einsetzen, wenn sie für den Pico als Zielsystem programmieren wollen:

  • macOS für Intel oder ARM.
  • Linux.
  • Windows 10 nativ.
  • Windows 10 plus Version 1 vom WSL, dem Windows Subsystem for Linux. WSL 2 eignet sich momentan nicht, weil es im Gegensatz zu WSL 1 nur über komplexe Winkelzüge einen Durchgriff auf die seriellen Ports gestattet.

Es gibt somit unterschiedliche Host-Umgebungen. Daher sind die nachfolgenden Beschreibungen bewusst allgemein formuliert. Wo nötig, erfolgen Details zu den jeweiligen Betriebssystemen. Voraussetzung für den weiter unten beschriebenen Installationsprozess ist die Verfügbarkeit von Python 3.x und Git auf dem jeweiligen Betriebssystem.

Auf Linux-Distributionen wie Debian oder Ubuntu sind folgende Schritte nötig:

sudo apt install git-all

gefolgt von:

sudo apt-get install python3 python3-pip

Mac-Besitzer benutzen Homebrew:

brew install git 
brew install python3

Sofern sich bereits Xcode auf dem System befindet, ist der erste Schritt nicht notwendig, zumal Xcode eine Git-Implementierung mitbringt.

Windows-Nutzer holen sich die Git-Installation über die Downloadseite. Für die Installation von Python 3 existiert ebenfalls eine Downloadseite.

Als Erstes ist an dieser Stelle zu erwähnen, dass sich C/C++-Programmierer unbedingt die Dokumentation zum Pico-SDK für C/C++ besorgen sollten, die sich hier befindet. Dort stehen alle wesentlichen Informationen über das SDK für den Pico bereit. Als gute Einführung eignet sich darüber hinaus das Dokument "Getting started with Raspberrry Pi Pico – C/C++ development with Raspberry Pi Pico and other RP2040-based microcontroller boards".

Im ersten Schritt geht es um die Inbetriebnahme der notwendigen Werkzeugkette. Zunächst ist die Installation des Pico-SDKs notwendig. Dieses kopieren Entwickler über Git in ein eigenes Verzeichnis:

git clone -b master —-recurse-submodules https://github.com/raspberrypi/pico-sdk.git

Anschließend bedarf es folgender Anweisung:

cd /path/to/pico-sdk git submodule update --init

Es existiert zusätzlich ein GitHub-Repository mit Beispielen, dessen Herunterladen lohnt:

git clone -b master https://github.com/raspberrypi/pico-examples.git

Damit die Werkzeuge das SDK finden, lässt sich die Umgebungsvariable PICO_SDK_PATH definieren:

PICO_SDK_PATH="/path/to/pico-sdk“

Als nächstes sind CMake und die Entwicklungswerkzeuge von ARM erforderlich, deren Installation sich beispielsweise auf macOS wie folgt gestaltet:

brew install cmake 
brew tap ArmMbed/homebrew-formulae 
brew install arm-none-eabi-gcc

Für Windows 10 gibt es die notwendigen Binärdateien zum Herunterladen auf der entsprechenden CMake- beziehungsweise auf der ARM-Webseite. Zusätzlich installieren Windows-Nutzer die Build-Tools for Visual Studio 2019.

Linux-Nutzer verwenden stattdessen:

sudo apt-get install cmake 
sudo apt install gcc-arm-none-eabi

An dieser Stelle soll eine (optionale) IDE zum Einsatz kommen. Hinsichtlich C/C++ bieten sich CLion, Eclipse oder Visual Studio Code als Alternativen an. Die meisten Seiten im Internet konzentrieren sich auf Visual Studio Code, weshalb im Folgenden ausschließlich davon die Rede sein soll. Im Anschluss an die eigentliche Installation von Visual Studio Code suchen Entwicklerinnen in der IDE nach der Extension CMake Tools und installieren diese zusätzlich.

Um Visual Studio Code (wie bei Windows oder Linux ohnehin der Fall) auf der Kommandozeile über „code“ aufrufen zu können, starten macOS-Nutzer die IDE, wählen mit Cmd + Shift + p oder über das View-Command Palette-Menü die Command-Palette.

Der Suchbegriff "Shell Command" führt zu einem Eintrag "Shell Command: Install 'code' command in PATH", dessen Aktivierung die IDE aus der Kommandozeile aufrufbar macht. Durch Eingabe von code öffnet sich fortan Visual Studio Code auch über die Shell. Als Argument lässt sich optional der Pfad zum gewünschten Projektordner angeben.

Um einen Build von Pico-Projekten durchführen zu können, bedarf es in der IDE verschiedener Erweiterungen, insbesondere Python, CMake und CMake Tools. Nach deren Installation in Visual Studio Code müssen In den Einstellungen für die CMake Tools noch diverse Einträge erfolgen:

Cmake: Build Environment: Hier ist als Schlüssel PICO_SDK_PATH und als Wert der entsprechende Pfad einzugeben.

Cmake: Configure Environment beziehungsweise Cmake: Environment: hier erfolgt derselbe Eintrag.

CMake Tools benötigen die Angabe des Pico-SDK-Pfads

Cmake: Generator: An dieser Stelle bittet CMake um die Angabe des verwendeten Build-Werkzeugs. Auf Unix-Systemen ist dies für gewöhnlich Unix Makefiles, unter Windows NMake Makefiles. Bei Nutzung von ninja als Generator ergeben sich Probleme, weshalb Entwickler lieber auf die vorgenannten Werkzeuge vertrauen sollten.

Unter CMake Generator ist das genutzte make-Tool einzutragen

Nun lässt sich auf einem beliebigen Ordner ein neues Projekt anlegen, und die Datei /path/to/pico-sdk/external/pico_sdk_import.cmake in den jeweiligen Ordner kopieren. Zusätzlich ist im Projektverzeichnis die Datei CMakeLists.txt bereitzustellen, die folgendermaßen aussehen muss:

# Minimal zulässige Version von CMake: 
cmake_minimum_required(VERSION 3.15)
# Inkludieren des Pico-SDK:

include(pico_sdk_import.cmake)
# Name und Version des Projekts:

project(MeinPicoProjekt VERSION 1.0.0)
# Das Projekt mit einer Quelldatei verknüpfen, das das Hauptprogramm enthält:

add_executable(MeinPicoProjekt MeinPicoProjekt.c)
# Angabe der benötigten Bibliothek(en):

target_link_libraries(MeinPicoProjekt hardware_i2c pico_stdlib)
# Initialisieren des SDK:

pico_sdk_init()
# Zugriff auf USB und UART ermöglichen (1 = enable, 0 = disable):

pico_enable_stdio_usb(MeinPicoProjekt 1)
pico_enable_stdio_uart(MeinPicoProjekt 1)
# Definition notwendiger Extra-Zieldateien. Hieraus generiert das Tooling
# die für die Übertragung auf den Pico benötigte UF2-Datei:

pico_add_extra_outputs(MeinPicoProjekt)

Natürlich kann die Implementierung auch mehrere Quelldateien a.c, b.c, z.c enthalten, die alle in add_executable() auftauchen müssen.

Im Projektordner sollten nun die Dateien MeinPicoProjekt.h und MeinPicoProjekt.c (sowie eventuell andere C-/C++-Programmdateien) bereitgestellt werden. In macOS/Linux zum Beispiel über:

touch MeinPicoProjekt.h 
echo '#include "MeinPicoProjekt.h"' > MeinPicoProjekt.c

Noch eine kleine Information am Rande: Falls in target_link_libraries() innerhalb von CMakeLists.txt eine Bibliothek wie hardware_i2c oder pico_stdlib als Argument auftaucht, gibt es dazu Header-Dateien, die Anwender in ihren Quelltextdateien einfügen müssen, etwa folgendermaßen:

#include "hardware/i2c.h" 
#include "pico/stdlib.h"

Sie erkennen jetzt sicher das Namensschema sowie die Korrelation zwischen Bibliotheksnamen und Header-Dateien. Die oben beschriebene Datei CMakeLists.txt enthält bereits die benötigten Bibliotheken hardware_i2c und pico_stdlib.

Es empfiehlt sich zusätzlich die Bereitstellung des Werkzeugs Doxygen, das aus Quelldateien Kommentare entnimmt und daraus eine Dokumentation erzeugt. Näheres dazu findet sich auf der Doxygen-Webseite.

Sobald Visual Studio Code nach dem verwendeten Kit für das Projekt fragt – gemeint sind die C/C++-Compiler-Werkzeuge –, ist GCC for arm-none-eabi ?.?.? anzugeben. Auch in der unteren Statusleiste ist die Eingabe des Kits möglich (Icon mit gekreuzten Werkzeugen). Dort finden sich ebenfalls Icons zur Auswahl der gewünschten Ausgabedateien (Icon CMake mit den Optionen Debug, Release, MinSizeRel, RelWithDebInfo) sowie ein Build-Icon mit Zahnrad, das den Build-Prozess anstößt. Über die Command Palette (Submenü von View) lässt sich der Build-Prozess für das Projekt ebenfalls initiieren (Kommando CMake : Build).

Auf der unteren Statusleiste präsentiert CMake Tools verschiedene Kommandos

Ist alles erfolgreich eingerichtet lassen sich Projekte kompilieren, übertragen und debuggen. Mit dem Kommando CMake : Build in der Command Palette lässt sich der Prozess anstoßen.

Der Build-Prozess läuft ...

Weil diese Schritte viel Mühe machen, müsste man sie bei jedem Projekt erneut durchlaufen, hat die Raspberry Pi Foundation dafür ein spezielles Werkzeug bereitgestellt, den Raspberry Pi Pico Project Generator.

Dieses Werkzeug ist in Python geschrieben, lässt sich mit einer GUI starten oder auch über die Kommandozeile. Jedenfalls ersparen sich Entwickler dadurch die oberen Schritte für das Anlegen eines neuen Projekts. Dieser hilfreiche Generator findet leider viel zu selten Erwähnung.

Project Generator mit Gui. Dazu ist der Parameter —gui an das Werkzeug pico_project.py zu übergeben

Achtung: Der Code des Pico-Python-Generators geht davon aus, dass die ARM-Werkzeuge auf dem Pfad /usr/bin/arm-none-eabi-gcc liegen, was aber nicht stimmen muss. In meinem Fall befinden sich die Werkzeuge unter macOS auf /usr/local/bin/arm-none-eabi.gcc. Es reicht, den richtigen Pfad in der Python-Datei einzutragen oder diesen Vorgang mit einem sed-Kommando zu automatisieren.

Natürlich ist nicht zwingend eine IDE wie Visual Studio Code, CLion oder Eclipse notwendig, um C/C++-Entwicklung für den Pico durchzuführen. Information über das Arbeiten mit diesen IDEs findet sich übrigens im Getting Started Manual ab Seite 31, und zwar in den Kapiteln 8, 9 und 10.

Die Arbeit auf der Kommandozeile ist mitunter sehr hilfreich, die dafür notwendigen Schritte relativ einfach – die Mithilfe des Project Generators vorausgesetzt.

Entwickler können unter der Kommandozeile beziehungsweise Shell in den Unterordner build des Projektordners wechseln, dort

cmake .. 

aufrufen, und anschließend das jeweilige Make-Tool des Hostsystems, etwa make unter Unix oder nmake unter Windows, starten. CMake generiert zu diesem Zweck die passenden Makefiles. Für CMake sollten Entwickler natürlich den Pfad (Umgebungsvariable PATH) mit dem Verzeichnis der CMake-Werkzeuge ergänzen.

Zur Beobachtung des USB-Ports, an dem der Pico hängt, empfehlen sich bei kommandozeilenorientierter Entwicklung Werkzeuge wie screen (macOS), minicom (macOS, Linux), CoolTerm (macOS, Windows, Linux) oder PuTTY (Windows).

Ein Hardware-Debugger für die Pico-Entwicklung lässt sich übrigens mit Hilfe eines zweiten Pico leicht und kostengünstig realisieren. Entsprechende Anleitungen finden sich auf verschiedenen Webseiten, etwa hier oder hier oder hier.

Die Dokumentation für den Raspberry Pi Pico spricht in diesem Zusammenhang von Picoprobe. Dabei werden die SWD-Pins des Entwicklungsboards (im unteren Bild rechts) mit dem zur Probe umfunktionierten zweiten Pico verbunden (im Bild unten links). Die Firmware für die Picoprobe basiert auf OpenOCD (Open On-Chip Debugger), das ein Debuggen verschiedenster Zielsysteme ermöglicht (siehe die OpenOCD-Webseite).

Der Schaltungsaufbau für das Picoprobe

(Bild: Raspberry Pi Foundation)

Die notwendige Software für die Picoprobe lässt sich über GitHub beziehen (Webseite). Es genügt, die Software mit cmake und (n)make zu generieren, um sie dann als UF2-Binärdatei auf den als Hardware-Debugger genutzten "Zweit"-Pico zu übertragen. Nähere Information über Picoprobe finden sich auf dem "Getting Started"-Dokument der Raspberry Pi Foundation im Anhang A.

Um in Visual Studio Code die Picoprobe einzusetzen, bedarf es einzig der Extension Cortex-Debug und einer speziellen Konfigurationsdatei launch.json, die sich im Unterverzeichnis .vscode des Projektordners befinden muss. Nähere Information und den Code für Cortex-Debug sind auf dem GitHub-Repository von Marus einsehbar.

Hardware-Debugging mit Picoprobe unter Visual Studio Code

Zum Schluss präsentiert dieser Beitrag ein kleines C/C++-Projekt für den Pico. Dieses Projekt kratzt nur an der Oberfläche des Pico und dient allein der Illustration. In dem Beispiel prüft ein mit Infrarot arbeitender Bewegungssensor des Typs HC-SR501 die Umgebung auf Bewegungen. Es handelt sich um einen sogenannten PIR-Sensor, wobei PIR für Passive Infrared steht.

Die Grundlagen dieser Sensoren und ein ähliches Programm für den Arduino gab es übrigens schon einmal in diesem Blog. Wen es interessiert, kann sich das damalige Posting gerne noch einmal zu Gemüte führen.

Beim Erkennen einer Bewegung aktiviert das System eine LED. Statt der LED ließe sich natürlich auch eine über ein Relais geschaltete Lampe nutzen.

Bill of Material:
Komponente                                Preis
HC-SR501                                  2 Euro                         
LED,Drähte,220 Ohm-Widerstand             1 Euro                    
RPi Pico                                  5 Euro
Gesamt                                    8 Euro

Die zugehörige Schaltung sieht folgendermaßen aus:

Das Fritzing-Schaltungsdiagramm für das PIR-Beispiel

Der PIR-Sensor wird durch GPIO-Port 10 des Pico eingelesen, die LED durch GPIO-Port 11 angesteuert. Versorgungsspannung und Erde erhält der Sensor durch den 3.3V-Ausgang des Pico sowie einen seiner GND-Pins. Ein 220-Ohm-Widerstand sorgt dafür, dass die LED nicht durchbrennt.

Alle Projektdateien befinden sich auf einem eigenen GitHub-Repository. Sollten Entwickler Picoprobe zum Hardware-unterstützten Debuggen benutzen, finden sie im Repository unter .vscode auch die notwendige und dazu passende Konfigurationsdatei namens launch.json. Das Projektverzeichnis hat der Raspberry Pi Project Generator erzeugt.

Das Programm benutzt einen Interrupthandler pir_irq_handler(), der auf steigende oder fallende Flanken des Eingangspins PIR_PIN reagiert – an diesem ist der Ausgang des PIR-Sensors angeschlossen. Die Registrierung des Handlers passiert in der Methode gpio_set_irq_enabled_with_callback(), der wir unter anderem den jeweiligen Pin und die zu behandelnden Ereignisse übergeben.

Bei einer ansteigenden Signalflanke (0 => 1) hat der Sensor eine Bewegung entdeckt, weshalb das Programm die Lampe (= LED) einschaltet. Deren Ausschaltung erfolgt bei einer fallenden Flanke (1 => 0). Die Methode calibrate() wartet, bis der Sensor etwas "eingeschwungen" ist, und lässt solange die eingebaute LED des Pico im Sekundentakt blinken.

Am PIR-Sensor ist es möglich, über zwei Potentiometer jeweils Empfindlichkeit der Erkennung und die Art beziehungsweise Dauer der Erkennung einstellen. Wer mag, kann hier etwas experimentieren.

Die zwei Potis am PIR-Sensor erlauben eine Feinjustierung
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/irq.h"
#include "hardware/gpio.h"
#include "pico/time.h"

const uint PIR_PIN = 10;
const uint LED_PIN = 11; // LED that signals motion detection
const uint BUSY_PIN = 25; // Built-in LED of Pico
const uint CALIBRATION_TIME = 6; // Calibration time in seconds

//
// IRQ handler called when rising or falling edge is detected
//

void pir_irq_handler(uint gpio, uint32_t event) {
if (event == GPIO_IRQ_EDGE_RISE) // rising edge => detection of movement
gpio_put(LED_PIN, 1); // turn LED on
else // falling edge
gpio_put(LED_PIN, 0);
}


//
// function used to calibrate PIR sensor
//

void calibrate () {
for (uint counter = 0; counter < CALIBRATION_TIME; counter++){
gpio_put(BUSY_PIN, 1);
sleep_ms(500);
gpio_put(BUSY_PIN, 0);
sleep_ms(500);
}
puts("Calibration completed");
}

int main()
{
stdio_init_all();
gpio_init(LED_PIN); // init LED Pin: used to signal motion detection
gpio_set_dir(LED_PIN, GPIO_OUT); // LED Pin is an output pin
gpio_init(BUSY_PIN); // init BUSY Pin: used to blink during calibration
gpio_set_dir(BUSY_PIN, GPIO_OUT); // BUSY Pin is an output pin

// Calibrate PIR for CALIBRATION_TIME seconds
calibrate();

// Enable interrupt handling for PIR Pin:
// Interrupt handling for rising or falling edges

gpio_set_irq_enabled_with_callback(PIR_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &pir_irq_handler);
while(true); // wait forever
return 0;
}

Das System im Einsatz

Das initiale Aufsetzen von C/C++-Werkzeugen und -Projekten für den Raspberry Pi Pico erfordert einen hohen Aufwand. Sind die Hürden erst einmal gemeistert, geht das Entwickeln von Lösungen zügig voran. Es wäre schön, wenn auch "Embedded"-IDEs wie PlatformIO und die Arduino IDE bald eine Pico-Unterstützung anbieten, was nur noch eine Frage der Zeit sein dürfte, zumal Arduino bereits entsprechende Schritte für das hauseigene Board Arduino Nano RP2040 Connect angekündigt hat.

Vorläufig stürzen sich viele Autoren und Firmen im Internet eher auf das Programmieren des Pico mit MicroPython oder CircuitPython, was daran liegen könnte, dass die MicroPython-Bibliotheken viel Komplexität des SDKs verbergen, während dem C/C++-Entwickler der raue Wind der Embedded-Entwicklung ins Gesicht bläst. Allerdings bieten sich Anhängern von C/C++ dadurch auch mehr Flexibilität und Potenziale beim Ausschöpfen der Möglichkeiten des RP2040-Microcontrollers.

Konnte das vorliegende Posting nur mit einem kleinen Projekt aufwarten, erkunden zukünftige Folgen komplexere Beispiele.

Bis dahin viel Spaß bei den eigenen Explorationen. ()