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/>.

//! Implementation of the BCM283x Peripherals (GPIO, PWM, Serial, Timers) using
//! their memory-mapped register segments.
//!
//! The normal use-case is to use the high-level interface provided by
//! `Peripherals`; however, the register accessors are also made available if
//! you want to write your own interface.
//!
//! This module is made entirely safe through the use of `Reservation`s at the
//! high level and atomics to enforce that the low-level register objects are
//! singletons. It is not possible (without some unsafe hacking) to make
//! multiple instances of anything that could possibly share access to a single
//! resource.
//!
//! Be aware that the high-level interface _does_ use some `RefCell`s so that
//! individual pin objects can share the same register maps. This means that
//! the low-level objects may still remain even after the main `Peripherals`
//! object is dropped, preventing the creation of a new instance. _All_ objects
//! created by a previous instance of `Peripherals` need to be dropped before
//! it can be reinstantiated.

mod consts;

pub mod clock;
pub mod gpio;
pub mod pwm;

use std::cell::RefCell;
use std::rc::Rc;
use std::thread;
use std::time::Duration;

use error::{Error, ErrorKind};
use util::reservation::{Reservations, Reservation};
use self::clock::{ClockManager, Clock, ClockSource};
use self::gpio::{Gpio, PinFunction, PinLevel, GPIO_PINS};
use self::pwm::{Pwm, PWM_CHANNELS};

const PWM_PINS: [Option<(usize, PinFunction)>; GPIO_PINS] = [
    None, None, None, None, None, None, None, None,
    None, None, None, None,

    // 12, 13
    Some((0, PinFunction::Alt0)), Some((1, PinFunction::Alt0)),

    None, None, None, None,

    // 18, 19
    Some((0, PinFunction::Alt5)), Some((1, PinFunction::Alt5)),

    None, None, None, None, None, None, None, None,
    None, None, None, None, None, None, None, None,
    None, None, None, None,

    // 40, 41
    Some((0, PinFunction::Alt0)), Some((1, PinFunction::Alt0)),

    None, None,

    // 44, 45
    None, Some((1, PinFunction::Alt0)),

    None, None, None, None, None, None, None, None,
];

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
enum Peripheral {
    Clock(Clock),
    GpioPin(usize),
    PwmChannel(usize),
}

/// The supported modes of PWM operation.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PwmMode {

    /// An algorithm which evenly distributes bits, attempting to stay
    /// close to the target ratio at all times. This has a higher update
    /// frequency compared to traditional mark-space algorithms at the
    /// sacrifice of an actual pulse width. This is the default mode.
    Balanced,

    /// The traditional algorithm where the line is held high and low for
    /// fixed periods of time to maintain the target ratio.
    MarkSpace,
}

/// The supported PWM configuration options.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct PwmConfig {
    
    /// The algorithm to use for standard operation. Default: `Balanced`.
    pub mode: PwmMode,

    /// The state that the line should be in when PWM is disabled.
    /// Default: `Low`.
    pub silence_bit: PinLevel,

    /// Whether to invert highs and lows on the output line.
    /// Default: `false`.
    pub invert_polarity: bool,
}

impl Default for PwmConfig {
    fn default() -> PwmConfig {
        PwmConfig {
            mode: PwmMode::Balanced,
            silence_bit: PinLevel::Low,
            invert_polarity: false,
        }
    }
}

/// High-level wrapper around the peripheral registers with utilities for
/// enforcing individual ownership of each component.
pub struct Peripherals {
    _pwm_clock: Reservation<Peripheral>,
    reservations: Reservations<Peripheral>,
    cm: Rc<RefCell<ClockManager>>,
    gpio: Rc<RefCell<Gpio>>,
    pwm: Rc<RefCell<Pwm>>,
}

impl Peripherals {

