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
10pub use rppal::spi::{Bus as SpiBus, Mode as SpiMode};
13
14#[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#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
32pub struct HardwareAddress(u8);
33
34impl HardwareAddress {
35 pub const MAX_HARDWARE_ADDRESS: u8 = 7;
37
38 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#[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#[allow(clippy::upper_case_acronyms)]
93#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
94#[repr(u8)]
95pub enum RegisterAddress {
96 IODIRA = 0x0,
98 IODIRB = 0x1,
100 IPOLA = 0x2,
102 IPOLB = 0x3,
104 GPINTENA = 0x4,
106 GPINTENB = 0x5,
108 DEFVALA = 0x6,
110 DEFVALB = 0x7,
112 INTCONA = 0x8,
114 INTCONB = 0x9,
116 IOCON = 0xA,
118 IOCON2 = 0xB,
120 GPPUA = 0xC,
122 GPPUB = 0xD,
124 INTFA = 0xE,
126 INTFB = 0xF,
128 INTCAPA = 0x10,
130 INTCAPB = 0x11,
132 GPIOA = 0x12,
134 GPIOB = 0x13,
136 OLATA = 0x14,
138 OLATB = 0x15,
140}
141
142impl RegisterAddress {
144 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
215bitflags! {
218 #[derive(Debug, PartialEq)]
220 pub struct IOCON: u8 {
221 const BANK = 0b1000_0000;
228
229 const MIRROR = 0b0100_0000;
236
237 const SEQOP = 0b0010_0000;
243
244 const DISSLW = 0b0001_0000;
250
251 const HAEN = 0b0000_1000;
257
258 const ODR = 0b0000_0100;
264
265 const INTPOL = 0b0000_0010;
271
272 const _NA = 0b0000_0001;
274 }
275}
276
277impl IOCON {
278 pub const BANK_ON: IOCON = IOCON::BANK;
281 pub const BANK_OFF: IOCON = IOCON::empty();
283 pub const MIRROR_ON: IOCON = IOCON::MIRROR;
285 pub const MIRROR_OFF: IOCON = IOCON::empty();
288 pub const SEQOP_ON: IOCON = IOCON::empty();
290 pub const SEQOP_OFF: IOCON = IOCON::SEQOP;
292 pub const DISSLW_SLEW_RATE_CONTROLLED: IOCON = IOCON::empty();
294 pub const DISSLW_SLEW_RATE_MAX: IOCON = IOCON::DISSLW;
296 pub const HAEN_ON: IOCON = IOCON::HAEN;
298 pub const HAEN_OFF: IOCON = IOCON::empty();
300 pub const ODR_ON: IOCON = IOCON::ODR;
302 pub const ODR_OFF: IOCON = IOCON::empty();
304 pub const INTPOL_HIGH: IOCON = IOCON::INTPOL;
306 pub const INTPOL_LOW: IOCON = IOCON::empty();
308}
309
310#[derive(Clone, Copy, Debug, PartialEq, Eq)]
312pub enum Port {
313 GpioA,
315 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#[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#[derive(Error, Debug)]
413pub enum Mcp23s17Error {
414 #[error("SPI error")]
416 SpiError {
417 #[from]
419 source: rppal::spi::Error,
420 },
421
422 #[error("Hardware address out of range")]
425 HardwareAddressBoundsError(u8),
426
427 #[error("Register address out of range")]
430 RegisterAddressBoundsError,
431
432 #[error("Unexpected number of bytes read")]
435 UnexpectedReadLength(usize),
436
437 #[error("Pin out of range or already in use")]
440 PinNotAvailable(u8),
441
442 #[error("Specified bit is out of range 0-7")]
445 RegisterBitBoundsError(u8),
446}
447
448pub type Result<T> = result::Result<T, Mcp23s17Error>;
450
451#[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 spi_bus: SpiBus,
467
468 address: HardwareAddress,
470
471 gpioa_pins_taken: [bool; 8],
473
474 gpiob_pins_taken: [bool; 8],
476}
477
478#[derive(Debug)]
505pub struct Mcp23s17 {
506 mcp23s17_state: Rc<RefCell<Mcp23s17State>>,
507}
508
509impl Mcp23s17 {
510 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 pub fn read(&self, register: RegisterAddress) -> Result<u8> {
540 self.mcp23s17_state.borrow().read(register)
541 }
542
543 pub fn write(&self, register: RegisterAddress, data: u8) -> Result<()> {
545 self.mcp23s17_state.borrow().write(register, data)
546 }
547
548 pub fn set_bits(&self, register: RegisterAddress, data: u8) -> Result<()> {
554 self.mcp23s17_state.borrow().set_bits(register, data)
555 }
556
557 pub fn clear_bits(&self, register: RegisterAddress, data: u8) -> Result<()> {
563 self.mcp23s17_state.borrow().clear_bits(register, data)
564 }
565
566 pub fn set_bit(&self, register: RegisterAddress, bit: u8) -> Result<()> {
573 self.mcp23s17_state.borrow().set_bit(register, bit)
574 }
575
576 pub fn clear_bit(&self, register: RegisterAddress, bit: u8) -> Result<()> {
583 self.mcp23s17_state.borrow().clear_bit(register, bit)
584 }
585
586 pub fn get_bit(&self, register: RegisterAddress, bit: u8) -> Result<Level> {
592 self.mcp23s17_state.borrow().get_bit(register, bit)
593 }
594
595 pub fn get(&self, port: Port, pin: u8) -> Result<Pin> {
604 if pin > 7 {
605 return Err(Mcp23s17Error::PinNotAvailable(pin));
606 }
607
608 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 pub fn get_spi_bus(&self) -> SpiBus {
635 self.mcp23s17_state.borrow().spi_bus
636 }
637
638 pub fn get_hardware_address(&self) -> HardwareAddress {
640 self.mcp23s17_state.borrow().address
641 }
642
643 #[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 #[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 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 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 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 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 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 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 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 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;