rppal_mcp23s17/
lib.rs

1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use std::{cell::RefCell, fmt, rc::Rc, result};
5
6use bitflags::bitflags;
7use log::{debug, error};
8use rppal::spi::SlaveSelect;
9
10/// Re-exports of [rppal::spi] module APIs used on this crate's APIs. Renamed to make
11/// sure that the intended usage is clear.
12pub use rppal::spi::{Bus as SpiBus, Mode as SpiMode};
13
14// Run with mock hardware in testing.
15#[cfg(any(test, feature = "mockspi"))]
16use mock_spi::MockSpi;
17#[cfg(not(any(test, feature = "mockspi")))]
18use rppal::spi::Spi;
19
20use thiserror::Error;
21
22pub mod pin;
23pub use self::pin::{InputPin, InterruptMode, Level, OutputPin, Pin};
24
25//--------------------------------------------------------------------------------------
26/// The hardware address of the device - three bits.
27///
28/// MCP23S17 is a client SPI device. The client address contains four fixed bits and
29/// three user-defined hardware address bits (pins `A2`, `A1` and `A0`), if enabled via
30/// [`IOCON::HAEN`] with the read/write bit filling out the control byte.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
32pub struct HardwareAddress(u8);
33
34impl HardwareAddress {
35    /// Hardware address space is three bits wide so 0-7 are valid.
36    pub const MAX_HARDWARE_ADDRESS: u8 = 7;
37
38    /// Create a HardwareAddress bounds-checking that it is valid.
39    pub fn new(address: u8) -> Result<Self> {
40        if address <= Self::MAX_HARDWARE_ADDRESS {
41            Ok(Self(address))
42        } else {
43            Err(Mcp23s17Error::HardwareAddressBoundsError(address))
44        }
45    }
46}
47
48impl fmt::Display for HardwareAddress {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        fmt::Display::fmt(&format!("{}", self.0), f)
51    }
52}
53
54impl TryFrom<u8> for HardwareAddress {
55    type Error = Mcp23s17Error;
56
57    fn try_from(value: u8) -> Result<Self> {
58        HardwareAddress::new(value)
59    }
60}
61
62impl From<HardwareAddress> for u8 {
63    fn from(addr: HardwareAddress) -> Self {
64        addr.0
65    }
66}
67
68//--------------------------------------------------------------------------------------
69/// The direction of the operation on the SPI bus.
70#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
71#[repr(u8)]
72enum SpiCommand {
73    Write = 0,
74    Read = 1,
75}
76
77impl fmt::Display for SpiCommand {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match *self {
80            SpiCommand::Read => fmt::Display::fmt("Read", f),
81            SpiCommand::Write => fmt::Display::fmt("Write", f),
82        }
83    }
84}
85
86//--------------------------------------------------------------------------------------
87/// The register address within the device.
88///
89/// Note that this follows the "interleaved" format for the register addresses so that
90/// the [`IOCON::BANK`] bit of [`IOCON`][`RegisterAddress::IOCON`] register must be set
91/// to 0 ([`IOCON::BANK_OFF`]).
92#[allow(clippy::upper_case_acronyms)]
93#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
94#[repr(u8)]
95pub enum RegisterAddress {
96    /// I/O direction A
97    IODIRA = 0x0,
98    /// I/O direction B
99    IODIRB = 0x1,
100    /// I/O polarity A
101    IPOLA = 0x2,
102    /// I/O polarity B
103    IPOLB = 0x3,
104    /// interrupt enable A
105    GPINTENA = 0x4,
106    /// interrupt enable B
107    GPINTENB = 0x5,
108    /// register default value A (interrupts)
109    DEFVALA = 0x6,
110    /// register default value B (interrupts)
111    DEFVALB = 0x7,
112    /// interrupt control A
113    INTCONA = 0x8,
114    /// interrupt control B
115    INTCONB = 0x9,
116    /// I/O config (also at 0xB)
117    IOCON = 0xA,
118    /// I/O config (duplicate)
119    IOCON2 = 0xB,
120    /// port A pull-ups
121    GPPUA = 0xC,
122    /// port B pull-ups
123    GPPUB = 0xD,
124    /// interrupt flag A (where the interrupt came from)
125    INTFA = 0xE,
126    /// interrupt flag B
127    INTFB = 0xF,
128    /// interrupt capture A (value at interrupt is saved here)
129    INTCAPA = 0x10,
130    /// interrupt capture B
131    INTCAPB = 0x11,
132    /// port A
133    GPIOA = 0x12,
134    /// port B
135    GPIOB = 0x13,
136    /// output latch A
137    OLATA = 0x14,
138    /// output latch B
139    OLATB = 0x15,
140}
141
142/// Total size of the MCP27S17 register space.
143impl RegisterAddress {
144    /// Total number of registers defined within the MCP23S17.
145    pub const LENGTH: usize = 0x16;
146}
147
148impl From<RegisterAddress> for u8 {
149    fn from(address: RegisterAddress) -> Self {
150        address as u8
151    }
152}
153
154impl TryFrom<usize> for RegisterAddress {
155    type Error = Mcp23s17Error;
156
157    fn try_from(value: usize) -> Result<Self> {
158        match value {
159            x if x == RegisterAddress::IODIRA as usize => Ok(RegisterAddress::IODIRA),
160            x if x == RegisterAddress::IODIRB as usize => Ok(RegisterAddress::IODIRB),
161            x if x == RegisterAddress::IPOLA as usize => Ok(RegisterAddress::IPOLA),
162            x if x == RegisterAddress::IPOLB as usize => Ok(RegisterAddress::IPOLB),
163            x if x == RegisterAddress::GPINTENA as usize => Ok(RegisterAddress::GPINTENA),
164            x if x == RegisterAddress::GPINTENB as usize => Ok(RegisterAddress::GPINTENB),
165            x if x == RegisterAddress::DEFVALA as usize => Ok(RegisterAddress::DEFVALA),
166            x if x == RegisterAddress::DEFVALB as usize => Ok(RegisterAddress::DEFVALB),
167            x if x == RegisterAddress::INTCONA as usize => Ok(RegisterAddress::INTCONA),
168            x if x == RegisterAddress::INTCONB as usize => Ok(RegisterAddress::INTCONB),
169            x if x == RegisterAddress::IOCON as usize => Ok(RegisterAddress::IOCON),
170            x if x == RegisterAddress::IOCON2 as usize => Ok(RegisterAddress::IOCON2),
171            x if x == RegisterAddress::GPPUA as usize => Ok(RegisterAddress::GPPUA),
172            x if x == RegisterAddress::GPPUB as usize => Ok(RegisterAddress::GPPUB),
173            x if x == RegisterAddress::INTFA as usize => Ok(RegisterAddress::INTFA),
174            x if x == RegisterAddress::INTFB as usize => Ok(RegisterAddress::INTFB),
175            x if x == RegisterAddress::INTCAPA as usize => Ok(RegisterAddress::INTCAPA),
176            x if x == RegisterAddress::INTCAPB as usize => Ok(RegisterAddress::INTCAPB),
177            x if x == RegisterAddress::GPIOA as usize => Ok(RegisterAddress::GPIOA),
178            x if x == RegisterAddress::GPIOB as usize => Ok(RegisterAddress::GPIOB),
179            x if x == RegisterAddress::OLATA as usize => Ok(RegisterAddress::OLATA),
180            x if x == RegisterAddress::OLATB as usize => Ok(RegisterAddress::OLATB),
181            _ => Err(Mcp23s17Error::RegisterAddressBoundsError),
182        }
183    }
184}
185
186impl fmt::Display for RegisterAddress {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        match *self {
189            RegisterAddress::IODIRA => fmt::Display::fmt("IODIRA", f),
190            RegisterAddress::IODIRB => fmt::Display::fmt("IODIRB", f),
191            RegisterAddress::IPOLA => fmt::Display::fmt("IPOLA", f),
192            RegisterAddress::IPOLB => fmt::Display::fmt("IPOLB", f),
193            RegisterAddress::GPINTENA => fmt::Display::fmt("GPINTENA", f),
194            RegisterAddress::GPINTENB => fmt::Display::fmt("GPINTENB", f),
195            RegisterAddress::DEFVALA => fmt::Display::fmt("DEFVALA", f),
196            RegisterAddress::DEFVALB => fmt::Display::fmt("DEFVALB", f),
197            RegisterAddress::INTCONA => fmt::Display::fmt("INTCONA", f),
198            RegisterAddress::INTCONB => fmt::Display::fmt("INTCONB", f),
199            RegisterAddress::IOCON => fmt::Display::fmt("IOCON", f),
200            RegisterAddress::IOCON2 => fmt::Display::fmt("IOCON (2)", f),
201            RegisterAddress::GPPUA => fmt::Display::fmt("GPPUA", f),
202            RegisterAddress::GPPUB => fmt::Display::fmt("GPPUB", f),
203            RegisterAddress::INTFA => fmt::Display::fmt("INTFA", f),
204            RegisterAddress::INTFB => fmt::Display::fmt("INTFB", f),
205            RegisterAddress::INTCAPA => fmt::Display::fmt("INTCAPA", f),
206            RegisterAddress::INTCAPB => fmt::Display::fmt("INTCAPB", f),
207            RegisterAddress::GPIOA => fmt::Display::fmt("GPIOA", f),
208            RegisterAddress::GPIOB => fmt::Display::fmt("GPIOB", f),
209            RegisterAddress::OLATA => fmt::Display::fmt("OLATA", f),
210            RegisterAddress::OLATB => fmt::Display::fmt("OLATB", f),
211        }
212    }
213}
214
215//--------------------------------------------------------------------------------------
216
217bitflags! {
218    /// I/O Expander Configuration Register (`IOCON`) bit definitions.
219    #[derive(Debug, PartialEq)]
220    pub struct IOCON: u8 {
221        /// Controls how the registers are addressed:
222        ///
223        ///   1 = The registers associated with each port are separated into different
224        ///       banks. (*Not currently supported in this library.*)
225        ///
226        ///   0 = The registers are in the same bank (addresses are sequential).
227        const BANK = 0b1000_0000;
228
229        /// `INT` Pins Mirror bit:
230        ///
231        ///   1 = The `INT` pins are internally connected.
232        ///
233        ///   0 = The `INT` pins are not connected. `INTA` is associated with `PORTA`
234        ///       and `INTB` is associated with `PORTB`.
235        const MIRROR = 0b0100_0000;
236
237        /// Sequential Operation mode bit:
238        ///
239        ///   1 = Sequential operation disabled, address pointer does not increment.
240        ///
241        ///   0 = Sequential operation enabled, address pointer increments.
242        const SEQOP = 0b0010_0000;
243
244        /// Slew Rate control bit for SDA output:
245        ///
246        ///   1 = Slew rate control disabled.
247        ///
248        ///   0 = Slew rate control enabled.
249        const DISSLW = 0b0001_0000;
250
251        /// Hardware Address Enable bit:
252        ///
253        ///   1 = Enables the MCP23S17 address pins.
254        ///
255        ///   0 = Disables the MCP23S17 address pins.
256        const HAEN = 0b0000_1000;
257
258        /// Configures the `INT` pin as an open-drain output:
259        ///
260        ///   1 = Open-drain output (overrides the `INTPOL` bit.)
261        ///
262        ///   0 = Active driver output (`INTPOL` bit sets the polarity.)
263        const ODR = 0b0000_0100;
264
265        /// Sets the polarity of the `INT` output pin:
266        ///
267        ///   1 = Active-high.
268        ///
269        ///   0 = Active-low.
270        const INTPOL = 0b0000_0010;
271
272        /// Unimplemented: Read as 0.
273        const _NA = 0b0000_0001;
274    }
275}
276
277impl IOCON {
278    /// The registers associated with each port are separated into different
279    /// banks. (*Not currently supported in this library.*)
280    pub const BANK_ON: IOCON = IOCON::BANK;
281    /// The registers are in the same bank (addresses are interleaved sequentially).
282    pub const BANK_OFF: IOCON = IOCON::empty();
283    /// The `INT` pins are internally connected.
284    pub const MIRROR_ON: IOCON = IOCON::MIRROR;
285    /// The `INT` pins are not connected. `INTA` is associated with `PORTA` and `INTB`
286    /// is associated with `PORTB`.
287    pub const MIRROR_OFF: IOCON = IOCON::empty();
288    /// Sequential operation enabled, address pointer increments.
289    pub const SEQOP_ON: IOCON = IOCON::empty();
290    /// Sequential operation disabled, address pointer does not increment.
291    pub const SEQOP_OFF: IOCON = IOCON::SEQOP;
292    /// Slew rate control enabled.
293    pub const DISSLW_SLEW_RATE_CONTROLLED: IOCON = IOCON::empty();
294    /// Slew rate control disabled.
295    pub const DISSLW_SLEW_RATE_MAX: IOCON = IOCON::DISSLW;
296    /// Enables the MCP23S17 address pins.
297    pub const HAEN_ON: IOCON = IOCON::HAEN;
298    /// Disables the MCP23S17 address pins.
299    pub const HAEN_OFF: IOCON = IOCON::empty();
300    /// Open-drain output (overrides the `INTPOL` bit.)
301    pub const ODR_ON: IOCON = IOCON::ODR;
302    /// Active driver output (`INTPOL` bit sets the polarity.)
303    pub const ODR_OFF: IOCON = IOCON::empty();
304    /// Active-high.
305    pub const INTPOL_HIGH: IOCON = IOCON::INTPOL;
306    /// Active-low.
307    pub const INTPOL_LOW: IOCON = IOCON::empty();
308}
309
310/// The MCP23S17 has two GPIO ports, GPIOA and GPIOB.
311#[derive(Clone, Copy, Debug, PartialEq, Eq)]
312pub enum Port {
313    /// GPIO A
314    GpioA,
315    /// GPIO B
316    GpioB,
317}
318
319impl fmt::Display for Port {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        match *self {
322            Port::GpioA => fmt::Display::fmt("GPIO A", f),
323            Port::GpioB => fmt::Display::fmt("GPIO B", f),
324        }
325    }
326}
327
328//--------------------------------------------------------------------------------------
329
330/// Which `Chip Select` line to use on the SPI bus.
331///
332/// This is a purely cosmetic facade in front of [`SlaveSelect`] so that we use less
333/// contentious language in our public API. Whilst both `CS` and `SS` terms are used
334/// across existing documentation, the "Chip Select" term seems the one favoured by the
335/// [Linux documentation for spidev](https://www.kernel.org/doc/html/latest/spi/spidev.html).
336///
337/// Since the definitions are homologous, hopefully the compiler will make this a zero-cost
338/// abstraction and, if not, it will at least be extremely cheap.
339///
340/// Which Chip Select lines are used for the different busses on the Raspberry Pi is
341/// documented in detail as part of the [`rppal::spi`] module.
342#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
343#[allow(missing_docs)]
344pub enum ChipSelect {
345    Cs0 = 0,
346    Cs1 = 1,
347    Cs2 = 2,
348    Cs3 = 3,
349    Cs4 = 4,
350    Cs5 = 5,
351    Cs6 = 6,
352    Cs7 = 7,
353    Cs8 = 8,
354    Cs9 = 9,
355    Cs10 = 10,
356    Cs11 = 11,
357    Cs12 = 12,
358    Cs13 = 13,
359    Cs14 = 14,
360    Cs15 = 15,
361}
362
363impl From<SlaveSelect> for ChipSelect {
364    fn from(ss: SlaveSelect) -> Self {
365        match ss {
366            SlaveSelect::Ss0 => ChipSelect::Cs0,
367            SlaveSelect::Ss1 => ChipSelect::Cs1,
368            SlaveSelect::Ss2 => ChipSelect::Cs2,
369            SlaveSelect::Ss3 => ChipSelect::Cs3,
370            SlaveSelect::Ss4 => ChipSelect::Cs4,
371            SlaveSelect::Ss5 => ChipSelect::Cs5,
372            SlaveSelect::Ss6 => ChipSelect::Cs6,
373            SlaveSelect::Ss7 => ChipSelect::Cs7,
374            SlaveSelect::Ss8 => ChipSelect::Cs8,
375            SlaveSelect::Ss9 => ChipSelect::Cs9,
376            SlaveSelect::Ss10 => ChipSelect::Cs10,
377            SlaveSelect::Ss11 => ChipSelect::Cs11,
378            SlaveSelect::Ss12 => ChipSelect::Cs12,
379            SlaveSelect::Ss13 => ChipSelect::Cs13,
380            SlaveSelect::Ss14 => ChipSelect::Cs14,
381            SlaveSelect::Ss15 => ChipSelect::Cs15,
382        }
383    }
384}
385
386impl From<ChipSelect> for SlaveSelect {
387    fn from(cs: ChipSelect) -> Self {
388        match cs {
389            ChipSelect::Cs0 => SlaveSelect::Ss0,
390            ChipSelect::Cs1 => SlaveSelect::Ss1,
391            ChipSelect::Cs2 => SlaveSelect::Ss2,
392            ChipSelect::Cs3 => SlaveSelect::Ss3,
393            ChipSelect::Cs4 => SlaveSelect::Ss4,
394            ChipSelect::Cs5 => SlaveSelect::Ss5,
395            ChipSelect::Cs6 => SlaveSelect::Ss6,
396            ChipSelect::Cs7 => SlaveSelect::Ss7,
397            ChipSelect::Cs8 => SlaveSelect::Ss8,
398            ChipSelect::Cs9 => SlaveSelect::Ss9,
399            ChipSelect::Cs10 => SlaveSelect::Ss10,
400            ChipSelect::Cs11 => SlaveSelect::Ss11,
401            ChipSelect::Cs12 => SlaveSelect::Ss12,
402            ChipSelect::Cs13 => SlaveSelect::Ss13,
403            ChipSelect::Cs14 => SlaveSelect::Ss14,
404            ChipSelect::Cs15 => SlaveSelect::Ss15,
405        }
406    }
407}
408
409//--------------------------------------------------------------------------------------
410
411/// Errors that operation of the MCP23S17 can raise.
412#[derive(Error, Debug)]
413pub enum Mcp23s17Error {
414    /// Errors from the [SPI][rppal::spi::Spi].
415    #[error("SPI error")]
416    SpiError {
417        /// Underlying error source.
418        #[from]
419        source: rppal::spi::Error,
420    },
421
422    /// Attempt to access an MCP23S17 beyond the hardware address range
423    /// (0 - [`HardwareAddress::MAX_HARDWARE_ADDRESS`]).
424    #[error("Hardware address out of range")]
425    HardwareAddressBoundsError(u8),
426
427    /// Attempt to access an MCP23S17 register beyond the valid set defined in
428    /// [`RegisterAddress`].
429    #[error("Register address out of range")]
430    RegisterAddressBoundsError,
431
432    /// The [SPI][rppal::spi::Spi] reported a number of bytes transferred that did not
433    /// match expected length.
434    #[error("Unexpected number of bytes read")]
435    UnexpectedReadLength(usize),
436
437    /// Either a [`Pin`] was requested beyond the width of the byte-wide GPIO port or
438    /// the [`Pin`] has already been taken.
439    #[error("Pin out of range or already in use")]
440    PinNotAvailable(u8),
441
442    /// A bit operation was attempted on an MCP23S17 register on an invalid (greater
443    /// than 7) bit number.
444    #[error("Specified bit is out of range 0-7")]
445    RegisterBitBoundsError(u8),
446}
447
448/// Convenient wrapper for Result types can have [`Mcp23s17Error`]s.
449pub type Result<T> = result::Result<T, Mcp23s17Error>;
450
451/// Struct to represent the state of an MCP23S17 I/O Expander.
452///
453/// This is separated from the `Mcp23s17` itself so that the state can be shared between
454/// various `Pin` objects etc.
455///
456/// In testing environments this uses mocked hardware.
457#[derive(Debug)]
458struct Mcp23s17State {
459    #[cfg(not(any(test, feature = "mockspi")))]
460    spi: Spi,
461
462    #[cfg(any(test, feature = "mockspi"))]
463    spi: MockSpi,
464
465    /// The SPI bus the device is connected to.
466    spi_bus: SpiBus,
467
468    /// The hardware address on the bus.
469    address: HardwareAddress,
470
471    /// Keep track of which pins are in use on `GPIOA`.
472    gpioa_pins_taken: [bool; 8],
473
474    /// Keep track of which pins are in use on `GPIOB`.
475    gpiob_pins_taken: [bool; 8],
476}
477
478/// A structure that represents an instance of the MCP23S17 I/O expander chip.
479///
480/// This is the key entrypoint into the driver. The user instantiates an `Mcp23s17` and
481/// then uses [`Mcp23s17::get()`] to acquire an unconfigured GPIO [`Pin`]. The [`Pin`] is
482/// then configured by turning it into an [`InputPin`] or [`OutputPin`] through one of the
483/// `Pin::into_*()` methods.
484///
485/// ```no_run
486/// use rppal_mcp23s17::{ChipSelect, HardwareAddress, Level, Mcp23s17, SpiBus, SpiMode, Port, RegisterAddress};
487///
488/// // Create an instance of the driver for the device with the hardware address
489/// // (A2, A1, A0) of 0b000.
490/// let mcp23s17 = Mcp23s17::new(
491///     HardwareAddress::new(0).expect("Invalid hardware address"),
492///     SpiBus::Spi0,
493///     ChipSelect::Cs0,
494///     100_000,
495///     SpiMode::Mode0,
496/// )
497/// .expect("Failed to create MCP23S17");
498///
499/// // Take ownership of the pin on bit 4 of GPIOA and then convert it into an
500/// // OutputPin. Initialisation of the OutputPin ensures that the MCP23S17
501/// // registers (e.g. IODIRA) are set accordingly.
502/// let pin = mcp23s17.get(Port::GpioA, 4).expect("Failed to get Pin");
503/// ```
504#[derive(Debug)]
505pub struct Mcp23s17 {
506    mcp23s17_state: Rc<RefCell<Mcp23s17State>>,
507}
508
509impl Mcp23s17 {
510    /// Create an MCP23S17 instance with either real or mock hardware.
511    ///
512    /// For now testing always uses mock hardware, which precludes running unit tests on
513    /// real hardware. In practice, that's not much of a practical limitation when running
514    /// tests in local or CI cross-compilation environments. Testing on real hardware
515    /// focuses on integration testing with the full build.
516    pub fn new(
517        address: HardwareAddress,
518        spi_bus: SpiBus,
519        chip_select: ChipSelect,
520        spi_clock: u32,
521        spi_mode: SpiMode,
522    ) -> Result<Self> {
523        let mcp23s17_state = Mcp23s17State {
524            #[cfg(not(any(test, feature = "mockspi")))]
525            spi: Spi::new(spi_bus, chip_select.into(), spi_clock, spi_mode)?,
526            #[cfg(any(test, feature = "mockspi"))]
527            spi: MockSpi::new(spi_bus, chip_select, spi_clock, spi_mode),
528            spi_bus,
529            address,
530            gpioa_pins_taken: [false; 8],
531            gpiob_pins_taken: [false; 8],
532        };
533        Ok(Mcp23s17 {
534            mcp23s17_state: Rc::new(RefCell::new(mcp23s17_state)),
535        })
536    }
537
538    /// Read a byte from the MCP23S17 register at the address `register`.
539    pub fn read(&self, register: RegisterAddress) -> Result<u8> {
540        self.mcp23s17_state.borrow().read(register)
541    }
542
543    /// Write the byte `data` to the MCP23S17 register at address `register`.
544    pub fn write(&self, register: RegisterAddress, data: u8) -> Result<()> {
545        self.mcp23s17_state.borrow().write(register, data)
546    }
547
548    /// Set the specified bits in the register.
549    ///
550    /// Sets the bits by first reading the MCP23S17 register at `register` and then ORing
551    /// it with `data` before writing it back to `register`. Note the race-hazard if
552    /// there are multiple [`Mcp23s17`]s that can be writing to the same device.
553    pub fn set_bits(&self, register: RegisterAddress, data: u8) -> Result<()> {
554        self.mcp23s17_state.borrow().set_bits(register, data)
555    }
556
557    /// Clear the specified bits in the register.
558    ///
559    /// Clears the bits by first reading the MCP23S17 register at `register` and then ANDing
560    /// it with `!data` before writing it back to `register`. Note the race-hazard if
561    /// there are multiple [`Mcp23s17`]s that can be writing to the same device.
562    pub fn clear_bits(&self, register: RegisterAddress, data: u8) -> Result<()> {
563        self.mcp23s17_state.borrow().clear_bits(register, data)
564    }
565
566    /// Set the specified bit in the register.
567    ///
568    /// Sets the bit at position `bit` (0-7) by first reading the MCP23S17 register at
569    /// `register` and then ORing with a mask with the appropriate bit set before
570    /// writing it back to `register`. Note the race-hazard if there are multiple
571    /// [`Mcp23s17`]s that can be writing to the same device.
572    pub fn set_bit(&self, register: RegisterAddress, bit: u8) -> Result<()> {
573        self.mcp23s17_state.borrow().set_bit(register, bit)
574    }
575
576    /// Clear the specified bit in the register.
577    ///
578    /// Clears the bit at position `bit` (0-7) by first reading the MCP23S17 register at
579    /// `register` and then ANDing with a mask with the appropriate bit cleared before
580    /// writing it back to `register`. Note the race-hazard if there are multiple
581    /// [`Mcp23s17`]s that can be writing to the same device.
582    pub fn clear_bit(&self, register: RegisterAddress, bit: u8) -> Result<()> {
583        self.mcp23s17_state.borrow().clear_bit(register, bit)
584    }
585
586    /// Get the specified bit in the register.
587    ///
588    /// Gets the bit at position `bit` (0-7) by first reading the MCP23S17 register at
589    /// `register` and then ANDing with a mask with the appropriate bit set before
590    /// converting to a [`Level`].
591    pub fn get_bit(&self, register: RegisterAddress, bit: u8) -> Result<Level> {
592        self.mcp23s17_state.borrow().get_bit(register, bit)
593    }
594
595    /// Returns a [`Pin`] for the specified GPIO port and pin number.
596    ///
597    /// Retrieving a GPIO pin grants access to the pin through an owned [`Pin`] instance.
598    /// If the pin is already in use, or the pin number `pin` is greater than 7 then
599    /// `Mcp23s17::get()` returns `Err(`[`Mcp23s17Error::PinNotAvailable`]`)`.
600    ///
601    /// After a [`Pin`] (or a derived [`InputPin`] or [`OutputPin`]) goes out of scope,
602    /// it can be retrieved again through another `get()` call.
603    pub fn get(&self, port: Port, pin: u8) -> Result<Pin> {
604        if pin > 7 {
605            return Err(Mcp23s17Error::PinNotAvailable(pin));
606        }
607
608        // Returns an error if the pin is already taken, otherwise sets it to true here
609        // Since we are guaranteed to be single-threaded this doesn't need to worry
610        // about synchronisation or races.
611        match port {
612            Port::GpioA => {
613                if self.mcp23s17_state.borrow().gpioa_pins_taken[pin as usize] {
614                    return Err(Mcp23s17Error::PinNotAvailable(pin));
615                }
616                {
617                    self.mcp23s17_state.borrow_mut().gpioa_pins_taken[pin as usize] = true;
618                }
619                Ok(Pin::new(port, pin, self.mcp23s17_state.clone()))
620            }
621            Port::GpioB => {
622                if self.mcp23s17_state.borrow().gpiob_pins_taken[pin as usize] {
623                    return Err(Mcp23s17Error::PinNotAvailable(pin));
624                }
625                {
626                    self.mcp23s17_state.borrow_mut().gpiob_pins_taken[pin as usize] = true;
627                }
628                Ok(Pin::new(port, pin, self.mcp23s17_state.clone()))
629            }
630        }
631    }
632
633    /// Get the SPI bus that the MCP23S17 is accessed over.
634    pub fn get_spi_bus(&self) -> SpiBus {
635        self.mcp23s17_state.borrow().spi_bus
636    }
637
638    /// Get the hardware address of the MCP23S17.
639    pub fn get_hardware_address(&self) -> HardwareAddress {
640        self.mcp23s17_state.borrow().address
641    }
642
643    /// In testing environments provide an API to read the MockSpi registers.
644    #[cfg(any(feature = "mockspi", test))]
645    pub fn get_mock_data(&self, register: RegisterAddress) -> (u8, usize, usize) {
646        self.mcp23s17_state.borrow().spi.get_mock_data(register)
647    }
648
649    /// In testing environments provide an API to write the MockSpi registers.
650    #[cfg(any(feature = "mockspi", test))]
651    pub fn set_mock_data(&self, register: RegisterAddress, data: u8) {
652        self.mcp23s17_state
653            .borrow_mut()
654            .spi
655            .set_mock_data(register, data);
656    }
657}
658
659impl Mcp23s17State {
660    /// Read an MCP23S17 register.
661    fn read(&self, register: RegisterAddress) -> Result<u8> {
662        debug!("Read {register:?}");
663
664        let mut read_buffer = [0u8; 3];
665        let mut write_buffer = [0u8; 3];
666        write_buffer[0] = self.spi_control_byte(SpiCommand::Read);
667        write_buffer[1] = register as u8;
668
669        let read_length = self.spi.transfer(&mut read_buffer, &write_buffer)?;
670        if read_length != 3 {
671            error!("Unexpected number of bytes read ({read_length})");
672            return Err(Mcp23s17Error::UnexpectedReadLength(read_length));
673        }
674        debug!("Read value = 0x{:02x}", read_buffer[2]);
675        Ok(read_buffer[2])
676    }
677
678    /// Write an MCP23S17 register.
679    fn write(&self, register: RegisterAddress, data: u8) -> Result<()> {
680        debug!("Write 0x{data:02x} to {register:?}");
681
682        let mut read_buffer = [0u8; 3];
683        let mut write_buffer = [0u8; 3];
684        write_buffer[0] = self.spi_control_byte(SpiCommand::Write);
685        write_buffer[1] = register as u8;
686        write_buffer[2] = data;
687
688        let read_length = self.spi.transfer(&mut read_buffer, &write_buffer)?;
689        if read_length != 3 {
690            error!("Unexpected number of bytes read ({read_length})");
691            return Err(Mcp23s17Error::UnexpectedReadLength(read_length));
692        }
693        Ok(())
694    }
695
696    /// Set the specified bits in the register.
697    fn set_bits(&self, register: RegisterAddress, data: u8) -> Result<()> {
698        debug!("Set bits {data:08b} in {register:?}");
699        self.write(register, self.read(register)? | data)
700    }
701
702    /// Clear the specified bits in the register.
703    fn clear_bits(&self, register: RegisterAddress, data: u8) -> Result<()> {
704        debug!("Clear bits {data:08b} in {register:?}");
705        self.write(register, self.read(register)? & !data)
706    }
707
708    /// Set the specified bit (0-7) in the register.
709    fn set_bit(&self, register: RegisterAddress, bit: u8) -> Result<()> {
710        debug!("Set bit {bit} in {register:?}");
711        if bit > 7 {
712            error!("Set bit {bit} is out of range (0-7)!");
713            return Err(Mcp23s17Error::RegisterBitBoundsError(bit));
714        }
715        self.set_bits(register, 0x01 << bit)
716    }
717
718    /// Clear the specified bit (0-7) in the register.
719    fn clear_bit(&self, register: RegisterAddress, bit: u8) -> Result<()> {
720        debug!("Clear bit {bit} in {register:?}");
721        if bit > 7 {
722            error!("Clear bit {bit} is out of range (0-7)!");
723            return Err(Mcp23s17Error::RegisterBitBoundsError(bit));
724        }
725        self.clear_bits(register, 0x01 << bit)
726    }
727
728    /// Read the level of the specified bit (0-7).
729    fn get_bit(&self, register: RegisterAddress, bit: u8) -> Result<Level> {
730        debug!("Get bit {bit} in {register:?}");
731        if bit > 7 {
732            error!("Get bit {bit} is out of range (0-7)!");
733            return Err(Mcp23s17Error::RegisterBitBoundsError(bit));
734        }
735        Ok((self.read(register)? & (0x01 << bit)).into())
736    }
737
738    /// Calculate the control byte to use in a message.
739    ///
740    /// The client address contains four fixed bits and three user-defined hardware
741    /// address bits (if enabled via `IOCON::HAEN`) (pins A2, A1 and A0) with the
742    /// read/write bit filling out the control byte.
743    fn spi_control_byte(&self, command: SpiCommand) -> u8 {
744        let control_byte = 0x40 | self.address.0 << 1 | command as u8;
745        debug!(
746            "ControlByte: 0x{:02x} (Command='{:?}' address={:?})",
747            control_byte,
748            command,
749            u8::from(self.address)
750        );
751        control_byte
752    }
753}
754
755#[cfg(any(test, feature = "mockspi"))]
756pub mod mock_spi;
757
758#[cfg(test)]
759mod test;