feat: support multiple pin extenders on the I²C bus
This commit is contained in:
parent
760a390680
commit
2bbabaff5c
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -802,6 +802,7 @@ dependencies = [
|
||||
"mcp23017",
|
||||
"panic-probe",
|
||||
"portable-atomic",
|
||||
"shared-bus",
|
||||
"smart-leds",
|
||||
"static_cell",
|
||||
"usbd-hid 0.7.0",
|
||||
@ -1384,6 +1385,16 @@ dependencies = [
|
||||
"syn 2.0.58",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared-bus"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6b8d3f0e34309c22ca4a9a27d24fa493e31573485f3493802b75b9d706756a6"
|
||||
dependencies = [
|
||||
"embedded-hal 0.2.7",
|
||||
"nb 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
|
@ -43,6 +43,7 @@ portable-atomic = { version = "1.5", features = ["critical-section"] }
|
||||
log = "0.4"
|
||||
|
||||
mcp23017 = { version = "1.0.0" }
|
||||
shared-bus = "0.3.1"
|
||||
|
||||
[profile.release]
|
||||
debug = 2
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(rust_2018_idioms)]
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::bind_interrupts;
|
||||
@ -78,13 +79,13 @@ async fn main(_spawner: Spawner) {
|
||||
let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
|
||||
|
||||
log::info!("main: starting transparent pin driver");
|
||||
let mut pin_driver = pins::TransparentPins::new(i2c, [0x20], []);
|
||||
let mut pin_driver = pins::TransparentPins::new(i2c, [0x20, 0x27], []);
|
||||
|
||||
log::info!("main: setting pins as input");
|
||||
for i in 0..16 {
|
||||
for i in 0..pins::N_EXTENDED_PINS {
|
||||
log::debug!("main: setting pin {} as input, pull up", i);
|
||||
unwrap(pin_driver.set_input(i)).await;
|
||||
unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).await;
|
||||
unwrap(pin_driver.set_input(i as u8)).await;
|
||||
unwrap(pin_driver.set_pull(i as u8, gpio::Pull::Up)).await;
|
||||
}
|
||||
|
||||
// these pins are faulty as inputs
|
||||
|
134
src/pins.rs
134
src/pins.rs
@ -18,39 +18,31 @@
|
||||
|
||||
//! Manage I²C and provide a transparent pin interface for both onboard and MCP23017 pins.
|
||||
|
||||
extern crate embedded_hal_02;
|
||||
use embassy_rp::{
|
||||
gpio::{AnyPin, Flex, Pull},
|
||||
i2c::{self, Blocking},
|
||||
peripherals::I2C0,
|
||||
};
|
||||
|
||||
extern crate mcp23017;
|
||||
use mcp23017;
|
||||
use mcp23017::MCP23017;
|
||||
|
||||
/// Number of pins driven by each MCP23017 pin extender.
|
||||
const PINS_PER_EXTENDER: usize = 16;
|
||||
/// Number of MCP23017 chips used. This can not be changed without changing code.
|
||||
const N_PIN_EXTENDERS: usize = 1;
|
||||
pub const PINS_PER_EXTENDER: usize = 16;
|
||||
/// Number of MCP23017 chips used.
|
||||
pub const N_PIN_EXTENDERS: usize = 2;
|
||||
/// Number of pins driven directly by the board.
|
||||
const N_REGULAR_PINS: usize = 0;
|
||||
pub const N_REGULAR_PINS: usize = 0;
|
||||
/// Number of total extended pins
|
||||
const N_EXTENDED_PINS: usize = PINS_PER_EXTENDER * N_PIN_EXTENDERS;
|
||||
pub const N_EXTENDED_PINS: usize = PINS_PER_EXTENDER * N_PIN_EXTENDERS;
|
||||
|
||||
/// "Transparent pins" to consistently interface with a GPIO extender + onboard GPIO ports.
|
||||
///
|
||||
/// This interface uses a single addressing scheme for all the pins it manages.
|
||||
/// ext0 is 0-15, ext1 is 16-31, regular pins are 32-63.
|
||||
pub struct TransparentPins {
|
||||
ext0: MCP23017<i2c::I2c<'static, I2C0, Blocking>>,
|
||||
//ext1: MCP23017<i2c::I2c<'static, I2C1, Blocking>>,
|
||||
pins: [Flex<'static, AnyPin>; N_REGULAR_PINS],
|
||||
}
|
||||
type I2cPeripheral = i2c::I2c<'static, I2C0, Blocking>;
|
||||
type I2cBus = shared_bus::BusManagerSimple<I2cPeripheral>;
|
||||
|
||||
/// GPIO extender pin
|
||||
struct ExtendedPin {
|
||||
/// ID (not address) of the extender being used
|
||||
ext_id: u8,
|
||||
/// Index of extender being used
|
||||
ext_id: usize,
|
||||
/// Pin number in the extender's addressing scheme
|
||||
loc_pin: u8,
|
||||
}
|
||||
@ -62,39 +54,81 @@ enum TransparentPin {
|
||||
Extended(ExtendedPin),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidPin(u8),
|
||||
I2cError(i2c::Error),
|
||||
ExtenderError,
|
||||
}
|
||||
|
||||
impl From<i2c::Error> for Error {
|
||||
fn from(err: i2c::Error) -> Error {
|
||||
Error::I2cError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<mcp23017::Error<E>> for Error {
|
||||
fn from(_err: mcp23017::Error<E>) -> Error {
|
||||
Error::ExtenderError
|
||||
}
|
||||
}
|
||||
|
||||
/// "Transparent pins" to consistently interface with a GPIO extender + onboard GPIO ports.
|
||||
///
|
||||
/// This interface uses a single addressing scheme for all the pins it manages.
|
||||
/// `ext[0]` is 0-15, `ext[1]` is 16-31, regular pins are 32-63.
|
||||
pub struct TransparentPins {
|
||||
addrs: [u8; N_PIN_EXTENDERS],
|
||||
pins: [Flex<'static, AnyPin>; N_REGULAR_PINS],
|
||||
i2c_bus: I2cBus,
|
||||
}
|
||||
|
||||
/// Create a new short-lived MCP23017 struct.
|
||||
///
|
||||
/// This is needed because our bus proxy uses references to the bus,
|
||||
/// and having long-lived references angers the borrow-checker
|
||||
macro_rules! extender {
|
||||
($self:ident,$ext_id:expr) => {
|
||||
MCP23017::new($self.i2c_bus.acquire_i2c(), $self.addrs[$ext_id])
|
||||
};
|
||||
}
|
||||
|
||||
impl TransparentPins {
|
||||
fn get_pin(pin: u8) -> TransparentPin {
|
||||
fn get_pin(&mut self, pin: u8) -> Result<TransparentPin, Error> {
|
||||
if pin as usize >= N_EXTENDED_PINS + N_REGULAR_PINS {
|
||||
return Err(Error::InvalidPin(pin));
|
||||
}
|
||||
if pin < (N_EXTENDED_PINS as u8) {
|
||||
let ext_id = pin / (PINS_PER_EXTENDER as u8);
|
||||
let ext_id = (pin as usize) / PINS_PER_EXTENDER;
|
||||
let loc_pin = pin % (PINS_PER_EXTENDER as u8);
|
||||
if ext_id >= N_PIN_EXTENDERS as u8 {
|
||||
panic!("invalid pin")
|
||||
}
|
||||
TransparentPin::Extended(ExtendedPin { ext_id, loc_pin })
|
||||
Ok(TransparentPin::Extended(ExtendedPin { ext_id, loc_pin }))
|
||||
} else {
|
||||
TransparentPin::Onboard(pin as usize - N_EXTENDED_PINS)
|
||||
Ok(TransparentPin::Onboard(pin as usize - N_EXTENDED_PINS))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
i2c0: i2c::I2c<'static, I2C0, Blocking>,
|
||||
//i2c1: i2c::I2c<'static, I2C1, Blocking>,
|
||||
i2c: i2c::I2c<'static, I2C0, Blocking>,
|
||||
addrs: [u8; N_PIN_EXTENDERS],
|
||||
pins: [AnyPin; N_REGULAR_PINS],
|
||||
) -> Self {
|
||||
let pin_init = pins.map(|x| Flex::new(x));
|
||||
return TransparentPins {
|
||||
ext0: MCP23017::new(i2c0, addrs[0]).unwrap(),
|
||||
pins: pin_init,
|
||||
};
|
||||
TransparentPins {
|
||||
addrs,
|
||||
pins: pins.map(|x| Flex::new(x)),
|
||||
i2c_bus: shared_bus::BusManagerSimple::new(i2c),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read all pins into a single 64-bit value.
|
||||
pub fn read_all(&mut self) -> Result<u64, i2c::Error> {
|
||||
///
|
||||
/// For a given extender's range, port B is in the lower byte and port A in the upper byte.
|
||||
pub fn read_all(&mut self) -> Result<u64, Error> {
|
||||
log::trace!("read_all: called");
|
||||
let mut ret: u64 = 0;
|
||||
// remember here port b is in the lower byte and port a in the upper byte
|
||||
ret |= self.ext0.read_gpioab()? as u64;
|
||||
for i in 0..N_PIN_EXTENDERS {
|
||||
let mut ext = extender!(self, i)?;
|
||||
ret |= (ext.read_gpioab()? as u64) << (i * PINS_PER_EXTENDER);
|
||||
}
|
||||
for pin in 0..N_REGULAR_PINS {
|
||||
log::trace!("pin read: {}", pin);
|
||||
ret |= (self.pins[pin].is_high() as u64) << (N_EXTENDED_PINS + pin);
|
||||
@ -106,8 +140,8 @@ impl TransparentPins {
|
||||
/// Set the pull on an individual pin (0-index).
|
||||
///
|
||||
/// Note: MCP23017 pins do not support pull-down.
|
||||
pub fn set_pull(&mut self, pin: u8, pull: Pull) -> Result<(), i2c::Error> {
|
||||
let pin = TransparentPins::get_pin(pin);
|
||||
pub fn set_pull(&mut self, pin: u8, pull: Pull) -> Result<(), Error> {
|
||||
let pin = self.get_pin(pin)?;
|
||||
match pin {
|
||||
TransparentPin::Onboard(p) => {
|
||||
self.pins[p].set_pull(pull);
|
||||
@ -119,41 +153,29 @@ impl TransparentPins {
|
||||
// Extended pins don't seem to support pull-down
|
||||
Pull::Down => unimplemented!("MCP23017 does not support pull-down."),
|
||||
};
|
||||
match p.ext_id {
|
||||
0 => self.ext0.pull_up(p.loc_pin, pull_on)?,
|
||||
//1 => self.ext1.pull_up(p.loc_pin, pull_on)?,
|
||||
_ => panic!("invalid pin"),
|
||||
}
|
||||
extender!(self, p.ext_id)?.pull_up(p.loc_pin, pull_on)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_input(&mut self, pin: u8) -> Result<(), i2c::Error> {
|
||||
let pin = TransparentPins::get_pin(pin);
|
||||
pub fn set_input(&mut self, pin: u8) -> Result<(), Error> {
|
||||
let pin = self.get_pin(pin)?;
|
||||
match pin {
|
||||
TransparentPin::Onboard(p) => self.pins[p].set_as_input(),
|
||||
TransparentPin::Extended(p) => {
|
||||
match p.ext_id {
|
||||
0 => self.ext0.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT)?,
|
||||
//1 => self.ext1.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT).unwrap(),
|
||||
_ => panic!("invalid pin"),
|
||||
}
|
||||
extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_output(&mut self, pin: u8) -> Result<(), i2c::Error> {
|
||||
let pin = TransparentPins::get_pin(pin);
|
||||
pub fn set_output(&mut self, pin: u8) -> Result<(), Error> {
|
||||
let pin = self.get_pin(pin)?;
|
||||
match pin {
|
||||
TransparentPin::Onboard(p) => self.pins[p].set_as_output(),
|
||||
TransparentPin::Extended(p) => {
|
||||
match p.ext_id {
|
||||
0 => self.ext0.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?,
|
||||
//1 => self.ext1.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT).unwrap(),
|
||||
_ => panic!("invalid pin"),
|
||||
}
|
||||
extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
Loading…
Reference in New Issue
Block a user