port_expander/dev/
pi4ioe5v6408.rs

1//! Support for the `PI4IOE5V6408` "Low-voltage Translating 8-bit I2C-bus I/O Expander"
2use crate::I2cExt;
3
4/// `PI4IOE5V6408` "Low-voltage Translating 8-bit I2C-bus I/O Expander"
5pub struct Pi4ioe5v6408<M>(M);
6
7impl<I2C> Pi4ioe5v6408<core::cell::RefCell<Driver<I2C>>>
8where
9    I2C: crate::I2cBus,
10{
11    /// Create a new driver for the `PI4IOE5V6408` "Low-voltage Translating 8-bit I2C-bus I/O Expander"
12    /// All pins will be configured as floating inputs
13    ///
14    /// # Arguments
15    /// - `i2c` - The I2C bus the device is connected to
16    /// - `addr` - The address of the device. The address is 0x43 if `addr` is `false` and 0x44 if `addr` is `true`
17    pub fn new(i2c: I2C, addr: bool) -> Result<Self, I2C::BusError> {
18        Self::with_mutex(i2c, addr)
19    }
20}
21
22impl<I2C, M> Pi4ioe5v6408<M>
23where
24    I2C: crate::I2cBus,
25    M: crate::PortMutex<Port = Driver<I2C>>,
26{
27    /// Create a new driver for the `PI4IOE5V6408` "Low-voltage Translating 8-bit I2C-bus I/O Expander"
28    /// with a mutex.
29    /// All pins will be configured as floating inputs
30    ///
31    /// # Arguments
32    /// - `i2c` - The I2C bus the device is connected to
33    /// - `addr` - The address of the device. The address is 0x43 if `addr` is `false` and 0x44 if `addr` is `true`
34    pub fn with_mutex(i2c: I2C, addr: bool) -> Result<Self, I2C::BusError> {
35        Ok(Self(crate::PortMutex::create(Driver::new(
36            i2c, addr, false,
37        )?)))
38    }
39
40    /// Create a new driver for the `PI4IOE5V6408` "Low-voltage Translating 8-bit I2C-bus I/O Expander"
41    /// retaining the previous (pullup/down and interrupt) configuration.
42    ///
43    /// Warning: Only use this constructor to recreate the driver for a chip that has been properly initialized before.
44    ///
45    /// # Arguments
46    /// - `i2c` - The I2C bus the device is connected to
47    /// - `addr` - The address of the device. The address is 0x43 if `addr` is `false` and 0x44 if `addr` is `true`
48    pub fn with_retained_pin_config(i2c: I2C, addr: bool) -> Result<Self, I2C::BusError> {
49        Ok(Self(crate::PortMutex::create(Driver::new(
50            i2c, addr, true,
51        )?)))
52    }
53
54    pub fn split<'a>(&'a mut self) -> Parts<'a, I2C, M> {
55        Parts {
56            io0: crate::Pin::new(0, &self.0),
57            io1: crate::Pin::new(1, &self.0),
58            io2: crate::Pin::new(2, &self.0),
59            io3: crate::Pin::new(3, &self.0),
60            io4: crate::Pin::new(4, &self.0),
61            io5: crate::Pin::new(5, &self.0),
62            io6: crate::Pin::new(6, &self.0),
63            io7: crate::Pin::new(7, &self.0),
64        }
65    }
66}
67
68pub struct Parts<'a, I2C, M = core::cell::RefCell<Driver<I2C>>>
69where
70    I2C: crate::I2cBus,
71    M: crate::PortMutex<Port = Driver<I2C>>,
72{
73    pub io0: crate::Pin<'a, crate::mode::Input, M>,
74    pub io1: crate::Pin<'a, crate::mode::Input, M>,
75    pub io2: crate::Pin<'a, crate::mode::Input, M>,
76    pub io3: crate::Pin<'a, crate::mode::Input, M>,
77    pub io4: crate::Pin<'a, crate::mode::Input, M>,
78    pub io5: crate::Pin<'a, crate::mode::Input, M>,
79    pub io6: crate::Pin<'a, crate::mode::Input, M>,
80    pub io7: crate::Pin<'a, crate::mode::Input, M>,
81}
82
83#[allow(dead_code)]
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85enum Regs {
86    DeviceIdControl = 0x01,
87    IODirection = 0x03,
88    OutputPort = 0x05,
89    OutputHighImpedance = 0x07,
90    InputDefaultState = 0x09,
91    PullUpPullDownEnable = 0x0b,
92    PullUpPullDownSelection = 0x0d,
93    InputStatusRegister = 0x0f,
94    InterruptMaskRegister = 0x11,
95    InterruptStatusRegister = 0x13,
96}
97
98impl From<Regs> for u8 {
99    fn from(r: Regs) -> u8 {
100        r as u8
101    }
102}
103
104pub struct Driver<I2C> {
105    i2c: I2C,
106    addr: u8,
107    out: u8,
108}
109
110impl<I2C: crate::I2cBus> Driver<I2C> {
111    pub fn new(mut i2c: I2C, addr: bool, retain_config: bool) -> Result<Self, I2C::BusError> {
112        let addr = if addr { 0x44 } else { 0x43 };
113
114        let device_id = i2c.read_reg(addr, Regs::DeviceIdControl)?; // Reset the "(Power on) Reset Interrupt" bit (and validate the device ID)
115        assert_eq!(
116            device_id & 0xFC, // Only check Manufacturer ID (0b101) and Firmware Revision (0b000)
117            0xA0,
118            "Unexpected Device ID for the PI4IOE5V6408: 0x{:02x}",
119            device_id
120        );
121
122        // The Reset values are the following:
123
124        // i2c.write_reg(addr, Regs::IODirection, 0)?; // All pins as inputs
125        // i2c.write_reg(addr, Regs::OutputPort, 0)?; // Set all outputs to low
126        // i2c.write_reg(addr, Regs::OutputHighImpedance, 0xff)?; // Set high impedance mode on all outputs
127        // i2c.write_reg(addr, Regs::InputDefaultState, 0)?; // The default state of all inputs is 0
128        // i2c.write_reg(addr, Regs::PullUpPullDownEnable, 0xff)?; // Pull-Up/Pull-Down enabled on all inputs
129        // i2c.write_reg(addr, Regs::PullUpPullDownSelection, 0)?; // Pull-Downs on all inputs
130        // i2c.write_reg(addr, Regs::InterruptMaskRegister, 0)?; // Interrupts enabled on all inputs
131
132        let mut out = 0;
133
134        if retain_config {
135            out = i2c.read_reg(addr, Regs::OutputPort)?; // Read the current output state once
136        } else {
137            // First time this driver is initialized, after it has been reset: Change reset values we don't want
138            i2c.write_reg(addr, Regs::OutputHighImpedance, 0)?; // Disable high impedance mode on all outputs
139            i2c.write_reg(addr, Regs::InterruptMaskRegister, 0xff)?; // Disable interrupts on all inputs
140            i2c.write_reg(addr, Regs::PullUpPullDownEnable, 0)?; // Disable pull-up/pull-down on all inputs
141        }
142
143        Ok(Self { i2c, addr, out })
144    }
145}
146
147impl<I2C: crate::I2cBus> crate::PortDriver for Driver<I2C> {
148    type Error = I2C::BusError;
149
150    fn set(&mut self, mask_high: u32, mask_low: u32) -> Result<(), Self::Error> {
151        let previous = self.out;
152        self.out |= mask_high as u8;
153        self.out &= !mask_low as u8;
154        if self.out != previous {
155            self.i2c.write_reg(self.addr, Regs::OutputPort, self.out)
156        } else {
157            // don't do the transfer when nothing changed
158            Ok(())
159        }
160    }
161
162    fn is_set(&mut self, mask_high: u32, mask_low: u32) -> Result<u32, Self::Error> {
163        Ok(((self.out as u32) & mask_high) | (!(self.out as u32) & mask_low))
164    }
165
166    fn get(&mut self, mask_high: u32, mask_low: u32) -> Result<u32, Self::Error> {
167        let in_ = self.i2c.read_reg(self.addr, Regs::InputStatusRegister)? as u32;
168        Ok((in_ & mask_high) | (!in_ & mask_low))
169    }
170}
171
172impl<I2C: crate::I2cBus> crate::PortDriverTotemPole for Driver<I2C> {
173    fn set_direction(
174        &mut self,
175        mask: u32,
176        dir: crate::Direction,
177        state: bool,
178    ) -> Result<(), Self::Error> {
179        // set state before switching direction to prevent glitch
180        if dir == crate::Direction::Output {
181            use crate::PortDriver;
182            if state {
183                self.set(mask, 0)?;
184            } else {
185                self.set(0, mask)?;
186            }
187        }
188
189        let (mask_set, mask_clear) = match dir {
190            crate::Direction::Output => (mask as u8, 0), // Outputs are set to 1
191            crate::Direction::Input => (0, mask as u8),  // Inputs are set to 0
192        };
193        self.i2c
194            .update_reg(self.addr, Regs::IODirection, mask_set, mask_clear)
195    }
196}
197
198impl<I2C: crate::I2cBus> crate::PortDriverPullDown for Driver<I2C> {
199    fn set_pull_down(&mut self, mask: u32, enable: bool) -> Result<(), Self::Error> {
200        if enable {
201            self.i2c
202                .update_reg(self.addr, Regs::PullUpPullDownSelection, 0, mask as u8)?;
203            self.i2c
204                .update_reg(self.addr, Regs::PullUpPullDownEnable, mask as u8, 0)?;
205        } else {
206            self.i2c
207                .update_reg(self.addr, Regs::PullUpPullDownEnable, 0, mask as u8)?;
208        }
209        Ok(())
210    }
211}
212
213impl<I2C: crate::I2cBus> crate::PortDriverPullUp for Driver<I2C> {
214    fn set_pull_up(&mut self, mask: u32, enable: bool) -> Result<(), Self::Error> {
215        if enable {
216            self.i2c
217                .update_reg(self.addr, Regs::PullUpPullDownSelection, mask as u8, 0)?;
218            self.i2c
219                .update_reg(self.addr, Regs::PullUpPullDownEnable, mask as u8, 0)?;
220        } else {
221            self.i2c
222                .update_reg(self.addr, Regs::PullUpPullDownEnable, 0, mask as u8)?;
223        }
224        Ok(())
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use core::cell::RefCell;
231    use embedded_hal_mock::eh1::i2c as mock_i2c;
232
233    #[test]
234    fn pi4ioe5v6408() {
235        let expectations = [
236            // driver setup
237            mock_i2c::Transaction::write_read(0x43, vec![0x01], vec![0xa2]),
238            mock_i2c::Transaction::write(0x43, vec![0x07, 0b00000000]),
239            mock_i2c::Transaction::write(0x43, vec![0x11, 0b11111111]),
240            mock_i2c::Transaction::write(0x43, vec![0x0b, 0b00000000]),
241            // pin setup io0
242            mock_i2c::Transaction::write_read(0x43, vec![0x03], vec![0]),
243            mock_i2c::Transaction::write(0x43, vec![0x03, 0b00000001]),
244            // pin setup io1
245            mock_i2c::Transaction::write(0x43, vec![0x05, 0b00000010]),
246            mock_i2c::Transaction::write_read(0x43, vec![0x03], vec![0b00000001]),
247            mock_i2c::Transaction::write(0x43, vec![0x03, 0b00000011]),
248            // pin setup io0 as input
249            mock_i2c::Transaction::write_read(0x43, vec![0x03], vec![0b00000011]),
250            mock_i2c::Transaction::write(0x43, vec![0x03, 0b00000010]),
251            // io1 writes
252            mock_i2c::Transaction::write(0x43, vec![0x05, 0b00000000]),
253            mock_i2c::Transaction::write(0x43, vec![0x05, 0b00000010]),
254            mock_i2c::Transaction::write(0x43, vec![0x05, 0b00000000]),
255            // io0 reads
256            mock_i2c::Transaction::write_read(0x43, vec![0x0f], vec![0b00000001]),
257            mock_i2c::Transaction::write_read(0x43, vec![0x0f], vec![0b00000000]),
258            // io0 activate pull-up
259            mock_i2c::Transaction::write_read(0x43, vec![0x0d], vec![0b10101010]),
260            mock_i2c::Transaction::write(0x43, vec![0x0d, 0b10101011]),
261            mock_i2c::Transaction::write_read(0x43, vec![0x0b], vec![0b00001010]),
262            mock_i2c::Transaction::write(0x43, vec![0x0b, 0b00001011]),
263            // io0 disable pull-up
264            mock_i2c::Transaction::write_read(0x43, vec![0x0b], vec![0b00001011]),
265            mock_i2c::Transaction::write(0x43, vec![0x0b, 0b00001010]),
266            // io0 activate pull-down
267            mock_i2c::Transaction::write_read(0x43, vec![0x0d], vec![0b10101011]),
268            mock_i2c::Transaction::write(0x43, vec![0x0d, 0b10101010]),
269            mock_i2c::Transaction::write_read(0x43, vec![0x0b], vec![0b00001010]),
270            mock_i2c::Transaction::write(0x43, vec![0x0b, 0b00001011]),
271            // io0 disable pull-down
272            mock_i2c::Transaction::write_read(0x43, vec![0x0b], vec![0b00001011]),
273            mock_i2c::Transaction::write(0x43, vec![0x0b, 0b00001010]),
274        ];
275        let mut bus = mock_i2c::Mock::new(&expectations);
276
277        let mut pca = super::Pi4ioe5v6408::new(bus.clone(), false).unwrap();
278        let pca_pins = pca.split();
279
280        let io0 = pca_pins.io0.into_output().unwrap();
281        let mut io1 = pca_pins.io1.into_output_high().unwrap();
282
283        let mut io0 = io0.into_input().unwrap();
284
285        io1.set_low().unwrap();
286        io1.set_high().unwrap();
287        io1.toggle().unwrap();
288
289        assert!(io0.is_high().unwrap());
290        assert!(io0.is_low().unwrap());
291
292        io0.enable_pull_up(true).unwrap();
293        io0.enable_pull_up(false).unwrap();
294        io0.enable_pull_down(true).unwrap();
295        io0.enable_pull_down(false).unwrap();
296
297        bus.done();
298    }
299
300    #[test]
301    fn pi4ioe5v6408_retained() {
302        let expectations = [
303            // driver setup
304            mock_i2c::Transaction::write_read(0x44, vec![0x01], vec![0xa2]),
305            mock_i2c::Transaction::write_read(0x44, vec![0x05], vec![0b10101111]),
306            // pin setup io0
307            mock_i2c::Transaction::write(0x44, vec![0x05, 0b10101110]),
308            mock_i2c::Transaction::write_read(0x44, vec![0x03], vec![0]),
309            mock_i2c::Transaction::write(0x44, vec![0x03, 0b00000001]),
310            // pin setup io1
311            mock_i2c::Transaction::write_read(0x44, vec![0x03], vec![0b00000001]),
312            mock_i2c::Transaction::write(0x44, vec![0x03, 0b00000011]),
313            // io1 writes
314            mock_i2c::Transaction::write(0x44, vec![0x05, 0b10101100]),
315            mock_i2c::Transaction::write(0x44, vec![0x05, 0b10101110]),
316            mock_i2c::Transaction::write(0x44, vec![0x05, 0b10101100]),
317        ];
318        let mut bus = mock_i2c::Mock::new(&expectations);
319
320        let mut pca: super::Pi4ioe5v6408<RefCell<_>> =
321            super::Pi4ioe5v6408::with_retained_pin_config(bus.clone(), true).unwrap();
322        let pca_pins = pca.split();
323
324        let _io0 = pca_pins.io0.into_output().unwrap();
325        let mut io1 = pca_pins.io1.into_output_high().unwrap();
326
327        io1.set_low().unwrap();
328        io1.set_high().unwrap();
329        io1.toggle().unwrap();
330
331        bus.done();
332    }
333}