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::{fmt, fs};
    use std::fs::File;
    use std::io::prelude::*;
    use std::path::Path;

    /// GPIO paths
    ///
    /// This enum represents the paths to the GPIO files.
    enum GpioPaths {
        /// Export GPIO pin
        EXPORT,
        /// Unexport GPIO pin
        UNEXPORT,
        /// Value of GPIO pin
        VALUE(i32),
        /// Direction of GPIO pin
        DIRECTION(i32),
    }

    /// Implement the `as_str` method for the `GpioPaths` enum.
    impl GpioPaths {
        /// Returns the path as a string slice.
        pub fn as_str(&self) -> &'static str {
            match *self {
                /// Path to export GPIO pin
                GpioPaths::EXPORT => "/sys/class/gpio/export",
                /// Path to unexport GPIO pin
                GpioPaths::UNEXPORT => "/sys/class/gpio/unexport",
                /// Path to value of GPIO pin
                GpioPaths::VALUE(num) => {
                    let path = format!("/sys/class/gpio/gpio{}/value", num);
                    Box::leak(path.into_boxed_str())
                },
                /// Path to direction of GPIO pin
                GpioPaths::DIRECTION(num) => {
                    let path = format!("/sys/class/gpio/gpio{}/direction", num);
                    Box::leak(path.into_boxed_str())
                },
            }
        }
    }

    /// GPIO directions
    ///
    /// This enum represents the directions of a GPIO pin.
    ///
    /// - Input
    /// - Output
    pub type Directions = directions::Directions;

    /// Implement the `as_str` method for the `Directions` enum.
    pub mod directions {
        /// GPIO directions
        ///
        /// This enum represents the directions of a GPIO pin.
        pub enum Directions {
            Input,
            Output,
        }

        /// Implement the `as_str` method for the `Directions` enum.
        impl Directions {
            /// Returns the direction as a string slice.
            pub fn as_str(&self) -> &'static str {
                match *self {
                    Directions::Input => "in",
                    Directions::Output => "out",
                }
            }

            /// Returns the direction as a byte slice.
            pub fn as_bytes(&self) -> &[u8] {
                self.as_str().as_bytes()
            }
        }
    }

    /// GPIO errors
    ///
    /// This enum represents the errors that can occur when interacting with GPIO pins.
    ///
    /// - IO error
    /// - ParseInt error
    #[derive(Debug)]
    pub enum GpioError {
        Io(std::io::Error),
        ParseInt(std::num::ParseIntError),
    }

    /// Implement the `Display` trait for the `GpioError` enum.
    ///
    /// This trait allows the `GpioError` enum to be formatted using the `format!` macro.
    impl fmt::Display for GpioError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match *self {
                GpioError::Io(ref err) => write!(f, "IO error: {}", err),
                GpioError::ParseInt(ref err) => write!(f, "ParseInt error: {}", err),
            }
        }
    }

    /// Implement the `From<std::io::Error>` trait for the `GpioError` enum.
    impl From<std::io::Error> for GpioError {
        fn from(err: std::io::Error) -> GpioError {
            GpioError::Io(err)
        }
    }

    /// Implement the `From<std::num::ParseIntError>` trait for the `GpioError` enum.
    impl From<std::num::ParseIntError> for GpioError {
        fn from(err: std::num::ParseIntError) -> GpioError {
            GpioError::ParseInt(err)
        }
    }

    /// GPIO result
    ///
    /// This type alias represents the result of a GPIO operation.
    type GpioResult<T> = Result<T, GpioError>;

    /// Open file
    ///
    /// This function opens a file and returns a file handle.
    ///
    /// # Arguments
    ///
    /// - `filepath` - A string slice that represents the path to the file.
    ///
    /// # Returns
    ///
    /// A `GpioResult` that contains a file handle.
    fn open_file(filepath: &'static str) -> GpioResult<File> {
        let path = Path::new(&filepath);
        Ok(fs::OpenOptions::new().write(true).open(path)?)
    }

    /// Export GPIO pin
    ///
    /// This function exports (enables) a GPIO pin. The GPIO pin is
    /// passed as an argument. The function writes the GPIO pin to
    /// the `/sys/class/gpio/export` file.
    ///
    ///
    /// # Arguments
    ///
    /// - `gpio_num` - An integer that represents the GPIO pin number.
    ///
    /// # Returns
    ///
    /// A `GpioResult` that contains the result of the operation.
    ///
    /// # Example
    ///
    /// ```rust
    /// use rgpiolib::gpio;
    ///
    /// fn main() {
    ///     let gpio_num = 4;
    ///     gpio::export(gpio_num);
    /// }
    /// ```
    ///
    /// # Note
    ///
    /// This function should be called before setting the direction of the GPIO pin.
    pub fn export(gpio_num: i32) -> GpioResult<()> {
       open_file(GpioPaths::EXPORT.as_str()).and_then(|mut file| {
            file.write_all(gpio_num.to_string().as_bytes()).map_err(|why| {
                GpioError::Io(why)
            })
        })
    }

    /// Unexport GPIO pin
    ///
    /// This function unexports (disables) a GPIO pin. The GPIO pin is
    /// passed as an argument. The function writes the GPIO pin to the
    /// `/sys/class/gpio/unexport` file.
    ///
    /// # Arguments
    ///
    /// - `gpio_num` - An integer that represents the GPIO pin number.
    ///
    /// # Returns
    ///
    /// A `GpioResult` that contains the result of the operation.
    ///
    /// # Example
    ///
    /// ```rust
    /// use rgpiolib::gpio;
    ///
    /// fn main() {
    ///     let gpio_num = 4;
    ///     gpio::export(gpio_num);
    ///     gpio::unexport(gpio_num);
    /// }
    /// ```
    ///
    /// # Note
    ///
    /// This function should be called after exporting the GPIO pin.
    pub fn unexport(gpio_num: i32) -> GpioResult<()> {
        open_file(GpioPaths::UNEXPORT.as_str()).and_then(|mut file| {
            file.write_all(gpio_num.to_string().as_bytes()).map_err(|why| {
                GpioError::Io(why)
            })
        })
    }

    /// Write to GPIO pin
    ///
    /// This function writes a signal to a GPIO pin. The GPIO pin and signal are
    /// passed as arguments. The function writes the signal to the
    /// `/sys/class/gpio/gpio{num}/value` file.
    ///
    /// # Arguments
    ///
    /// - `gpio_num` - An integer that represents the GPIO pin number.
    /// - `signal` - A boolean that represents the signal to write to the GPIO pin.
    ///
    /// # Returns
    ///
    /// A `GpioResult` that contains the result of the operation.
    pub fn write(gpio_num: i32, signal: bool) -> GpioResult<()> {
        Ok(
            open_file(GpioPaths::VALUE(gpio_num).as_str()).and_then(|mut file| {
                file.write_all(signal.to_string().as_bytes()).map_err(GpioError::from)
            })?
        )
    }

    /// Read from GPIO pin
    ///
    /// This function reads a signal from a GPIO pin. The GPIO pin is passed as an argument.
    /// The function reads the signal from the `/sys/class/gpio/gpio{num}/value` file.
    ///
    /// # Arguments
    ///
    /// - `gpio_num` - An integer that represents the GPIO pin number.
    ///
    /// # Returns
    ///
    /// A `GpioResult` that contains the signal read from the GPIO pin.
    pub fn read(gpio_num: i32) -> GpioResult<bool> {
        let value = fs::read_to_string(GpioPaths::VALUE(gpio_num).as_str()).and_then(|contents| {
           match contents.parse::<i32>() {
                Ok(val) => Ok(val),
                Err(why) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, why)),
           }
        }).map_err(GpioError::from);

        value.map(|val| val > 0)
    }

    /// Set GPIO pin direction
    ///
    /// This function sets the direction of a GPIO pin. The GPIO pin and direction are passed as arguments.
    /// The function writes the direction to the `/sys/class/gpio/gpio{num}/direction` file.
    ///
    /// # Arguments
    ///
    /// - `gpio_num` - An integer that represents the GPIO pin number.
    /// - `direction` - A `Directions` enum that represents the direction of the GPIO pin.
    ///
    /// # Returns
    ///
    /// A `GpioResult` that contains the result of the operation.
    ///
    /// # Example
    ///
    /// ```rust
    /// use rgpiolib::gpio;
    ///
    /// fn main() {
    ///     let gpio_num = 4;
    ///     gpio::export(gpio_num);
    ///     gpio::set_direction(gpio_num, gpio::Directions::Output);
    /// }
    /// ```
    ///
    /// # Note
    ///
    /// This function should be called after exporting the GPIO pin.
    pub fn set_direction(gpio_num: i32, direction: Directions) -> GpioResult<()> {
        open_file(GpioPaths::DIRECTION(gpio_num).as_str()).and_then(|mut file| {
            file.write_all(direction.as_bytes()).map_err(GpioError::from)
        })
    }
}

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

