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}