    /// Attempts to reserve the register segments that it requires,
    /// returning a `Peripherals` object upon success.
    ///
    /// # Errors
    ///
    /// Returns `Error` with the kind `Reserved` if one of the segments is
    /// currently reserved and owned by another object.
    pub fn new() -> Result<Peripherals, Error> {
        let reservations = Reservations::new();
        let pwm_clock = reservations.try_reserve(
            Peripheral::Clock(Clock::Pwm)
        ).unwrap();

        let mut peripherals = Peripherals {
            _pwm_clock: pwm_clock,
            reservations,
            cm: Rc::new(RefCell::new(ClockManager::new()?)),
            gpio: Rc::new(RefCell::new(Gpio::new()?)),
            pwm: Rc::new(RefCell::new(Pwm::new()?)),
        };
        peripherals.set_pwm_clock(32, 0).unwrap();

        Ok(peripherals)
    }

    /// Attempts to reserve a GPIO pin as a digital input.
    ///
    /// Upon acquisition, the pin's pull state will automatically be set to
    /// `None` as the default.
    ///
    /// # Errors
    ///
    /// Returns `Error` with the kind `Reserved` if the pin is already
    /// reserved, or `OutOfRange` if the pin number is invalid.
    pub fn get_digital_input(&self, pin: usize)
        -> Result<DigitalInput, Error>
    {
        let reservation = self.try_reserve_gpio(pin)?;

        let mut gpio = self.gpio.borrow_mut();
        gpio.set_pull(pin, None);
        gpio.set_function(pin, PinFunction::In);

        Ok(DigitalInput {
            _reservation: reservation,
            pin,
            gpio: Rc::clone(&self.gpio),
        })
    }

    /// Attempts to reserve a GPIO pin as a digital output.
    ///
    /// Upon acquisition, the pin's value will automatically be set to `Low`
    /// as the default.
    ///
    /// # Errors
    ///
    /// Returns `Error` with the kind `Reserved` if the pin is already
    /// reserved, or `OutOfRange` if the pin number is invalid.
    pub fn get_digital_output(&self, pin: usize)
        -> Result<DigitalOutput, Error>
    {
        let reservation = self.try_reserve_gpio(pin)?;

        let mut gpio = self.gpio.borrow_mut();
        gpio.clear_pin(pin);
        gpio.set_function(pin, PinFunction::Out);

        Ok(DigitalOutput {
            _reservation: reservation,
            pin,
            gpio: Rc::clone(&self.gpio),
        })
    }

    /// Attempts to reserve a GPIO pin and PWM channel to create a PWM output.
    ///
    /// Upon acquisition, the PWM channel's value will automatically be set to
    /// zero and its range to 1024 as the default.
    ///
    /// Currently, only a single GPIO pin may correspond to a PWM channel,
    /// although each channel supports driving multiple pins in hardware. This
    /// may change in the future.
    ///
    /// # Errors
    ///
    /// Returns `Error` with the kind `Reserved` if the pin is already
    /// reserved, or `OutOfRange` if the pin number is invalid or does not
    /// support PWM mode.
    pub fn get_pwm_output(&self, pin: usize, config: PwmConfig)
        -> Result<PwmOutput, Error>
    {
        let gpio_reservation = self.try_reserve_gpio(pin)?;
        let (channel, func) = PWM_PINS[pin]
            .ok_or(Error::new(ErrorKind::OutOfRange))?;
        let pwm_reservation = self.try_reserve_pwm(channel)?;

        let mut gpio = self.gpio.borrow_mut();
        let mut pwm = self.pwm.borrow_mut();
        gpio.set_function(pin, func);
        thread::sleep(Duration::from_micros(110));
        match config.mode {
            PwmMode::MarkSpace => pwm.enable_mark_space(channel),
            PwmMode::Balanced => pwm.disable_mark_space(channel),
        }
        match config.silence_bit {
            PinLevel::Low => pwm.set_sbit_low(channel),
            PinLevel::High => pwm.set_sbit_high(channel),
        }
        match config.invert_polarity {
            false => pwm.set_polarity_normal(channel),
            true => pwm.set_polarity_inverse(channel),
        }
        pwm.set_data(channel, 0);
        pwm.set_range(channel, 1024);
        pwm.enable(channel);

        Ok(PwmOutput {
            _gpio_reservation: gpio_reservation,
            _pwm_reservation: pwm_reservation,
            channel,
            pwm: Rc::clone(&self.pwm),
        })
    }

