feat: support multiple pin extenders on the I²C bus

This commit is contained in:
dogeystamp 2024-04-14 14:51:21 -04:00
parent 760a390680
commit 2bbabaff5c
Signed by: dogeystamp
GPG Key ID: 7225FE3592EFFA38
4 changed files with 95 additions and 60 deletions

11
Cargo.lock generated
View File

@ -802,6 +802,7 @@ dependencies = [
"mcp23017", "mcp23017",
"panic-probe", "panic-probe",
"portable-atomic", "portable-atomic",
"shared-bus",
"smart-leds", "smart-leds",
"static_cell", "static_cell",
"usbd-hid 0.7.0", "usbd-hid 0.7.0",
@ -1384,6 +1385,16 @@ dependencies = [
"syn 2.0.58", "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]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"

View File

@ -43,6 +43,7 @@ portable-atomic = { version = "1.5", features = ["critical-section"] }
log = "0.4" log = "0.4"
mcp23017 = { version = "1.0.0" } mcp23017 = { version = "1.0.0" }
shared-bus = "0.3.1"
[profile.release] [profile.release]
debug = 2 debug = 2

View File

@ -1,5 +1,6 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![deny(rust_2018_idioms)]
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_rp::bind_interrupts; 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); let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
log::info!("main: starting transparent pin driver"); 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"); 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); log::debug!("main: setting pin {} as input, pull up", i);
unwrap(pin_driver.set_input(i)).await; unwrap(pin_driver.set_input(i as u8)).await;
unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).await; unwrap(pin_driver.set_pull(i as u8, gpio::Pull::Up)).await;
} }
// these pins are faulty as inputs // these pins are faulty as inputs

View File

