perf: don't read/write pin mode, write only
vendored mcp23017 library too
This commit is contained in:
parent
65fbaa323b
commit
35e97a92e3
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -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",
|
||||||
]
|
]
|
||||||
|
@ -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]
|
||||||
|
@ -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 {
|
||||||
|
21
src/pins.rs
21
src/pins.rs
@ -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(())
|
||||||
|
7
vendor/mcp23017/.github/dependabot.yml
vendored
Normal file
7
vendor/mcp23017/.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: cargo
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
open-pull-requests-limit: 10
|
144
vendor/mcp23017/.github/workflows/mcp23017-ci.yml
vendored
Normal file
144
vendor/mcp23017/.github/workflows/mcp23017-ci.yml
vendored
Normal 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
10
vendor/mcp23017/.gitignore
vendored
Normal 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
18
vendor/mcp23017/Cargo.toml
vendored
Normal 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
21
vendor/mcp23017/LICENSE
vendored
Normal 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
62
vendor/mcp23017/README.md
vendored
Normal 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
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
466
vendor/mcp23017/src/lib.rs
vendored
Normal 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,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user