rustberry 0.1.0

All-purpose Raspberry Pi library for Rust
Documentation
// Copyright (C) 2018  Adam Gausmann
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

//! Frontend for the GPIO register map.

use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
use std::thread;
use std::time::Duration;

use error::{Error, ErrorKind};
use peripherals::consts::*;
use system::System;
use util::mem::RegisterMap;

static RESERVED: AtomicBool = ATOMIC_BOOL_INIT; //false

pub use peripherals::consts::GP_PINS as GPIO_PINS;

/// Low-level (but safe) access to the GPIO register segment.
pub struct Gpio {
    registers: RegisterMap,
}

impl Gpio {

    /// Attempts to reserve the GPIO register segment, returning it upon
    /// success.
    ///
    /// # Errors
    ///
    /// This struct is a singleton object to enforce single ownership of the
    /// GPIO pins. Returns `Error::Reserved` if another object exists.
    pub fn new() -> Result<Gpio, Error> {
        if RESERVED.fetch_or(true, Ordering::SeqCst) {
            return Err(Error::new(ErrorKind::Reserved));
        }

        Ok(Gpio {
            registers: unsafe { RegisterMap::map(
                System::detect()?.peripheral_offset() + GP_PAGE_OFFSET,
                PAGE_SIZE,
            )}?,
        })
    }

    /// Checks whether the pin number corresponds to a valid pin, returning
    /// `true` if so.
    pub fn is_valid_pin(pin: usize) -> bool {
        pin < GPIO_PINS
    }

    /// Convenience wrapper around `is_valid_pin` for use with `try!`, mapping
    /// `true` to `Ok` and `false` to `Err` with the kind `OutOfRange`.
    pub fn check_pin(pin: usize) -> Result<(), Error> {
        if Gpio::is_valid_pin(pin) {
            Ok(())
        } else {
            Err(Error::new(ErrorKind::OutOfRange))
        }
    }

    /// Sets the pin function using the `GP_FSEL` register.
    ///
    /// # Panics
    ///
    /// Panics if the given pin number is out of range.
    pub fn set_function(&mut self, pin: usize, function: PinFunction) {
        let mut reg = self.registers.load(GP_FSEL[pin]);
        reg &= !GP_FSEL_MASK[pin];
        reg |= function.value() << GP_FSEL_SHIFT[pin];
        self.registers.store(GP_FSEL[pin], reg);
    }

    /// Sets the pin's pull value using `GP_PUD` and `GP_PUDCLK`.
    ///
    /// The input level corresponds to the pin's physical voltage level,
    /// with `Some(High)` being pulled to source voltage (3.3v) and
    /// `Some(Low)` being pulled to ground (0v). `None` is used if no pull
    /// is desired, leaving the pin floating if disconnected.
    ///
    /// # Panics
    ///
    /// Panics if the given pin number is out of range.
    pub fn set_pull(&mut self, pin: usize, pull: Option<PinLevel>) {
        let pull_value = match pull {
            None                 => 0b00,
            Some(PinLevel::Low)  => 0b01,
            Some(PinLevel::High) => 0b10,
        };

        self.registers.store(GP_PUD, pull_value);
        thread::sleep(Duration::from_micros(5));

        self.registers.store(GP_PUDCLK[pin], GP_PIN[pin]);
        thread::sleep(Duration::from_micros(5));

        self.registers.store(GP_PUD, 0);
        self.registers.store(GP_PUDCLK[pin], GP_PIN[pin]);
    }

    /// Gets the pin's current level by reading the `GP_LEV` register.
    ///
    /// This is only guaranteed to work properly if the pin's function is set
    /// to `In`.
    ///
    /// The resulting level corresponds to the pin's physical voltage level,
    /// with `High` being the source voltage (3.3v) and `Low` being ground
    /// (0v).
    ///
    /// # Panics
    ///
    /// Panics if the given pin number is out of range.
    pub fn get_level(&self, pin: usize) -> PinLevel {
        let mut reg = self.registers.load(GP_LEV[pin]);
        reg &= GP_PIN[pin];

        if reg == 0 {
            PinLevel::Low
        } else {
            PinLevel::High
        }
    }

