1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
//! This is a platform agnostic Rust driver for the PCF8574, PCF8574A and PCF8575
//! I/O expanders, based on the [`embedded-hal`] traits.
//!
//! [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal
//!
//! This driver allows you to:
//! - Set all the outputs to `0` or `1` at once. See `set()`.
//! - Read selected inputs. See `get()`.
//! - Set all the outputs repeatedly looping through an array. See `write_array()`.
//! - Read selected inputs repeatedly filling up an array. See `read_array()`.
//! - Split the device into individual input/output pins. See `split()`.
//!
//! ## The devices
//! The devices consist of 8 or 16 quasi-bidirectional ports, I²C-bus interface, three
//! hardware address inputs and interrupt output. The quasi-bidirectional port can be
//! independently assigned as an input to monitor interrupt status or keypads, or as an
//! output to activate indicator devices such as LEDs.
//!
//! The active LOW open-drain interrupt output (INT) can be connected to the interrupt logic
//! of the microcontroller and is activated when any input state differs from its corresponding
//! input port register state.
//!
//! Datasheets:
//! - [PCF8574 / PCF8574A](https://www.nxp.com/docs/en/data-sheet/PCF8574_PCF8574A.pdf)
//! - [PCF8575](https://www.nxp.com/documents/data_sheet/PCF8575.pdf)
//!
//! ## Splitting the device into individual input/output pins
//!
//! By calling `split()` on the device it is possible to get a structure holding the
//! individual pins as separate elements. These pins implement the `OutputPin` and
//! `InputPin` traits (the latter only if activating the `unproven` feature).
//! This way it is possible to use the pins transparently as normal I/O pins regardless
//! of the fact that an I/O expander is connected in between.
//! You can therefore also pass them to code expecting an `OutputPin` or `InputPin`.
//!
//! However, you need to keep the device you split alive (lifetime annotations have
//! put in place for Rust to enforce this).
//!
//! For each operation done on an input/output pin, a `read` or `write` will be done
//! through I2C for all the pins, using a cached value for the rest of pins not being
//! operated on. This should all be transparent to the user but if operating on more
//! than one pin at once, the `set` and `get` methods will be faster.
//! Similarly, if several pins must be changed/read at the same time, the `set` and
//! `get` methods would be the correct choice.
//!
//! At the moment, no mutex has been implemented for the individual pin access.
//!
//! ## Usage examples (see also examples folder)
//!
//! Please find additional examples using hardware in this repository: [driver-examples]
//!
//! [driver-examples]: https://github.com/eldruin/driver-examples
//!
//! ### Instantiating with the default address
//!
//! Import this crate and an `embedded_hal` implementation, then instantiate
//! the device:
//!
//! ```no_run
//! extern crate linux_embedded_hal as hal;
//! extern crate pcf857x;
//!
//! use hal::I2cdev;
//! use pcf857x::{ Pcf8574, SlaveAddr };
//!
//! # fn main() {
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
//! let address = SlaveAddr::default();
//! let mut expander = Pcf8574::new(dev, address);
//! # }
//! ```
//!
//! ### Providing an alternative address
//!
//! ```no_run
//! extern crate linux_embedded_hal as hal;
//! extern crate pcf857x;
//!
//! use hal::I2cdev;
//! use pcf857x::{ Pcf8574, SlaveAddr };
//!
//! # fn main() {
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
//! let (a2, a1, a0) = (false, false, true);
//! let address = SlaveAddr::Alternative(a2, a1, a0);
//! let mut expander = Pcf8574::new(dev, address);
//! # }
//! ```
//!
//! ### Setting the output pins and reading P0 and P7
//!
//! ```no_run
//! extern crate linux_embedded_hal as hal;
//! extern crate pcf857x;
//!
//! use hal::I2cdev;
//! use pcf857x::{ Pcf8574, SlaveAddr, PinFlag };
//!
//! # fn main() {
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
//! let address = SlaveAddr::default();
//! let mut expander = Pcf8574::new(dev, address);
//! let output_pin_status = 0b1010_1010;
//! expander.set(output_pin_status).unwrap();
//!
//! let mask_of_pins_to_be_read = PinFlag::P0 | PinFlag::P7;
//! let read_input_pin_status = expander.get(&mask_of_pins_to_be_read).unwrap();
//!
//! println!("Input pin status: {}", read_input_pin_status);
//! # }
//! ```
//!
//! ### Splitting device into individual input/output pins and setting them.
//!
//! ```no_run
//! extern crate linux_embedded_hal as hal;
//! extern crate pcf857x;
//!
//! use hal::I2cdev;
//! use pcf857x::{ Pcf8574, SlaveAddr, PinFlag, OutputPin };
//!
//! # fn main() {
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
//! let address = SlaveAddr::default();
//! let expander = Pcf8574::new(dev, address);
//! let mut parts = expander.split();
//! parts.p0.set_high().unwrap();
//! parts.p7.set_low().unwrap();
//! # }
//! ```
//!
//! ### Splitting device into individual input/output pins and reading them.
//!
//! Only available if compiling with the "`unproven`" feature
//!
//! ```no_run
//! extern crate linux_embedded_hal as hal;
//! extern crate pcf857x;
//!
//! use hal::I2cdev;
//! use pcf857x::{ Pcf8574, SlaveAddr, PinFlag };
//! #[cfg(feature="unproven")]
//! use pcf857x::InputPin;
//!
//! # fn main() {
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
//! let address = SlaveAddr::default();
//! let expander = Pcf8574::new(dev, address);
//! let mut parts = expander.split();
//! #[cfg(feature="unproven")]
//! {
//!     let is_input_p0_low = parts.p0.is_low().unwrap();
//!     let is_input_p2_low = parts.p2.is_low().unwrap();
//! }
//! # }
//! ```

