rppal_mcp23s17/
pin.rs

1//! Various flavours of "Pin" that the I/O Expander GPIO ports support.
2//!
3//! * [`InputPin`] - GPIO input that may either be high impedance or have an internal
4//!                  pull-up resistor connected.
5//! * [`OutputPin`] - GPIO output that can be initialised to high or low [`Level`].
6//!
7//! # Acknowledgements
8//!
9//! The design of this module is heavily influenced by the
10//! [RPPAL GPIO design](https://github.com/golemparts/rppal/blob/master/src/gpio.rs)
11
12use std::{cell::RefCell, fmt, ops::Not, rc::Rc};
13
14use super::{Mcp23s17State, Port, RegisterAddress, Result};
15
16// There is a lot of repetitious code in each of the flavours of [`Pin`] so use macros
17// to reduce that complexity.
18
19/// Create the functions impl block for all the flavours of [`Pin`] that support input.
20///
21/// Note that this macro should be called from within an existing impl block.
22macro_rules! impl_input {
23    () => {
24        /// Reads the pin's logic level.
25        #[inline]
26        pub fn read(&self) -> Result<Level> {
27            self.pin.read()
28        }
29
30        /// Reads the pin's logic level, and returns [`true`] if it is set to
31        /// [`Level::Low`].
32        #[inline]
33        pub fn is_low(&self) -> Result<bool> {
34            Ok(self.pin.read()? == Level::Low)
35        }
36
37        /// Reads the pin's logic level, and returns [`true`] if it is set to
38        /// [`Level::High`].
39        #[inline]
40        pub fn is_high(&self) -> Result<bool> {
41            Ok(self.pin.read()? == Level::High)
42        }
43
44        /// Gets the pin's bit number (0-7).
45        #[inline]
46        pub fn get_pin_number(&self) -> u8 {
47            self.pin.pin
48        }
49    };
50}
51
52/// Pin logic levels.
53#[derive(Debug, PartialEq, Eq, Copy, Clone)]
54#[repr(u8)]
55pub enum Level {
56    /// Low logic-level.
57    Low = 0,
58    /// High logic-level.
59    High = 1,
60}
61
62impl From<bool> for Level {
63    fn from(e: bool) -> Level {
64        match e {
65            true => Level::High,
66            false => Level::Low,
67        }
68    }
69}
70
71impl From<Level> for bool {
72    fn from(level: Level) -> Self {
73        level == Level::High
74    }
75}
76
77impl From<rppal::gpio::Level> for Level {
78    fn from(level: rppal::gpio::Level) -> Self {
79        match level {
80            rppal::gpio::Level::Low => Self::Low,
81            rppal::gpio::Level::High => Self::High,
82        }
83    }
84}
85
86impl From<Level> for rppal::gpio::Level {
87    fn from(level: Level) -> Self {
88        match level {
89            Level::Low => Self::Low,
90            Level::High => Self::High,
91        }
92    }
93}
94
95impl fmt::Display for Level {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match *self {
98            Level::Low => write!(f, "Low"),
99            Level::High => write!(f, "High"),
100        }
101    }
102}
103
104impl From<u8> for Level {
105    fn from(value: u8) -> Self {
106        if value == 0 {
107            Level::Low
108        } else {
109            Level::High
110        }
111    }
112}
113
114impl Not for Level {
115    type Output = Level;
116
117    fn not(self) -> Level {
118        match self {
119            Level::Low => Level::High,
120            Level::High => Level::Low,
121        }
122    }
123}
124
125/// InputPin modes.
126#[derive(Debug, PartialEq, Eq, Copy, Clone)]
127pub enum InputPinMode {
128    /// The input pin is high-impedance (e.g. driven from a logic gate.)
129    HighImpedance = 0,
130    /// The input pin has an internal pull-up resistor connected (e.g. driven by switch
131    /// contacts).
132    PullUp = 1,
133}
134
135impl fmt::Display for InputPinMode {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match *self {
138            InputPinMode::HighImpedance => write!(f, "High Impedance"),
139            InputPinMode::PullUp => write!(f, "Pull Up"),
140        }
141    }
142}
143
144/// Interrupt input trigger modes that an InputPin supports.
145#[derive(Debug, PartialEq, Eq, Copy, Clone)]
146pub enum InterruptMode {
147    /// Interrupts are disabled.
148    None,
149    /// Interrupts are raised when the input is [`Level::High`] and so will typically
150    /// happen on the [`Level::Low`] to [`Level::High`] transition. If interrupts are
151    /// re-enabled while the input remains `High`, a new interrupt will be raised
152    /// without another transition being necessary.
153    ActiveHigh,
154    /// Interrupts are raised when the input is [`Level::Low`] and so will typically
155    /// happen on the [`Level::High`] to [`Level::Low`] transition. If interrupts are
156    /// re-enabled while the input remains `Low`, a new interrupt will be raised
157    /// without another transition being necessary.
158    ActiveLow,
159    /// Interrupts are enabled on both the [`Level::High`] to [`Level::Low`] transition
160    /// and the  [`Level::Low`] to [`Level::High`] transition. If interrupts are
161    /// re-enabled while the input remains in the state that triggered the interrupt, a
162    /// new interrupt will _not_ be raised until another transition to the opposite
163    /// state occurs.
164    BothEdges,
165}
166
167impl fmt::Display for InterruptMode {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        match *self {
170            InterruptMode::None => write!(f, "Off"),
171            InterruptMode::ActiveHigh => write!(f, "↑"),
172            InterruptMode::ActiveLow => write!(f, "↓"),
173            InterruptMode::BothEdges => write!(f, "⇅"),
174        }
175    }
176}
177
178/// An unconfigured GPIO pin that implements functionality shared between pin types.
179///
180/// An instance of a [`Pin`] can be converted into a configured pin type using one of the
181/// `into_*` methods that consume the [`Pin`] and return the specific pin type.
182#[derive(Debug)]
183pub struct Pin {
184    port: Port,
185    pub(crate) pin: u8,
186    mcp23s17_state: Rc<RefCell<Mcp23s17State>>,
187}
188
189/// A pin on a GPIO port configured for input.
190///
191/// Two flavours exist that: either a high-impedance input (_e.g._ driven by an external
192/// logic gate) or an input with a pull-up for use with switch inputs. Use the
193/// appropriate method on the [`Pin`]: [`Pin::into_input_pin`] or
194/// [`Pin::into_pullup_input_pin`].
195#[derive(Debug)]
196pub struct InputPin {
197    pin: Pin,
198    /// Whether interrupts are enabled - controls `Drop` behaviour.
199    interrupts_enabled: bool,
200}
201
202/// A pin on a GPIO port configured for output.
203#[derive(Debug)]
204pub struct OutputPin {
205    pin: Pin,
206}
207
208impl Pin {
209    /// Create a new pin that maintains a reference to the MCP23S17.
210    ///
211    /// Generally this will be converted into a specific kind of Pin (_e.g._ InputPin)
212    /// through one of the various `into_xxx()` methods.
213    pub(crate) fn new(port: Port, pin: u8, mcp23s17_state: Rc<RefCell<Mcp23s17State>>) -> Pin {
214        Pin {
215            port,
216            pin,
217            mcp23s17_state,
218        }
219    }
220
221    /// Read the state of the pin.
222    pub fn read(&self) -> Result<Level> {
223        match self.port {
224            Port::GpioA => Ok(Level::from(
225                self.mcp23s17_state.borrow().read(RegisterAddress::GPIOA)? & (0x01 << self.pin),
226            )),
227            Port::GpioB => Ok(Level::from(
228                self.mcp23s17_state.borrow().read(RegisterAddress::GPIOB)? & (0x01 << self.pin),
229            )),
230        }
231    }
232
233    /// Turn the unconfigured `Pin` into an `InputPin` consuming the `Pin` in the process.
234    ///
235    /// The InputPin is high-impedance (does not have internal pull-up resistor
236    /// connected).
237    pub fn into_input_pin(self) -> Result<InputPin> {
238        InputPin::new(self, InputPinMode::HighImpedance)
239    }
240
241    /// Turn the unconfigured `Pin` into an `InputPin` consuming the `Pin` in the process.
242    ///
243    /// The InputPin has internal pull-up resistor connected.
244    pub fn into_pullup_input_pin(self) -> Result<InputPin> {
245        InputPin::new(self, InputPinMode::PullUp)
246    }
247
248    /// Turn the unconfigured `Pin` into an `OutputPin` consuming the `Pin` in the process.
249    pub fn into_output_pin(self) -> Result<OutputPin> {
250        OutputPin::new(self)
251    }
252
253    /// Turn the unconfigured `Pin` into an `OutputPin` consuming the `Pin` in the process.
254    ///
255    /// Initialise the pin to be high.
256    pub fn into_output_pin_high(self) -> Result<OutputPin> {
257        let pin = OutputPin::new(self)?;
258        pin.set_high()?;
259        Ok(pin)
260    }
261
262    /// Turn the unconfigured `Pin` into an `OutputPin` consuming the `Pin` in the process.
263    ///
264    /// Initialise the pin to be low.
265    pub fn into_output_pin_low(self) -> Result<OutputPin> {
266        let pin = OutputPin::new(self)?;
267        pin.set_low()?;
268        Ok(pin)
269    }
270}
271
272impl Drop for Pin {
273    fn drop(&mut self) {
274        self.mcp23s17_state.borrow_mut().gpioa_pins_taken[self.pin as usize] = false;
275    }
276}
277
278impl InputPin {
279    /// Constructs an `InputPin` consuming the unconfigured `Pin` in the process.
280    ///
281    /// Sets the direction of the appropriate GPIO line and configuration of the Pull-up
282    /// control register.
283    fn new(pin: Pin, mode: InputPinMode) -> Result<Self> {
284        // Set the direction of the GPIO port.
285        // Need to scope to drop the reference to the MCP23S17 state before we move the
286        // pin into the return value.
287        {
288            let mcp23s17_state = pin.mcp23s17_state.borrow();
289            mcp23s17_state.set_bit(
290                if pin.port == Port::GpioA {
291                    RegisterAddress::IODIRA
292                } else {
293                    RegisterAddress::IODIRB
294                },
295                pin.pin,
296            )?;
297
298            // Set whether pull-up is used, or not.
299            match mode {
300                InputPinMode::HighImpedance => mcp23s17_state.clear_bit(
301                    if pin.port == Port::GpioA {
302                        RegisterAddress::GPPUA
303                    } else {
304                        RegisterAddress::GPPUB
305                    },
306                    pin.pin,
307                )?,
308                InputPinMode::PullUp => mcp23s17_state.set_bit(
309                    if pin.port == Port::GpioA {
310                        RegisterAddress::GPPUA
311                    } else {
312                        RegisterAddress::GPPUB
313                    },
314                    pin.pin,
315                )?,
316            }
317        }
318        Ok(InputPin {
319            pin,
320            interrupts_enabled: false,
321        })
322    }
323
324    /// Set the [`InputPin`] to the requested `mode` (_i.e._ which edge(s) on the input
325    /// trigger an interrupt.)
326    ///
327    /// Note that setting an `mode` of [`InterruptMode::None`] disables
328    /// interrupts.
329    ///
330    /// The relevant register bits are set according to the following table:
331    ///
332    /// | Mode                           | `GPINTEN` | `INTCON` | `DEFVAL` |
333    /// |--------------------------------|:---------:|:--------:|:--------:|
334    /// | [`InterruptMode::None`]        |    `L`    |    `X`   |   `X`    |
335    /// | [`InterruptMode::ActiveHigh`]  |    `H`    |    `H`   |   `L`    |
336    /// | [`InterruptMode::ActiveLow`]   |    `H`    |    `H`   |   `H`    |
337    /// | [`InterruptMode::BothEdges`]   |    `H`    |    `L`   |   `X`    |
338    ///
339    /// `X` = "Don't care" so register unchanged when setting this mode.
340    ///
341    /// Because the MCP23S17 is solely concerned with raising the interrupt and not with
342    /// handling it, the `InputPin` API just allows control of the relevant registers
343    /// that affect the device's interrupt behaviour with handlers expected to be in
344    /// some type that contains the [`Mcp23s17`][super::Mcp23s17].
345    pub fn set_interrupt_mode(&mut self, mode: InterruptMode) -> Result<()> {
346        let (gpinten, intcon, defval) = match self.pin.port {
347            Port::GpioA => (
348                RegisterAddress::GPINTENA,
349                RegisterAddress::INTCONA,
350                RegisterAddress::DEFVALA,
351            ),
352            Port::GpioB => (
353                RegisterAddress::GPINTENB,
354                RegisterAddress::INTCONB,
355                RegisterAddress::DEFVALB,
356            ),
357        };
358
359        // Set up the registers. Note that GPINTEN is set last so that the correct
360        // criteria are set before enabling interrupts to avoid an spurious initial
361        // interrupts.
362        let mcp23s17_state = self.pin.mcp23s17_state.borrow();
363        match mode {
364            InterruptMode::None => {
365                self.interrupts_enabled = false;
366                mcp23s17_state.clear_bit(gpinten, self.pin.pin)?;
367            }
368            InterruptMode::ActiveHigh => {
369                self.interrupts_enabled = true;
370                mcp23s17_state.set_bit(intcon, self.pin.pin)?;
371                mcp23s17_state.clear_bit(defval, self.pin.pin)?;
372                mcp23s17_state.set_bit(gpinten, self.pin.pin)?;
373            }
374            InterruptMode::ActiveLow => {
375                self.interrupts_enabled = true;
376                mcp23s17_state.set_bit(intcon, self.pin.pin)?;
377                mcp23s17_state.set_bit(defval, self.pin.pin)?;
378                mcp23s17_state.set_bit(gpinten, self.pin.pin)?;
379            }
380            InterruptMode::BothEdges => {
381                self.interrupts_enabled = true;
382                mcp23s17_state.clear_bit(intcon, self.pin.pin)?;
383                mcp23s17_state.set_bit(gpinten, self.pin.pin)?;
384            }
385        }
386        Ok(())
387    }
388
389    impl_input!();
390}
391
392impl Drop for InputPin {
393    fn drop(&mut self) {
394        if self.interrupts_enabled {
395            let _ = self.set_interrupt_mode(InterruptMode::None);
396        }
397    }
398}
399
400impl OutputPin {
401    /// Constructs an `OutputPin` consuming the unconfigured `Pin` in the process.
402    ///
403    /// Sets the direction of the appropriate GPIO line and configuration of the Pull-up
404    /// control register.
405    fn new(pin: Pin) -> Result<Self> {
406        // Set the direction of the GPIO port.
407        // Need to scope to drop the reference to the MCP23S17 state before we move the
408        // pin into the return value.
409        {
410            let mcp23s17_state = pin.mcp23s17_state.borrow();
411            mcp23s17_state.clear_bit(
412                if pin.port == Port::GpioA {
413                    RegisterAddress::IODIRA
414                } else {
415                    RegisterAddress::IODIRB
416                },
417                pin.pin,
418            )?;
419
420            // Turn-off the pull-up.
421            mcp23s17_state.clear_bit(
422                if pin.port == Port::GpioA {
423                    RegisterAddress::GPPUA
424                } else {
425                    RegisterAddress::GPPUB
426                },
427                pin.pin,
428            )?;
429        }
430        Ok(OutputPin { pin })
431    }
432
433    /// Set the state of the pin.
434    pub fn write(&self, level: Level) -> Result<()> {
435        let gpio = match self.pin.port {
436            Port::GpioA => RegisterAddress::GPIOA,
437            Port::GpioB => RegisterAddress::GPIOB,
438        };
439        let mcp23s17_state = self.pin.mcp23s17_state.borrow();
440        match level {
441            Level::Low => mcp23s17_state.clear_bit(gpio, self.pin.pin),
442            Level::High => mcp23s17_state.set_bit(gpio, self.pin.pin),
443        }
444    }
445
446    /// Set the output to `Level::High`.
447    pub fn set_high(&self) -> Result<()> {
448        self.write(Level::High)
449    }
450
451    /// Set the output to `Level::Low`.
452    pub fn set_low(&self) -> Result<()> {
453        self.write(Level::Low)
454    }
455
456    // Reading from an OutputPin is valid.
457    impl_input!();
458}