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); } }