From fef59580494974bdf092c56f9fc4477f61dfe804 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Wed, 28 Jan 2026 12:38:06 -0800 Subject: [PATCH 1/3] initial addition of Pca9555 i/o expander driver. --- src/devices/expander_pca9555.rs | 119 ++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/devices/expander_pca9555.rs diff --git a/src/devices/expander_pca9555.rs b/src/devices/expander_pca9555.rs new file mode 100644 index 0000000..7eafd7f --- /dev/null +++ b/src/devices/expander_pca9555.rs @@ -0,0 +1,119 @@ +use crate::hal; +use hal::i2c::I2c; +use hal::prelude::*; +use stm32h7xx_hal::prelude::{ + _embedded_hal_blocking_i2c_Write as Write, _embedded_hal_blocking_i2c_WriteRead as WriteRead, +}; + +/// Data corresponding to a single PCA9555 I/O Expander +/// +/// In the future we can add: +/// - chained-device support (similar to pca9685) +/// - output configuration +/// - INT pin support for non-polling behavior +/// +/// For now this is a minimal driver for providing access +/// to 16 additional inptus. +pub struct Pca9555 { + address: u8, + states: [bool; 16], +} + +#[derive(Debug)] +pub enum I2cError { + Write(W), + WriteRead(WR), +} + +#[expect(type_alias_bounds)] +pub type WriteReadError = + I2cError<::Error, ::Error>; + +impl Pca9555 { + pub fn new(address: u8, bus: &mut W) -> Result> { + let dev = Pca9555 { + address, + states: Default::default(), + }; + + // Configuration + // Default config appears to be: + // configuration = input + // polarity = not-inverted + // So for our initial implementation.. no config is necessary + + // Fill internal data with current states on hardware. + self.update(); + + // Return + Ok(dev) + } + + pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { + // Register pairs flip automatically after reads. + // So we can basically just send the `InputPort0` command, + // and read two data bytes back every time we want new data + let mut raw_states = [u8; 2]; + self.read(bus, Register::InputPort0, raw_states)?; + + for i in 0..16 { + let raw_union: u16 = ((raw_states[0] as u16) | ((raw_states as u16) << 8)); + self.states[i] = (raw_union & (1 << i)) != 0; + } + Ok(()) + } + + fn write( + &self, + bus: &mut W, + register: Register, + data: u8, + ) -> Result<(), WriteReadError> { + self.write_raw(bus, register as u8, data) + } + + fn write_raw( + &self, + bus: &mut W, + register: u8, + data: u8, + ) -> Result<(), WriteReadError> { + // This is probably not what we need + bus.write(self.address, &[register, data]) + .map_err(I2cError::Write)?; + + Ok(()) + } + + fn read( + &self, + bus: &mut W, + register: Register, + buffer: &mut [u8], + ) -> Result<(), WriteReadError> { + self.read_raw(bus, register as u8, buffer) + } + + fn read_raw( + &self, + bus: &mut W, + register: u8, + buffer: &mut [u8], + ) -> Result<(), WriteReadError> { + bus.write_read(self.address, &[register], buffer) + .map_err(I2cError::WriteRead) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +enum Register { + InputPort0 = 0x00, + InputPort1 = 0x01, + OutputPort0 = 0x02, + OutputPort1 = 0x03, + PolarityInversion0 = 0x04, + PolarityInversion1 = 0x05, + ConfigurationPort0 = 0x06, + ConfigurationPort1 = 0x07, +} From 3c2be352000f7fb9ea41b8623af19242b913d14e Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Wed, 28 Jan 2026 15:40:12 -0800 Subject: [PATCH 2/3] pca9555 - fix obvious issues/missing bits --- src/devices/expander_pca9555.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/devices/expander_pca9555.rs b/src/devices/expander_pca9555.rs index 7eafd7f..fe2ca5d 100644 --- a/src/devices/expander_pca9555.rs +++ b/src/devices/expander_pca9555.rs @@ -31,8 +31,10 @@ pub type WriteReadError = impl Pca9555 { pub fn new(address: u8, bus: &mut W) -> Result> { + let base_address = 0x20; + let dev_addr = base_address + address; let dev = Pca9555 { - address, + address: dev_addr, states: Default::default(), }; @@ -43,12 +45,13 @@ impl Pca9555 { // So for our initial implementation.. no config is necessary // Fill internal data with current states on hardware. - self.update(); + dev.update(); - // Return Ok(dev) } + /// Polls the device, reading both of it's input registers to fill the + /// internal state data pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { // Register pairs flip automatically after reads. // So we can basically just send the `InputPort0` command, @@ -63,6 +66,12 @@ impl Pca9555 { Ok(()) } + /// Returns the current state recorded for a particular pin + pub fn get_state(self, index: u16) -> bool { + let idx = index.clamp(0, 16); + self.states[idx] + } + fn write( &self, bus: &mut W, From 61ac43b6a777ab69aaafb331e68891f2b2ab3da5 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Fri, 30 Jan 2026 12:35:28 -0800 Subject: [PATCH 3/3] potentially functional driver after latest changes/cleanup. --- src/devices/expander_pca9555.rs | 89 ++++++++++++--------------------- src/devices/mod.rs | 1 + 2 files changed, 34 insertions(+), 56 deletions(-) diff --git a/src/devices/expander_pca9555.rs b/src/devices/expander_pca9555.rs index fe2ca5d..abed2fc 100644 --- a/src/devices/expander_pca9555.rs +++ b/src/devices/expander_pca9555.rs @@ -2,7 +2,8 @@ use crate::hal; use hal::i2c::I2c; use hal::prelude::*; use stm32h7xx_hal::prelude::{ - _embedded_hal_blocking_i2c_Write as Write, _embedded_hal_blocking_i2c_WriteRead as WriteRead, + _embedded_hal_blocking_i2c_Read as Read, _embedded_hal_blocking_i2c_Write as Write, + _embedded_hal_blocking_i2c_WriteRead as WriteRead, }; /// Data corresponding to a single PCA9555 I/O Expander @@ -14,7 +15,8 @@ use stm32h7xx_hal::prelude::{ /// /// For now this is a minimal driver for providing access /// to 16 additional inptus. -pub struct Pca9555 { +pub struct Pca9555 { + i2c: I2c, address: u8, states: [bool; 16], } @@ -29,11 +31,15 @@ pub enum I2cError { pub type WriteReadError = I2cError<::Error, ::Error>; -impl Pca9555 { - pub fn new(address: u8, bus: &mut W) -> Result> { +impl Pca9555 +where + I2c: Write + WriteRead + Read, +{ + pub fn new(address: u8, mut bus: I2c) -> Self { let base_address = 0x20; - let dev_addr = base_address + address; - let dev = Pca9555 { + let dev_addr = base_address | (address & 0x07); + let mut dev = Pca9555 { + i2c: bus, address: dev_addr, states: Default::default(), }; @@ -44,74 +50,45 @@ impl Pca9555 { // polarity = not-inverted // So for our initial implementation.. no config is necessary + // Any attempts to write here will cause an infinite loop during if device is not + // present and serial callback hasn't been started. So maybe we don't do this here yet. + + // This was here to test why device wasn't sending ACK (but it wasn't on the PCB ...) + // Will remove these comments, and finish driver once I have appropriate HW + // crate::delay::CycleDelay::new().delay_ms(30u8); + // let _ = dev + // .i2c + // .write(dev.address, &[Register::ConfigurationPort0 as u8, 0xff]); + // Fill internal data with current states on hardware. - dev.update(); + // dev.update(); - Ok(dev) + dev } /// Polls the device, reading both of it's input registers to fill the /// internal state data - pub fn update(&mut self, bus: &mut W) -> Result<(), WriteReadError> { + // pub fn update(&mut self) -> Result<(), WriteReadError> { + pub fn update(&mut self) { // Register pairs flip automatically after reads. // So we can basically just send the `InputPort0` command, // and read two data bytes back every time we want new data - let mut raw_states = [u8; 2]; - self.read(bus, Register::InputPort0, raw_states)?; + let mut raw_states = [0u8; 2]; + let _ = self + .i2c + .write_read(self.address, &[Register::InputPort0 as u8], &mut raw_states); + let raw_union: u16 = (raw_states[0] as u16) | ((raw_states[1] as u16) << 8); for i in 0..16 { - let raw_union: u16 = ((raw_states[0] as u16) | ((raw_states as u16) << 8)); self.states[i] = (raw_union & (1 << i)) != 0; } - Ok(()) } /// Returns the current state recorded for a particular pin - pub fn get_state(self, index: u16) -> bool { - let idx = index.clamp(0, 16); + pub fn get_state(&self, index: usize) -> bool { + let idx = index.min(15); self.states[idx] } - - fn write( - &self, - bus: &mut W, - register: Register, - data: u8, - ) -> Result<(), WriteReadError> { - self.write_raw(bus, register as u8, data) - } - - fn write_raw( - &self, - bus: &mut W, - register: u8, - data: u8, - ) -> Result<(), WriteReadError> { - // This is probably not what we need - bus.write(self.address, &[register, data]) - .map_err(I2cError::Write)?; - - Ok(()) - } - - fn read( - &self, - bus: &mut W, - register: Register, - buffer: &mut [u8], - ) -> Result<(), WriteReadError> { - self.read_raw(bus, register as u8, buffer) - } - - fn read_raw( - &self, - bus: &mut W, - register: u8, - buffer: &mut [u8], - ) -> Result<(), WriteReadError> { - bus.write_read(self.address, &[register], buffer) - .map_err(I2cError::WriteRead) - } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/src/devices/mod.rs b/src/devices/mod.rs index 8f58ac2..e71d384 100644 --- a/src/devices/mod.rs +++ b/src/devices/mod.rs @@ -1,4 +1,5 @@ pub mod encoder; +pub mod expander_pca9555; pub mod led_driver_pca9685; pub mod mpr121; pub mod multiplexer;