RustEmbeddedRaspberry Pi

Ansteuern von Raspberry Pi GPIO in Rust

Die Bibliothek wiringPi wird schon einige Zeit nicht mehr gepflegt. Sie liegt auch dem RaspberryPi OS längst nicht mehr standardmäßig bei. Die heutzutage empfohlene Variante für erfahrenere Entwickler um die GPIO zu steuern sieht vor mittels SysFS die entsprechenden File Descriptors zu nutzen.

Das ist demnach nicht mehr als Dateien öffnen, lesen, schreiben und wieder schließen.

Ich hatte für Azubinen und Azubis eine kleine Aufgabe erstellt, die vorsah eine entsprechende Library zu programmieren und diese dann in einem kleinen Konsolenprogramm zu verwenden. Dabei sollten sie die Lösung in C entwickeln.

Meine Musterlösung habe ich in Rust übersetzt und diese möchte ich hier teilen.


Die Library

Wir fangen mit der Implementation der Bibliothek an, die wir später in einem Beispielprogramm verwenden möchten. Hierzu erstellen wir erst den Projektordner und danach das Projekt mit dem Rust Packet Manager Cargo.

~$ mkdir rgpio
~$ cd rgpio
~$ cargo new --lib rgpiolib

In der Datei ./src/lib.rs werden wir nun einige Funktionen schreiben, die zum Konfigurieren, dem Lesen und dem Schreiben der einzelnen Ports dienen sollen.

./src/lib.rs
pub mod gpio {       
    use std::fs;
    use std::fs::File;
    use std::io::prelude::*;
    use std::path::Path;

    pub type Directions = self::directions::Directions;

    pub mod directions {        
        pub enum Directions {
            Input,
            Output,
        }        
    
        impl Directions {
            pub fn as_str(&self) -> &'static str {
                match *self {
                    Directions::Input => "in",
                    Directions::Output => "out",
                }
            }
    
            pub fn as_bytes(&self) -> &[u8] {
                self.as_str().as_bytes()
            }
        } 
    }

    fn open_file(filepath: &String) -> Result<File, std::io::Error> {
        let file: File;
        let path = Path::new(&filepath);
        match fs::OpenOptions::new().write(true).open(path) {
            Err(why) => return Err(why),
            Ok(f) => file = f,
        };    
    
        Ok(file)
    }
    
    pub fn export(gpio_num: i32) {
        let mut file: File;    
        let filepath = String::from("/sys/class/gpio/export");        
    
        match open_file(&filepath) {
            Err(why) => panic!("couldn't open {}: {}", filepath, why),
            Ok(f) => file = f,
        }
    
        if let Err(why) = file.write_all(gpio_num.to_string().as_bytes()) {
            panic!("couldn't write to {}: {}", filepath, why);
        }
    }
    
    pub fn unexport(gpio_num: i32) {  
        let filepath = String::from("/sys/class/gpio/unexport");        
        let mut file: File;    
    
        match open_file(&filepath) {
            Err(why) => panic!("couldn't open {}: {}", filepath, why),
            Ok(f) => file = f,
        }
    
        if let Err(why) = file.write_all(gpio_num.to_string().as_bytes()) {
            panic!("couldn't write to {}: {}", filepath, why);
        }
    }
    
    pub fn write(gpio_num: i32, signal: bool) {
        let mut file: File;
        let mut filepath = String::from("/sys/class/gpio/gpio%/value");
        let gpio = gpio_num.to_string();    
        filepath = filepath.replace("%", gpio.as_str());
        
        match open_file(&filepath) {
            Err(why) => panic!("couldn't open {}: {}", filepath, why),
            Ok(f) => file = f,
        }
    
        if let Err(why) = file.write_all(i32::from(signal).to_string().as_bytes()) {
            panic!("couldn't write to {}: {}", filepath, why);
        }
    }
    
    pub fn read(gpio_num: i32) -> bool {
        let mut filepath = String::from("/sys/class/gpio/gpio%/value");
        let gpio = gpio_num.to_string();    
        filepath = filepath.replace("%", gpio.as_str());
    

        match fs::read_to_string(filepath) {
            Err(why) => panic!("couldn't read: {}", why),
            Ok(contents) => contents.parse::<i32>().unwrap() > 0,
        }
    }
    
    pub fn set_direction(gpio_num: i32, direction: Directions) {
        let mut file: File;
        let mut filepath = String::from("/sys/class/gpio/gpio%/direction");
        let gpio = gpio_num.to_string();  
        filepath = filepath.replace("%", gpio.as_str());
    
        match open_file(&filepath) {
            Err(why) => panic!("couldn't open {}: {}", filepath, why),
            Ok(f) => file = f,
        }
    
        if let Err(why) = file.write_all(direction.as_bytes()) {
            panic!("couldn't write to {}: {}", filepath, why);
        }
    }
}

Die Konsolenanwendung

Danach können wir die Library in einer Anwendung einbinden. Als Beispiel soll ein CLI Anwendung dienen die alle GPIO, die mit den Startparametern übergeben wurden, nacheinander einschaltet, 500ms wartet und dann wieder ausschaltet.

Dafür wechseln wir in das Projekt - Hauptverzeichnis und führen wieder Cargo aus.

~$ cd ..
~$ cargo new rgpio

In der Datei ./cargo.toml fügen wir nun die vorab erstellte Bibliothek rgpiolib ein.

./cargo.toml
[package]
name = "rgpio"
version = "0.1.0"
edition = "2021"
authors = [Manfred Michaelis <mm@michm.de>]

[dependencies]
rgpiolib = { path = "../rgpiolib" }

Danach können wir die Library in unserem Source nutzen. Was wir auch direkt in der Datei ./src/main.rs ausprobieren werden.

./src/main.rs
use std::env;
use std::{thread, time};
use rgpiolib::gpio;

fn main() {
    let args: Vec<String> = env::args().collect();

    if args.len() == 1 {
        print!("usage: rgpio ...[num]");
    } else {
        for i in 1..args.len() {
            let gpio: i32;

            match args[i].parse() {
                Err(why) => panic!("an error has occurred: {}", why),
                Ok(num) => gpio = num,
            }

            gpio::export(gpio);
            gpio::set_direction(gpio, gpio::Directions::Output);
            gpio::write(gpio, true);

            thread::sleep(time::Duration::from_millis(500));

            gpio::write(gpio, false);
            gpio::unexport(gpio);
        }   
    }     
}