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",
"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"

View File

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

View File

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

View File

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