Mit Zephyr 4.1 kann man eine eigene Applikation in Rust entwickeln und auf Zephyr-Funktionen zugreifen.
Wie in anderen Blogbeiträgen bereits erwähnt, ist Rust eine vielversprechende Programmiersprache, die sich auch hervorragend für Microcontroller eignet. Hauptvorteil gegenüber C/C++ ist meines Erachtens das Memory Management, welches viele Bugs bereits zur Kompilierzeit verhindert und Security-Vorteile mit sich bringt.
Wegen den Versprechungen von Rust wollen wir nun ein Projekt damit umsetzen. Egal ob privates Bastelprojekt oder Lösung in der Industrie: um einen guten Startpunkt zu haben, wollen wir uns nicht um Treiber usw. kümmern müssen, sondern uns auf die Business-Logik konzentrieren können. Wir suchen also nach einem Ansatz mit «Batteries included». Wir haben dabei grob folgende Optionen:
Variante 1 entspricht dem Ansatz «Rewrite It In Rust (RIIR)», welches unter Rust-Entwicklern sehr verbreitet ist. Ein anderer Noser-Blogbeitrag zeigt, wie sich damit auch relativ schnell ein Blinky-Beispiel auf dem Raspberry Pico W debuggen lässt. Sämtliche Treiber und Logik, die nicht in diesem Projekt vorhanden sind, müssen aber dennoch neu geschrieben werden und das Einbinden von bestehenden Bibliotheken aus einem Unternehmen ist aufwändig. Deshalb hat dieser Ansatz in Industrieprojekten wohl oft schlechte Karten.
Die Grundlagen für Variante 2 ist im offiziellen «Embedded Rust Book» im Kapitel «A little Rust with your C» beschrieben. Dies kann in jedem bestehenden C/C++-Projekt angewendet werden. Zephyr nimmt uns dabei aber einiges an Arbeit ab, weil es seit Zephyr 4.1 bereits Bindings für Rust anbietet.
So können Zephyr-Applikationen mit Rust entwickelt werden. Rust wird dabei als optionales Modul behandelt und eingebunden wie im Rust Language Support beschrieben. Dies wollen wir nachfolgend ausprobieren.
Damit haben wir eine vielversprechende Grundlage: Wir können Treiber und 3rd-Party-Komponenten aus dem umfangreichen, verbreiteten und etablierten Zephyr-Projekt nutzen, können aber modular eigene Applikationslogik in Rust umsetzen und von dessen Vorteilen profitieren. Wir nehmen dabei in Kauf, dass nicht das gesamte Projekt in Rust geschrieben ist.
Das Setup von WSL ist von Microsoft beschrieben, aber reduziert sich im Wesentlichen auf zwei Zeilen
wsl --install wsl --install ubuntu
Im WSL halten wir uns an die Anleitung vom Zephyr Getting-Started-Guide für Ubuntu:
sudo apt update ; sudo apt upgrade wget https://apt.kitware.com/kitware-archive.sh sudo bash kitware-archive.sh sudo apt install --no-install-recommends git cmake ninja-build gperf \ ccache dfu-util device-tree-compiler wget \ python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \ make gcc gcc-multilib g++-multilib libsdl2-dev libmagic1 pip install west west init ~/zephyrproject cd ~/zephyrproject west update west zephyr-export west packages pip --install cd ~/zephyrproject/zephyr west sdk install
Der offizielle Guide nutzt ein Python-venv, um west etc. getrennt zu installieren. In den Befehlen oben habe ich dies nicht berücksichtigt, weil ich für jedes Projekt eine eigene WSL-Umgebung nutze.
Die Installation erfolgt wie auf der Rust-Website beschrieben:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Wir brauchen auch die Rust-Toolchain für den Raspberry Pico
rustup target add thumbv6m-none-eabi
Nun «verbinden» wir Rust mit Zephyr wie im Rust Language Support beschrieben.
cd ~/zephyrproject/zephyr west config manifest.project-filter +zephyr-lang-rust west update sudo apt-get install clang
Und dann können wir endlich das Blinky-Beispiel für unseren Raspberry Pico kompilieren:
cd ~/zephyrproject/zephyr west build -p always -b rpi_pico ../modules/lang/rust/samples/blinky/
Mit -b können wir auch ein anderes Board wählen.
Auf dem Pico-Board den BOOT-Button gedrückt halten, während das Board über USB am Computer angeschlossen wird und dann den Button loslassen. Das Pico-Board erscheint dann als USB-Stick im Explorer.
Das Binary von ~/zephyrproject/zephyr/build/zephyr/zephyr.uf2 darauf kopieren und die LED blinkt.
Der Hauptcode für das Zephyr-Blinky in Rust findet sich unter modules/rust/samples/src/lib.rs:
// Copyright (c) 2024 Linaro LTD
// SPDX-License-Identifier: Apache-2.0
#![no_std]
// Sigh. The check config system requires that the compiler be told what possible config values
// there might be. This is completely impossible with both Kconfig and the DT configs, since the
// whole point is that we likely need to check for configs that aren't otherwise present in the
// build. So, this is just always necessary.
#![allow(unexpected_cfgs)]
use log::warn;
use zephyr::raw::GPIO_OUTPUT_ACTIVE;
use zephyr::time::{sleep, Duration};
#[no_mangle]
extern "C" fn rust_main() {
unsafe {
zephyr::set_logger().unwrap();
}
warn!("Starting blinky");
do_blink();
}
#[cfg(dt = "aliases::led0")]
fn do_blink() {
warn!("Inside of blinky");
let mut led0 = zephyr::devicetree::aliases::led0::get_instance().unwrap();
let mut gpio_token = unsafe { zephyr::device::gpio::GpioToken::get_instance().unwrap() };
if !led0.is_ready() {
warn!("LED is not ready");
loop {}
}
unsafe {
led0.configure(&mut gpio_token, GPIO_OUTPUT_ACTIVE);
}
let duration = Duration::millis_at_least(500);
loop {
unsafe {
led0.toggle_pin(&mut gpio_token);
}
sleep(duration);
}
}
#[cfg(not(dt = "aliases::led0"))]
fn do_blink() {
warn!("No leds configured");
loop {}
}
#![no_std]: Kommt vielen Embedded-Rust-Entwicklern bekannt vor. Damit wird nicht der std-crate gelinkt, wodurch Bootstrapping-Kompilate wie Firmware etc. erzeugt werden können (siehe Embedded Rust Book)#[cfg(dt = "aliases::led0")]: Wir machen Definitionen aus dem Zephyr-Device-Tree in Rust verfügbar. Zeile 51 behandelt den Fall, falls led0 nicht definiert ist.led0.toggle_pin(&mut gpio_token);Wir toggeln die LED im Rust-CodeEs ist auch zu beachten, dass sämtliche Zephyr-Schnittstellen in unsafe-Blöcken enthalten sind, wie dies üblich ist wenn aus Rust hinaus auf C-Funktionen zugegriffen wird.
Einen Einblick in weitere Bindings finden sich im Source-Code.
Das Beispiel zeigt, wie wir von einer Rust-Applikation auf den Device-Tree von Zephyr zugreifen. Es zeigt also auf, dass man eine Applikation oder Teile davon in Rust schreiben kann, ohne auf die Vielzahl von Funktionen von Zephyr zu verzichten. Neben Hardware-Abstraktion bietet Zephyr auch Lösungen für Logging, Software-Updates (Bootloader), Netzwerk-Schnittstellen, Dateisysteme u.v.m. . Es finden sich also für viele Herausforderungen des Projektalltags Vorlagen.
Dass dabei nicht das ganze Projekt in Rust entwickelt ist, mag einigen Rust-Enthusiasten nicht genügen. Aber im Projektalltag macht es die Entwicklung wesentlich schneller, wenn auch auf bestehende C/C++-Lösungen zurückgegriffen wird.
Zephyr wird von der Linux Foundation unterhalten. Auch in Linux kommt vermehrt Rust zum Einsatz. Deshalb erwarte ich, dass die Rust-Unterstützung in den kommenden Releases von Zepyhr weiter ausgebaut wird, z.B. indem mehr Boards unterstützt werden. Vielleicht werden auch einzelne Teile im Kernel mit Rust entwickelt, wie dies im Linux Kernel der Fall ist.
Schreiben Sie einen Kommentar