#![no_std]
#![deny(missing_docs)]
use bitflags::bitflags;
use core::result;
type Result<T> = result::Result<T, Mcp23s17SpiError>;
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum RegisterAddress {
IODIRA = 0x0,
IODIRB = 0x1,
IPOLA = 0x2,
IPOLB = 0x3,
GPINTENA = 0x4,
GPINTENB = 0x5,
DEFVALA = 0x6,
DEFVALB = 0x7,
INTCONA = 0x8,
INTCONB = 0x9,
IOCON = 0xA,
IOCON2 = 0xB,
GPPUA = 0xC,
GPPUB = 0xD,
INTFA = 0xE,
INTFB = 0xF,
INTCAPA = 0x10,
INTCAPB = 0x11,
GPIOA = 0x12,
GPIOB = 0x13,
OLATA = 0x14,
OLATB = 0x15,
}
bitflags! {
pub struct IOCON: u8 {
const BANK = 0b1000_0000;
const MIRROR = 0b0100_0000;
const SEQOP = 0b0010_0000;
const DISSLW = 0b0001_0000;
const HAEN = 0b0000_1000;
const ODR = 0b0000_0100;
const INTPOL = 0b0000_0010;
const _NA = 0b0000_0001;
}
}
impl IOCON {
pub const BANK_ON: IOCON = IOCON::BANK;
pub const BANK_OFF: IOCON = IOCON::empty();
pub const MIRROR_ON: IOCON = IOCON::MIRROR;
pub const MIRROR_OFF: IOCON = IOCON::empty();
pub const SEQOP_ON: IOCON = IOCON::empty();
pub const SEQOP_OFF: IOCON = IOCON::SEQOP;
pub const DISSLW_SLEW_RATE_CONTROLLED: IOCON = IOCON::empty();
pub const DISSLW_SLEW_RATE_MAX: IOCON = IOCON::DISSLW;
pub const HAEN_ON: IOCON = IOCON::HAEN;
pub const HAEN_OFF: IOCON = IOCON::empty();
pub const ODR_ON: IOCON = IOCON::ODR;
pub const ODR_OFF: IOCON = IOCON::empty();
pub const INTPOL_HIGH: IOCON = IOCON::INTPOL;
pub const INTPOL_LOW: IOCON = IOCON::empty();
}
#[derive(core::fmt::Debug)]
pub struct Mcp23s17SpiError {}
#[repr(u8)]
pub enum PinMode {
InputFloating,
InputPullup,
Output,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum InterruptMode {
None,
ActiveHigh,
ActiveLow,
BothEdges,
}
pub struct Mcp23s17<SPI: embedded_hal::spi::SpiDevice> {
spi: SPI,
spi_read_control_byte: u8,
spi_write_control_byte: u8,
pin_io_configurations_a: u8,
pin_io_configurations_b: u8,
pin_interrupt_configurations_a: u8,
pin_interrupt_configurations_b: u8,
}
#[inline]
fn pin_mask(pin_num: u8) -> u8 {
0b1 << pin_num
}
impl<SPI: embedded_hal::spi::SpiDevice> Mcp23s17<SPI> {
#[allow(clippy::identity_op)]
pub fn new(spi: SPI, address: u8) -> Result<Self> {
let mut mcp = Mcp23s17 {
spi,
spi_read_control_byte: 0b01000000_u8 | 0b000 << 1 | 1 << 0,
spi_write_control_byte: 0b01000000_u8 | 0b000 << 1 | 0 << 0,
pin_io_configurations_a: 0,
pin_io_configurations_b: 0,
pin_interrupt_configurations_a: 0,
pin_interrupt_configurations_b: 0,
};
let iocon = mcp.read_byte(RegisterAddress::IOCON)?;
mcp.write_byte(RegisterAddress::IOCON, iocon | IOCON::HAEN.bits())?;
mcp.spi_read_control_byte = 0b01000000_u8 | address << 1 | 1 << 0;
mcp.spi_write_control_byte = 0b01000000_u8 | address << 1 | 0 << 0;
Ok(mcp)
}
pub fn pin_mode(&mut self, pin: u8, mode: PinMode) -> Result<()> {
let pin_num = pin % 8;
let mask = pin_mask(pin_num);
let (data_direction_register, pullup_register, has_interrupt) = if pin < 8 {
(
RegisterAddress::IODIRA,
RegisterAddress::GPPUA,
self.pin_interrupt_configurations_a & mask > 0,
)
} else {
(
RegisterAddress::IODIRB,
RegisterAddress::GPPUB,
self.pin_interrupt_configurations_b & mask > 0,
)
};
match mode {
PinMode::InputFloating | PinMode::Output => {
self.clear_bit(pullup_register, pin_num)?;
}
PinMode::InputPullup => {
self.set_bit(pullup_register, pin_num)?;
}
}
match mode {
PinMode::InputFloating | PinMode::InputPullup => {
self.set_bit(data_direction_register, pin_num)?;
if has_interrupt {
self.set_interrupt_mode(pin, InterruptMode::None)?;
}
}
PinMode::Output => {
self.clear_bit(data_direction_register, pin_num)?;
if pin < 8 {
self.clear_bit(RegisterAddress::GPIOA, pin_num)?;
} else {
self.clear_bit(RegisterAddress::GPIOB, pin_num)?;
}
}
}
let configuration = if pin < 8 {
&mut self.pin_io_configurations_a
} else {
&mut self.pin_io_configurations_b
};
match mode {
PinMode::InputFloating | PinMode::InputPullup => {
*configuration |= 1 << pin_num;
}
PinMode::Output => {
*configuration &= !(1 << pin_num);
}
}
Ok(())
}
pub fn pin_mode_all(&mut self, mode: PinMode) -> Result<()> {
match mode {
PinMode::InputFloating => {
self.write_2_bytes(RegisterAddress::GPPUA, (0x00, 0x00))?;
self.write_2_bytes(RegisterAddress::IODIRA, (0xff, 0xff))?;
(self.pin_io_configurations_a, self.pin_io_configurations_b) = (0xff, 0xff);
}
PinMode::InputPullup => {
self.write_2_bytes(RegisterAddress::GPPUA, (0xff, 0xff))?;
self.write_2_bytes(RegisterAddress::IODIRA, (0xff, 0xff))?;
(self.pin_io_configurations_a, self.pin_io_configurations_b) = (0xff, 0xff);
}
PinMode::Output => {
self.write_2_bytes(RegisterAddress::GPPUA, (0x00, 0x00))?;
self.write_2_bytes(RegisterAddress::IODIRA, (0x00, 0x00))?;
(self.pin_io_configurations_a, self.pin_io_configurations_b) = (0x00, 0x00);
}
}
Ok(())
}
pub fn set_all_high(&mut self) -> Result<()> {
self.set_all_value((0xff, 0xff))
}
pub fn set_all_low(&mut self) -> Result<()> {
self.set_all_value((0x00, 0x00))
}
pub fn set_all_value(&mut self, value: (u8, u8)) -> Result<()> {
if self.pin_io_configurations_a != 0x00 || self.pin_io_configurations_b != 0x00 {
self.pin_mode_all(PinMode::Output)?;
}
self.write_2_bytes(RegisterAddress::GPIOA, value)
}
fn get_num_mask_register_config(&self, pin: u8) -> (u8, RegisterAddress, bool) {
let pin_num = pin % 8;
let mask = 0b1 << (pin % 8);
if pin < 8 {
(
pin_num,
RegisterAddress::GPIOA,
self.pin_io_configurations_a & mask > 0,
)
} else {
(
pin_num,
RegisterAddress::GPIOB,
self.pin_io_configurations_b & mask > 0,
)
}
}
pub fn set_high(&mut self, pin: u8) -> Result<()> {
let (pin_num, gpio_register, is_input) = self.get_num_mask_register_config(pin);
if is_input {
self.pin_mode(pin, PinMode::Output)?;
}
self.set_bit(gpio_register, pin_num)
}
pub fn set_low(&mut self, pin: u8) -> Result<()> {
let (pin_num, gpio_register, is_input) = self.get_num_mask_register_config(pin);
if is_input {
self.pin_mode(pin, PinMode::Output)?;
}
self.clear_bit(gpio_register, pin_num)
}
pub fn set_value(&mut self, pin: u8, value: bool) -> Result<()> {
if value {
self.set_high(pin)
} else {
self.set_low(pin)
}
}
pub fn read_all(&mut self) -> Result<(u8, u8)> {
self.read_2_bytes(RegisterAddress::GPIOA)
}
pub fn read(&mut self, pin: u8) -> Result<bool> {
let gpio_register = if pin < 8 {
RegisterAddress::GPIOA
} else {
RegisterAddress::GPIOB
};
self.get_bit(gpio_register, pin % 8)
}
pub fn set_interrupt_mode(&mut self, pin: u8, mode: InterruptMode) -> Result<()> {
let pin_num = pin % 8;
let mask = 0b1 << (pin % 8);
let (gpinten, intcon, defval, is_input) = if pin < 8 {
(
RegisterAddress::GPINTENA,
RegisterAddress::INTCONA,
RegisterAddress::DEFVALA,
self.pin_io_configurations_a & mask > 0,
)
} else {
(
RegisterAddress::GPINTENB,
RegisterAddress::INTCONB,
RegisterAddress::DEFVALB,
self.pin_io_configurations_b & mask > 0,
)
};
if !is_input {
self.pin_mode(pin, PinMode::InputPullup)?;
}
match mode {
InterruptMode::None => {
self.clear_bit(gpinten, pin_num)?;
self.set_pin_interrupt_disabled(pin);
}
InterruptMode::ActiveHigh => {
self.set_bit(intcon, pin_num)?;
self.clear_bit(defval, pin_num)?;
self.set_bit(gpinten, pin_num)?;
self.set_pin_interrupt_enabled(pin);
}
InterruptMode::ActiveLow => {
self.set_bit(intcon, pin_num)?;
self.set_bit(defval, pin_num)?;
self.set_bit(gpinten, pin_num)?;
self.set_pin_interrupt_enabled(pin);
}
InterruptMode::BothEdges => {
self.clear_bit(intcon, pin_num)?;
self.set_bit(gpinten, pin_num)?;
self.set_pin_interrupt_enabled(pin);
}
}
Ok(())
}
}
impl<SPI: embedded_hal::spi::SpiDevice> Mcp23s17<SPI> {
fn set_pin_interrupt_enabled(&mut self, pin: u8) {
let pin_num = pin % 8;
let mask = pin_mask(pin_num);
if pin < 8 {
self.pin_interrupt_configurations_a |= mask;
} else {
self.pin_interrupt_configurations_b |= mask;
}
}
fn set_pin_interrupt_disabled(&mut self, pin: u8) {
let pin_num = pin % 8;
let mask = pin_mask(pin_num);
if pin < 8 {
self.pin_interrupt_configurations_a &= !mask;
} else {
self.pin_interrupt_configurations_b &= !mask;
}
}
fn get_bit(&mut self, register: RegisterAddress, bit: u8) -> Result<bool> {
Ok(self.read_byte(register)? & (0x01 << bit) > 0)
}
fn set_bits(&mut self, register: RegisterAddress, data: u8) -> Result<()> {
let data = self.read_byte(register)? | data;
self.write_byte(register, data)
}
fn set_bit(&mut self, register: RegisterAddress, bit: u8) -> Result<()> {
self.set_bits(register, 0x01 << bit)
}
fn clear_bits(&mut self, register: RegisterAddress, data: u8) -> Result<()> {
let data = self.read_byte(register)? & !data;
self.write_byte(register, data)
}
fn clear_bit(&mut self, register: RegisterAddress, bit: u8) -> Result<()> {
self.clear_bits(register, 0x01 << bit)
}
fn read_byte(&mut self, register: RegisterAddress) -> Result<u8> {
let mut write_buffer = [0u8; 3];
write_buffer[0] = self.spi_read_control_byte;
write_buffer[1] = register as u8;
let read_buffer = self._transfer(&mut write_buffer)?;
if read_buffer.len() != 3 {
Err(Mcp23s17SpiError {})
} else {
Ok(read_buffer[2])
}
}
fn read_2_bytes(&mut self, register: RegisterAddress) -> Result<(u8, u8)> {
let mut write_buffer = [self.spi_read_control_byte, register as u8, 0, 0];
let read_buffer = self._transfer(&mut write_buffer)?;
Ok((read_buffer[2], read_buffer[3]))
}
fn _transfer<'a>(&mut self, write_buffer: &'a mut [u8]) -> Result<&'a [u8]> {
self.spi
.transfer_in_place(write_buffer)
.map_err(|_| Mcp23s17SpiError {})?;
Ok(write_buffer)
}
fn write_byte(&mut self, register: RegisterAddress, data: u8) -> Result<()> {
let write_buffer = [self.spi_write_control_byte, register as u8, data];
self._write(&write_buffer)
}
fn write_2_bytes(&mut self, register: RegisterAddress, data: (u8, u8)) -> Result<()> {
let write_buffer = [self.spi_write_control_byte, register as u8, data.0, data.1];
self._write(&write_buffer)
}
fn _write(&mut self, write_buffer: &[u8]) -> Result<()> {
self.spi
.write(write_buffer)
.map_err(|_| Mcp23s17SpiError {})
}
}