From ae995295e8e95dd0c91c2c289cec450b1a92a508 Mon Sep 17 00:00:00 2001 From: Kevin Kelley Date: Fri, 22 Apr 2022 18:34:52 -0700 Subject: [PATCH 1/5] Add support for MCP23017 --- src/dev/mcp23017.rs | 285 ++++++++++++++++++++++++++++++++++++++++++++ src/dev/mod.rs | 1 + src/lib.rs | 2 + 3 files changed, 288 insertions(+) create mode 100644 src/dev/mcp23017.rs diff --git a/src/dev/mcp23017.rs b/src/dev/mcp23017.rs new file mode 100644 index 0000000..f66f1f2 --- /dev/null +++ b/src/dev/mcp23017.rs @@ -0,0 +1,285 @@ +//! Support for the `MCP23017` "16-Bit I/O Expander with Serial Interface" +//! +//! Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf +//! +//! The MCP23017 offers two eight-bit GPIO ports. It has three +//! address pins, so eight devices can coexist on an I2C bus. +//! +//! Each port has an interrupt, which can be configured to work +//! together or independently. +//! +//! When passing 16-bit values to this driver, the upper byte corresponds to port +//! B (pins 7..0) and the lower byte corresponds to port B (pins 7..0). +use crate::I2cExt; + +/// `MCP23017` "16-Bit I/O Expander with Serial Interface" +pub struct Mcp23017(M); + +impl Mcp23017>> +where + I2C: crate::I2cBus, +{ + pub fn new(i2c: I2C, a0: bool, a1: bool, a2: bool) -> Self { + Self::with_mutex(i2c, a0, a1, a2) + } +} + +impl Mcp23017 +where + I2C: crate::I2cBus, + M: shared_bus::BusMutex>, +{ + pub fn with_mutex(i2c: I2C, a0: bool, a1: bool, a2: bool) -> Self { + Self(shared_bus::BusMutex::create(Driver::new(i2c, a0, a1, a2))) + } + + pub fn split<'a>(&'a mut self) -> Parts<'a, I2C, M> { + Parts { + gpa0: crate::Pin::new(0, &self.0), + gpa1: crate::Pin::new(1, &self.0), + gpa2: crate::Pin::new(2, &self.0), + gpa3: crate::Pin::new(3, &self.0), + gpa4: crate::Pin::new(4, &self.0), + gpa5: crate::Pin::new(5, &self.0), + gpa6: crate::Pin::new(6, &self.0), + gpa7: crate::Pin::new(7, &self.0), + gpb0: crate::Pin::new(8, &self.0), + gpb1: crate::Pin::new(9, &self.0), + gpb2: crate::Pin::new(10, &self.0), + gpb3: crate::Pin::new(11, &self.0), + gpb4: crate::Pin::new(12, &self.0), + gpb5: crate::Pin::new(13, &self.0), + gpb6: crate::Pin::new(14, &self.0), + gpb7: crate::Pin::new(15, &self.0), + } + } +} + +pub struct Parts<'a, I2C, M = shared_bus::NullMutex>> +where + I2C: crate::I2cBus, + M: shared_bus::BusMutex>, +{ + pub gpa0: crate::Pin<'a, crate::mode::Input, M>, + pub gpa1: crate::Pin<'a, crate::mode::Input, M>, + pub gpa2: crate::Pin<'a, crate::mode::Input, M>, + pub gpa3: crate::Pin<'a, crate::mode::Input, M>, + pub gpa4: crate::Pin<'a, crate::mode::Input, M>, + pub gpa5: crate::Pin<'a, crate::mode::Input, M>, + pub gpa6: crate::Pin<'a, crate::mode::Input, M>, + pub gpa7: crate::Pin<'a, crate::mode::Input, M>, + pub gpb0: crate::Pin<'a, crate::mode::Input, M>, + pub gpb1: crate::Pin<'a, crate::mode::Input, M>, + pub gpb2: crate::Pin<'a, crate::mode::Input, M>, + pub gpb3: crate::Pin<'a, crate::mode::Input, M>, + pub gpb4: crate::Pin<'a, crate::mode::Input, M>, + pub gpb5: crate::Pin<'a, crate::mode::Input, M>, + pub gpb6: crate::Pin<'a, crate::mode::Input, M>, + pub gpb7: crate::Pin<'a, crate::mode::Input, M>, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Regs { + // N.B.: These values are for BANK=0, which is the reset state of + // the chip (and this driver does not change). + // + // For all registers, the reset value is 0x00, except for + // IODIR{A,B} which are 0xFF (making all pins inputs) at reset. + // + // IODIR: input/output direction: 0=output; 1=input + // IPOL: input polarity: 0=register values match input pins; 1=opposite + // GPINTEN: interrupt-on-change: 0=disable; 1=enable + // DEFVAL: default values for interrupt-on-change + // INTCON: interrupt-on-change config: 0=compare to previous pin value; + // 1=compare to corresponding bit in DEFVAL + // IOCON: configuration register + // - Pin 7: BANK (which driver assumes stays 0) + // - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either + // port will cause both pins to activate + // - Pin 5: SEQOP: controls the incrementing function of the address pointer + // - Pin 4: DISSLW: disables slew rate control on SDA + // - Pin 3: HAEN: no effect on MCP23017 + // - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) + // or 1=open-drain outputs (overrides INTPOL) + // - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high + // - Pin 0: unused + // GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured + // as an input) + // INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt + // INTCAP: interrupt captured value: reflects value of each pin at the time that they + // caused an interrupt + // GPIO: reflects logic level on pins + // OLAT: output latches: sets state for pins configured as outputs + IODIRA = 0x00, + IPOLA = 0x02, + GPINTENA = 0x04, + DEFVALA = 0x06, + INTCONA = 0x08, + IOCONA = 0x0a, + GPPUA = 0x0c, + INTFA = 0x0e, + INTCAPA = 0x10, + GPIOA = 0x12, + OLATA = 0x14, + IODIRB = 0x01, + IPOLB = 0x03, + GPINTENB = 0x05, + DEFVALB = 0x07, + INTCONB = 0x09, + IOCONB = 0x0b, + GPPUB = 0x0d, + INTFB = 0x0f, + INTCAPB = 0x11, + GPIOB = 0x13, + OLATB = 0x15, +} + +impl From for u8 { + fn from(r: Regs) -> u8 { + r as u8 + } +} + +pub struct Driver { + i2c: I2C, + out: u16, + addr: u8, +} + +impl Driver { + pub fn new(i2c: I2C, a0: bool, a1: bool, a2: bool) -> Self { + let addr = 0x20 | ((a2 as u8) << 2) | ((a1 as u8) << 1) | (a0 as u8); + Self { + i2c, + out: 0xffff, + addr, + } + } +} + +impl crate::PortDriver for Driver { + type Error = I2C::BusError; + + fn set(&mut self, mask_high: u32, mask_low: u32) -> Result<(), Self::Error> { + self.out |= mask_high as u16; + self.out &= !mask_low as u16; + if (mask_high | mask_low) & 0x00FF != 0 { + self.i2c + .write_reg(self.addr, Regs::GPIOA, (self.out & 0xFF) as u8)?; + } + if (mask_high | mask_low) & 0xFF00 != 0 { + self.i2c + .write_reg(self.addr, Regs::GPIOB, (self.out >> 8) as u8)?; + } + Ok(()) + } + + fn is_set(&mut self, mask_high: u32, mask_low: u32) -> Result { + Ok(((self.out as u32) & mask_high) | (!(self.out as u32) & mask_low)) + } + + fn get(&mut self, mask_high: u32, mask_low: u32) -> Result { + let io0 = if (mask_high | mask_low) & 0x00FF != 0 { + self.i2c.read_reg(self.addr, Regs::GPIOA)? + } else { + 0 + }; + let io1 = if (mask_high | mask_low) & 0xFF00 != 0 { + self.i2c.read_reg(self.addr, Regs::GPIOB)? + } else { + 0 + }; + let in_ = ((io1 as u32) << 8) | io0 as u32; + Ok((in_ & mask_high) | (!in_ & mask_low)) + } +} + +impl crate::PortDriverTotemPole for Driver { + fn set_direction(&mut self, mask: u32, dir: crate::Direction) -> Result<(), Self::Error> { + let (mask_set, mask_clear) = match dir { + crate::Direction::Input => (mask as u16, 0), + crate::Direction::Output => (0, mask as u16), + }; + if mask & 0x00FF != 0 { + self.i2c.update_reg( + self.addr, + Regs::IODIRA, + (mask_set & 0xFF) as u8, + (mask_clear & 0xFF) as u8, + )?; + } + if mask & 0xFF00 != 0 { + self.i2c.update_reg( + self.addr, + Regs::IODIRB, + (mask_set >> 8) as u8, + (mask_clear >> 8) as u8, + )?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_hal_mock::i2c as mock_i2c; + + #[test] + fn mcp23017() { + let expectations = [ + // pin setup gpa0 + mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xff]), + mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]), + // pin setup gpa7 + mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xfe]), + mock_i2c::Transaction::write(0x22, vec![0x00, 0x7e]), + mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0x7e]), + mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]), + // pin setup gpb0 + mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xff]), + mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]), + // pin setup gpb7 + mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xfe]), + mock_i2c::Transaction::write(0x22, vec![0x01, 0x7e]), + mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0x7e]), + mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]), + // output gpa0, gpb0 + mock_i2c::Transaction::write(0x22, vec![0x12, 0xff]), + mock_i2c::Transaction::write(0x22, vec![0x12, 0xfe]), + mock_i2c::Transaction::write(0x22, vec![0x13, 0xff]), + mock_i2c::Transaction::write(0x22, vec![0x13, 0xfe]), + // input gpa7, gpb7 + mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x80]), + mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x7f]), + mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x80]), + mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x7f]), + ]; + let mut bus = mock_i2c::Mock::new(&expectations); + + let mut pca = super::Mcp23017::new(bus.clone(), false, true, false); + let pca_pins = pca.split(); + + let mut gpa0 = pca_pins.gpa0.into_output().unwrap(); + let gpa7 = pca_pins.gpa7.into_output().unwrap(); + let gpa7 = gpa7.into_input().unwrap(); + + let mut gpb0 = pca_pins.gpb0.into_output().unwrap(); + let gpb7 = pca_pins.gpb7.into_output().unwrap(); + let gpb7 = gpb7.into_input().unwrap(); + + // output high and low + gpa0.set_high().unwrap(); + gpa0.set_low().unwrap(); + gpb0.set_high().unwrap(); + gpb0.set_low().unwrap(); + + // input high and low + assert!(gpa7.is_high().unwrap()); + assert!(gpa7.is_low().unwrap()); + assert!(gpb7.is_high().unwrap()); + assert!(gpb7.is_low().unwrap()); + + bus.done(); + } +} diff --git a/src/dev/mod.rs b/src/dev/mod.rs index 471bae5..2d4008d 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -13,3 +13,4 @@ pub mod pcf8574; pub mod pcf8575; pub mod pi4ioe5v6408; pub mod tca6408a; +pub mod mcp23017; diff --git a/src/lib.rs b/src/lib.rs index dd3f7dc..5deed91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ //! - [`PCF8574`](Pcf8574) //! - [`PCF8575`](Pcf8575) //! - [`TCA6408A`](Tca6408a) +//! - [`MCP23017`](Mcp23017) //! //! ## Non-local sharing //! `port-expander` uses a custom trait for abstracting different kinds of mutexes: @@ -89,3 +90,4 @@ pub use dev::pcf8574::Pcf8574; pub use dev::pcf8574::Pcf8574a; pub use dev::pcf8575::Pcf8575; pub use dev::tca6408a::Tca6408a; +pub use dev::mcp23017::Mcp23017; From 56215e5f220efc1c3c353cf811641cb510cc114b Mon Sep 17 00:00:00 2001 From: Markus Kasten Date: Sun, 24 Mar 2024 18:13:27 +0100 Subject: [PATCH 2/5] mcp23x17: convert mcp23017 to a generic driver over I2C and SPI version --- src/bus.rs | 13 +- src/dev/mcp23017.rs | 285 -------------------------- src/dev/mcp23x17.rs | 482 ++++++++++++++++++++++++++++++++++++++++++++ src/dev/mod.rs | 2 +- src/lib.rs | 5 +- 5 files changed, 498 insertions(+), 289 deletions(-) delete mode 100644 src/dev/mcp23017.rs create mode 100644 src/dev/mcp23x17.rs diff --git a/src/bus.rs b/src/bus.rs index 7254c9c..f783dff 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,4 +1,4 @@ -use embedded_hal::i2c as hal_i2c; +use embedded_hal::{i2c as hal_i2c, spi as hal_spi}; /// Blanket trait for types implementing `i2c::I2c pub trait I2cBus: hal_i2c::I2c { @@ -56,3 +56,14 @@ impl I2cExt for I2C { Ok(buf[0]) } } + +pub trait SpiBus: hal_spi::SpiDevice { + type BusError: From<::Error>; +} + +impl SpiBus for T +where + T: hal_spi::SpiDevice, +{ + type BusError = E; +} diff --git a/src/dev/mcp23017.rs b/src/dev/mcp23017.rs deleted file mode 100644 index f66f1f2..0000000 --- a/src/dev/mcp23017.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! Support for the `MCP23017` "16-Bit I/O Expander with Serial Interface" -//! -//! Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf -//! -//! The MCP23017 offers two eight-bit GPIO ports. It has three -//! address pins, so eight devices can coexist on an I2C bus. -//! -//! Each port has an interrupt, which can be configured to work -//! together or independently. -//! -//! When passing 16-bit values to this driver, the upper byte corresponds to port -//! B (pins 7..0) and the lower byte corresponds to port B (pins 7..0). -use crate::I2cExt; - -/// `MCP23017` "16-Bit I/O Expander with Serial Interface" -pub struct Mcp23017(M); - -impl Mcp23017>> -where - I2C: crate::I2cBus, -{ - pub fn new(i2c: I2C, a0: bool, a1: bool, a2: bool) -> Self { - Self::with_mutex(i2c, a0, a1, a2) - } -} - -impl Mcp23017 -where - I2C: crate::I2cBus, - M: shared_bus::BusMutex>, -{ - pub fn with_mutex(i2c: I2C, a0: bool, a1: bool, a2: bool) -> Self { - Self(shared_bus::BusMutex::create(Driver::new(i2c, a0, a1, a2))) - } - - pub fn split<'a>(&'a mut self) -> Parts<'a, I2C, M> { - Parts { - gpa0: crate::Pin::new(0, &self.0), - gpa1: crate::Pin::new(1, &self.0), - gpa2: crate::Pin::new(2, &self.0), - gpa3: crate::Pin::new(3, &self.0), - gpa4: crate::Pin::new(4, &self.0), - gpa5: crate::Pin::new(5, &self.0), - gpa6: crate::Pin::new(6, &self.0), - gpa7: crate::Pin::new(7, &self.0), - gpb0: crate::Pin::new(8, &self.0), - gpb1: crate::Pin::new(9, &self.0), - gpb2: crate::Pin::new(10, &self.0), - gpb3: crate::Pin::new(11, &self.0), - gpb4: crate::Pin::new(12, &self.0), - gpb5: crate::Pin::new(13, &self.0), - gpb6: crate::Pin::new(14, &self.0), - gpb7: crate::Pin::new(15, &self.0), - } - } -} - -pub struct Parts<'a, I2C, M = shared_bus::NullMutex>> -where - I2C: crate::I2cBus, - M: shared_bus::BusMutex>, -{ - pub gpa0: crate::Pin<'a, crate::mode::Input, M>, - pub gpa1: crate::Pin<'a, crate::mode::Input, M>, - pub gpa2: crate::Pin<'a, crate::mode::Input, M>, - pub gpa3: crate::Pin<'a, crate::mode::Input, M>, - pub gpa4: crate::Pin<'a, crate::mode::Input, M>, - pub gpa5: crate::Pin<'a, crate::mode::Input, M>, - pub gpa6: crate::Pin<'a, crate::mode::Input, M>, - pub gpa7: crate::Pin<'a, crate::mode::Input, M>, - pub gpb0: crate::Pin<'a, crate::mode::Input, M>, - pub gpb1: crate::Pin<'a, crate::mode::Input, M>, - pub gpb2: crate::Pin<'a, crate::mode::Input, M>, - pub gpb3: crate::Pin<'a, crate::mode::Input, M>, - pub gpb4: crate::Pin<'a, crate::mode::Input, M>, - pub gpb5: crate::Pin<'a, crate::mode::Input, M>, - pub gpb6: crate::Pin<'a, crate::mode::Input, M>, - pub gpb7: crate::Pin<'a, crate::mode::Input, M>, -} - -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Regs { - // N.B.: These values are for BANK=0, which is the reset state of - // the chip (and this driver does not change). - // - // For all registers, the reset value is 0x00, except for - // IODIR{A,B} which are 0xFF (making all pins inputs) at reset. - // - // IODIR: input/output direction: 0=output; 1=input - // IPOL: input polarity: 0=register values match input pins; 1=opposite - // GPINTEN: interrupt-on-change: 0=disable; 1=enable - // DEFVAL: default values for interrupt-on-change - // INTCON: interrupt-on-change config: 0=compare to previous pin value; - // 1=compare to corresponding bit in DEFVAL - // IOCON: configuration register - // - Pin 7: BANK (which driver assumes stays 0) - // - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either - // port will cause both pins to activate - // - Pin 5: SEQOP: controls the incrementing function of the address pointer - // - Pin 4: DISSLW: disables slew rate control on SDA - // - Pin 3: HAEN: no effect on MCP23017 - // - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) - // or 1=open-drain outputs (overrides INTPOL) - // - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high - // - Pin 0: unused - // GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured - // as an input) - // INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt - // INTCAP: interrupt captured value: reflects value of each pin at the time that they - // caused an interrupt - // GPIO: reflects logic level on pins - // OLAT: output latches: sets state for pins configured as outputs - IODIRA = 0x00, - IPOLA = 0x02, - GPINTENA = 0x04, - DEFVALA = 0x06, - INTCONA = 0x08, - IOCONA = 0x0a, - GPPUA = 0x0c, - INTFA = 0x0e, - INTCAPA = 0x10, - GPIOA = 0x12, - OLATA = 0x14, - IODIRB = 0x01, - IPOLB = 0x03, - GPINTENB = 0x05, - DEFVALB = 0x07, - INTCONB = 0x09, - IOCONB = 0x0b, - GPPUB = 0x0d, - INTFB = 0x0f, - INTCAPB = 0x11, - GPIOB = 0x13, - OLATB = 0x15, -} - -impl From for u8 { - fn from(r: Regs) -> u8 { - r as u8 - } -} - -pub struct Driver { - i2c: I2C, - out: u16, - addr: u8, -} - -impl Driver { - pub fn new(i2c: I2C, a0: bool, a1: bool, a2: bool) -> Self { - let addr = 0x20 | ((a2 as u8) << 2) | ((a1 as u8) << 1) | (a0 as u8); - Self { - i2c, - out: 0xffff, - addr, - } - } -} - -impl crate::PortDriver for Driver { - type Error = I2C::BusError; - - fn set(&mut self, mask_high: u32, mask_low: u32) -> Result<(), Self::Error> { - self.out |= mask_high as u16; - self.out &= !mask_low as u16; - if (mask_high | mask_low) & 0x00FF != 0 { - self.i2c - .write_reg(self.addr, Regs::GPIOA, (self.out & 0xFF) as u8)?; - } - if (mask_high | mask_low) & 0xFF00 != 0 { - self.i2c - .write_reg(self.addr, Regs::GPIOB, (self.out >> 8) as u8)?; - } - Ok(()) - } - - fn is_set(&mut self, mask_high: u32, mask_low: u32) -> Result { - Ok(((self.out as u32) & mask_high) | (!(self.out as u32) & mask_low)) - } - - fn get(&mut self, mask_high: u32, mask_low: u32) -> Result { - let io0 = if (mask_high | mask_low) & 0x00FF != 0 { - self.i2c.read_reg(self.addr, Regs::GPIOA)? - } else { - 0 - }; - let io1 = if (mask_high | mask_low) & 0xFF00 != 0 { - self.i2c.read_reg(self.addr, Regs::GPIOB)? - } else { - 0 - }; - let in_ = ((io1 as u32) << 8) | io0 as u32; - Ok((in_ & mask_high) | (!in_ & mask_low)) - } -} - -impl crate::PortDriverTotemPole for Driver { - fn set_direction(&mut self, mask: u32, dir: crate::Direction) -> Result<(), Self::Error> { - let (mask_set, mask_clear) = match dir { - crate::Direction::Input => (mask as u16, 0), - crate::Direction::Output => (0, mask as u16), - }; - if mask & 0x00FF != 0 { - self.i2c.update_reg( - self.addr, - Regs::IODIRA, - (mask_set & 0xFF) as u8, - (mask_clear & 0xFF) as u8, - )?; - } - if mask & 0xFF00 != 0 { - self.i2c.update_reg( - self.addr, - Regs::IODIRB, - (mask_set >> 8) as u8, - (mask_clear >> 8) as u8, - )?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use embedded_hal_mock::i2c as mock_i2c; - - #[test] - fn mcp23017() { - let expectations = [ - // pin setup gpa0 - mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xff]), - mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]), - // pin setup gpa7 - mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xfe]), - mock_i2c::Transaction::write(0x22, vec![0x00, 0x7e]), - mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0x7e]), - mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]), - // pin setup gpb0 - mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xff]), - mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]), - // pin setup gpb7 - mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xfe]), - mock_i2c::Transaction::write(0x22, vec![0x01, 0x7e]), - mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0x7e]), - mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]), - // output gpa0, gpb0 - mock_i2c::Transaction::write(0x22, vec![0x12, 0xff]), - mock_i2c::Transaction::write(0x22, vec![0x12, 0xfe]), - mock_i2c::Transaction::write(0x22, vec![0x13, 0xff]), - mock_i2c::Transaction::write(0x22, vec![0x13, 0xfe]), - // input gpa7, gpb7 - mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x80]), - mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x7f]), - mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x80]), - mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x7f]), - ]; - let mut bus = mock_i2c::Mock::new(&expectations); - - let mut pca = super::Mcp23017::new(bus.clone(), false, true, false); - let pca_pins = pca.split(); - - let mut gpa0 = pca_pins.gpa0.into_output().unwrap(); - let gpa7 = pca_pins.gpa7.into_output().unwrap(); - let gpa7 = gpa7.into_input().unwrap(); - - let mut gpb0 = pca_pins.gpb0.into_output().unwrap(); - let gpb7 = pca_pins.gpb7.into_output().unwrap(); - let gpb7 = gpb7.into_input().unwrap(); - - // output high and low - gpa0.set_high().unwrap(); - gpa0.set_low().unwrap(); - gpb0.set_high().unwrap(); - gpb0.set_low().unwrap(); - - // input high and low - assert!(gpa7.is_high().unwrap()); - assert!(gpa7.is_low().unwrap()); - assert!(gpb7.is_high().unwrap()); - assert!(gpb7.is_low().unwrap()); - - bus.done(); - } -} diff --git a/src/dev/mcp23x17.rs b/src/dev/mcp23x17.rs new file mode 100644 index 0000000..02d9dc6 --- /dev/null +++ b/src/dev/mcp23x17.rs @@ -0,0 +1,482 @@ +//! Support for the `MCP23017` and `MCP23S17` "16-Bit I/O Expander with Serial Interface" +//! +//! Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf +//! +//! The MCP23x17 offers two eight-bit GPIO ports. It has three +//! address pins, so eight devices can coexist on an I2C bus. +//! +//! Each port has an interrupt, which can be configured to work +//! together or independently. +//! +//! When passing 16-bit values to this driver, the upper byte corresponds to port +//! B (pins 7..0) and the lower byte corresponds to port B (pins 7..0). +use crate::I2cExt; + +/// `MCP23x17` "16-Bit I/O Expander with Serial Interface" with I2C or SPI interface +pub struct Mcp23x17(M); + +impl Mcp23x17>>> +where + I2C: crate::I2cBus, +{ + /// Create a new instance of the MCP23017 with I2C interface + pub fn new_mcp23017(bus: I2C, a0: bool, a1: bool, a2: bool) -> Self { + Self::with_mutex(Mcp23017Bus(bus), a0, a1, a2) + } +} + +impl Mcp23x17>>> +where + SPI: crate::SpiBus, +{ + /// Create a new instance of the MCP23S17 with SPI interface + pub fn new_mcp23s17(bus: SPI, a0: bool, a1: bool, a2: bool) -> Self { + Self::with_mutex(Mcp23S17Bus(bus), a0, a1, a2) + } +} + +impl Mcp23x17 +where + B: Mcp23x17Bus, + M: crate::PortMutex>, +{ + pub fn with_mutex(bus: B, a0: bool, a1: bool, a2: bool) -> Self { + Self(crate::PortMutex::create(Driver::new(bus, a0, a1, a2))) + } + + pub fn split<'a>(&'a mut self) -> Parts<'a, B, M> { + Parts { + gpa0: crate::Pin::new(0, &self.0), + gpa1: crate::Pin::new(1, &self.0), + gpa2: crate::Pin::new(2, &self.0), + gpa3: crate::Pin::new(3, &self.0), + gpa4: crate::Pin::new(4, &self.0), + gpa5: crate::Pin::new(5, &self.0), + gpa6: crate::Pin::new(6, &self.0), + gpa7: crate::Pin::new(7, &self.0), + gpb0: crate::Pin::new(8, &self.0), + gpb1: crate::Pin::new(9, &self.0), + gpb2: crate::Pin::new(10, &self.0), + gpb3: crate::Pin::new(11, &self.0), + gpb4: crate::Pin::new(12, &self.0), + gpb5: crate::Pin::new(13, &self.0), + gpb6: crate::Pin::new(14, &self.0), + gpb7: crate::Pin::new(15, &self.0), + } + } +} + +pub struct Parts<'a, B, M = core::cell::RefCell>> +where + B: Mcp23x17Bus, + M: crate::PortMutex>, +{ + pub gpa0: crate::Pin<'a, crate::mode::Input, M>, + pub gpa1: crate::Pin<'a, crate::mode::Input, M>, + pub gpa2: crate::Pin<'a, crate::mode::Input, M>, + pub gpa3: crate::Pin<'a, crate::mode::Input, M>, + pub gpa4: crate::Pin<'a, crate::mode::Input, M>, + pub gpa5: crate::Pin<'a, crate::mode::Input, M>, + pub gpa6: crate::Pin<'a, crate::mode::Input, M>, + pub gpa7: crate::Pin<'a, crate::mode::Input, M>, + pub gpb0: crate::Pin<'a, crate::mode::Input, M>, + pub gpb1: crate::Pin<'a, crate::mode::Input, M>, + pub gpb2: crate::Pin<'a, crate::mode::Input, M>, + pub gpb3: crate::Pin<'a, crate::mode::Input, M>, + pub gpb4: crate::Pin<'a, crate::mode::Input, M>, + pub gpb5: crate::Pin<'a, crate::mode::Input, M>, + pub gpb6: crate::Pin<'a, crate::mode::Input, M>, + pub gpb7: crate::Pin<'a, crate::mode::Input, M>, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Regs { + // N.B.: These values are for BANK=0, which is the reset state of + // the chip (and this driver does not change). + // + // For all registers, the reset value is 0x00, except for + // IODIR{A,B} which are 0xFF (making all pins inputs) at reset. + // + // IODIR: input/output direction: 0=output; 1=input + // IPOL: input polarity: 0=register values match input pins; 1=opposite + // GPINTEN: interrupt-on-change: 0=disable; 1=enable + // DEFVAL: default values for interrupt-on-change + // INTCON: interrupt-on-change config: 0=compare to previous pin value; + // 1=compare to corresponding bit in DEFVAL + // IOCON: configuration register + // - Pin 7: BANK (which driver assumes stays 0) + // - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either + // port will cause both pins to activate + // - Pin 5: SEQOP: controls the incrementing function of the address pointer + // - Pin 4: DISSLW: disables slew rate control on SDA + // - Pin 3: HAEN: no effect on MCP23017 + // - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) + // or 1=open-drain outputs (overrides INTPOL) + // - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high + // - Pin 0: unused + // GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured + // as an input) + // INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt + // INTCAP: interrupt captured value: reflects value of each pin at the time that they + // caused an interrupt + // GPIO: reflects logic level on pins + // OLAT: output latches: sets state for pins configured as outputs + IODIRA = 0x00, + IPOLA = 0x02, + GPINTENA = 0x04, + DEFVALA = 0x06, + INTCONA = 0x08, + IOCONA = 0x0a, + GPPUA = 0x0c, + INTFA = 0x0e, + INTCAPA = 0x10, + GPIOA = 0x12, + OLATA = 0x14, + IODIRB = 0x01, + IPOLB = 0x03, + GPINTENB = 0x05, + DEFVALB = 0x07, + INTCONB = 0x09, + IOCONB = 0x0b, + GPPUB = 0x0d, + INTFB = 0x0f, + INTCAPB = 0x11, + GPIOB = 0x13, + OLATB = 0x15, +} + +impl From for u8 { + fn from(r: Regs) -> u8 { + r as u8 + } +} + +pub struct Driver { + bus: B, + out: u16, + addr: u8, +} + +impl Driver { + pub fn new(bus: B, a0: bool, a1: bool, a2: bool) -> Self { + let addr = 0x20 | ((a2 as u8) << 2) | ((a1 as u8) << 1) | (a0 as u8); + Self { + bus, + out: 0xffff, + addr, + } + } +} + +impl crate::PortDriver for Driver { + type Error = B::BusError; + + fn set(&mut self, mask_high: u32, mask_low: u32) -> Result<(), Self::Error> { + self.out |= mask_high as u16; + self.out &= !mask_low as u16; + if (mask_high | mask_low) & 0x00FF != 0 { + self.bus + .write_reg(self.addr, Regs::GPIOA, (self.out & 0xFF) as u8)?; + } + if (mask_high | mask_low) & 0xFF00 != 0 { + self.bus + .write_reg(self.addr, Regs::GPIOB, (self.out >> 8) as u8)?; + } + Ok(()) + } + + fn is_set(&mut self, mask_high: u32, mask_low: u32) -> Result { + Ok(((self.out as u32) & mask_high) | (!(self.out as u32) & mask_low)) + } + + fn get(&mut self, mask_high: u32, mask_low: u32) -> Result { + let io0 = if (mask_high | mask_low) & 0x00FF != 0 { + self.bus.read_reg(self.addr, Regs::GPIOA)? + } else { + 0 + }; + let io1 = if (mask_high | mask_low) & 0xFF00 != 0 { + self.bus.read_reg(self.addr, Regs::GPIOB)? + } else { + 0 + }; + let in_ = ((io1 as u32) << 8) | io0 as u32; + Ok((in_ & mask_high) | (!in_ & mask_low)) + } +} + +impl crate::PortDriverTotemPole for Driver { + fn set_direction( + &mut self, + mask: u32, + dir: crate::Direction, + _state: bool, + ) -> Result<(), Self::Error> { + let (mask_set, mask_clear) = match dir { + crate::Direction::Input => (mask as u16, 0), + crate::Direction::Output => (0, mask as u16), + }; + if mask & 0x00FF != 0 { + self.bus.update_reg( + self.addr, + Regs::IODIRA, + (mask_set & 0xFF) as u8, + (mask_clear & 0xFF) as u8, + )?; + } + if mask & 0xFF00 != 0 { + self.bus.update_reg( + self.addr, + Regs::IODIRB, + (mask_set >> 8) as u8, + (mask_clear >> 8) as u8, + )?; + } + Ok(()) + } +} + +// We need these newtype wrappers since we can't implement `Mcp23x17Bus` for both `I2cBus` and `SpiBus` +// at the same time +pub struct Mcp23017Bus(I2C); +pub struct Mcp23S17Bus(SPI); + +/// Special -Bus trait for the Mcp23x17 since the SPI version is a bit special/weird in terms of writing +/// SPI registers, which can't necessarily be generialized for other devices. +pub trait Mcp23x17Bus { + type BusError; + + fn write_reg>(&mut self, addr: u8, reg: R, value: u8) + -> Result<(), Self::BusError>; + fn read_reg>(&mut self, addr: u8, reg: R) -> Result; + + fn update_reg>( + &mut self, + addr: u8, + reg: R, + mask_set: u8, + mask_clear: u8, + ) -> Result<(), Self::BusError> { + let reg = reg.into(); + let mut val = self.read_reg(addr, reg)?; + val |= mask_set; + val &= !mask_clear; + self.write_reg(addr, reg, val)?; + Ok(()) + } +} + +impl Mcp23x17Bus for Mcp23S17Bus { + type BusError = SPI::BusError; + + fn write_reg>( + &mut self, + addr: u8, + reg: R, + value: u8, + ) -> Result<(), Self::BusError> { + self.0.write(&[0x40 | addr << 1, reg.into(), value])?; + + Ok(()) + } + + fn read_reg>(&mut self, addr: u8, reg: R) -> Result { + let mut val = [0; 1]; + let write = [0x40 | addr << 1 | 0x1, reg.into()]; + let mut tx = [ + embedded_hal::spi::Operation::Write(&write), + embedded_hal::spi::Operation::Read(&mut val), + ]; + self.0.transaction(&mut tx)?; + + Ok(val[0]) + } +} + +impl Mcp23x17Bus for Mcp23017Bus { + type BusError = I2C::BusError; + + fn write_reg>( + &mut self, + addr: u8, + reg: R, + value: u8, + ) -> Result<(), Self::BusError> { + self.0.write_reg(addr, reg, value) + } + + fn read_reg>(&mut self, addr: u8, reg: R) -> Result { + self.0.read_reg(addr, reg) + } +} + +#[cfg(test)] +mod tests { + use embedded_hal_mock::eh1::{i2c as mock_i2c, spi as mock_spi}; + + #[test] + fn mcp23017() { + let expectations = [ + // pin setup gpa0 + mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xff]), + mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]), + // pin setup gpa7 + mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0xfe]), + mock_i2c::Transaction::write(0x22, vec![0x00, 0x7e]), + mock_i2c::Transaction::write_read(0x22, vec![0x00], vec![0x7e]), + mock_i2c::Transaction::write(0x22, vec![0x00, 0xfe]), + // pin setup gpb0 + mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xff]), + mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]), + // pin setup gpb7 + mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0xfe]), + mock_i2c::Transaction::write(0x22, vec![0x01, 0x7e]), + mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0x7e]), + mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]), + // output gpa0, gpb0 + mock_i2c::Transaction::write(0x22, vec![0x12, 0xff]), + mock_i2c::Transaction::write(0x22, vec![0x12, 0xfe]), + mock_i2c::Transaction::write(0x22, vec![0x13, 0xff]), + mock_i2c::Transaction::write(0x22, vec![0x13, 0xfe]), + // input gpa7, gpb7 + mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x80]), + mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x7f]), + mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x80]), + mock_i2c::Transaction::write_read(0x22, vec![0x13], vec![0x7f]), + ]; + let mut bus = mock_i2c::Mock::new(&expectations); + + let mut pca = super::Mcp23x17::new_mcp23017(bus.clone(), false, true, false); + let pca_pins = pca.split(); + + let mut gpa0 = pca_pins.gpa0.into_output().unwrap(); + let gpa7 = pca_pins.gpa7.into_output().unwrap(); + let gpa7 = gpa7.into_input().unwrap(); + + let mut gpb0 = pca_pins.gpb0.into_output().unwrap(); + let gpb7 = pca_pins.gpb7.into_output().unwrap(); + let gpb7 = gpb7.into_input().unwrap(); + + // output high and low + gpa0.set_high().unwrap(); + gpa0.set_low().unwrap(); + gpb0.set_high().unwrap(); + gpb0.set_low().unwrap(); + + // input high and low + assert!(gpa7.is_high().unwrap()); + assert!(gpa7.is_low().unwrap()); + assert!(gpb7.is_high().unwrap()); + assert!(gpb7.is_low().unwrap()); + + bus.done(); + } + + #[test] + fn mcp23s17() { + let expectations = [ + // pin setup gpa0 + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x00]), + mock_spi::Transaction::read(0xff), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x00, 0xfe]), + mock_spi::Transaction::transaction_end(), + // pin setup gpa7 + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x00]), + mock_spi::Transaction::read(0xfe), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x00, 0x7e]), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x00]), + mock_spi::Transaction::read(0x7e), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x00, 0xfe]), + mock_spi::Transaction::transaction_end(), + // pin setup gpb0 + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x01]), + mock_spi::Transaction::read(0xff), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x01, 0xfe]), + mock_spi::Transaction::transaction_end(), // pin setup gpb7 + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x01]), + mock_spi::Transaction::read(0xfe), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x01, 0x7e]), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x01]), + mock_spi::Transaction::read(0x7e), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x01, 0xfe]), + mock_spi::Transaction::transaction_end(), + // output gpa0, gpb0 + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x12, 0xff]), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x12, 0xfe]), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x13, 0xff]), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x44, 0x13, 0xfe]), + mock_spi::Transaction::transaction_end(), + // input gpa7, gpb7 + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x12]), + mock_spi::Transaction::read(0x80), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x12]), + mock_spi::Transaction::read(0x7f), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x13]), + mock_spi::Transaction::read(0x80), + mock_spi::Transaction::transaction_end(), + mock_spi::Transaction::transaction_start(), + mock_spi::Transaction::write_vec(vec![0x45, 0x13]), + mock_spi::Transaction::read(0x7f), + mock_spi::Transaction::transaction_end(), + ]; + let mut bus = mock_spi::Mock::new(&expectations); + + let mut pca = super::Mcp23x17::new_mcp23s17(bus.clone(), false, true, false); + let pca_pins = pca.split(); + + let mut gpa0 = pca_pins.gpa0.into_output().unwrap(); + let gpa7 = pca_pins.gpa7.into_output().unwrap(); + let gpa7 = gpa7.into_input().unwrap(); + + let mut gpb0 = pca_pins.gpb0.into_output().unwrap(); + let gpb7 = pca_pins.gpb7.into_output().unwrap(); + let gpb7 = gpb7.into_input().unwrap(); + + // output high and low + gpa0.set_high().unwrap(); + gpa0.set_low().unwrap(); + gpb0.set_high().unwrap(); + gpb0.set_low().unwrap(); + + // input high and low + assert!(gpa7.is_high().unwrap()); + assert!(gpa7.is_low().unwrap()); + assert!(gpb7.is_high().unwrap()); + assert!(gpb7.is_low().unwrap()); + + bus.done(); + } +} diff --git a/src/dev/mod.rs b/src/dev/mod.rs index 2d4008d..61c173f 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -4,6 +4,7 @@ //! the crate should be enough. pub mod max7321; +pub mod mcp23x17; pub mod pca9536; pub mod pca9538; pub mod pca9555; @@ -13,4 +14,3 @@ pub mod pcf8574; pub mod pcf8575; pub mod pi4ioe5v6408; pub mod tca6408a; -pub mod mcp23017; diff --git a/src/lib.rs b/src/lib.rs index 5deed91..6f78906 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ //! - [`PCF8574`](Pcf8574) //! - [`PCF8575`](Pcf8575) //! - [`TCA6408A`](Tca6408a) -//! - [`MCP23017`](Mcp23017) +//! - [`MCP23x17`](Mcp23x17) //! //! ## Non-local sharing //! `port-expander` uses a custom trait for abstracting different kinds of mutexes: @@ -73,6 +73,7 @@ pub use mutex::PortMutex; pub use pin::Pin; pub(crate) use bus::I2cExt; +pub(crate) use bus::SpiBus; pub(crate) use common::Direction; pub(crate) use common::PortDriver; pub(crate) use common::PortDriverPolarity; @@ -81,6 +82,7 @@ pub(crate) use common::PortDriverPullUp; pub(crate) use common::PortDriverTotemPole; pub use dev::max7321::Max7321; +pub use dev::mcp23x17::Mcp23x17; pub use dev::pca9536::Pca9536; pub use dev::pca9538::Pca9538; pub use dev::pca9555::Pca9555; @@ -90,4 +92,3 @@ pub use dev::pcf8574::Pcf8574; pub use dev::pcf8574::Pcf8574a; pub use dev::pcf8575::Pcf8575; pub use dev::tca6408a::Tca6408a; -pub use dev::mcp23017::Mcp23017; From d6f0256121bc9613565729e15c03f214bc833172 Mon Sep 17 00:00:00 2001 From: Markus Kasten Date: Sun, 24 Mar 2024 18:32:09 +0100 Subject: [PATCH 3/5] mcp23x17: implement suggested changes from pull request #2 [0] [0] https://github.com/Rahix/port-expander/pull/2#pullrequestreview-954757474 --- src/dev/mcp23x17.rs | 87 ++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/dev/mcp23x17.rs b/src/dev/mcp23x17.rs index 02d9dc6..a3b1bcb 100644 --- a/src/dev/mcp23x17.rs +++ b/src/dev/mcp23x17.rs @@ -9,7 +9,7 @@ //! together or independently. //! //! When passing 16-bit values to this driver, the upper byte corresponds to port -//! B (pins 7..0) and the lower byte corresponds to port B (pins 7..0). +//! B (pins 7..0) and the lower byte corresponds to port A (pins 7..0). use crate::I2cExt; /// `MCP23x17` "16-Bit I/O Expander with Serial Interface" with I2C or SPI interface @@ -91,58 +91,81 @@ where #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// N.B.: These values are for BANK=0, which is the reset state of +/// the chip (and this driver does not change). +/// +/// For all registers, the reset value is 0x00, except for +/// IODIR{A,B} which are 0xFF (making all pins inputs) at reset. enum Regs { - // N.B.: These values are for BANK=0, which is the reset state of - // the chip (and this driver does not change). - // - // For all registers, the reset value is 0x00, except for - // IODIR{A,B} which are 0xFF (making all pins inputs) at reset. - // - // IODIR: input/output direction: 0=output; 1=input - // IPOL: input polarity: 0=register values match input pins; 1=opposite - // GPINTEN: interrupt-on-change: 0=disable; 1=enable - // DEFVAL: default values for interrupt-on-change - // INTCON: interrupt-on-change config: 0=compare to previous pin value; - // 1=compare to corresponding bit in DEFVAL - // IOCON: configuration register - // - Pin 7: BANK (which driver assumes stays 0) - // - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either - // port will cause both pins to activate - // - Pin 5: SEQOP: controls the incrementing function of the address pointer - // - Pin 4: DISSLW: disables slew rate control on SDA - // - Pin 3: HAEN: no effect on MCP23017 - // - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) - // or 1=open-drain outputs (overrides INTPOL) - // - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high - // - Pin 0: unused - // GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured - // as an input) - // INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt - // INTCAP: interrupt captured value: reflects value of each pin at the time that they - // caused an interrupt - // GPIO: reflects logic level on pins - // OLAT: output latches: sets state for pins configured as outputs + /// IODIR: input/output direction: 0=output; 1=input IODIRA = 0x00, + /// IPOL: input polarity: 0=register values match input pins; 1=opposite IPOLA = 0x02, + /// GPINTEN: interrupt-on-change: 0=disable; 1=enable GPINTENA = 0x04, + /// DEFVAL: default values for interrupt-on-change DEFVALA = 0x06, + /// INTCON: interrupt-on-change config: 0=compare to previous pin value; + /// 1=compare to corresponding bit in DEFVAL INTCONA = 0x08, + /// IOCON: configuration register + /// - Pin 7: BANK (which driver assumes stays 0) + /// - Pin 6: MIRROR: if enabled, INTA is logically ORed; an interrupt on either + /// port will cause both pins to activate + /// - Pin 5: SEQOP: controls the incrementing function of the address pointer + /// - Pin 4: DISSLW: disables slew rate control on SDA + /// - Pin 3: HAEN: no effect on MCP23017 + /// - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) + /// or 1=open-drain outputs (overrides INTPOL) + /// - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high + /// - Pin 0: unused IOCONA = 0x0a, + /// GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured + /// as an input) GPPUA = 0x0c, + /// INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt INTFA = 0x0e, + /// INTCAP: interrupt captured value: reflects value of each pin at the time that they + /// caused an interrupt INTCAPA = 0x10, + /// GPIO: reflects logic level on pins GPIOA = 0x12, + /// OLAT: output latches: sets state for pins configured as outputs OLATA = 0x14, + /// IODIR: input/output direction: 0=output; 1=input IODIRB = 0x01, + /// IPOL: input polarity: 0=register values match input pins; 1=opposite IPOLB = 0x03, + /// GPINTEN: interrupt-on-change: 0=disable; 1=enable GPINTENB = 0x05, + /// DEFVAL: default values for interrupt-on-change DEFVALB = 0x07, + /// INTCON: interrupt-on-change config: 0=compare to previous pin value; + /// 1=compare to corresponding bit in DEFVAL INTCONB = 0x09, + /// IOCON: configuration register + /// - Pin 7: BANK (which driver assumes stays 0) + /// - Pin 6: MIRROR: if enabled, INTB is logically ORed; an interrupt on either + /// port will cause both pins to activate + /// - Pin 5: SEQOP: controls the incrementing function of the address pointer + /// - Pin 4: DISSLW: disables slew rate control on SDA + /// - Pin 3: HAEN: no effect on MCP23017 + /// - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) + /// or 1=open-drain outputs (overrides INTPOL) + /// - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high + /// - Pin 0: unused INTCONB = 0x09, IOCONB = 0x0b, + /// GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured + /// as an input) GPPUB = 0x0d, + /// INTF: interrupt flags: 0=no interrupt pending; 1=corresponding pin caused interrupt INTFB = 0x0f, + /// INTCAP: interrupt captured value: reflects value of each pin at the time that they + /// caused an interrupt INTCAPB = 0x11, + /// GPIO: reflects logic level on pins GPIOB = 0x13, + /// OLAT: output latches: sets state for pins configured as outputs OLATB = 0x15, } @@ -163,7 +186,7 @@ impl Driver { let addr = 0x20 | ((a2 as u8) << 2) | ((a1 as u8) << 1) | (a0 as u8); Self { bus, - out: 0xffff, + out: 0x0000, addr, } } From 3fc914f3b16375c20bc4ecbe5bb7ba0306cf2d98 Mon Sep 17 00:00:00 2001 From: Markus Kasten Date: Sun, 24 Mar 2024 18:52:44 +0100 Subject: [PATCH 4/5] mcp23x17: fix small error in IOCON doc comment --- src/dev/mcp23x17.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dev/mcp23x17.rs b/src/dev/mcp23x17.rs index a3b1bcb..515bcbf 100644 --- a/src/dev/mcp23x17.rs +++ b/src/dev/mcp23x17.rs @@ -110,11 +110,11 @@ enum Regs { INTCONA = 0x08, /// IOCON: configuration register /// - Pin 7: BANK (which driver assumes stays 0) - /// - Pin 6: MIRROR: if enabled, INTA is logically ORed; an interrupt on either + /// - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either /// port will cause both pins to activate /// - Pin 5: SEQOP: controls the incrementing function of the address pointer /// - Pin 4: DISSLW: disables slew rate control on SDA - /// - Pin 3: HAEN: no effect on MCP23017 + /// - Pin 3: HAEN: no effect on MCP23017, enables address pins on MCP23S17 /// - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) /// or 1=open-drain outputs (overrides INTPOL) /// - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high @@ -145,15 +145,15 @@ enum Regs { INTCONB = 0x09, /// IOCON: configuration register /// - Pin 7: BANK (which driver assumes stays 0) - /// - Pin 6: MIRROR: if enabled, INTB is logically ORed; an interrupt on either + /// - Pin 6: MIRROR: if enabled, INT{A,B} are logically ORed; an interrupt on either /// port will cause both pins to activate /// - Pin 5: SEQOP: controls the incrementing function of the address pointer /// - Pin 4: DISSLW: disables slew rate control on SDA - /// - Pin 3: HAEN: no effect on MCP23017 + /// - Pin 3: HAEN: no effect on MCP23017, enables address pins on MCP23S17 /// - Pin 2: ODR: interrupt pins are 0=active-driver outputs (INTPOL sets polarity) /// or 1=open-drain outputs (overrides INTPOL) /// - Pin 1: INTPOL: interrupt pin is 0=active-low or 1=active-high - /// - Pin 0: unused INTCONB = 0x09, + /// - Pin 0: unused IOCONB = 0x0b, /// GPPU: GPIO pull-ups: enables weak internal pull-ups on each pin (when configured /// as an input) From ee795c5fb6faea20e10c4729c6ba45b715f5e997 Mon Sep 17 00:00:00 2001 From: Markus Kasten Date: Sun, 24 Mar 2024 19:04:59 +0100 Subject: [PATCH 5/5] mcp23x17: fix test after setting default out to 0x000 and remove address pin support for MCP23S17 since there's currently no way to enable address pin support, we shouldn't offer setting address pins on the SPI variant --- src/dev/mcp23x17.rs | 54 ++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/dev/mcp23x17.rs b/src/dev/mcp23x17.rs index 515bcbf..e47d809 100644 --- a/src/dev/mcp23x17.rs +++ b/src/dev/mcp23x17.rs @@ -30,8 +30,8 @@ where SPI: crate::SpiBus, { /// Create a new instance of the MCP23S17 with SPI interface - pub fn new_mcp23s17(bus: SPI, a0: bool, a1: bool, a2: bool) -> Self { - Self::with_mutex(Mcp23S17Bus(bus), a0, a1, a2) + pub fn new_mcp23s17(bus: SPI) -> Self { + Self::with_mutex(Mcp23S17Bus(bus), false, false, false) } } @@ -358,10 +358,10 @@ mod tests { mock_i2c::Transaction::write_read(0x22, vec![0x01], vec![0x7e]), mock_i2c::Transaction::write(0x22, vec![0x01, 0xfe]), // output gpa0, gpb0 - mock_i2c::Transaction::write(0x22, vec![0x12, 0xff]), - mock_i2c::Transaction::write(0x22, vec![0x12, 0xfe]), - mock_i2c::Transaction::write(0x22, vec![0x13, 0xff]), - mock_i2c::Transaction::write(0x22, vec![0x13, 0xfe]), + mock_i2c::Transaction::write(0x22, vec![0x12, 0x01]), + mock_i2c::Transaction::write(0x22, vec![0x12, 0x00]), + mock_i2c::Transaction::write(0x22, vec![0x13, 0x01]), + mock_i2c::Transaction::write(0x22, vec![0x13, 0x00]), // input gpa7, gpb7 mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x80]), mock_i2c::Transaction::write_read(0x22, vec![0x12], vec![0x7f]), @@ -401,83 +401,83 @@ mod tests { let expectations = [ // pin setup gpa0 mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x00]), + mock_spi::Transaction::write_vec(vec![0x41, 0x00]), mock_spi::Transaction::read(0xff), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x00, 0xfe]), + mock_spi::Transaction::write_vec(vec![0x40, 0x00, 0xfe]), mock_spi::Transaction::transaction_end(), // pin setup gpa7 mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x00]), + mock_spi::Transaction::write_vec(vec![0x41, 0x00]), mock_spi::Transaction::read(0xfe), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x00, 0x7e]), + mock_spi::Transaction::write_vec(vec![0x40, 0x00, 0x7e]), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x00]), + mock_spi::Transaction::write_vec(vec![0x41, 0x00]), mock_spi::Transaction::read(0x7e), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x00, 0xfe]), + mock_spi::Transaction::write_vec(vec![0x40, 0x00, 0xfe]), mock_spi::Transaction::transaction_end(), // pin setup gpb0 mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x01]), + mock_spi::Transaction::write_vec(vec![0x41, 0x01]), mock_spi::Transaction::read(0xff), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x01, 0xfe]), + mock_spi::Transaction::write_vec(vec![0x40, 0x01, 0xfe]), mock_spi::Transaction::transaction_end(), // pin setup gpb7 mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x01]), + mock_spi::Transaction::write_vec(vec![0x41, 0x01]), mock_spi::Transaction::read(0xfe), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x01, 0x7e]), + mock_spi::Transaction::write_vec(vec![0x40, 0x01, 0x7e]), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x01]), + mock_spi::Transaction::write_vec(vec![0x41, 0x01]), mock_spi::Transaction::read(0x7e), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x01, 0xfe]), + mock_spi::Transaction::write_vec(vec![0x40, 0x01, 0xfe]), mock_spi::Transaction::transaction_end(), // output gpa0, gpb0 mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x12, 0xff]), + mock_spi::Transaction::write_vec(vec![0x40, 0x12, 0x01]), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x12, 0xfe]), + mock_spi::Transaction::write_vec(vec![0x40, 0x12, 0x00]), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x13, 0xff]), + mock_spi::Transaction::write_vec(vec![0x40, 0x13, 0x01]), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x44, 0x13, 0xfe]), + mock_spi::Transaction::write_vec(vec![0x40, 0x13, 0x00]), mock_spi::Transaction::transaction_end(), // input gpa7, gpb7 mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x12]), + mock_spi::Transaction::write_vec(vec![0x41, 0x12]), mock_spi::Transaction::read(0x80), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x12]), + mock_spi::Transaction::write_vec(vec![0x41, 0x12]), mock_spi::Transaction::read(0x7f), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x13]), + mock_spi::Transaction::write_vec(vec![0x41, 0x13]), mock_spi::Transaction::read(0x80), mock_spi::Transaction::transaction_end(), mock_spi::Transaction::transaction_start(), - mock_spi::Transaction::write_vec(vec![0x45, 0x13]), + mock_spi::Transaction::write_vec(vec![0x41, 0x13]), mock_spi::Transaction::read(0x7f), mock_spi::Transaction::transaction_end(), ]; let mut bus = mock_spi::Mock::new(&expectations); - let mut pca = super::Mcp23x17::new_mcp23s17(bus.clone(), false, true, false); + let mut pca = super::Mcp23x17::new_mcp23s17(bus.clone()); let pca_pins = pca.split(); let mut gpa0 = pca_pins.gpa0.into_output().unwrap();