xl9555-hal 0.3.0

embedded-hal driver for the XL9555 GPIO expander
Documentation
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),
        }
    }
}