#![deny(unsafe_code)]
#![deny(missing_docs)]
#![no_std]

extern crate embedded_hal as hal;
#[cfg(feature = "unproven")]
pub use hal::digital::v2::InputPin;
pub use hal::digital::v2::OutputPin;

/// All possible errors in this crate
#[derive(Debug)]
pub enum Error<E> {
    /// I²C bus error
    I2C(E),
    /// Invalid input data
    InvalidInputData,
    /// Could not acquire device. Maybe it is already acquired.
    CouldNotAcquireDevice,
}

/// I/O pin flags, used to select which pins to read in the `get` functions.
/// It is possible to select multiple of them using the binary _or_ operator (`|`).
/// ```
/// # use pcf857x::PinFlag;
/// let pins_to_be_read = PinFlag::P0 | PinFlag::P1;
/// ```
/// Note that P10-17 can only be used with PCF8575 devices.
#[derive(Debug, Clone)]
pub struct PinFlag {
    mask: u16,
}

impl PinFlag {
    /// Pin 0
    pub const P0: PinFlag = PinFlag { mask: 1 };
    /// Pin 1
    pub const P1: PinFlag = PinFlag { mask: 2 };
    /// Pin 2
    pub const P2: PinFlag = PinFlag { mask: 4 };
    /// Pin 3
    pub const P3: PinFlag = PinFlag { mask: 8 };
    /// Pin 4
    pub const P4: PinFlag = PinFlag { mask: 16 };
    /// Pin 5
    pub const P5: PinFlag = PinFlag { mask: 32 };
    /// Pin 6
    pub const P6: PinFlag = PinFlag { mask: 64 };
    /// Pin 7
    pub const P7: PinFlag = PinFlag { mask: 128 };