mod tests;

use std::env;
use std::{thread, time};
use std::error::Error;
use rgpiolib::gpio;

fn main() -> Result<(), dyn Error> {
    // Collect command-line arguments into a vector of strings
    let args: Vec<String> = env::args().collect();

    // Check if no arguments are provided
    if args.len() == 1 {
        print!("usage: rgpio ...[num]");
    } else {
        // Iterate over the provided arguments
        for i in 1..args.len() {
            // Parse the argument to an integer
            let gpio_num = match args[i].parse() {
                Err(why) => panic!("an error has occurred: {}", why),
                Ok(num) => num,
            };

            // Export the GPIO pin and set its direction to output
            if (gpio::export(gpio_num).is_ok()) {
                gpio::set_direction(gpio_num, gpio::Directions::Output)?;
                gpio::write(gpio_num, true)?;

                // Read the GPIO pin value and print it
                let mut val = gpio::read(gpio_num)?;
                println!("{}", val);

                // Sleep for 500 milliseconds
                thread::sleep(time::Duration::from_millis(500));

                // Write false to the GPIO pin and sleep again
                gpio::write(gpio_num, false)?;
                thread::sleep(time::Duration::from_millis(500));

                // Read the GPIO pin value again and print it
                val = gpio::read(gpio_num)?;
                println!("{}", val);

                // Unexport the GPIO pin
                gpio::unexport(gpio_num)?;
            } else {
                println!("failed to export gpio {}", gpio_num);
            }
        }
    }

    Ok(())
}

Repository

GitHub - jibbex/rgpio
Contribute to jibbex/rgpio development by creating an account on GitHub.
image