port_expander/dev/
pcal6408a.rs

1//! Support for the `PCAL6408A` "8-bit I2C-bus and SMBus I/O port with interrupt"
2use crate::I2cExt;
3
4/// `PCAL6408A` "8-bit I2C-bus and SMBus I/O port with interrupt"
5pub struct Pcal6408a<M>(M);
6
7impl<I2C> Pcal6408a<core::cell::RefCell<Driver<I2C>>>
8where
9    I2C: crate::I2cBus,
10{
11    pub fn new(i2c: I2C, addr: bool) -> Self {
12        Self::with_mutex(i2c, addr)
13    }
14}
15
16impl<I2C, M> Pcal6408a<M>
17where
18    I2C: crate::I2cBus,
19    M: crate::PortMutex<Port = Driver<I2C>>,
20{
21    pub fn with_mutex(i2c: I2C, addr: bool) -> Self {
22        Self(crate::PortMutex::create(Driver::new(i2c, addr)))
23    }
24
25    pub fn split(&mut self) -> Parts<'_, I2C, M> {
26        Parts {
27            io0: crate::Pin::new(0, &self.0),
28            io1: crate::Pin::new(1, &self.0),
29            io2: crate::Pin::new(2, &self.0),
30            io3: crate::Pin::new(3, &self.0),
31            io4: crate::Pin::new(4, &self.0),
32            io5: crate::Pin::new(5, &self.0),
33            io6: crate::Pin::new(6, &self.0),
34            io7: crate::Pin::new(7, &self.0),
35        }
36    }
37}
38
39pub struct Parts<'a, I2C, M = core::cell::RefCell<Driver<I2C>>>
40where
41    I2C: crate::I2cBus,
42    M: crate::PortMutex<Port = Driver<I2C>>,
43{
44    pub io0: crate::Pin<'a, crate::mode::Input, M>,
45    pub io1: crate::Pin<'a, crate::mode::Input, M>,
46    pub io2: crate::Pin<'a, crate::mode::Input, M>,
47    pub io3: crate::Pin<'a, crate::mode::Input, M>,
48    pub io4: crate::Pin<'a, crate::mode::Input, M>,
49    pub io5: crate::Pin<'a, crate::mode::Input, M>,
50    pub io6: crate::Pin<'a, crate::mode::Input, M>,
51    pub io7: crate::Pin<'a, crate::mode::Input, M>,
52}
53
54#[allow(dead_code)]
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56enum Regs {
57    InputPort = 0x00,
58    OutputPort = 0x01,
59    PolarityInversion = 0x02,
60    Configuration = 0x03,
61    OutputDriveStrength0 = 0x40,
62    OutputDriveStrength1 = 0x41,
63    InputLatch = 0x42,
64    PullEnable = 0x43,
65    PullSelection = 0x44,
66    InterruptMask = 0x45,
67    InterruptStatus = 0x46,
68    OutputPortConfiguration = 0x47, // Bit 0: Push-Pull (0) or Open-Drain (1) for all Outputs
69}
70
71impl From<Regs> for u8 {
72    fn from(r: Regs) -> u8 {
73        r as u8
74    }
75}
76
77pub struct Driver<I2C> {
78    i2c: I2C,
79    out: Option<u8>,
80    addr: u8,
81}
82
83impl<I2C> Driver<I2C> {
84    pub fn new(i2c: I2C, addr: bool) -> Self {
85        let addr = 0x20 | (addr as u8);
86        Self {
87            i2c,
88            out: None,
89            addr,
90        }
91    }
92}
93
94impl<I2C: crate::I2cBus> Driver<I2C> {
95    fn get_out(&mut self) -> Result<u8, I2C::BusError> {
96        // Make sure the state of the OutputPort register is actually known instead of assumed to avoid glitches on reboot.
97        // This is necessary because the OutputPort register is written instead of updated.
98        match self.out {
99            Some(out) => Ok(out),
100            None => {
101                let out = self.i2c.read_reg(self.addr, Regs::OutputPort)?;
102                self.out = Some(out);
103                Ok(out)
104            }
105        }
106    }
107}
108
109impl<I2C: crate::I2cBus> crate::PortDriver for Driver<I2C> {
110    type Error = I2C::BusError;
111
112    fn set(&mut self, mask_high: u32, mask_low: u32) -> Result<(), Self::Error> {
113        let mut out = self.get_out()?;
114        out |= mask_high as u8;
115        out &= !mask_low as u8;
116        self.out = Some(out);
117        if (mask_high | mask_low) & 0x00FF != 0 {
118            self.i2c.write_reg(self.addr, Regs::OutputPort, out)?;
119        }
120        Ok(())
121    }
122
123    fn is_set(&mut self, mask_high: u32, mask_low: u32) -> Result<u32, Self::Error> {
124        let out = self.get_out()?;
125        Ok(((out as u32) & mask_high) | (!(out as u32) & mask_low))
126    }
127
128    fn get(&mut self, mask_high: u32, mask_low: u32) -> Result<u32, Self::Error> {
129        let io0 = if (mask_high | mask_low) & 0x00FF != 0 {
130            self.i2c.read_reg(self.addr, Regs::InputPort)?
131        } else {
132            0
133        };
134        let in_ = io0 as u32;
135        Ok((in_ & mask_high) | (!in_ & mask_low))
136    }
137}
138
139impl<I2C: crate::I2cBus> crate::PortDriverTotemPole for Driver<I2C> {
140    fn set_direction(
141        &mut self,
142        mask: u32,
143        dir: crate::Direction,
144        state: bool,
145    ) -> Result<(), Self::Error> {
146        if mask & 0xFF == 0 {
147            return Ok(());
148        }
149        // set state before switching direction to prevent glitch
150        if dir == crate::Direction::Output {
151            use crate::PortDriver;
152            if state {
153                self.set(mask, 0)?;
154            } else {
155                self.set(0, mask)?;
156            }
157        }
158
159        let (mask_set, mask_clear) = match dir {
160            crate::Direction::Input => (mask as u8, 0),
161            crate::Direction::Output => (0, mask as u8),
162        };
163        self.i2c
164            .update_reg(self.addr, Regs::Configuration, mask_set, mask_clear)?;
165        Ok(())
166    }
167}
168
169impl<I2C: crate::I2cBus> crate::PortDriverPolarity for Driver<I2C> {
170    fn set_polarity(&mut self, mask: u32, inverted: bool) -> Result<(), Self::Error> {
171        if mask & 0xFF == 0 {
172            return Ok(());
173        }
174        let (mask_set, mask_clear) = match inverted {
175            false => (0, mask as u8),
176            true => (mask as u8, 0),
177        };
178
179        self.i2c
180            .update_reg(self.addr, Regs::PolarityInversion, mask_set, mask_clear)?;
181        Ok(())
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use embedded_hal_mock::eh1::i2c as mock_i2c;
188
189    #[test]
190    fn pca6408a() {
191        let expectations = [
192            // pin setup io0
193            mock_i2c::Transaction::write_read(0x21, vec![0x01], vec![0xff]),
194            mock_i2c::Transaction::write(0x21, vec![0x01, 0xfe]),
195            mock_i2c::Transaction::write_read(0x21, vec![0x03], vec![0xff]),
196            mock_i2c::Transaction::write(0x21, vec![0x03, 0xfe]),
197            // pin setup io7
198            mock_i2c::Transaction::write(0x21, vec![0x01, 0x7e]),
199            mock_i2c::Transaction::write_read(0x21, vec![0x03], vec![0xfe]),
200            mock_i2c::Transaction::write(0x21, vec![0x03, 0x7e]),
201            mock_i2c::Transaction::write_read(0x21, vec![0x03], vec![0x7e]),
202            mock_i2c::Transaction::write(0x21, vec![0x03, 0xfe]),
203            // output io0
204            mock_i2c::Transaction::write(0x21, vec![0x01, 0x7f]),
205            mock_i2c::Transaction::write(0x21, vec![0x01, 0x7e]),
206            // input io7
207            mock_i2c::Transaction::write_read(0x21, vec![0x00], vec![0x80]),
208            mock_i2c::Transaction::write_read(0x21, vec![0x00], vec![0x7f]),
209            // polarity io7
210            mock_i2c::Transaction::write_read(0x21, vec![0x02], vec![0x00]),
211            mock_i2c::Transaction::write(0x21, vec![0x02, 0x80]),
212            mock_i2c::Transaction::write_read(0x21, vec![0x02], vec![0xff]),
213            mock_i2c::Transaction::write(0x21, vec![0x02, 0x7f]),
214        ];
215        let mut bus = mock_i2c::Mock::new(&expectations);
216
217        let mut pcal = super::Pcal6408a::new(bus.clone(), true);
218        let pcal_pins = pcal.split();
219
220        let mut io0 = pcal_pins.io0.into_output().unwrap();
221        let io7 = pcal_pins.io7.into_output().unwrap();
222        let io7 = io7.into_input().unwrap();
223
224        // output high and low
225        io0.set_high().unwrap();
226        io0.set_low().unwrap();
227
228        // input high and low
229        assert!(io7.is_high().unwrap());
230        assert!(io7.is_low().unwrap());
231
232        let mut io7 = io7.into_inverted().unwrap();
233        io7.set_inverted(false).unwrap();
234
235        bus.done();
236    }
237}