use core::sync::atomic::AtomicU8;
#[cfg(feature = "async")]
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use embedded_hal::{
digital::PinState,
i2c::{I2c, SevenBitAddress},
};
#[cfg(feature = "async")]
use crate::types::WaitMode;
use crate::{
error::Xl9555Error,
types::{Direction, PinId, Port::Port0},
};
pub(crate) struct Xl9555<I2c: embedded_hal::i2c::I2c> {
i2c: I2c,
address: SevenBitAddress,
input_cache: [u8; 2],
output_cache: [u8; 2],
direction_cache: [u8; 2],
}
#[cfg(feature = "async")]
pub struct PinSignal {
pub(crate) signal: Signal<CriticalSectionRawMutex, ()>,
pub(crate) wait_mode: AtomicU8,
}
#[cfg(feature = "async")]
impl PinSignal {
pub(crate) const fn new() -> Self {
Self {
signal: Signal::new(),
wait_mode: AtomicU8::new(WaitMode::None as u8),
}
}
}
impl<I2c: embedded_hal::i2c::I2c> Xl9555<I2c> {
pub fn new(i2c: I2c, a3: bool, a2: bool, a1: bool) -> Result<Self, I2c::Error> {
let address = 0x20 | ((a3 as u8) << 3) | ((a2 as u8) << 2) | ((a1 as u8) << 1);
let mut this = Self {
i2c,
address,
input_cache: [0; 2],
output_cache: [0; 2],
direction_cache: [0; 2],
};
this.i2c.write_read(
this.address,
&Port0.input_reg().addr_arr(),
&mut this.input_cache,
)?;
this.i2c.write_read(
this.address,
&Port0.output_reg().addr_arr(),
&mut this.output_cache,
)?;
this.i2c.write_read(
this.address,
&Port0.config_reg().addr_arr(),
&mut this.direction_cache,
)?;
Ok(this)
}
#[allow(dead_code)]
pub fn release(self) -> I2c {
self.i2c
}
}
pub struct ReadInputsResult {
pub now: u16,
pub old: u16,
}
impl<I> Xl9555<I>
where
I: I2c,
{
pub(crate) fn read_inputs(&mut self) -> Result<ReadInputsResult, Xl9555Error<I::Error>> {
let mut inputs = [0, 2];
self.i2c
.write_read(self.address, &Port0.input_reg().addr_arr(), &mut inputs)?;
let ret = ReadInputsResult {
now: u16::from_le_bytes(inputs),
old: u16::from_le_bytes(self.input_cache),
};
self.input_cache = inputs;
Ok(ret)
}
pub(crate) fn set_direction(
&mut self,
pin: PinId,
direction: Direction,
) -> Result<(), Xl9555Error<I::Error>> {
let reg_val = &mut self.direction_cache[pin.port().index()];
let mask = pin.mask();
match direction {
Direction::Input => *reg_val |= mask,
Direction::Output => *reg_val &= !mask,
}
self.i2c
.write(self.address, &[pin.port().config_reg().addr(), *reg_val])
.map_err(Xl9555Error)
}
pub(crate) fn get_direction(&self, pin: PinId) -> Result<Direction, Xl9555Error<I::Error>> {
if self.direction_cache[pin.port().index()] & pin.mask() != 0 {
Ok(Direction::Input)
} else {
Ok(Direction::Output)
}
}
pub(crate) fn set_level(
&mut self,
pin: PinId,
level: PinState,
) -> Result<(), Xl9555Error<I::Error>> {
let reg = &mut self.output_cache[pin.port().index()];
match level {
PinState::High => *reg |= pin.mask(),
PinState::Low => *reg &= !pin.mask(),
}
self.i2c
.write(self.address, &[pin.port().output_reg().addr(), *reg])
.map_err(Xl9555Error)
}
pub(crate) fn get_level(&mut self, pin: PinId) -> Result<PinState, Xl9555Error<I::Error>> {
let mut reg_val = [0; 1];
self.i2c.write_read(
self.address,
&pin.port().input_reg().addr_arr(),
&mut reg_val,
)?;
if reg_val[0] & pin.mask() != 0 {
Ok(PinState::High)
} else {
Ok(PinState::Low)
}
}
pub(crate) fn get_set_level(&self, pin: PinId) -> Result<PinState, Xl9555Error<I::Error>> {
if self.output_cache[pin.port().index()] & pin.mask() != 0 {
Ok(PinState::High)
} else {
Ok(PinState::Low)
}
}
#[allow(dead_code)]
pub(crate) fn toggle(&mut self, pin: PinId) -> Result<(), Xl9555Error<I::Error>> {
match self.output_cache[pin.port().index()] & pin.mask() {
0 => self.set_level(pin, PinState::High),
_ => self.set_level(pin, PinState::Low),
}
}
}