Embedded-Programmierung gehört zu den Kernthemen von Rust. Den Raspberry Pi mit Rust zu programmieren, geht in diese Richtung. Die Industrie steigt immer häufiger von ihren geliebten Microcontrollern auf Ein-Platinen-Rechner um. Mit Rust auf dem RaspPi etwas zu steuern, ist außergewöhnlich, aber machbar.
Vor Kurzem war ich bei einem Bekannten zu Besuch. Seine Tochter ist im Grundschulalter und fragte mich ganz unvermittelt: “Du bist doch Programmierer, oder?” War das eine Fangfrage? Ich bestätigte durch ein Nicken, und sie flitze davon, kam aber gleich wieder mit einem Raspberry Pi zurück, an dem mit Drähten eine LED und ein Taster befestigt waren. Voller Stolz präsentierte sie mir ihr Python-Programm: Wenn man auf den Taster drückte, ging die Leuchte an. Cool, was die Kinder heute alles in der Schule lernen.
Mir ging in diesem Moment innerlich ebenfalls ein Licht auf: Das müsste sich doch genauso mit Rust realisieren lassen. Eine neue Herausforderung hatte mich in ihren Bann gezogen, und ich sah mich kurz darauf nach der passenden Hardware um. Für meine Experimente gönnte ich mir einen schnellen 64-Bit-Raspberry Pi 4B. Bei den Betriebssystemen gibt es zwar eine breite Auswahl, aber ich entschied mich für das klassische Raspberry Pi OS in der 64-Bit-Variante.
Mithilfe der kostenfreien Software Raspberry Pi Imager [1] beschrieb ich für meinen Rechner eine Speicherkarte mit dem gewünschten Betriebssystem und den entsprechenden Einstellungen, etwa fürs WLAN (Abbildung 1).
Nach dem Einlegen der Speicherkarte in den RaspPi startete sie ohne Probleme, und ich konnte direkt remote darauf zugreifen. Die Installation von Rust mit Compiler und allem, was dazugehört, unterscheidet sich nicht von der anderer Linux-basierter Systeme. Wie auf der Webseite von Rust [2] beschrieben, genügt das Laden eines Shell-Skripts, das den Rest erledigt (Abbildung 2).
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Das Skript lief ohne Fehlermeldungen durch. Am Ende teilte es mir allerdings freundlicherweise mit, dass ich den Befehl »source “$HOME/.cargo/env”« eintippen und das System neu starten sollte, wenn ich den Zugriff auf die Rust-Programme direkt in den Pfad der Shell einfügen möchte. Das ist sinnvoll. Bei den Entwicklungsumgebungen für Rust existieren unzählige unterschiedliche Möglichkeiten – Microsofts Visual Studio Code [3] bringen Sie mit einem einzigen Kommando auf dem Raspberry Pi: »sudo apt install code«. Die IDE verfügt über einige spezielle Erweiterungen für Rust, die Sie beispielsweise mit Syntax-Highlighting unterstützen (Abbildung 3). Die Erweiterung Rust-Analyser leistete mir ebenfalls gute Dienste.

Abbildung 3: Für die Entwicklung von Rust-Programmen auf dem RaspPi empfiehlt sich eine Entwicklungsumgebung wie Visual Studio Code.
Statt direkt auf dem RaspPi können Sie auch mit Visual Studio Code auf einem anderen Rechner die Programme auf dem Einplatinencomputer verändern und per Remote-Terminal ausführen. Dazu benötigen Sie auf Ihrem Rechner nur die Erweiterung Remote-SSH und müssen logischerweise den Zugriff per SSH in den Einstellungen des Raspberry Pi freigeben [4]. Nach der Installation verbinden Sie sich mit dem Verzeichnis des RaspPi und arbeiten dort wie mit Ihrem lokalen Desktop-Rechner.
Hallo Rustberry
Mit dem Rust-Werkzeug »cargo« legen Sie zunächst ein neues Projekt auf dem Raspberry Pi an, beispielsweise »rust-pi: cargo new rust-pi«. Daraufhin wechseln Sie in das neue Verzeichnis »rust-pi« und sehen, ob das automatisch von Rust generierte “Hello World”-Programm läuft. Zum Starten geben Sie schließlich noch das Kommando »cargo run« ein.
Das Kompilieren benötigte zwar etwas Zeit, aber dann erschienen ohne Fehlermeldung die Worte “Hello World” auf dem Bildschirm. Sowohl der Rust-Compiler als auch die Werkzeuge funktionieren ohne Anpassung auf dem Raspberry Pi (Abbildung 4).

