Skip to main content

rpi_pal/
gpio.rs

1//! Interface for the GPIO peripheral.
2//!
3//! To ensure fast performance, rpi-pal controls the GPIO peripheral by directly
4//! accessing the registers through either `/dev/gpiomem` or `/dev/mem`. GPIO interrupts
5//! are configured using the `gpiochip` character device.
6//!
7//! ## Pins
8//!
9//! GPIO pins are retrieved from a [`Gpio`] instance by their BCM GPIO number by calling
10//! [`Gpio::get`]. The returned unconfigured [`Pin`] can be used to read the pin's
11//! mode and logic level. Converting the [`Pin`] to an [`InputPin`], [`OutputPin`] or
12//! [`IoPin`] through the various `into_` methods available on [`Pin`] configures the
13//! appropriate mode, and provides access to additional methods relevant to the selected pin mode.
14//!
15//! Retrieving a GPIO pin with [`Gpio::get`] grants access to the pin through an owned [`Pin`]
16//! instance. If the pin is already in use, or the GPIO peripheral doesn't expose a pin with
17//! the specified number, [`Gpio::get`] returns `Err(`[`Error::PinNotAvailable`]`)`. After a [`Pin`]
18//! (or a derived [`InputPin`], [`OutputPin`] or [`IoPin`]) goes out of scope, it can be
19//! retrieved again through another [`Gpio::get`] call.
20//!
21//! By default, pins are reset to their original state when they go out of scope.
22//! Use [`InputPin::set_reset_on_drop(false)`], [`OutputPin::set_reset_on_drop(false)`]
23//! or [`IoPin::set_reset_on_drop(false)`], respectively, to disable this behavior.
24//! Note that `drop` methods aren't called when a process is abnormally terminated (for
25//! instance when a `SIGINT` signal isn't caught).
26//!
27//! ## Interrupts
28//!
29//! [`InputPin`] supports both synchronous and asynchronous interrupt handlers.
30//!
31//! Synchronous (blocking) interrupt triggers are configured using [`InputPin::set_interrupt`].
32//! An interrupt trigger for a single pin can be polled with [`InputPin::poll_interrupt`],
33//! which blocks the current thread until a trigger event occurs, or until the timeout period
34//! elapses. [`Gpio::poll_interrupts`] should be used when multiple pins have been configured
35//! for synchronous interrupt triggers, and need to be polled simultaneously.
36//!
37//! Asynchronous interrupt triggers are configured using [`InputPin::set_async_interrupt`]. The
38//! specified callback function will be executed on a separate thread when a trigger event occurs.
39//!
40//! ## Software-based PWM
41//!
42//! [`OutputPin`] and [`IoPin`] feature a software-based PWM implementation. The PWM signal is
43//! emulated by toggling the pin's output state on a separate thread, combined with sleep and
44//! busy-waiting.
45//!
46//! Software-based PWM is inherently inaccurate on a multi-threaded OS due to scheduling/preemption.
47//! If an accurate or faster PWM signal is required, use the hardware [`Pwm`] peripheral instead.
48//!
49//! PWM threads may occasionally sleep longer than needed. If the active or inactive part of the
50//! signal is shorter than 250 µs, only busy-waiting is used, which will increase CPU usage. Due to
51//! function call overhead, typical jitter is expected to be up to 10 µs on debug builds, and up to
52//! 2 µs on release builds.
53//!
54//! ## Examples
55//!
56//! Basic example:
57//!
58//! ```
59//! use std::thread;
60//! use std::time::Duration;
61//!
62//! use rpi_pal::gpio::Gpio;
63//!
64//! # fn main() -> rpi_pal::gpio::Result<()> {
65//! let gpio = Gpio::new()?;
66//! let mut pin = gpio.get(23)?.into_output();
67//!
68//! pin.set_high();
69//! thread::sleep(Duration::from_secs(1));
70//! pin.set_low();
71//! # Ok(())
72//! # }
73//! ```
74//!
75//! Additional examples can be found in the `examples` directory.
76//!
77//! ## Troubleshooting
78//!
79//! ### Permission denied
80//!
81//! In recent releases of Raspberry Pi OS (December 2017 or later), users that are part of the
82//! `gpio` group (like the default `pi` user) can access `/dev/gpiomem` and
83//! `/dev/gpiochipN` (N = 0-2) without needing additional permissions. If you encounter any
84//! [`PermissionDenied`] errors when constructing a new [`Gpio`] instance, either the current
85//! user isn't a member of the `gpio` group, or your Raspberry Pi OS distribution isn't
86//! up-to-date and doesn't automatically configure permissions for the above-mentioned
87//! files. Updating Raspberry Pi OS to the latest release should fix any permission issues.
88//! Alternatively, although not recommended, you can run your application with superuser
89//! privileges by using `sudo`.
90//!
91//! If you're unable to update Raspberry Pi OS and its packages (namely `raspberrypi-sys-mods`) to
92//! the latest available release, or updating hasn't fixed the issue, you might be able to
93//! manually update your `udev` rules to set the appropriate permissions. More information
94//! can be found at [raspberrypi/linux#1225] and [raspberrypi/linux#2289].
95//!
96//! [`Error::PinNotAvailable`]: enum.Error.html#variant.PinNotAvailable
97//! [`PermissionDenied`]: enum.Error.html#variant.PermissionDenied
98//! [raspberrypi/linux#1225]: https://github.com/raspberrypi/linux/issues/1225
99//! [raspberrypi/linux#2289]: https://github.com/raspberrypi/linux/issues/2289
100//! [`Gpio`]: struct.Gpio.html
101//! [`Gpio::get`]: struct.Gpio.html#method.get
102//! [`Gpio::poll_interrupts`]: struct.Gpio.html#method.poll_interrupts
103//! [`Pin`]: struct.Pin.html
104//! [`InputPin`]: struct.InputPin.html
105//! [`InputPin::set_reset_on_drop(false)`]: struct.InputPin.html#method.set_reset_on_drop
106//! [`InputPin::set_interrupt`]: struct.InputPin.html#method.set_interrupt
107//! [`InputPin::poll_interrupt`]: struct.InputPin.html#method.poll_interrupt
108//! [`InputPin::set_async_interrupt`]: struct.InputPin.html#method.set_async_interrupt
109//! [`OutputPin`]: struct.OutputPin.html
110//! [`OutputPin::set_reset_on_drop(false)`]: struct.OutputPin.html#method.set_reset_on_drop
111//! [`IoPin`]: struct.IoPin.html
112//! [`IoPin::set_reset_on_drop(false)`]: struct.IoPin.html#method.set_reset_on_drop
113//! [`Pwm`]: ../pwm/struct.Pwm.html
114#![allow(clippy::missing_transmute_annotations)]
115#![allow(static_mut_refs)]
116
117use std::error;
118use std::fmt;
119use std::io;
120use std::mem::MaybeUninit;
121use std::ops::Not;
122use std::os::unix::io::AsRawFd;
123use std::result;
124use std::sync::atomic::{AtomicBool, Ordering};
125use std::sync::{Arc, Mutex, Once, Weak};
126use std::time::Duration;
127
128mod epoll;
129mod gpiomem;
130#[cfg(any(
131    feature = "embedded-hal-0",
132    feature = "embedded-hal",
133    feature = "embedded-hal-nb"
134))]
135mod hal;
136#[cfg(feature = "hal-unproven")]
137mod hal_unproven;
138mod interrupt;
139mod ioctl;
140mod pin;
141mod soft_pwm;
142
143use crate::system;
144use crate::system::DeviceInfo;
145
146pub use self::pin::{InputPin, IoPin, OutputPin, Pin};
147
148/// Errors that can occur when accessing the GPIO peripheral.
149#[derive(Debug)]
150pub enum Error {
151    /// Unknown model.
152    ///
153    /// The Raspberry Pi model or SoC can't be identified. Support for
154    /// new models is usually added shortly after they are officially
155    /// announced and available to the public. Make sure you're using
156    /// the latest release of rpi-pal.
157    ///
158    /// You may also encounter this error if your Linux distribution
159    /// doesn't provide any of the common user-accessible system files
160    /// that are used to identify the model and SoC.
161    UnknownModel,
162    /// Pin is already in use.
163    ///
164    /// The pin is already in use elsewhere in your application. If the pin is currently in
165    /// use, you may retrieve it again after the [`Pin`] (or a derived [`InputPin`],
166    /// [`OutputPin`] or [`IoPin`]) instance goes out of scope.
167    ///
168    /// [`Pin`]: struct.Pin.html
169    /// [`InputPin`]: struct.InputPin.html
170    /// [`OutputPin`]: struct.OutputPin.html
171    /// [`IoPin`]: struct.IoPin.html
172    PinUsed(u8),
173    /// Pin is not available.
174    ///
175    /// The GPIO peripheral doesn't expose a GPIO pin with the specified number. Pins are
176    /// addressed by their BCM GPIO numbers, rather than their physical location on the GPIO
177    /// header.
178    PinNotAvailable(u8),
179    /// Permission denied when opening `/dev/gpiomem`, `/dev/mem` or `/dev/gpiochipN` for
180    /// read/write access.
181    ///
182    /// More information on possible causes for this error can be found [here].
183    ///
184    /// [here]: index.html#permission-denied
185    PermissionDenied(String),
186    /// I/O error.
187    Io(io::Error),
188    /// Thread panicked.
189    ThreadPanic,
190}
191
192impl fmt::Display for Error {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        match *self {
195            Error::UnknownModel => write!(f, "Unknown Raspberry Pi model"),
196            Error::PinUsed(pin) => write!(f, "Pin {} is already in use", pin),
197            Error::PinNotAvailable(pin) => write!(f, "Pin {} is not available", pin),
198            Error::PermissionDenied(ref path) => write!(f, "Permission denied: {}", path),
199            Error::Io(ref err) => write!(f, "I/O error: {}", err),
200            Error::ThreadPanic => write!(f, "Thread panicked"),
201        }
202    }
203}
204
205impl error::Error for Error {}
206
207impl From<io::Error> for Error {
208    fn from(err: io::Error) -> Error {
209        Error::Io(err)
210    }
211}
212
213impl From<system::Error> for Error {
214    fn from(_err: system::Error) -> Error {
215        Error::UnknownModel
216    }
217}
218
219/// Result type returned from methods that can have `rpi_pal::gpio::Error`s.
220pub type Result<T> = result::Result<T, Error>;
221
222/// Pin modes.
223#[derive(Debug, PartialEq, Eq, Copy, Clone)]
224#[repr(u8)]
225pub enum Mode {
226    Input,
227    Output,
228    Alt0,
229    Alt1,
230    Alt2,
231    Alt3,
232    Alt4,
233    Alt5,
234    Alt6,
235    Alt7,
236    Alt8,
237    Null,
238}
239
240impl fmt::Display for Mode {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        match *self {
243            Mode::Input => write!(f, "In"),
244            Mode::Output => write!(f, "Out"),
245            Mode::Alt0 => write!(f, "Alt0"),
246            Mode::Alt1 => write!(f, "Alt1"),
247            Mode::Alt2 => write!(f, "Alt2"),
248            Mode::Alt3 => write!(f, "Alt3"),
249            Mode::Alt4 => write!(f, "Alt4"),
250            Mode::Alt5 => write!(f, "Alt5"),
251            Mode::Alt6 => write!(f, "Alt6"),
252            Mode::Alt7 => write!(f, "Alt7"),
253            Mode::Alt8 => write!(f, "Alt8"),
254            Mode::Null => write!(f, "Null"),
255        }
256    }
257}
258
259/// Pin logic levels.
260#[derive(Debug, PartialEq, Eq, Copy, Clone)]
261#[repr(u8)]
262pub enum Level {
263    Low = 0,
264    High = 1,
265}
266
267impl From<bool> for Level {
268    fn from(e: bool) -> Level {
269        if e {
270            Level::High
271        } else {
272            Level::Low
273        }
274    }
275}
276
277impl fmt::Display for Level {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        match *self {
280            Level::Low => write!(f, "Low"),
281            Level::High => write!(f, "High"),
282        }
283    }
284}
285
286impl From<u8> for Level {
287    fn from(value: u8) -> Self {
288        if value == 0 {
289            Level::Low
290        } else {
291            Level::High
292        }
293    }
294}
295
296impl Not for Level {
297    type Output = Level;
298
299    fn not(self) -> Level {
300        match self {
301            Level::Low => Level::High,
302            Level::High => Level::Low,
303        }
304    }
305}
306
307/// Built-in pull-up/pull-down resistor states.
308#[derive(Debug, PartialEq, Eq, Copy, Clone)]
309pub enum Bias {
310    Off = 0b00,
311    PullDown = 0b01,
312    PullUp = 0b10,
313}
314
315impl fmt::Display for Bias {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        match *self {
318            Bias::Off => write!(f, "Off"),
319            Bias::PullDown => write!(f, "PullDown"),
320            Bias::PullUp => write!(f, "PullUp"),
321        }
322    }
323}
324
325/// Interrupt trigger conditions.
326#[derive(Debug, PartialEq, Eq, Copy, Clone)]
327pub enum Trigger {
328    Disabled = 0,
329    RisingEdge = 1,
330    FallingEdge = 2,
331    Both = 3,
332}
333
334impl fmt::Display for Trigger {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        match *self {
337            Trigger::Disabled => write!(f, "Disabled"),
338            Trigger::RisingEdge => write!(f, "RisingEdge"),
339            Trigger::FallingEdge => write!(f, "FallingEdge"),
340            Trigger::Both => write!(f, "Both"),
341        }
342    }
343}
344
345/// Interrupt trigger event.
346#[derive(Debug, Copy, Clone)]
347pub struct Event {
348    /// Best estimate of time of event occurrence, measured in elapsed time since the system was booted.
349    pub timestamp: Duration,
350    /// Sequence number for this event in the sequence of interrupt trigger events for this pin.
351    pub seqno: u32,
352    /// Interrupt trigger. This will contain either [Trigger::RisingEdge] or [Trigger::FallingEdge].
353    pub trigger: Trigger,
354}
355
356impl Default for Event {
357    fn default() -> Self {
358        Self {
359            timestamp: Duration::default(),
360            seqno: 0,
361            trigger: Trigger::Both,
362        }
363    }
364}
365
366// Store Gpio's state separately, so we can conveniently share it through
367// a cloned Arc.
368pub(crate) struct GpioState {
369    gpio_mem: Box<dyn gpiomem::GpioRegisters>,
370    cdev: std::fs::File,
371    sync_interrupts: Mutex<interrupt::EventLoop>,
372    pins_taken: [AtomicBool; u8::MAX as usize],
373    gpio_lines: u8,
374}
375
376impl fmt::Debug for GpioState {
377    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378        f.debug_struct("EventLoop")
379            .field("gpio_mem", &self.gpio_mem)
380            .field("cdev", &self.cdev)
381            .field("sync_interrupts", &self.sync_interrupts)
382            .field("pins_taken", &format_args!("{{ .. }}"))
383            .field("gpio_lines", &self.gpio_lines)
384            .finish()
385    }
386}
387
388/// Provides access to the Raspberry Pi's GPIO peripheral.
389#[derive(Clone, Debug)]
390pub struct Gpio {
391    inner: Arc<GpioState>,
392}
393
394impl Gpio {
395    /// Constructs a new `Gpio`.
396    pub fn new() -> Result<Gpio> {
397        // Replace this when std::sync::SyncLazy is stabilized. https://github.com/rust-lang/rust/issues/74465
398
399        // Shared state between Gpio and Pin instances. GpioState is dropped after
400        // all Gpio and Pin instances go out of scope, guaranteeing we won't have
401        // any pins simultaneously using different EventLoop or GpioMem instances.
402        static mut GPIO_STATE: MaybeUninit<Mutex<Weak<GpioState>>> = MaybeUninit::uninit();
403        static ONCE: Once = Once::new();
404
405        // call_once is thread-safe, guaranteed to be called only once, and memory writes performed
406        // by the closure can be observed by other threads after execution completes.
407        let mut weak_state = unsafe {
408            ONCE.call_once(|| {
409                GPIO_STATE.write(Mutex::new(Weak::new()));
410            });
411
412            // GPIO_STATE will always be initialized at this point.
413            GPIO_STATE.assume_init_ref().lock().unwrap()
414        };
415
416        // Clone a strong reference if a GpioState instance already exists, otherwise
417        // initialize it here so we can return any relevant errors.
418        if let Some(ref state) = weak_state.upgrade() {
419            Ok(Gpio {
420                inner: state.clone(),
421            })
422        } else {
423            let device_info = DeviceInfo::new().map_err(|_| Error::UnknownModel)?;
424
425            let gpio_mem: Box<dyn gpiomem::GpioRegisters> = match device_info.gpio_interface() {
426                system::GpioInterface::Bcm => Box::new(gpiomem::bcm::GpioMem::open()?),
427                system::GpioInterface::Rp1 => Box::new(gpiomem::rp1::GpioMem::open()?),
428            };
429
430            let cdev = ioctl::find_gpiochip()?;
431            let sync_interrupts = Mutex::new(interrupt::EventLoop::new(
432                cdev.as_raw_fd(),
433                u8::MAX as usize,
434            )?);
435            let pins_taken = init_array!(AtomicBool::new(false), u8::MAX as usize);
436            let gpio_lines = device_info.gpio_lines();
437
438            let gpio_state = Arc::new(GpioState {
439                gpio_mem,
440                cdev,
441                sync_interrupts,
442                pins_taken,
443                gpio_lines,
444            });
445
446            // Store a weak reference to our state. This gets dropped when
447            // all Gpio and Pin instances go out of scope.
448            *weak_state = Arc::downgrade(&gpio_state);
449
450            Ok(Gpio { inner: gpio_state })
451        }
452    }
453
454    /// Returns a [`Pin`] for the specified BCM GPIO number.
455    ///
456    /// Retrieving a GPIO pin grants access to the pin through an owned [`Pin`] instance.
457    /// If the pin is already in use, `get` returns `Err(`[`Error::PinUsed`]`)`.
458    /// After a [`Pin`] (or a derived [`InputPin`], [`OutputPin`] or [`IoPin`]) goes out
459    /// of scope, it can be retrieved again through another `get` call.
460    ///
461    /// [`Pin`]: struct.Pin.html
462    /// [`InputPin`]: struct.InputPin.html
463    /// [`OutputPin`]: struct.OutputPin.html
464    /// [`IoPin`]: struct.IoPin.html
465    /// [`Error::PinUsed`]: enum.Error.html#variant.PinUsed
466    pub fn get(&self, pin: u8) -> Result<Pin> {
467        if pin >= self.inner.gpio_lines {
468            return Err(Error::PinNotAvailable(pin));
469        }
470
471        // Returns an error if the pin is already taken, otherwise atomically sets it to true here
472        if self.inner.pins_taken[pin as usize]
473            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
474            .is_err()
475        {
476            // Pin is taken
477            Err(Error::PinUsed(pin))
478        } else {
479            // Return an owned Pin
480            Ok(Pin::new(pin, self.inner.clone()))
481        }
482    }
483
484    /// Blocks until an interrupt is triggered on any of the specified pins, or until a timeout occurs.
485    ///
486    /// Only pins that have been previously configured for synchronous interrupts using [`InputPin::set_interrupt`]
487    /// can be polled. Asynchronous interrupt triggers are automatically polled on a separate thread.
488    ///
489    /// Calling `poll_interrupts` blocks any other calls to `poll_interrupts` or [`InputPin::poll_interrupt`] until
490    /// it returns. If you need to poll multiple pins simultaneously on different threads, consider using
491    /// asynchronous interrupts with [`InputPin::set_async_interrupt`] instead.
492    ///
493    /// Setting `reset` to `false` returns any cached interrupt trigger events if available. Setting `reset` to `true`
494    /// clears all cached events before polling for new events.
495    ///
496    /// The `timeout` duration indicates how long the call to `poll_interrupts` will block while waiting
497    /// for interrupt trigger events, after which an `Ok(None)` is returned.
498    /// `timeout` can be set to `None` to wait indefinitely.
499    ///
500    /// When an interrupt event is triggered, `poll_interrupts` returns
501    /// `Ok((&`[`InputPin`]`, `[`Event`]`))` containing the corresponding pin and trigger event details. If multiple events
502    /// trigger at the same time, only the first one is returned. The remaining events are cached and will be returned
503    /// the next time [`InputPin::poll_interrupt`] or `poll_interrupts` is called.
504    ///
505    /// [`InputPin::set_interrupt`]: struct.InputPin.html#method.set_interrupt
506    /// [`InputPin::poll_interrupt`]: struct.InputPin.html#method.poll_interrupt
507    /// [`InputPin::set_async_interrupt`]: struct.InputPin.html#method.set_async_interrupt
508    /// [`InputPin`]: struct.InputPin.html
509    /// [`Event`]: struct.Event.html
510    pub fn poll_interrupts<'a>(
511        &self,
512        pins: &[&'a InputPin],
513        reset: bool,
514        timeout: Option<Duration>,
515    ) -> Result<Option<(&'a InputPin, Event)>> {
516        (*self.inner.sync_interrupts.lock().unwrap()).poll(pins, reset, timeout)
517    }
518}