    /// Sets the PWM clock divider and resets the PWM clock.
    ///
    /// # Errors
    ///
    /// To prevent modifying any currently-owned PWM channels, all channels
    /// must be free when the clock divisor is being set. Returns `Error` with
    /// the kind `Reserved` if any PWM channels are owned upon invocation.
    pub fn set_pwm_clock(&mut self, int: u32, frac: u32) -> Result<(), Error> {
        if (0..PWM_CHANNELS)
            .any(|x| self.reservations.is_reserved(&Peripheral::PwmChannel(x)))
        {
            return Err(Error::new(ErrorKind::Reserved));
        }

        let mut cm = self.cm.borrow_mut();
        cm.disable(Clock::Pwm);
        cm.set_source(Clock::Pwm, ClockSource::Oscillator);
        cm.set_divider(Clock::Pwm, int, frac);
        cm.enable(Clock::Pwm);

        Ok(())
    }

    fn try_reserve_gpio(&self, pin: usize)
        -> Result<Reservation<Peripheral>, Error>
    {
        Gpio::check_pin(pin)?;
        self.reservations.try_reserve(Peripheral::GpioPin(pin))
    }

    fn try_reserve_pwm(&self, channel: usize)
        -> Result<Reservation<Peripheral>, Error>
    {
        Pwm::check_channel(channel)?;
        self.reservations.try_reserve(Peripheral::PwmChannel(channel))
    }
}

/// Encapsulates a pin reserved as a digital input.
pub struct DigitalInput {
    _reservation: Reservation<Peripheral>,
    pin: usize,
    gpio: Rc<RefCell<Gpio>>,
}

impl DigitalInput {
    
    /// Sets the pin's pull state to the given value.
    ///
    /// The input level is interpreted as a physical voltage level where
    /// `Some(High)` is the source voltage (3.3v), `Some(Low)` is ground (0v),
    /// and `None` is floating, according to `Gpio::set_pull`.
    pub fn set_pull(&mut self, pull: Option<PinLevel>) {
        let mut gpio = self.gpio.borrow_mut();
        gpio.set_pull(self.pin, pull);
    }

    /// Gets the current level of the pin.
    ///
    /// The result should be interpreted as a physical voltage level where
    /// `High` is the source voltage (3.3v) and `Low` is ground (0v).
    pub fn get_level(&self) -> PinLevel {
        let gpio = self.gpio.borrow();
        gpio.get_level(self.pin)
    }
}

/// Encapsulates a pin reserved as a digital output.
pub struct DigitalOutput {
    _reservation: Reservation<Peripheral>,
    pin: usize,
    gpio: Rc<RefCell<Gpio>>,
}

impl DigitalOutput {

    /// Sets the pin's output state to `High` (the source voltage, 3.3v).
    pub fn set(&mut self) {
        let mut gpio = self.gpio.borrow_mut();
        gpio.set_pin(self.pin);
    }

    /// Sets the pin's output state to `Low` (ground, 0v).
    pub fn clear(&mut self) {
        let mut gpio = self.gpio.borrow_mut();
        gpio.clear_pin(self.pin);
    }

    /// Sets the pin's output state according to the given level.
    ///
    /// The input level is interpreted as the physical voltage level where
    /// `High` is the source voltage (3.3v) and `Low` is ground (0v).
    pub fn set_level(&mut self, level: PinLevel) {
        let mut gpio = self.gpio.borrow_mut();
        gpio.set_level(self.pin, level);
    }
}

/// Encapsulates a pin reserved as a PWM output.
pub struct PwmOutput {
    _gpio_reservation: Reservation<Peripheral>,
    _pwm_reservation: Reservation<Peripheral>,
    channel: usize,
    pwm: Rc<RefCell<Pwm>>,
}

impl PwmOutput {

    /// Sets the output value of the pin, which may be anywhere from zero
    /// to its current range/maximum. Larger values will be capped.
    pub fn set_value(&mut self, value: u32) {
        let mut pwm = self.pwm.borrow_mut();
        pwm.set_data(self.channel, value);
    }

    /// Sets the channel's range (resolution), which will be the maximum output
    /// value of the PWM channel which will also correspond to an always-on
    /// signal.
    pub fn set_range(&mut self, range: u32) {
        let mut pwm = self.pwm.borrow_mut();
        pwm.set_range(self.channel, range);
    }
}