perf: don't read/write pin mode, write only

vendored mcp23017 library too
This commit is contained in:
dogeystamp 2024-11-10 21:10:21 -05:00
parent 65fbaa323b
commit 35e97a92e3
12 changed files with 749 additions and 9 deletions

4
Cargo.lock generated
View File

@ -989,9 +989,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]] [[package]]
name = "mcp23017" name = "mcp23017"
version = "1.0.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c32fd6627e73f1cfa95c00ddcdcb5a6a6ddbd10b308d08588a502c018b6e12c"
dependencies = [ dependencies = [
"embedded-hal 0.2.7", "embedded-hal 0.2.7",
] ]

View File

@ -42,7 +42,8 @@ static_cell = "2"
portable-atomic = { version = "1.5", features = ["critical-section"] } portable-atomic = { version = "1.5", features = ["critical-section"] }
log = "0.4" 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" shared-bus = "0.3.1"
[profile.release] [profile.release]

View File

@ -39,7 +39,7 @@ fn velocity_heavy(us: u64) -> u8 {
} }
fn velocity_linear(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 { pub struct Config {

View File

@ -108,6 +108,8 @@ impl IntoIterator for PinCollection {
pub struct TransparentPins { pub struct TransparentPins {
addrs: [u8; N_PIN_EXTENDERS], addrs: [u8; N_PIN_EXTENDERS],
onboard_pins: [Flex<'static, AnyPin>; N_REGULAR_PINS], 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, i2c_bus: I2cBus,
disable_unsafe_pins: bool, disable_unsafe_pins: bool,
/// Usable pins per extender. Depends on `disable_unsafe_pins`. /// Usable pins per extender. Depends on `disable_unsafe_pins`.
@ -191,6 +193,7 @@ impl TransparentPins {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut ret = TransparentPins { let mut ret = TransparentPins {
addrs, addrs,
io_state: (1 << (N_REGULAR_PINS + N_EXTENDED_PINS)) - 1,
onboard_pins: pins.map(Flex::new), onboard_pins: pins.map(Flex::new),
i2c_bus: shared_bus::BusManagerSimple::new(i2c), i2c_bus: shared_bus::BusManagerSimple::new(i2c),
disable_unsafe_pins: false, disable_unsafe_pins: false,
@ -221,7 +224,12 @@ impl TransparentPins {
// ports are flipped from what it should be // ports are flipped from what it should be
let port_a = (val & (0xff00)) >> 8; let port_a = (val & (0xff00)) >> 8;
let port_b = val & (0x00ff); 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) (port_a & 0x7f) | ((port_b & 0x7f) << 7)
} else { } else {
val val
@ -300,10 +308,13 @@ impl TransparentPins {
pub fn set_input(&mut self, addr: u8) -> Result<(), Error> { pub fn set_input(&mut self, addr: u8) -> Result<(), Error> {
let pin_n = self.addr_to_pin(addr); let pin_n = self.addr_to_pin(addr);
let pin = self.get_pin(pin_n)?; let pin = self.get_pin(pin_n)?;
self.io_state |= 1 << pin_n;
match pin { match pin {
TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_input(), TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_input(),
TransparentPin::Extended(p) => { 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(()) Ok(())
@ -313,11 +324,13 @@ impl TransparentPins {
pub fn set_output(&mut self, addr: u8) -> Result<(), Error> { pub fn set_output(&mut self, addr: u8) -> Result<(), Error> {
let pin_n = self.addr_to_pin(addr); let pin_n = self.addr_to_pin(addr);
let pin = self.get_pin(pin_n)?; let pin = self.get_pin(pin_n)?;
self.io_state &= !(1 << pin_n);
match pin { match pin {
TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_output(), TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_output(),
TransparentPin::Extended(p) => { TransparentPin::Extended(p) => {
let mut ext = extender!(self, p.ext_id)?; let ext_io_word = (self.io_state >> (p.ext_id * PINS_PER_EXTENDER))
ext.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?; & ((1 << PINS_PER_EXTENDER) - 1);
extender!(self, p.ext_id)?.overwrite_pin_mode(p.loc_pin, ext_io_word as u16)?;
} }
} }
Ok(()) Ok(())

View File

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

View File

@ -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

10
vendor/mcp23017/.gitignore vendored Normal file
View File

@ -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

18
vendor/mcp23017/Cargo.toml vendored Normal file
View File

@ -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 <lucagiuggia@gmail.com>"]
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"

21
vendor/mcp23017/LICENSE vendored Normal file
View File

@ -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.

62
vendor/mcp23017/README.md vendored Normal file
View File

@ -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 = "<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)

BIN
vendor/mcp23017/docs/address-pins.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

466
vendor/mcp23017/src/lib.rs vendored Normal file
View File

@ -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<I2C: Write + WriteRead> {
com: I2C,
/// The I2C slave address of this device.
pub address: u8,
}
/// Defines errors
#[derive(Debug, Copy, Clone)]
pub enum Error<E> {
/// Underlying bus error
BusError(E),
/// Interrupt pin not found
InterruptPinError,
}
impl<E> From<E> for Error<E> {
fn from(error: E) -> Self {
Error::BusError(error)
}
}
impl<I2C, E> MCP23017<I2C>
where
I2C: WriteRead<Error = E> + Write<Error = E>,
{
/// Creates an expander with the default configuration.
pub fn default(i2c: I2C) -> Result<MCP23017<I2C>, Error<E>>
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
MCP23017::new(i2c, DEFAULT_ADDRESS)
}
/// Creates an expander with specific address.
pub fn new(i2c: I2C, address: u8) -> Result<MCP23017<I2C>, Error<E>>
where
I2C: Write<Error = E> + WriteRead<Error = E>,
{
let chip = MCP23017 { com: i2c, address };
Ok(chip)
}
/// Initiates hardware with basic setup.
pub fn init_hardware(&mut self) -> Result<(), Error<E>> {
// 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<u8, E> {
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<u16, E> {
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<u8, E> {
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<bool, E> {
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<u8, Error<E>> {
// 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<u8, Error<E>> {
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<u8, E> {
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,
}