diff --git a/Cargo.lock b/Cargo.lock index 042a5f3..90cec55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -989,9 +989,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "mcp23017" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c32fd6627e73f1cfa95c00ddcdcb5a6a6ddbd10b308d08588a502c018b6e12c" +version = "1.1.0" dependencies = [ "embedded-hal 0.2.7", ] diff --git a/Cargo.toml b/Cargo.toml index 31d17c6..5e76bd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,8 @@ static_cell = "2" portable-atomic = { version = "1.5", features = ["critical-section"] } log = "0.4" -mcp23017 = { version = "1.0.0" } +# vendored for performance reasons +mcp23017 = { version = "1.1.0", path = "vendor/mcp23017" } shared-bus = "0.3.1" [profile.release] diff --git a/src/matrix.rs b/src/matrix.rs index df248b6..9afa290 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -39,7 +39,7 @@ fn velocity_heavy(us: u64) -> u8 { } fn velocity_linear(us: u64) -> u8 { - (max(116000 - (us as i32), 5000) / 1000) as u8 + (max(118000 - (us as i32), 5000) / 1000) as u8 } pub struct Config { diff --git a/src/pins.rs b/src/pins.rs index ec1cd9e..2c85dfd 100644 --- a/src/pins.rs +++ b/src/pins.rs @@ -108,6 +108,8 @@ impl IntoIterator for PinCollection { pub struct TransparentPins { addrs: [u8; N_PIN_EXTENDERS], onboard_pins: [Flex<'static, AnyPin>; N_REGULAR_PINS], + /// Input/output state of each pin. 1 bit is input, 0 bit is output. + io_state: u64, i2c_bus: I2cBus, disable_unsafe_pins: bool, /// Usable pins per extender. Depends on `disable_unsafe_pins`. @@ -191,6 +193,7 @@ impl TransparentPins { ) -> Result { let mut ret = TransparentPins { addrs, + io_state: (1 << (N_REGULAR_PINS + N_EXTENDED_PINS)) - 1, onboard_pins: pins.map(Flex::new), i2c_bus: shared_bus::BusManagerSimple::new(i2c), disable_unsafe_pins: false, @@ -221,7 +224,12 @@ impl TransparentPins { // ports are flipped from what it should be let port_a = (val & (0xff00)) >> 8; let port_b = val & (0x00ff); - defmt::trace!("raw_to_usable: raw {:016b} a {:08b} b {:08b}", val, port_a, port_b); + defmt::trace!( + "raw_to_usable: raw {:016b} a {:08b} b {:08b}", + val, + port_a, + port_b + ); (port_a & 0x7f) | ((port_b & 0x7f) << 7) } else { val @@ -300,10 +308,13 @@ impl TransparentPins { pub fn set_input(&mut self, addr: u8) -> Result<(), Error> { let pin_n = self.addr_to_pin(addr); let pin = self.get_pin(pin_n)?; + self.io_state |= 1 << pin_n; match pin { TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_input(), TransparentPin::Extended(p) => { - extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT)?; + let ext_io_word = (self.io_state >> (p.ext_id * PINS_PER_EXTENDER)) + & ((1 << PINS_PER_EXTENDER) - 1); + extender!(self, p.ext_id)?.overwrite_pin_mode(p.loc_pin, ext_io_word as u16)?; } } Ok(()) @@ -313,11 +324,13 @@ impl TransparentPins { pub fn set_output(&mut self, addr: u8) -> Result<(), Error> { let pin_n = self.addr_to_pin(addr); let pin = self.get_pin(pin_n)?; + self.io_state &= !(1 << pin_n); match pin { TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_output(), TransparentPin::Extended(p) => { - let mut ext = extender!(self, p.ext_id)?; - ext.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?; + let ext_io_word = (self.io_state >> (p.ext_id * PINS_PER_EXTENDER)) + & ((1 << PINS_PER_EXTENDER) - 1); + extender!(self, p.ext_id)?.overwrite_pin_mode(p.loc_pin, ext_io_word as u16)?; } } Ok(()) diff --git a/vendor/mcp23017/.github/dependabot.yml b/vendor/mcp23017/.github/dependabot.yml new file mode 100644 index 0000000..5cde165 --- /dev/null +++ b/vendor/mcp23017/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/vendor/mcp23017/.github/workflows/mcp23017-ci.yml b/vendor/mcp23017/.github/workflows/mcp23017-ci.yml new file mode 100644 index 0000000..c89eea8 --- /dev/null +++ b/vendor/mcp23017/.github/workflows/mcp23017-ci.yml @@ -0,0 +1,144 @@ +--- +name: mcp23017-ci + +on: + push: + branches: + - '**' + tags: + - "*.*.*" + +env: + RUSTFLAGS: '--deny warnings' + +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + matrix: + rust: [ stable, beta, nightly, 1.52.1 ] + TARGET: + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + - arm-unknown-linux-gnueabi # Raspberry Pi 1 + - armv7-unknown-linux-gnueabihf # Raspberry Pi 2, 3, etc + # Bare metal + - thumbv6m-none-eabi + - thumbv7em-none-eabi + - thumbv7em-none-eabihf + - thumbv7m-none-eabi + + include: + - rust: nightly + experimental: true + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.TARGET }} + override: true + + - name: Checkout CI scripts + uses: actions/checkout@v2 + with: + repository: 'eldruin/rust-driver-ci-scripts' + ref: 'master' + path: 'ci' + + - run: ./ci/patch-no-std.sh + if: ${{ ! contains(matrix.TARGET, 'x86_64') }} + + checks: + name: Checks + runs-on: ubuntu-latest + strategy: + matrix: + rust: [ stable, beta ] + TARGET: + - x86_64-unknown-linux-gnu + + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.TARGET }} + override: true + components: clippy, rustfmt + + - name: Doc + uses: actions-rs/cargo@v1 + with: + command: doc + + - name: Formatting + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + strategy: + matrix: + rust: [ stable ] + TARGET: + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + - arm-unknown-linux-gnueabi # Raspberry Pi 1 + - armv7-unknown-linux-gnueabihf # Raspberry Pi 2, 3, etc + # Bare metal + - thumbv6m-none-eabi + - thumbv7em-none-eabi + - thumbv7em-none-eabihf + - thumbv7m-none-eabi + include: + - experimental: true + + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.TARGET }} + override: true + - run: cargo clippy --all-features -- --deny=warnings + + test: + name: Tests + runs-on: ubuntu-latest + strategy: + matrix: + rust: [ stable, beta, nightly ] + TARGET: [ x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl ] + include: + - rust: nightly + experimental: true + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.TARGET }} + override: true + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --target=${{ matrix.TARGET }} + + - name: Build examples + uses: actions-rs/cargo@v1 + if: contains(matrix.TARGET, 'x86_64') + with: + command: build + args: --target=${{ matrix.TARGET }} --examples diff --git a/vendor/mcp23017/.gitignore b/vendor/mcp23017/.gitignore new file mode 100644 index 0000000..088ba6b --- /dev/null +++ b/vendor/mcp23017/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/vendor/mcp23017/Cargo.toml b/vendor/mcp23017/Cargo.toml new file mode 100644 index 0000000..b414886 --- /dev/null +++ b/vendor/mcp23017/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mcp23017" +version = "1.1.0" +description = "A rust driver for the MCP23017 (16-Bit I2C I/O Expander with Serial Interface)" +authors = ["Luca Zulian "] +categories = ["embedded", "hardware-support", "no-std"] +keywords = ["hal", "IO"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/circuitry-maker/mcp23017" +edition = "2018" +exclude = [ + "docs/", + "docs/*", +] + +[dependencies] +embedded-hal = "0.2.3" diff --git a/vendor/mcp23017/LICENSE b/vendor/mcp23017/LICENSE new file mode 100644 index 0000000..4363cf1 --- /dev/null +++ b/vendor/mcp23017/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Luca Zulian + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/mcp23017/README.md b/vendor/mcp23017/README.md new file mode 100644 index 0000000..33f25e2 --- /dev/null +++ b/vendor/mcp23017/README.md @@ -0,0 +1,62 @@ +# `mcp23017` + +> no_std driver for [MCP23017](http://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) (16-Bit I2C I/O Expander with Serial Interface module) + +[![Build Status](https://github.com/circuitry-maker/mcp23017/workflows/mcp23017-ci/badge.svg)](https://github.com/circuitry-maker/mcp23017/actions?query=workflow%3Amcp23017-ci) +[![crates.io](https://img.shields.io/crates/v/mcp23017.svg)](https://crates.io/crates/mcp23017) +[![Docs](https://docs.rs/mcp23017/badge.svg)](https://docs.rs/mcp23017) + +## Basic usage + +Include this [library](https://crates.io/crates/mcp23017) as a dependency in your `Cargo.toml`: + +```rust +[dependencies.mcp23017] +version = "" +``` +Use [embedded-hal](https://github.com/rust-embedded/embedded-hal) implementation to get I2C handle and then create mcp23017 handle: + +```rust +extern crate mcp23017; + +match mcp23017::MCP23017::default(i2c) { + Ok(mut u) => { + u.init_hardware(); + u.pin_mode(1, mcp23017::PinMode::OUTPUT); // for the first pin + u.all_pin_mode(mcp23017::PinMode::OUTPUT); // or for all pins + + let status = u.read_gpioab().unwrap(); + println!("all {:#?}", status).unwrap(); + + let read_a = u.read_gpio(mcp23017::Port::GPIOA).unwrap(); + println!("port a {:#?}", read_a).unwrap(); + + match u.write_gpioab(65503){ + Ok(_) => { + println!("ok").unwrap(); + } + _ => { + println!("something wrong").unwrap(); + } + } + } + Err(mcp23017::MCP23017::Error::BusError(error)) => { + println!("{:#?}", error).unwrap();; + panic!(); + } + _ => { + panic!(); + } +}; +``` + +### Hardware address pins +![](docs/address-pins.jpg) + +## Documentation + +API Docs available on [docs.rs](https://docs.rs/mcp23017/0.1.0/mcp23017/) + +## License + +[MIT license](http://opensource.org/licenses/MIT) diff --git a/vendor/mcp23017/docs/address-pins.jpg b/vendor/mcp23017/docs/address-pins.jpg new file mode 100644 index 0000000..eb85f5d Binary files /dev/null and b/vendor/mcp23017/docs/address-pins.jpg differ diff --git a/vendor/mcp23017/src/lib.rs b/vendor/mcp23017/src/lib.rs new file mode 100644 index 0000000..716811e --- /dev/null +++ b/vendor/mcp23017/src/lib.rs @@ -0,0 +1,466 @@ +#![no_std] + +//! Manages an MCP23017, a 16-Bit I2C I/O Expander with Serial Interface module. +//! +//! This operates the chip in `IOCON.BANK=0` mode, i.e. the registers are mapped sequentially. +//! This driver does not set `IOCON.BANK`, but the factory default is `0` and this driver does +//! not change that value. +//! +//! See [the datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) for more +//! information on the device. + +#![deny( + missing_docs, + missing_debug_implementations, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unstable_features, + unused_import_braces, + unused_qualifications, + warnings +)] +#![allow(dead_code, non_camel_case_types)] +#![allow(clippy::uninit_assumed_init, clippy::upper_case_acronyms)] + +extern crate embedded_hal as ehal; + +use ehal::blocking::i2c::{Write, WriteRead}; + +/// The default I2C address of the MCP23017. +const DEFAULT_ADDRESS: u8 = 0x20; + +/// Binary constants. +const HIGH: bool = true; +const LOW: bool = false; + +/// Struct for an MCP23017. +/// See the crate-level documentation for general info on the device and the operation of this +/// driver. +#[derive(Clone, Copy, Debug)] +pub struct MCP23017 { + com: I2C, + /// The I2C slave address of this device. + pub address: u8, +} + +/// Defines errors +#[derive(Debug, Copy, Clone)] +pub enum Error { + /// Underlying bus error + BusError(E), + /// Interrupt pin not found + InterruptPinError, +} + +impl From for Error { + fn from(error: E) -> Self { + Error::BusError(error) + } +} + +impl MCP23017 +where + I2C: WriteRead + Write, +{ + /// Creates an expander with the default configuration. + pub fn default(i2c: I2C) -> Result, Error> + where + I2C: Write + WriteRead, + { + MCP23017::new(i2c, DEFAULT_ADDRESS) + } + + /// Creates an expander with specific address. + pub fn new(i2c: I2C, address: u8) -> Result, Error> + where + I2C: Write + WriteRead, + { + let chip = MCP23017 { com: i2c, address }; + + Ok(chip) + } + + /// Initiates hardware with basic setup. + pub fn init_hardware(&mut self) -> Result<(), Error> { + // set all inputs to defaults on port A and B + self.write_register(Register::IODIRA, 0xff)?; + self.write_register(Register::IODIRB, 0xff)?; + + Ok(()) + } + + fn read_register(&mut self, reg: Register) -> Result { + let mut data: [u8; 1] = [0]; + self.com.write_read(self.address, &[reg as u8], &mut data)?; + Ok(data[0]) + } + + fn read_double_register(&mut self, reg: Register) -> Result<[u8; 2], E> { + let mut buffer: [u8; 2] = [0; 2]; + self.com + .write_read(self.address, &[reg as u8], &mut buffer)?; + Ok(buffer) + } + + fn write_register(&mut self, reg: Register, byte: u8) -> Result<(), E> { + self.com.write(self.address, &[reg as u8, byte]) + } + + fn write_double_register(&mut self, reg: Register, word: u16) -> Result<(), E> { + let msb = (word >> 8) as u8; + self.com.write(self.address, &[reg as u8, word as u8, msb]) + } + + /// Updates a single bit in the register associated with the given pin. + /// This will read the register (`port_a_reg` for pins 0-7, `port_b_reg` for the other eight), + /// set the bit (as specified by the pin position within the register), and write the register + /// back to the device. + fn update_register_bit( + &mut self, + pin: u8, + pin_value: bool, + port_a_reg: Register, + port_b_reg: Register, + ) -> Result<(), E> { + let reg = register_for_pin(pin, port_a_reg, port_b_reg); + let bit = bit_for_pin(pin); + let reg_value = self.read_register(reg)?; + let reg_value_mod = write_bit(reg_value, bit, pin_value); + self.write_register(reg, reg_value_mod) + } + + /// Sets the mode for a single pin to either `Mode::INPUT` or `Mode::OUTPUT`. + pub fn pin_mode(&mut self, pin: u8, pin_mode: PinMode) -> Result<(), E> { + self.update_register_bit( + pin, + pin_mode.bit_value(), + Register::IODIRA, + Register::IODIRB, + ) + } + + /// Updates the entire register associated with the given pin. + fn overwrite_register_bit( + &mut self, + pin: u8, + byte: u8, + port_a_reg: Register, + port_b_reg: Register, + ) -> Result<(), E> { + let reg = register_for_pin(pin, port_a_reg, port_b_reg); + self.write_register(reg, byte) + } + + /// Write pin mode state for a pin without first reading from it. + /// + /// Input pin is a 1 bit, output pin is a 0 bit. + /// Note that only one register (byte) containing the pin is written to. + pub fn overwrite_pin_mode(&mut self, pin: u8, word: u16) -> Result<(), E> { + let byte = if pin < 8 { + (word & 0xff) as u8 + } else { + ((word & 0xff00) >> 8) as u8 + }; + self.overwrite_register_bit( + pin, + byte, + Register::IODIRA, + Register::IODIRB, + ) + } + + /// Sets all pins' modes to either `Mode::INPUT` or `Mode::OUTPUT`. + pub fn all_pin_mode(&mut self, pin_mode: PinMode) -> Result<(), E> { + self.write_register(Register::IODIRA, pin_mode.register_value())?; + self.write_register(Register::IODIRB, pin_mode.register_value()) + } + + /// Reads all 16 pins (port A and B) into a single 16 bit variable. + pub fn read_gpioab(&mut self) -> Result { + let buffer = self.read_double_register(Register::GPIOA)?; + Ok((buffer[0] as u16) << 8 | (buffer[1] as u16)) + } + + /// Reads a single port, A or B, and returns its current 8 bit value. + pub fn read_gpio(&mut self, port: Port) -> Result { + let reg = match port { + Port::GPIOA => Register::GPIOA, + Port::GPIOB => Register::GPIOB, + }; + self.read_register(reg) + } + + /// Writes all the pins with the value at the same time. + pub fn write_gpioab(&mut self, value: u16) -> Result<(), E> { + self.write_double_register(Register::GPIOA, value) + } + + /// Writes all the pins of one port with the value at the same time. + pub fn write_gpio(&mut self, port: Port, value: u8) -> Result<(), E> { + let reg = match port { + Port::GPIOA => Register::GPIOA, + Port::GPIOB => Register::GPIOB, + }; + self.write_register(reg, value) + } + + /// Writes a single bit to a single pin. + /// This function internally reads from the output latch register (`OLATA`/`OLATB`) and writes + /// to the GPIO register. + pub fn digital_write(&mut self, pin: u8, value: bool) -> Result<(), E> { + let bit = bit_for_pin(pin); + // Read the current GPIO output latches. + let ol_register = register_for_pin(pin, Register::OLATA, Register::OLATB); + let gpio = self.read_register(ol_register)?; + + // Set the pin. + let gpio_mod = write_bit(gpio, bit, value); + + // Write the modified register. + let reg_gp = register_for_pin(pin, Register::GPIOA, Register::GPIOB); + self.write_register(reg_gp, gpio_mod) + } + + /// Reads a single pin. + pub fn digital_read(&mut self, pin: u8) -> Result { + let bit = bit_for_pin(pin); + let reg = register_for_pin(pin, Register::GPIOA, Register::GPIOB); + let value = self.read_register(reg)?; + Ok(read_bit(value, bit)) + } + + /// Enables or disables the internal pull-up resistor for a single pin. + pub fn pull_up(&mut self, pin: u8, value: bool) -> Result<(), E> { + self.update_register_bit(pin, value, Register::GPPUA, Register::GPPUB) + } + + /// Inverts the input polarity for a single pin. + /// This uses the `IPOLA` or `IPOLB` registers, see the datasheet for more information. + pub fn invert_input_polarity(&mut self, pin: u8, value: bool) -> Result<(), E> { + self.update_register_bit(pin, value, Register::IPOLA, Register::IPOLB) + } + + /// Configures the interrupt system. both port A and B are assigned the same configuration. + /// mirroring will OR both INTA and INTB pins. + /// open_drain will set the INT pin to value or open drain. + /// polarity will set LOW or HIGH on interrupt. + /// Default values after Power On Reset are: (false, false, LOW) + pub fn setup_interrupts( + &mut self, + mirroring: bool, + open_drain: bool, + polarity: Polarity, + ) -> Result<(), E> { + // configure port A + self.setup_interrupt_port(Register::IOCONA, mirroring, open_drain, polarity)?; + + // configure port B + self.setup_interrupt_port(Register::IOCONB, mirroring, open_drain, polarity) + } + + fn setup_interrupt_port( + &mut self, + register: Register, + mirroring: bool, + open_drain: bool, + polarity: Polarity, + ) -> Result<(), E> { + let mut io_conf_value = self.read_register(register)?; + io_conf_value = write_bit(io_conf_value, 6, mirroring); + io_conf_value = write_bit(io_conf_value, 2, open_drain); + io_conf_value = write_bit(io_conf_value, 1, polarity.bit_value()); + self.write_register(register, io_conf_value) + } + + /// Sets up a pin for interrupt. + /// Note that the interrupt condition finishes when you read the information about + /// the port / value that caused the interrupt or you read the port itself. + pub fn setup_interrupt_pin(&mut self, pin: u8, int_mode: InterruptMode) -> Result<(), E> { + // set the pin interrupt control (0 means change, 1 means compare against given value) + self.update_register_bit( + pin, + int_mode != InterruptMode::CHANGE, + Register::INTCONA, + Register::INTCONB, + )?; + + // in a RISING interrupt the default value is 0, interrupt is triggered when the pin goes to 1 + // in a FALLING interrupt the default value is 1, interrupt is triggered when pin goes to 0 + self.update_register_bit( + pin, + int_mode == InterruptMode::FALLING, + Register::DEFVALA, + Register::DEFVALB, + )?; + + // enable the pin for interrupt + self.update_register_bit(pin, HIGH, Register::GPINTENA, Register::GPINTENB) + } + + /// Get last interrupt pin + pub fn get_last_interrupt_pin(&mut self) -> Result> { + // try port A + let intf_a = self.read_register(Register::INTFA)?; + for x in 0..8 { + if read_bit(intf_a, x) { + return Ok(x); + } + } + + // try port B + let intf_b = self.read_register(Register::INTFB)?; + for x in 0..8 { + if read_bit(intf_b, x) { + return Ok(x + 8); + } + } + + Err(Error::InterruptPinError) + } + + /// Gets last interrupt value + pub fn get_last_interrupt_value(&mut self) -> Result> { + match self.get_last_interrupt_pin() { + Ok(pin) => { + let int_reg = register_for_pin(pin, Register::INTCAPA, Register::INTCAPB); + let bit = bit_for_pin(pin); + let val = self.read_register(int_reg)?; + Ok((val >> bit) & 0x01) + } + Err(e) => Err(e), + } + } + + /// Get the complete value captured at the last interrupt of the specified port + pub fn get_captured_value(&mut self, port: Port) -> Result { + let reg = match port { + Port::GPIOA => Register::INTCAPA, + Port::GPIOB => Register::INTCAPB, + }; + self.read_register(reg) + } +} + +/// Changes the bit at position `bit` within `reg` to `val`. +fn write_bit(reg: u8, bit: u8, val: bool) -> u8 { + let mut res = reg; + if val { + res |= 1 << bit; + } else { + res &= !(1 << bit); + } + + res +} + +/// Returns whether the bit at position `bit` within `reg` is set. +fn read_bit(reg: u8, bit: u8) -> bool { + reg & (1 << bit) != 0 +} + +/// Returns the bit index associated with a given pin. +fn bit_for_pin(pin: u8) -> u8 { + pin % 8 +} + +/// Returns the register address, port dependent, for a given pin. +fn register_for_pin(pin: u8, port_a_addr: Register, port_b_addr: Register) -> Register { + if pin < 8 { + port_a_addr + } else { + port_b_addr + } +} + +/// Pin modes. +#[derive(Debug, Copy, Clone)] +pub enum PinMode { + /// Represents input mode. + INPUT = 1, + /// Represents output mode. + OUTPUT = 0, +} + +impl PinMode { + /// Returns the binary value of the `PinMode`, as used in IODIR. + fn bit_value(&self) -> bool { + match *self { + PinMode::INPUT => true, + PinMode::OUTPUT => false, + } + } + + /// Returns a whole register full of the binary value of the `PinMode`. + fn register_value(&self) -> u8 { + match *self { + PinMode::INPUT => 0xff, + PinMode::OUTPUT => 0x00, + } + } +} + +/// Interrupt modes. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum InterruptMode { + /// Represents change mode. + CHANGE = 0, + /// Represents falling mode. + FALLING = 1, + /// Represents rising mode. + RISING = 2, +} + +/// Polarity modes. +#[derive(Debug, Copy, Clone)] +pub enum Polarity { + /// Represents active-low mode. + LOW = 0, + /// Represents active-high mode. + HIGH = 1, +} + +impl Polarity { + /// Returns the binary value of the Polarity, as used in IOCON. + fn bit_value(&self) -> bool { + match self { + Polarity::LOW => false, + Polarity::HIGH => true, + } + } +} + +/// Generic port definitions. +#[derive(Debug, Copy, Clone)] +pub enum Port { + /// Represent port A. + GPIOA, + /// Represent port B. + GPIOB, +} + +#[derive(Debug, Copy, Clone)] +enum Register { + IODIRA = 0x00, + IPOLA = 0x02, + GPINTENA = 0x04, + DEFVALA = 0x06, + INTCONA = 0x08, + IOCONA = 0x0A, + GPPUA = 0x0C, + INTFA = 0x0E, + INTCAPA = 0x10, + GPIOA = 0x12, + OLATA = 0x14, + IODIRB = 0x01, + IPOLB = 0x03, + GPINTENB = 0x05, + DEFVALB = 0x07, + INTCONB = 0x09, + IOCONB = 0x0B, + GPPUB = 0x0D, + INTFB = 0x0F, + INTCAPB = 0x11, + GPIOB = 0x13, + OLATB = 0x15, +}