@ -18,39 +18,31 @@
//! Manage I²C and provide a transparent pin interface for both onboard and MCP23017 pins. //! Manage I²C and provide a transparent pin interface for both onboard and MCP23017 pins.
extern crate embedded_hal_02;
use embassy_rp::{ use embassy_rp::{
gpio::{AnyPin, Flex, Pull}, gpio::{AnyPin, Flex, Pull},
i2c::{self, Blocking}, i2c::{self, Blocking},
peripherals::I2C0, peripherals::I2C0,
}; };
extern crate mcp23017; use mcp23017;
use mcp23017::MCP23017; use mcp23017::MCP23017;
/// Number of pins driven by each MCP23017 pin extender. /// Number of pins driven by each MCP23017 pin extender.
const PINS_PER_EXTENDER: usize = 16; pub const PINS_PER_EXTENDER: usize = 16;
/// Number of MCP23017 chips used. This can not be changed without changing code. /// Number of MCP23017 chips used.
const N_PIN_EXTENDERS: usize = 1; pub const N_PIN_EXTENDERS: usize = 2;
/// Number of pins driven directly by the board. /// 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 /// 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. type I2cPeripheral = i2c::I2c<'static, I2C0, Blocking>;
/// type I2cBus = shared_bus::BusManagerSimple<I2cPeripheral>;
/// 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],
}
/// GPIO extender pin /// GPIO extender pin
struct ExtendedPin { struct ExtendedPin {
/// ID (not address) of the extender being used /// Index of extender being used
ext_id: u8, ext_id: usize,
/// Pin number in the extender's addressing scheme /// Pin number in the extender's addressing scheme
loc_pin: u8, loc_pin: u8,
} }
@ -62,39 +54,81 @@ enum TransparentPin {
Extended(ExtendedPin), 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 { 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) { 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); let loc_pin = pin % (PINS_PER_EXTENDER as u8);
if ext_id >= N_PIN_EXTENDERS as u8 { Ok(TransparentPin::Extended(ExtendedPin { ext_id, loc_pin }))
panic!("invalid pin")
}
TransparentPin::Extended(ExtendedPin { ext_id, loc_pin })
} else { } else {
TransparentPin::Onboard(pin as usize - N_EXTENDED_PINS) Ok(TransparentPin::Onboard(pin as usize - N_EXTENDED_PINS))
} }
} }
pub fn new( pub fn new(
i2c0: i2c::I2c<'static, I2C0, Blocking>, i2c: i2c::I2c<'static, I2C0, Blocking>,
//i2c1: i2c::I2c<'static, I2C1, Blocking>,
addrs: [u8; N_PIN_EXTENDERS], addrs: [u8; N_PIN_EXTENDERS],
pins: [AnyPin; N_REGULAR_PINS], pins: [AnyPin; N_REGULAR_PINS],
) -> Self { ) -> Self {
let pin_init = pins.map(|x| Flex::new(x)); TransparentPins {
return TransparentPins { addrs,
ext0: MCP23017::new(i2c0, addrs[0]).unwrap(), pins: pins.map(|x| Flex::new(x)),
pins: pin_init, i2c_bus: shared_bus::BusManagerSimple::new(i2c),
}; }
} }
/// Read all pins into a single 64-bit value. /// 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"); log::trace!("read_all: called");
let mut ret: u64 = 0; let mut ret: u64 = 0;
// remember here port b is in the lower byte and port a in the upper byte for i in 0..N_PIN_EXTENDERS {
ret |= self.ext0.read_gpioab()? as u64; let mut ext = extender!(self, i)?;
ret |= (ext.read_gpioab()? as u64) << (i * PINS_PER_EXTENDER);
}
for pin in 0..N_REGULAR_PINS { for pin in 0..N_REGULAR_PINS {
log::trace!("pin read: {}", pin); log::trace!("pin read: {}", pin);
ret |= (self.pins[pin].is_high() as u64) << (N_EXTENDED_PINS + 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). /// Set the pull on an individual pin (0-index).
/// ///
/// Note: MCP23017 pins do not support pull-down. /// Note: MCP23017 pins do not support pull-down.
pub fn set_pull(&mut self, pin: u8, pull: Pull) -> Result<(), i2c::Error> { pub fn set_pull(&mut self, pin: u8, pull: Pull) -> Result<(), Error> {
let pin = TransparentPins::get_pin(pin); let pin = self.get_pin(pin)?;
match pin { match pin {
TransparentPin::Onboard(p) => { TransparentPin::Onboard(p) => {
self.pins[p].set_pull(pull); self.pins[p].set_pull(pull);
@ -119,41 +153,29 @@ impl TransparentPins {
// Extended pins don't seem to support pull-down // Extended pins don't seem to support pull-down
Pull::Down => unimplemented!("MCP23017 does not support pull-down."), Pull::Down => unimplemented!("MCP23017 does not support pull-down."),
}; };
match p.ext_id { extender!(self, p.ext_id)?.pull_up(p.loc_pin, pull_on)?
0 => self.ext0.pull_up(p.loc_pin, pull_on)?,
//1 => self.ext1.pull_up(p.loc_pin, pull_on)?,
_ => panic!("invalid pin"),
}
} }
} }
Ok(()) Ok(())
} }
pub fn set_input(&mut self, pin: u8) -> Result<(), i2c::Error> { pub fn set_input(&mut self, pin: u8) -> Result<(), Error> {
let pin = TransparentPins::get_pin(pin); let pin = self.get_pin(pin)?;
match pin { match pin {
TransparentPin::Onboard(p) => self.pins[p].set_as_input(), TransparentPin::Onboard(p) => self.pins[p].set_as_input(),
TransparentPin::Extended(p) => { TransparentPin::Extended(p) => {
match p.ext_id { extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT)?
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"),
}
} }
} }
Ok(()) Ok(())
} }
pub fn set_output(&mut self, pin: u8) -> Result<(), i2c::Error> { pub fn set_output(&mut self, pin: u8) -> Result<(), Error> {
let pin = TransparentPins::get_pin(pin); let pin = self.get_pin(pin)?;
match pin { match pin {
TransparentPin::Onboard(p) => self.pins[p].set_as_output(), TransparentPin::Onboard(p) => self.pins[p].set_as_output(),
TransparentPin::Extended(p) => { TransparentPin::Extended(p) => {
match p.ext_id { extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?
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"),
}
} }
} }
Ok(()) Ok(())