From 35e97a92e3aca6fdd6d42210e99327e46357f55d Mon Sep 17 00:00:00 2001 From: dogeystamp Date: Sun, 10 Nov 2024 21:10:21 -0500 Subject: [PATCH] perf: don't read/write pin mode, write only vendored mcp23017 library too --- Cargo.lock | 4 +- Cargo.toml | 3 +- src/matrix.rs | 2 +- src/pins.rs | 21 +- vendor/mcp23017/.github/dependabot.yml | 7 + .../.github/workflows/mcp23017-ci.yml | 144 ++++++ vendor/mcp23017/.gitignore | 10 + vendor/mcp23017/Cargo.toml | 18 + vendor/mcp23017/LICENSE | 21 + vendor/mcp23017/README.md | 62 +++ vendor/mcp23017/docs/address-pins.jpg | Bin 0 -> 28634 bytes vendor/mcp23017/src/lib.rs | 466 ++++++++++++++++++ 12 files changed, 749 insertions(+), 9 deletions(-) create mode 100644 vendor/mcp23017/.github/dependabot.yml create mode 100644 vendor/mcp23017/.github/workflows/mcp23017-ci.yml create mode 100644 vendor/mcp23017/.gitignore create mode 100644 vendor/mcp23017/Cargo.toml create mode 100644 vendor/mcp23017/LICENSE create mode 100644 vendor/mcp23017/README.md create mode 100644 vendor/mcp23017/docs/address-pins.jpg create mode 100644 vendor/mcp23017/src/lib.rs 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 0000000000000000000000000000000000000000..eb85f5d09c7aa73187e497d8bd5d40dc75c1a38a GIT binary patch literal 28634 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 MDflBJkiQoG597I{w*UYD literal 0 HcmV?d00001 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, +}