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]]
|
||||
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",
|
||||
]
|
||||
|
@ -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]
|
||||
|
@ -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 {
|
||||
|
21
src/pins.rs
21
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<Self, Error> {
|
||||
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(())
|
||||
|
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