    /// Sets the pin's level by wrapping the lower-level `set_pin` and
    /// `clear_pin` methods.
    ///
    /// This is only guaranteed to work properly if the pin's function is set
    /// to `Out`.
    ///
    /// The input level corresponds to the pin's resulting physical voltage
    /// level, with `High` being the source voltage (3.3v) and `Low` being
    /// ground (0v).
    ///
    /// # Panics
    ///
    /// Panics if the given pin number is out of range.
    pub fn set_level(&mut self, pin: usize, level: PinLevel){
        match level {
            PinLevel::High => self.set_pin(pin),
            PinLevel::Low  => self.clear_pin(pin),
        };
    }

    /// Sets the pin's level to high (the source voltage, 3.3v) by writing to
    /// the `GP_SET` register.
    ///
    /// This is only guaranteed to work properly if the pin's function is set
    /// to `Out`.
    ///
    /// # Panics
    ///
    /// Panics if the given pin number is out of range.
    pub fn set_pin(&mut self, pin: usize) {
        self.registers.store(GP_SET[pin], GP_PIN[pin]);
    }

    /// Sets the pin's level to low (ground, 0v) by writing to the `GP_CLR`
    /// register.
    ///
    /// This is only guaranteed to work properly if the pin's function is set
    /// to `Out`.
    ///
    /// # Panics
    ///
    /// Panics if the given pin number is out of range.
    pub fn clear_pin(&mut self, pin: usize) {
        self.registers.store(GP_CLR[pin], GP_PIN[pin]);
    }
}

/// Defines the two primary states a pin can be in.
/// 
/// The value of this enum is not directly tied to logic level or physical
/// voltage; its meaning as a parameter or return value should be documented in
/// the function that uses it.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PinLevel {

    /// A logic 0 or physical low (0v).
    Low,

    /// A logic 1 or physical high (3.3v).
    High,
}

impl From<bool> for PinLevel {
    fn from(x: bool) -> PinLevel {
        match x {
            false => PinLevel::Low,
            true => PinLevel::High,
        }
    }
}

impl From<PinLevel> for bool {
    fn from(x: PinLevel) -> bool {
        match x {
            PinLevel::Low => false,
            PinLevel::High => true,
        }
    }
}

/// Enumeration of the functions that pins can have.
///
/// This is a low-level interface to pin functions, and the meaning of each
/// `Alt*` value varies depending on which pin it is applied to. This is
/// managed automatically by the higher-level `Peripherals` interface.
///
/// See [this comprehensive table][table] compiled and published by eLinux.org
/// for details about how each function applies to each pin.
///
/// [table]: https://elinux.org/RPi_BCM2835_GPIOs
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(dead_code)]
#[allow(missing_docs)] // Nothing meaningful to put there.
pub enum PinFunction {
    In,
    Out,
    Alt0,
    Alt1,
    Alt2,
    Alt3,
    Alt4,
    Alt5,
}

impl PinFunction {

    /// Maps the function to its value that is used in the `GP_FSEL*`
    /// registers.
    ///
    /// This value is not shifted to account for a pin's specific location in
    /// the register; that must be done manually with the constants provided by
    /// `GP_FSEL_SHIFT`.
    fn value(self) -> u32 {
        match self {
            PinFunction::In   => GP_FSEL_IN,
            PinFunction::Out  => GP_FSEL_OUT,
            PinFunction::Alt0 => GP_FSEL_ALT0,
            PinFunction::Alt1 => GP_FSEL_ALT1,
            PinFunction::Alt2 => GP_FSEL_ALT2,
            PinFunction::Alt3 => GP_FSEL_ALT3,
            PinFunction::Alt4 => GP_FSEL_ALT4,
            PinFunction::Alt5 => GP_FSEL_ALT5,
        }
    }
}