use core::cell::{Cell, RefCell};
use embedded_hal::digital::{ErrorKind, ErrorType, InputPin, OutputPin, StatefulOutputPin};
use embedded_hal::i2c::I2c;
const REG_IODIRA: u8 = 0x00;
const REG_IODIRB: u8 = 0x01;
const REG_IPOLA: u8 = 0x02;
const REG_IPOLB: u8 = 0x03;
const REG_GPINTENA: u8 = 0x04;
const REG_GPINTENB: u8 = 0x05;
const REG_DEFVALA: u8 = 0x06;
const REG_DEFVALB: u8 = 0x07;
const REG_INTCONA: u8 = 0x08;
const REG_INTCONB: u8 = 0x09;
const REG_IOCON: u8 = 0x0A;
const REG_GPPUA: u8 = 0x0C;
const REG_GPPUB: u8 = 0x0D;
const REG_INTFA: u8 = 0x0E;
const REG_INTFB: u8 = 0x0F;
const REG_INTCAPA: u8 = 0x10;
const REG_INTCAPB: u8 = 0x11;
const REG_GPIOA: u8 = 0x12;
const REG_GPIOB: u8 = 0x13;
const REG_OLATA: u8 = 0x14;
const REG_OLATB: u8 = 0x15;
#[derive(Debug)]
pub struct PinError<E>(pub E);
impl<E: embedded_hal::i2c::Error> embedded_hal::digital::Error for PinError<E> {
fn kind(&self) -> ErrorKind { ErrorKind::Other }
}
pub struct Mcp23017Minimal<I2C> {
i2c: RefCell<I2C>,
addr: u8,
pub shadow: [Cell<u8>; 2],
}
impl<I2C: I2c> Mcp23017Minimal<I2C> {
pub fn new(i2c: I2C, addr: u8) -> Result<Self, I2C::Error> {
let chip = Self {
i2c: RefCell::new(i2c),
addr,
shadow: [Cell::new(0), Cell::new(0)],
};
chip.write_reg(REG_OLATA, 0x00)?;
chip.write_reg(REG_OLATB, 0x00)?;
chip.write_reg(REG_IODIRA, 0x7F)?;
chip.write_reg(REG_IODIRB, 0x7F)?;
chip.write_reg(REG_IPOLA, 0x00)?;
chip.write_reg(REG_IPOLB, 0x00)?;
chip.write_reg(REG_GPPUA, 0x00)?;
chip.write_reg(REG_GPPUB, 0x00)?;
Ok(chip)
}
fn write_reg(&self, reg: u8, value: u8) -> Result<(), I2C::Error> {
self.i2c.borrow_mut().write(self.addr, &[reg, value])
}
fn read_reg(&self, reg: u8) -> Result<u8, I2C::Error> {
let mut buf = [0u8; 1];
self.i2c.borrow_mut().write_read(self.addr, &[reg], &mut buf)?;
Ok(buf[0])
}
pub fn configure_direction(&self, port: u8, mask: u8) -> Result<(), I2C::Error> {
self.write_reg(REG_IODIRA + port, mask)
}
pub fn write_port(&self, port: u8, mask: u8) -> Result<(), I2C::Error> {
self.shadow[port as usize].set(mask & 0xFF);
self.write_reg(REG_OLATA + port, mask)
}
pub fn read_port(&self, port: u8) -> Result<u8, I2C::Error> {
self.read_reg(REG_GPIOA + port)
}
pub fn pin(&self, n: u8) -> ExPin<'_, I2C> {
ExPin { chip: self, n }
}
pub(crate) fn set_pin(&self, n: u8, high: bool) -> Result<(), I2C::Error> {
let port = (n >> 3) as usize;
let bit = n & 7;
let mut s = self.shadow[port].get();
if high { s |= 1 << bit; }
else { s &= !(1 << bit); }
self.shadow[port].set(s);
self.write_reg(REG_OLATA + port as u8, s)
}
pub(crate) fn read_pin(&self, n: u8) -> Result<u8, I2C::Error> {
let port = (n >> 3) as u8;
Ok((self.read_port(port)? >> (n & 7)) & 1)
}
}
pub struct ExPin<'a, I2C> {
chip: &'a Mcp23017Minimal<I2C>,
pub n: u8,
}
impl<I2C: I2c> ErrorType for ExPin<'_, I2C> {
type Error = PinError<I2C::Error>;
}
impl<I2C: I2c> OutputPin for ExPin<'_, I2C> {
fn set_high(&mut self) -> Result<(), PinError<I2C::Error>> {
self.chip.set_pin(self.n, true).map_err(PinError)
}
fn set_low(&mut self) -> Result<(), PinError<I2C::Error>> {
self.chip.set_pin(self.n, false).map_err(PinError)
}
}
impl<I2C: I2c> InputPin for ExPin<'_, I2C> {
fn is_high(&mut self) -> Result<bool, PinError<I2C::Error>> {
Ok(self.chip.read_pin(self.n).map_err(PinError)? == 1)
}
fn is_low(&mut self) -> Result<bool, PinError<I2C::Error>> {
Ok(!self.is_high()?)
}
}
impl<I2C: I2c> StatefulOutputPin for ExPin<'_, I2C> {
fn is_set_high(&mut self) -> Result<bool, PinError<I2C::Error>> {
let port = (self.n >> 3) as usize;
Ok((self.chip.shadow[port].get() >> (self.n & 7)) & 1 == 1)
}
fn is_set_low(&mut self) -> Result<bool, PinError<I2C::Error>> {
Ok(!self.is_set_high()?)
}
}
pub struct Mcp23017Full<I2C> {
inner: Mcp23017Minimal<I2C>,
pub prev: [Cell<u8>; 2],
flags: Cell<u8>,
#[allow(dead_code)]
callback: Cell<Option<fn(u8, u8)>>,
poll_timer: Cell<Option<core::time::Duration>>,
}
impl<I2C: I2c> Mcp23017Full<I2C> {
pub fn new(i2c: I2C, addr: u8) -> Result<Self, I2C::Error> {
let inner = Mcp23017Minimal::new(i2c, addr)?;
Ok(Self {
inner,
prev: [Cell::new(0), Cell::new(0)],
flags: Cell::new(0),
callback: Cell::new(None),
poll_timer: Cell::new(None),
})
}
pub fn pin(&self, n: u8) -> ExPin<'_, I2C> {
self.inner.pin(n)
}
pub fn write_port(&self, port: u8, mask: u8) -> Result<(), I2C::Error> {
self.inner.write_port(port, mask)
}
pub fn read_port(&self, port: u8) -> Result<u8, I2C::Error> {
self.inner.read_port(port)
}
pub fn configure_pullup(&self, port: u8, mask: u8) -> Result<(), I2C::Error> {
self.inner.write_reg(REG_GPPUA + port, mask)
}
pub fn configure_polarity(&self, port: u8, mask: u8) -> Result<(), I2C::Error> {
self.inner.write_reg(REG_IPOLA + port, mask)
}
pub fn clear_interrupt(&self, port: u8) -> Result<u8, I2C::Error> {
let captured = self.inner.read_reg(REG_INTCAPA + port)?;
let current = self.inner.read_reg(REG_GPIOA + port)?;
let changed = (current ^ self.prev[port as usize].get()) & 0xFF;
self.prev[port as usize].set(current);
Ok(changed)
}
pub fn read_interrupt_flags(&self, port: u8) -> Result<u8, I2C::Error> {
self.inner.read_reg(REG_INTFA + port)
}
}