tca9555/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, you can obtain one at https://mozilla.org/MPL/2.0/.
4
5#![cfg_attr(not(test), no_std)]
6#![deny(missing_docs)]
7
8//! Driver for the TCA9555/TCA9535 16-port I/O expander.
9//!
10//! This crate currently only has byte-oriented operation implemented.
11//! Individual control of pins will be the subject of a future release.
12//!
13//! ## Example
14//! ```no_run
15//! use embedded_hal::blocking::i2c::WriteRead;
16//! use tca9555::{Tca9555, DeviceAddr};
17//! fn read_ports<E>(i2c: impl WriteRead<Error = E>) -> Result<(), E> {
18//!     let mut tca = Tca9555::new(i2c, DeviceAddr::default());
19//!     let port0: u8 = tca.read_port_0()?;
20//!     let port1: u8 = tca.read_port_1()?;
21//!     let all_inputs: u16 = tca.read_all()?;
22//!     Ok(())
23//! }
24//! ```
25
26use embedded_hal::blocking::i2c::{Write, WriteRead};
27
28mod command {
29    pub const READ_PORT_0: u8 = 0x00;
30    pub const READ_PORT_1: u8 = 0x01;
31    pub const WRITE_PORT_0: u8 = 0x02;
32    pub const WRITE_PORT_1: u8 = 0x03;
33    pub const POLARITY_INVERT_PORT_0: u8 = 0x04;
34    pub const POLARITY_INVERT_PORT_1: u8 = 0x05;
35    pub const CONFIGURATION_PORT_0: u8 = 0x06;
36    pub const CONFIGURATION_PORT_1: u8 = 0x07;
37}
38
39use command::*;
40
41/// Represents the address of a connected TCA9555
42#[derive(Copy, Clone, Debug)]
43pub enum DeviceAddr {
44    /// Default address when all address pins are connected to GND (0x20)
45    Default,
46    /// Set an alternative address with the values of the (A0, A1, A2)
47    /// pins
48    Alternative(bool, bool, bool),
49}
50
51impl Default for DeviceAddr {
52    fn default() -> Self {
53        Self::Default
54    }
55}
56
57impl DeviceAddr {
58    const DEFAULT: u8 = 0x20;
59
60    /// Get the raw address
61    pub fn addr(self) -> u8 {
62        match self {
63            DeviceAddr::Default => Self::DEFAULT,
64            DeviceAddr::Alternative(a0, a1, a2) => {
65                Self::DEFAULT | a0 as u8 | ((a1 as u8) << 1) | ((a2 as u8) << 2)
66            }
67        }
68    }
69}
70
71/// Type alias for TCA9555. Both chips implement the same I2C commands
72/// so in theory are interchangeable here. Use with a TCA9535 has not
73/// been tested.
74pub type Tca9535<I2C> = Tca9555<I2C>;
75
76/// TCA9555 device
77pub struct Tca9555<I2C> {
78    address: DeviceAddr,
79    i2c: I2C,
80}
81
82impl<I2C> Tca9555<I2C> {
83    /// Create a TCA9555 device with the given address
84    pub fn new(i2c: I2C, address: DeviceAddr) -> Self {
85        Self { i2c, address }
86    }
87}
88
89impl<I2C, E> Tca9555<I2C>
90where
91    I2C: WriteRead<Error = E>,
92{
93    /// Read input port 0 in full, returning a u8. Reads the current logic
94    /// level of the pins, regardless of whether they have been configured
95    /// as inputs or outputs
96    pub fn read_port_0(&mut self) -> Result<u8, E> {
97        let mut value = [0];
98        self.i2c
99            .write_read(self.address.addr(), &[READ_PORT_0], &mut value)
100            .and(Ok(value[0]))
101    }
102
103    /// Read input port 1 in full, returning a u8. Reads the current logic
104    /// level of the pins, regardless of whether they have been configured
105    /// as inputs or outputs
106    pub fn read_port_1(&mut self) -> Result<u8, E> {
107        let mut value = [0];
108        self.i2c
109            .write_read(self.address.addr(), &[READ_PORT_1], &mut value)
110            .and(Ok(value[0]))
111    }
112
113    /// Read both input ports in turn, combining their values into a u16.
114    /// Reads the current logic level of the pins, regardless of whether
115    /// they have been configured as inputs or outputs
116    pub fn read_all(&mut self) -> Result<u16, E> {
117        let port0 = self.read_port_0()?;
118        let port1 = self.read_port_1()?;
119        Ok(u16::from_be_bytes([port1, port0]))
120    }
121}
122
123impl<I2C, E> Tca9555<I2C>
124where
125    I2C: Write<Error = E>,
126{
127    /// Write the given byte to port 0. Has no effect on pins which have
128    /// been configured as inputs.
129    pub fn write_port_0(&mut self, value: u8) -> Result<(), E> {
130        self.i2c.write(self.address.addr(), &[WRITE_PORT_0, value])
131    }
132
133    /// Set the port 0 direction register. Bits set to 0 are in output
134    /// mode, while bits set to 1 are in input mode.
135    pub fn set_port_0_direction(&mut self, dir_mask: u8) -> Result<(), E> {
136        self.i2c
137            .write(self.address.addr(), &[CONFIGURATION_PORT_0, dir_mask])
138    }
139
140    /// Set the port 0 polarity inversion register. Bits set to 1 have
141    /// their polarity inverted
142    pub fn set_port_0_polarity_invert(
143        &mut self,
144        polarity_mask: u8,
145    ) -> Result<(), E> {
146        self.i2c.write(
147            self.address.addr(),
148            &[POLARITY_INVERT_PORT_0, polarity_mask],
149        )
150    }
151
152    /// Write the given byte to port 1. Has no effect on pins which have
153    /// been configured as inputs.
154    pub fn write_port_1(&mut self, value: u8) -> Result<(), E> {
155        self.i2c.write(self.address.addr(), &[WRITE_PORT_1, value])
156    }
157
158    /// Set the port 1 direction register. Bits set to 0 are in output
159    /// mode, while bits set to 1 are in input mode.
160    pub fn set_port_1_direction(&mut self, dir_mask: u8) -> Result<(), E> {
161        self.i2c
162            .write(self.address.addr(), &[CONFIGURATION_PORT_1, dir_mask])
163    }
164
165    /// Set the port 1 polarity inversion register. Bits set to 1 have
166    /// their polarity inverted
167    pub fn set_port_1_polarity_invert(
168        &mut self,
169        polarity_mask: u8,
170    ) -> Result<(), E> {
171        self.i2c.write(
172            self.address.addr(),
173            &[POLARITY_INVERT_PORT_1, polarity_mask],
174        )
175    }
176
177    /// Write the given u16 across all 16 output pins
178    pub fn write_all(&mut self, value: u16) -> Result<(), E> {
179        let [port1, port0] = value.to_be_bytes();
180        self.write_port_0(port0)?;
181        self.write_port_1(port1)
182    }
183}
184
185#[cfg(test)]
186mod test {
187    use super::*;
188
189    #[test]
190    fn address_set() {
191        assert_eq!(DeviceAddr::DEFAULT, 0x20);
192        assert_eq!(DeviceAddr::Alternative(false, false, false).addr(), 0x20);
193        assert_eq!(DeviceAddr::Alternative(true, false, false).addr(), 0x21);
194        assert_eq!(DeviceAddr::Alternative(false, true, true).addr(), 0x26);
195    }
196}