    /// Pin 10 (only PCF8575)
    pub const P10: PinFlag = PinFlag { mask: 256 };
    /// Pin 11 (only PCF8575)
    pub const P11: PinFlag = PinFlag { mask: 512 };
    /// Pin 12 (only PCF8575)
    pub const P12: PinFlag = PinFlag { mask: 1024 };
    /// Pin 13 (only PCF8575)
    pub const P13: PinFlag = PinFlag { mask: 2048 };
    /// Pin 14 (only PCF8575)
    pub const P14: PinFlag = PinFlag { mask: 4096 };
    /// Pin 15 (only PCF8575)
    pub const P15: PinFlag = PinFlag { mask: 8192 };
    /// Pin 16 (only PCF8575)
    pub const P16: PinFlag = PinFlag { mask: 16384 };
    /// Pin 17 (only PCF8575)
    pub const P17: PinFlag = PinFlag { mask: 32768 };
}

use core::ops::BitOr;

impl BitOr for PinFlag {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self {
        PinFlag {
            mask: self.mask | rhs.mask,
        }
    }
}

/// Possible slave addresses
#[derive(Debug)]
pub enum SlaveAddr {
    /// Default slave address
    Default,
    /// Alternative slave address providing bit values for A2, A1 and A0
    Alternative(bool, bool, bool),
}

impl Default for SlaveAddr {
    /// Default slave address
    fn default() -> Self {
        SlaveAddr::Default
    }
}

impl SlaveAddr {
    fn addr(self, default: u8) -> u8 {
        match self {
            SlaveAddr::Default => default,
            SlaveAddr::Alternative(a2, a1, a0) => {
                default | ((a2 as u8) << 2) | ((a1 as u8) << 1) | a0 as u8
            }
        }
    }
}

mod pins;
pub use pins::{
    pcf8574, pcf8575, P0, P1, P10, P11, P12, P13, P14, P15, P16, P17, P2, P3, P4, P5, P6, P7,
};
mod devices;
pub use devices::pcf8574::{Pcf8574, Pcf8574a};
pub use devices::pcf8575::Pcf8575;

#[cfg(test)]
mod tests {
    extern crate embedded_hal_mock as hal;

    use super::*;

    #[test]
    fn can_get_default_address() {
        let addr = SlaveAddr::default();
        assert_eq!(0b010_0000, addr.addr(0b010_0000));
    }

    #[test]
    fn can_generate_alternative_addresses() {
        let default = 0b010_0000;
        assert_eq!(
            0b010_0000,
            SlaveAddr::Alternative(false, false, false).addr(default)
        );
        assert_eq!(
            0b010_0001,
            SlaveAddr::Alternative(false, false, true).addr(default)
        );
        assert_eq!(
            0b010_0010,
            SlaveAddr::Alternative(false, true, false).addr(default)
        );
        assert_eq!(
            0b010_0100,
            SlaveAddr::Alternative(true, false, false).addr(default)
        );
        assert_eq!(
            0b010_0111,
            SlaveAddr::Alternative(true, true, true).addr(default)
        );
    }

    #[test]
    fn pin_flags_are_correct() {
        assert_eq!(1, PinFlag::P0.mask);
        assert_eq!(2, PinFlag::P1.mask);
        assert_eq!(4, PinFlag::P2.mask);
        assert_eq!(8, PinFlag::P3.mask);
        assert_eq!(16, PinFlag::P4.mask);
        assert_eq!(32, PinFlag::P5.mask);
        assert_eq!(64, PinFlag::P6.mask);
        assert_eq!(128, PinFlag::P7.mask);

        assert_eq!(1 << 8, PinFlag::P10.mask);
        assert_eq!(2 << 8, PinFlag::P11.mask);
        assert_eq!(4 << 8, PinFlag::P12.mask);
        assert_eq!(8 << 8, PinFlag::P13.mask);
        assert_eq!(16 << 8, PinFlag::P14.mask);
        assert_eq!(32 << 8, PinFlag::P15.mask);
        assert_eq!(64 << 8, PinFlag::P16.mask);
        assert_eq!(128 << 8, PinFlag::P17.mask);
    }
}