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}