Abbildung 4: Mit Visual Studio Code fällt das Arbeiten auf dem Raspberry Pi von einem anderen Rechner aus sehr leicht.
Physical Computing
Was den RaspPi von anderen Rechnern unterscheidet, ist die einfach Art und Weise, wie sich Dinge in der physischen Welt steuern oder Daten von Sensoren einlesen lassen. Deswegen ist dieser als Physical Computing bezeichnete Prozess mit Rust der Schwerpunkt dieses Artikels.
Um mit Rust auf die entsprechenden Schnittstellen des RaspPi zuzugreifen, benötigen Sie die Bibliothek RPPAL (Raspberry Pi Peripheral Access Library) [5]. Sie ermöglicht es Rust, GPIO, I2C, PWM, SPI und UART des Raspberry Pi zu nutzen. Für die Beispiele innerhalb dieses Artikels genügt GPIO vollkommen.
RPPAL setzt Raspberry Pi OS oder ein ähnlich aktuelles Linux-Betriebssystem voraus und harmoniert mit allen RaspPi-Generationen und der Zero-Variante. Damit Rust die Bibliothek kennt, braucht es lediglich einen Eintrag in die Datei »Cargo.toml«. Im Beispiel liegt sie im Verzeichnis »rust-pi«. Unter dem Abschnitt »dependencies« müssen Sie die gewünschte Versionsnummer angeben.
[dependencies] rppal = ?0.14.1?
Auf weitere Installationen auf dem RaspPi können Sie getrost verzichten – den Rest übernimmt die Rust-Umgebung. Damit RPPAL so performant wie möglich arbeiten kann, greift es über die GPIO-Schnittstelle direkt auf »/dev/gpiomem« oder »/dev/mem« zu. Selbstverständlich benötigt der ausführende Benutzer die passenden Zugriffsrechte.
Hello blinkende LED
Was bei einer Programmiersprache die Ausgabe der Worte »Hello World« auf dem Bildschirm ist, ist bei Steuerungen eine blinkende LED. Da ich wenig Begeisterung für Elektrotechnik mit Schaltungsdesign und Löten aufbringe, legte ich mir dafür ein Einsteiger-Kit für Kinder von Cambridge Raspberry Jam (CamJam) [6] zu. Damit konnte ich meine Projekte problemlos umsetzen.
Die im CamJam-Kit enthaltenen Standardbauteile wie LED, Widerstand, Taster und Steckbrett (Breadboard) erhalten Sie im Elektronikhandel. Die für alle Beispielprogramme eingesetzte Schaltung gestaltet sich simpel: Ein Port steuert die LED an, und ein weiterer Port bekommt die Information, ob der Taster gerade gedrückt ist oder nicht (Abbildung 5).
Für die Programmierung der blinkenden LED brauchen Sie in Rust im Wesentlichen den Datentyp »Gpio« mit all seinen Funktionalitäten: »use rppal::gpio::Gpio;«. Indem Sie drei Methoden hintereinander reihen, bekommen Sie Zugriff auf die LED.
let mut led = Gpio::new()?
.get(LED)?
.into_output();
Die Funktion »new« erzeugt eine Variable des Typs »Gpio« (erste Zeile). Dahinter steckt der Zugriff auf die GPIO-Schnittstelle des RaspPi. Hierbei fällt das Fragezeichen nach den Klammern auf, eine Verkürzung einer Abfrage, die an dieser Stelle stehen würde. Der Hintergrund dazu: Die Funktion »new« liefert nicht direkt den Typ »Gpio« zurück, sondern den Datentyp »Result«. Dabei kann es sich um »Err(err)«, also einen Fehler, oder um »OK(gpio)« handeln. Dementsprechend gibt das Fragezeichen entweder im Fehlerfall den Fehler weiter oder liefert den Datentyp »Gpio« zurück.
Mithilfe der zweiten Methode »get« und der entsprechenden Nummer verschaffen Sie sich Zugriff auf den gewünschten Pin des GPIO (Zeile 2). Danach kommt erneut das Fragezeichen zum Einsatz. Die Methode »get« liefert ebenfalls den Datentyp »Result« zurück. Im Fehlerfall, falls der Pin bereits im Einsatz ist, ist dies »Error::PinNotAvailable«, sonst der Zugriff auf den Pin.
Die dritte Methode »into_output« wandelt den Datentyp »Pin« in »OutputPin« um (Zeile 3). Damit stehen Ihnen mehr Methoden für die Ausgabe zur Verfügung. Hinter einer LED verbirgt sich nichts anderes als eine Ausgabe, da Sie von ihr niemals Werte zurückerhalten.
Um die LED zum Leuchten zu bringen, schalten Sie mit der Methode »set_high« den Status des Pins auf »high«: »led.set_high();«. Das Ausschalten funktioniert genauso über »led.set_low();«. Blinken bedeutet schlicht das Ein- und Ausschalten im Wechsel (Listing 1, Zeilen 13 und 15).
An diesem Punkt haben Sie die erste Herausforderung in Sachen Rust und Raspberry Pi bewältigt. Ich möchte noch eine kurze Anmerkung für alle ergänzen, die nicht ständig mit elektronischen Bauteilen hantieren: Bei einer LED ist die Richtung entscheidend, in der Sie sie einbauen. Der lange Draht entspricht der positiven Richtung (Anode), der kurze der negativen Richtung (Kathode).
Listing 1
hello_pi.rs
use std::error::Error;
use std::thread;
use std::time::Duration;
use rppal::gpio::Gpio;
use rppal::system::DeviceInfo;
const LED:u8 = 18;
fn main() ->Result<(),Box<dyn Error>> {
println!("Blinking LED on {}", DeviceInfo::new()?.model());
let mut led = Gpio::new()?
.get(LED)?
.into_output();
for _ in 0..8{
led.set_high();
thread::sleep(Duration::from_millis(500));
led.set_low();
thread::sleep(Duration::from_millis(500));
}
Ok(())
}
Programmaufbau
Der bequemste Weg, das Programm aus Listing 1 mit einem einzigen Befehl »cargo run« zu starten, liegt darin, es in die Datei »/rust-pi/src/main.rs« zu kopieren. Die Definition der Funktion »main« sieht etwas kompliziert aus, da sie ein Ergebnis vom Typ »Result« an das umgebende System zurückreicht (Listing 1, Zeile 7). Im positiven Fall ist dies »Ok(())«, was am Ende der Funktion steht. Im Fehlerfall gibt es den Fehler weiter. Die Begriffe »Box« und »dyn« beziehen sich auf die Speicherverwaltung von Rust. Dazu mehr in einer der nächsten Folgen von Planet Rust.
Die For-Schleife aus Zeile 12 wird acht Mal durchlaufen. Da dieses Programm keine Zählvariable (häufig »i«) benötigt, steht dort der Unterstrich »_« . Dieses Konstrukt nennt sich Anonymous Loop. Damit zwischen Ein- und Ausschalten der LED etwas Zeit vergeht, soll das Programm 0,5 Sekunden (500 Millisekunden) warten (Zeilen 14 und 16).
Input und Output zusammen
Das nächste Beispielprogramm aus Listing 2 besteht in einer einfachen Hausautomation: Sobald Sie auf einen Taster drücken, geht das Licht aus, in diesem Fall die LED.
let mut button = Gpio::new()\ ?.get(BUTTON)?.into_input();
Die Eingaben des Tasters über einen Gpio-Pin zu lesen, funktioniert genauso wie die Ausgabe an die LED. Der einzige Unterschied ist die Methode »into_input« am Schluss, die den Pin als Input-Pin markiert (Listing 2, Zeile 18). Ob der Taster gedrückt ist (»low«) oder nicht (»high«), kann das Programm über die Methode »is_high« abfragen und entsprechend reagieren (Zeile 27).
Signale des Systems hören
Damit das Beispielprogramm nicht unendlich weiterläuft, bis Sie es irgendwann abbrechen, reagiert es auf [Strg]+[C] oder das Schließen des Fensters. Zum Schluss schaltet es die LED immer aus. Um die Signale des Betriebssystems mitzubekommen, benötigt das Programm im ersten Schritt zusätzlich die Rust-Bibliothek »simple-signal« in der Datei »Cargo.toml« unter »Dependencies«. Hier tragen Sie die Zeile »simple-signal = “1.1.1”« ein.
Die grundsätzliche Idee ist, dass es eine boolesche Variable »running« gibt. Weist sie den Wert »true« auf, läuft das Programm in einer While-Schleife. Sobald ein entsprechendes Unterbrechungssignal vom Betriebssystem ankommt, wechselt der Wert der Variable »running« auf »false«; und das Programm beendet sich. Das Knifflige an der Geschichte: Hier laufen zwei Dinge in einem Programm gleichzeitig ab. Der eine Programmstrang (Thread) ist die While-Schleife; und der zweite Programmstrang (Thread) kümmert sich um die Betriebssystem-Signale.
Für die boolesche Variable brauchen Sie den speziellen Typ »AtomicBool« (Listing 2, Zeile 19). Er garantiert, dass alle Operationen ununterbrochen bleiben und damit unteilbar sind – daher rührt das “Atomic”. Dies ist notwendig, sobald mehrere Threads mit derselben Variable arbeiten. Da beide Threads die Variable gleichberechtigt nutzen sollen, reicht im Artikelbeispiel die Standard-Speicherverwaltung von Rust nicht aus. Deswegen setzen Sie hier einen sogenannten Reference Counter (rc) ein. Jeder Thread verfügt über eine Referenz auf die Variable. Die Gesamtzahl der Referenzen führt Rust in einem Zähler (Counter) mit. Sind die Referenzen irgendwann wieder 0, kann Rust den Speicher freigeben. Für mehrere Threads genügt der eine Standard-Reference-Count (rc) nicht, hier benötigt man einen Atomic-RC (Arc).
Den Wert der Variable bekommen Sie über die Methode »load« (Zeile 26). Wenn diese den Wert »true« zurückliefert, erfolgt die Abarbeitung der While-Schleife. Das Argument »Ordering« bezieht sich darauf, ob die CPU oder der Compiler die Verarbeitungsschritte innerhalb der Methode umsortieren dürfen – eine gängige Praxis, um die Abarbeitung zu beschleunigen. Bei mehreren Threads kann sie sich allerdings als problematisch erweisen. In diesem Fall heißt »Ordering::SeqCst« also nichts anderes, als dass auf Umsortierung zu verzichten ist. Genauso funktioniert das Setzen des Werts der Variable über die Methode »store« (Zeile 23).
Der zweite Thread
Der erste Thread ist die While-Schleife. Den zweiten Thread startet das Programm über die Methode »set_handler« (Zeile 20). Das erste Argument sind die Signale, auf die dieser Thread reagieren soll. Das zweite Argument enthält die Befehle, die der Thread ausführt. Mehr über solche Anonymen Funktionen (Closures) und ähnliches lesen Sie in einer weiteren Folge von Planet Rust.
Listing 2
house.rs
use std::error::Error;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use rppal::gpio::Gpio;
use rppal::system::DeviceInfo;
use simple_signal::{self, Signal};
const LED:u8 = 18;
const BUTTON:u8 = 27;
fn main() ->Result<(),Box<dyn Error>> {
println!("Turn LED on {}", DeviceInfo::new()?.model());
let mut led = Gpio::new()?
.get(LED)?
.into_output();
let mut button = Gpio::new()?
.get(BUTTON)?
.into_input();
let running = Arc::new(AtomicBool::new(true));
simple_signal::set_handler(&[Signal::Int, Signal::Term],{
let running = running.clone();
move |_|{
running.store(false,Ordering::SeqCst);
}
});
while running.load(Ordering::SeqCst){
if button.is_high(){
println!("high");
led.set_high();
} else {
println!{"low"};
led.set_low();
}
thread::sleep(Duration::from_millis(500));
}
led.set_low();
Ok(())
}
Fazit und Ausblick
Wie einfach mit Rust Software für den Raspberry Pi zu programmieren ist, hat mich tatsächlich etwas überrascht. Es ist auf jeden Fall eine Alternative zu Python – besonders, wenn Sie schnellere Programme brauchen. Wenn Sie die längeren Kompilierzeiten auf dem Raspberry Pi nerven, können Sie mit Rust auf Ihrem Desktop-Rechner kompilieren und danach das Ergebnis auf den RasPi kopieren. Relativ einfach geht dies mit dem Rust-Werkzeug Cross [7].
Zum Thema “Rustberry Pi: Baby-steps in Embedded Rust” möchte ich Ihnen schließlich noch den Vortrag von Lisa Passing im September 2022 auf der Konferenz “Rust Linz” ans Herz legen, den Sie bei Youtube finden [8].
Auf dem Raspberry Pi wollte ich außerdem die 3D-Fähigkeiten mit Rust austesten, was mir jedoch nicht gelungen ist. Vermutlich fehlte die passende Vulkan-Installation. Sollten Sie damit Erfahrung haben, freue ich über einen Tipp per E-Mail unter vulkan@planet-rust.com. Rust ist der Weg. (csi)
Infos
- Raspberry Pi Imager: https://www.raspberrypi.com/software/
- Rust-Webseite: https://www.rust-lang.org/
- Visual Studio Code: https://code.visualstudio.com/
- Remote-Entwicklung auf dem RaspPi: https://www.raspberrypi.com/news/coding-on-raspberry-pi-remotely-with-visual-studio-code/
- RPPAL (Raspberry Pi Peripheral Access Library): https://github.com/golemparts/rppal
- CamJam: https://camjam.me
- Cross:https://github.com/cross-rs/cross
- “Rustberry Pi: Baby-steps in Embedded Rust”, Lisa Passing, Rust Linz, September 2022 https://www.youtube.com/watch?v=IgC2HvBesms









