mcp23017/
lib.rs

1#![no_std]
2
3//! Manages an MCP23017, a 16-Bit I2C I/O Expander with Serial Interface module.
4//!
5//! This operates the chip in `IOCON.BANK=0` mode, i.e. the registers are mapped sequentially.
6//! This driver does not set `IOCON.BANK`, but the factory default is `0` and this driver does
7//! not change that value.
8//!
9//! See [the datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) for more
10//! information on the device.
11
12#![deny(
13    missing_docs,
14    missing_debug_implementations,
15    missing_copy_implementations,
16    trivial_casts,
17    trivial_numeric_casts,
18    unstable_features,
19    unused_import_braces,
20    unused_qualifications,
21    warnings
22)]
23#![allow(dead_code, non_camel_case_types)]
24#![allow(clippy::uninit_assumed_init, clippy::upper_case_acronyms)]
25
26extern crate embedded_hal as ehal;
27
28use ehal::blocking::i2c::{Write, WriteRead};
29
30/// The default I2C address of the MCP23017.
31const DEFAULT_ADDRESS: u8 = 0x20;
32
33/// Binary constants.
34const HIGH: bool = true;
35const LOW: bool = false;
36
37/// Struct for an MCP23017.
38/// See the crate-level documentation for general info on the device and the operation of this
39/// driver.
40#[derive(Clone, Copy, Debug)]
41pub struct MCP23017<I2C: Write + WriteRead> {
42    com: I2C,
43    /// The I2C slave address of this device.
44    pub address: u8,
45}
46
47/// Defines errors
48#[derive(Debug, Copy, Clone)]
49pub enum Error<E> {
50    /// Underlying bus error
51    BusError(E),
52    /// Interrupt pin not found
53    InterruptPinError,
54}
55
56impl<E> From<E> for Error<E> {
57    fn from(error: E) -> Self {
58        Error::BusError(error)
59    }
60}
61
62impl<I2C, E> MCP23017<I2C>
63where
64    I2C: WriteRead<Error = E> + Write<Error = E>,
65{
66    /// Creates an expander with the default configuration.
67    pub fn default(i2c: I2C) -> Result<MCP23017<I2C>, Error<E>>
68    where
69        I2C: Write<Error = E> + WriteRead<Error = E>,
70    {
71        MCP23017::new(i2c, DEFAULT_ADDRESS)
72    }
73
74    /// Creates an expander with specific address.
75    pub fn new(i2c: I2C, address: u8) -> Result<MCP23017<I2C>, Error<E>>
76    where
77        I2C: Write<Error = E> + WriteRead<Error = E>,
78    {
79        let chip = MCP23017 { com: i2c, address };
80
81        Ok(chip)
82    }
83
84    /// Initiates hardware with basic setup.
85    pub fn init_hardware(&mut self) -> Result<(), Error<E>> {
86        // set all inputs to defaults on port A and B
87        self.write_register(Register::IODIRA, 0xff)?;
88        self.write_register(Register::IODIRB, 0xff)?;
89
90        Ok(())
91    }
92
93    fn read_register(&mut self, reg: Register) -> Result<u8, E> {
94        let mut data: [u8; 1] = [0];
95        self.com.write_read(self.address, &[reg as u8], &mut data)?;
96        Ok(data[0])
97    }
98
99    fn read_double_register(&mut self, reg: Register) -> Result<[u8; 2], E> {
100        let mut buffer: [u8; 2] = [0; 2];
101        self.com
102            .write_read(self.address, &[reg as u8], &mut buffer)?;
103        Ok(buffer)
104    }
105
106    fn write_register(&mut self, reg: Register, byte: u8) -> Result<(), E> {
107        self.com.write(self.address, &[reg as u8, byte])
108    }
109
110    fn write_double_register(&mut self, reg: Register, word: u16) -> Result<(), E> {
111        let msb = (word >> 8) as u8;
112        self.com.write(self.address, &[reg as u8, word as u8, msb])
113    }
114
115    /// Updates a single bit in the register associated with the given pin.
116    /// This will read the register (`port_a_reg` for pins 0-7, `port_b_reg` for the other eight),
117    /// set the bit (as specified by the pin position within the register), and write the register
118    /// back to the device.
119    fn update_register_bit(
120        &mut self,
121        pin: u8,
122        pin_value: bool,
123        port_a_reg: Register,
124        port_b_reg: Register,
125    ) -> Result<(), E> {
126        let reg = register_for_pin(pin, port_a_reg, port_b_reg);
127        let bit = bit_for_pin(pin);
128        let reg_value = self.read_register(reg)?;
129        let reg_value_mod = write_bit(reg_value, bit, pin_value);
130        self.write_register(reg, reg_value_mod)
131    }
132
133    /// Sets the mode for a single pin to either `Mode::INPUT` or `Mode::OUTPUT`.
134    pub fn pin_mode(&mut self, pin: u8, pin_mode: PinMode) -> Result<(), E> {
135        self.update_register_bit(
136            pin,
137            pin_mode.bit_value(),
138            Register::IODIRA,
139            Register::IODIRB,
140        )
141    }
142
143    /// Sets all pins' modes to either `Mode::INPUT` or `Mode::OUTPUT`.
144    pub fn all_pin_mode(&mut self, pin_mode: PinMode) -> Result<(), E> {
145        self.write_register(Register::IODIRA, pin_mode.register_value())?;
146        self.write_register(Register::IODIRB, pin_mode.register_value())
147    }
148
149    /// Reads all 16 pins (port A and B) into a single 16 bit variable.
150    pub fn read_gpioab(&mut self) -> Result<u16, E> {
151        let buffer = self.read_double_register(Register::GPIOA)?;
152        Ok((buffer[0] as u16) << 8 | (buffer[1] as u16))
153    }
154
155    /// Reads a single port, A or B, and returns its current 8 bit value.
156    pub fn read_gpio(&mut self, port: Port) -> Result<u8, E> {
157        let reg = match port {
158            Port::GPIOA => Register::GPIOA,
159            Port::GPIOB => Register::GPIOB,
160        };
161        self.read_register(reg)
162    }
163
164    /// Writes all the pins with the value at the same time.
165    pub fn write_gpioab(&mut self, value: u16) -> Result<(), E> {
166        self.write_double_register(Register::GPIOA, value)
167    }
168
169    /// Writes all the pins of one port with the value at the same time.
170    pub fn write_gpio(&mut self, port: Port, value: u8) -> Result<(), E> {
171        let reg = match port {
172            Port::GPIOA => Register::GPIOA,
173            Port::GPIOB => Register::GPIOB,
174        };
175        self.write_register(reg, value)
176    }
177
178    /// Writes a single bit to a single pin.
179    /// This function internally reads from the output latch register (`OLATA`/`OLATB`) and writes
180    /// to the GPIO register.
181    pub fn digital_write(&mut self, pin: u8, value: bool) -> Result<(), E> {
182        let bit = bit_for_pin(pin);
183        // Read the current GPIO output latches.
184        let ol_register = register_for_pin(pin, Register::OLATA, Register::OLATB);
185        let gpio = self.read_register(ol_register)?;
186
187        // Set the pin.
188        let gpio_mod = write_bit(gpio, bit, value);
189
190        // Write the modified register.
191        let reg_gp = register_for_pin(pin, Register::GPIOA, Register::GPIOB);
192        self.write_register(reg_gp, gpio_mod)
193    }
194
195    /// Reads a single pin.
196    pub fn digital_read(&mut self, pin: u8) -> Result<bool, E> {
197        let bit = bit_for_pin(pin);
198        let reg = register_for_pin(pin, Register::GPIOA, Register::GPIOB);
199        let value = self.read_register(reg)?;
200        Ok(read_bit(value, bit))
201    }
202
203    /// Enables or disables the internal pull-up resistor for a single pin.
204    pub fn pull_up(&mut self, pin: u8, value: bool) -> Result<(), E> {
205        self.update_register_bit(pin, value, Register::GPPUA, Register::GPPUB)
206    }
207
208    /// Inverts the input polarity for a single pin.
209    /// This uses the `IPOLA` or `IPOLB` registers, see the datasheet for more information.
210    pub fn invert_input_polarity(&mut self, pin: u8, value: bool) -> Result<(), E> {
211        self.update_register_bit(pin, value, Register::IPOLA, Register::IPOLB)
212    }
213
214    /// Configures the interrupt system. both port A and B are assigned the same configuration.
215    /// mirroring will OR both INTA and INTB pins.
216    /// open_drain will set the INT pin to value or open drain.
217    /// polarity will set LOW or HIGH on interrupt.
218    /// Default values after Power On Reset are: (false, false, LOW)
219    pub fn setup_interrupts(
220        &mut self,
221        mirroring: bool,
222        open_drain: bool,
223        polarity: Polarity,
224    ) -> Result<(), E> {
225        // configure port A
226        self.setup_interrupt_port(Register::IOCONA, mirroring, open_drain, polarity)?;
227
228        // configure port B
229        self.setup_interrupt_port(Register::IOCONB, mirroring, open_drain, polarity)
230    }
231
232    fn setup_interrupt_port(
233        &mut self,
234        register: Register,
235        mirroring: bool,
236        open_drain: bool,
237        polarity: Polarity,
238    ) -> Result<(), E> {
239        let mut io_conf_value = self.read_register(register)?;
240        io_conf_value = write_bit(io_conf_value, 6, mirroring);
241        io_conf_value = write_bit(io_conf_value, 2, open_drain);
242        io_conf_value = write_bit(io_conf_value, 1, polarity.bit_value());
243        self.write_register(register, io_conf_value)
244    }
245
246    /// Sets up a pin for interrupt.
247    /// Note that the interrupt condition finishes when you read the information about
248    /// the port / value that caused the interrupt or you read the port itself.
249    pub fn setup_interrupt_pin(&mut self, pin: u8, int_mode: InterruptMode) -> Result<(), E> {
250        // set the pin interrupt control (0 means change, 1 means compare against given value)
251        self.update_register_bit(
252            pin,
253            int_mode != InterruptMode::CHANGE,
254            Register::INTCONA,
255            Register::INTCONB,
256        )?;
257
258        // in a RISING interrupt the default value is 0, interrupt is triggered when the pin goes to 1
259        // in a FALLING interrupt the default value is 1, interrupt is triggered when pin goes to 0
260        self.update_register_bit(
261            pin,
262            int_mode == InterruptMode::FALLING,
263            Register::DEFVALA,
264            Register::DEFVALB,
265        )?;
266
267        // enable the pin for interrupt
268        self.update_register_bit(pin, HIGH, Register::GPINTENA, Register::GPINTENB)
269    }
270
271    /// Get last interrupt pin
272    pub fn get_last_interrupt_pin(&mut self) -> Result<u8, Error<E>> {
273        // try port A
274        let intf_a = self.read_register(Register::INTFA)?;
275        for x in 0..8 {
276            if read_bit(intf_a, x) {
277                return Ok(x);
278            }
279        }
280
281        // try port B
282        let intf_b = self.read_register(Register::INTFB)?;
283        for x in 0..8 {
284            if read_bit(intf_b, x) {
285                return Ok(x + 8);
286            }
287        }
288
289        Err(Error::InterruptPinError)
290    }
291
292    /// Gets last interrupt value
293    pub fn get_last_interrupt_value(&mut self) -> Result<u8, Error<E>> {
294        match self.get_last_interrupt_pin() {
295            Ok(pin) => {
296                let int_reg = register_for_pin(pin, Register::INTCAPA, Register::INTCAPB);
297                let bit = bit_for_pin(pin);
298                let val = self.read_register(int_reg)?;
299                Ok((val >> bit) & 0x01)
300            }
301            Err(e) => Err(e),
302        }
303    }
304}
305
306/// Changes the bit at position `bit` within `reg` to `val`.
307fn write_bit(reg: u8, bit: u8, val: bool) -> u8 {
308    let mut res = reg;
309    if val {
310        res |= 1 << bit;
311    } else {
312        res &= !(1 << bit);
313    }
314
315    res
316}
317
318/// Returns whether the bit at position `bit` within `reg` is set.
319fn read_bit(reg: u8, bit: u8) -> bool {
320    reg & (1 << bit) != 0
321}
322
323/// Returns the bit index associated with a given pin.
324fn bit_for_pin(pin: u8) -> u8 {
325    pin % 8
326}
327
328/// Returns the register address, port dependent, for a given pin.
329fn register_for_pin(pin: u8, port_a_addr: Register, port_b_addr: Register) -> Register {
330    if pin < 8 {
331        port_a_addr
332    } else {
333        port_b_addr
334    }
335}
336
337/// Pin modes.
338#[derive(Debug, Copy, Clone)]
339pub enum PinMode {
340    /// Represents input mode.
341    INPUT = 1,
342    /// Represents output mode.
343    OUTPUT = 0,
344}
345
346impl PinMode {
347    /// Returns the binary value of the `PinMode`, as used in IODIR.
348    fn bit_value(&self) -> bool {
349        match *self {
350            PinMode::INPUT => true,
351            PinMode::OUTPUT => false,
352        }
353    }
354
355    /// Returns a whole register full of the binary value of the `PinMode`.
356    fn register_value(&self) -> u8 {
357        match *self {
358            PinMode::INPUT => 0xff,
359            PinMode::OUTPUT => 0x00,
360        }
361    }
362}
363
364/// Interrupt modes.
365#[derive(Debug, Copy, Clone, PartialEq)]
366pub enum InterruptMode {
367    /// Represents change mode.
368    CHANGE = 0,
369    /// Represents falling mode.
370    FALLING = 1,
371    /// Represents rising mode.
372    RISING = 2,
373}
374
375/// Polarity modes.
376#[derive(Debug, Copy, Clone)]
377pub enum Polarity {
378    /// Represents active-low mode.
379    LOW = 0,
380    /// Represents active-high mode.
381    HIGH = 1,
382}
383
384impl Polarity {
385    /// Returns the binary value of the Polarity, as used in IOCON.
386    fn bit_value(&self) -> bool {
387        match self {
388            Polarity::LOW => false,
389            Polarity::HIGH => true,
390        }
391    }
392}
393
394/// Generic port definitions.
395#[derive(Debug, Copy, Clone)]
396pub enum Port {
397    /// Represent port A.
398    GPIOA,
399    /// Represent port B.
400    GPIOB,
401}
402
403#[derive(Debug, Copy, Clone)]
404enum Register {
405    IODIRA = 0x00,
406    IPOLA = 0x02,
407    GPINTENA = 0x04,
408    DEFVALA = 0x06,
409    INTCONA = 0x08,
410    IOCONA = 0x0A,
411    GPPUA = 0x0C,
412    INTFA = 0x0E,
413    INTCAPA = 0x10,
414    GPIOA = 0x12,
415    OLATA = 0x14,
416    IODIRB = 0x01,
417    IPOLB = 0x03,
418    GPINTENB = 0x05,
419    DEFVALB = 0x07,
420    INTCONB = 0x09,
421    IOCONB = 0x0B,
422    GPPUB = 0x0D,
423    INTFB = 0x0F,
424    INTCAPB = 0x11,
425    GPIOB = 0x13,
426    OLATB = 0x15,
427}