lilkaoxide 0.1.0

Rust support library for Lilka console
#[cfg(all(feature = "blocking", any(feature = "RefCellBus", feature = "CriticalSectionBus")))]
use core::cell::RefCell;
#[cfg(all(feature = "async", any(feature = "CriticalSectionBus", feature = "AsyncBus")))]
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice as EmbassySpiDevice;
#[cfg(all(feature = "async", feature = "CriticalSectionBus"))]
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
#[cfg(all(feature = "async", feature = "AsyncBus"))]
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
#[cfg(all(feature = "async", any(feature = "CriticalSectionBus", feature = "AsyncBus")))]
use embassy_sync::mutex::Mutex as AsyncMutex;
#[cfg(feature = "AtomicBus")]
use embedded_hal_bus::{spi::AtomicDevice, util::AtomicCell};
use esp_hal::gpio::Output;
use esp_hal::gpio::interconnect::{PeripheralInput, PeripheralOutput};
use esp_hal::peripherals::SPI2;
use esp_hal::spi::master::{Config as SpiConfig, Spi};
use esp_hal::time::Rate;
use static_cell::StaticCell;

#[cfg(all(feature = "async", feature = "blocking"))]
compile_error!("'async' і 'blocking' — взаємовиключні features");
#[cfg(not(any(feature = "async", feature = "blocking")))]
compile_error!("Either 'async' or 'blocking' feature must be enabled");
#[cfg(all(feature = "RefCellBus", feature = "CriticalSectionBus"))]
compile_error!("'RefCellBus' і 'CriticalSectionBus' — взаємовиключні");
#[cfg(all(feature = "RefCellBus", feature = "AtomicBus"))]
compile_error!("'RefCellBus' і 'AtomicBus' — взаємовиключні");
#[cfg(all(feature = "CriticalSectionBus", feature = "AtomicBus"))]
compile_error!("'CriticalSectionBus' і 'AtomicBus' — взаємовиключні");
#[cfg(all(feature = "AsyncBus", feature = "RefCellBus"))]
compile_error!("'AsyncBus' і 'RefCellBus' — взаємовиключні");
#[cfg(all(feature = "AsyncBus", feature = "CriticalSectionBus"))]
compile_error!("'AsyncBus' і 'CriticalSectionBus' — взаємовиключні");
#[cfg(all(feature = "AsyncBus", feature = "AtomicBus"))]
compile_error!("'AsyncBus' і 'AtomicBus' — взаємовиключні");
#[cfg(all(feature = "async", feature = "RefCellBus"))]
compile_error!("'RefCellBus' дозволено лише з feature 'blocking'");
#[cfg(all(feature = "blocking", feature = "AsyncBus"))]
compile_error!("'AsyncBus' дозволено лише з feature 'async'");
#[cfg(all(
    feature = "async",
    not(any(feature = "CriticalSectionBus", feature = "AtomicBus", feature = "AsyncBus"))
))]
compile_error!("Для 'async' потрібно ввімкнути 'CriticalSectionBus', 'AtomicBus' або 'AsyncBus'");
#[cfg(all(
    feature = "blocking",
    not(any(feature = "RefCellBus", feature = "CriticalSectionBus", feature = "AtomicBus"))
))]
compile_error!("Для 'blocking' потрібно ввімкнути 'RefCellBus', 'CriticalSectionBus' або 'AtomicBus'");

#[cfg(feature = "blocking")]
pub type SpiMode = esp_hal::Blocking;
#[cfg(feature = "async")]
pub type SpiMode = esp_hal::Async;

pub type SpiBusInner = Spi<'static, SpiMode>;

#[cfg(feature = "blocking")]
pub type InnerDelay = esp_hal::delay::Delay;
#[cfg(feature = "async")]
pub type InnerDelay = embassy_time::Delay;

#[cfg(all(feature = "blocking", feature = "RefCellBus"))]
pub type SpiBusWrapper = RefCell<SpiBusInner>;
#[cfg(all(feature = "blocking", feature = "CriticalSectionBus"))]
pub type SpiBusWrapper = critical_section::Mutex<RefCell<SpiBusInner>>;
#[cfg(all(feature = "blocking", feature = "AtomicBus"))]
pub type SpiBusWrapper = AtomicCell<SpiBusInner>;
#[cfg(all(feature = "async", feature = "CriticalSectionBus"))]
type SpiRawMutex = CriticalSectionRawMutex;
#[cfg(all(feature = "async", feature = "AsyncBus"))]
type SpiRawMutex = NoopRawMutex;
#[cfg(all(feature = "async", any(feature = "CriticalSectionBus", feature = "AsyncBus")))]
pub type SpiBusWrapper = AsyncMutex<SpiRawMutex, SpiBusInner>;
#[cfg(all(feature = "async", feature = "AtomicBus"))]
pub type SpiBusWrapper = AtomicCell<SpiBusInner>;

#[cfg(all(feature = "blocking", feature = "RefCellBus"))]
pub type SpiDev<'a> =
    embedded_hal_bus::spi::RefCellDevice<'a, SpiBusInner, Output<'a>, InnerDelay>;
#[cfg(all(feature = "blocking", feature = "CriticalSectionBus"))]
pub type SpiDev<'a> =
    embedded_hal_bus::spi::CriticalSectionDevice<'a, SpiBusInner, Output<'a>, InnerDelay>;
#[cfg(all(feature = "blocking", feature = "AtomicBus"))]
pub type SpiDev<'a> = AtomicDevice<'a, SpiBusInner, Output<'a>, InnerDelay>;
#[cfg(all(feature = "async",  feature = "AsyncBus"))]
pub type SpiDev<'a> = EmbassySpiDevice<'a, SpiRawMutex, SpiBusInner, Output<'a>>;
#[cfg(all(feature = "async", feature = "AtomicBus"))]
pub type SpiDev<'a> = AtomicDevice<'a, SpiBusInner, Output<'a>, InnerDelay>;

static SPI_BUS: StaticCell<SpiBusWrapper> = StaticCell::new();

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpiInitError {
    DeviceInitFailed,
    BusInitFailed,
}

impl SpiInitError {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::DeviceInitFailed => "SPI device init failed",
            Self::BusInitFailed => "SPI bus init failed",
        }
    }
}

#[cfg(all(feature = "blocking", feature = "RefCellBus"))]
pub fn init_bus(spi: SpiBusInner) -> &'static SpiBusWrapper {
    SPI_BUS.init(RefCell::new(spi))
}
#[cfg(all(feature = "blocking", feature = "CriticalSectionBus"))]
pub fn init_bus(spi: SpiBusInner) -> &'static SpiBusWrapper {
    use critical_section::Mutex;
    SPI_BUS.init(Mutex::new(RefCell::new(spi)))
}
#[cfg(all(feature = "blocking", feature = "AtomicBus"))]
pub fn init_bus(spi: SpiBusInner) -> &'static SpiBusWrapper {
    SPI_BUS.init(AtomicCell::new(spi))
}
#[cfg(all(feature = "async", feature = "AsyncBus"))]
pub fn init_bus(spi: SpiBusInner) -> &'static SpiBusWrapper {
    SPI_BUS.init(AsyncMutex::new(spi))
}
#[cfg(all(feature = "async", feature = "AtomicBus"))]
pub fn init_bus(spi: SpiBusInner) -> &'static SpiBusWrapper {
    SPI_BUS.init(AtomicCell::new(spi))
}

pub fn make_device<'a>(
    bus: &'a SpiBusWrapper,
    cs: Output<'a>,
    delay: InnerDelay,
) -> Result<SpiDev<'a>, SpiInitError> {
    #[cfg(feature = "blocking")]
    {
        #[cfg(feature = "RefCellBus")]
        {
            embedded_hal_bus::spi::RefCellDevice::new(bus, cs, delay)
                .map_err(|_| SpiInitError::DeviceInitFailed)
        }
        #[cfg(feature = "CriticalSectionBus")]
        {
            embedded_hal_bus::spi::CriticalSectionDevice::new(bus, cs, delay)
                .map_err(|_| SpiInitError::DeviceInitFailed)
        }
        #[cfg(feature = "AtomicBus")]
        {
            AtomicDevice::new(bus, cs, delay).map_err(|_| SpiInitError::DeviceInitFailed)
        }
    }
    #[cfg(feature = "async")]
    {
        #[cfg(any(feature = "CriticalSectionBus", feature = "AsyncBus"))]
        {
            let mut cs = cs;
            let _ = delay;
            cs.set_high().map_err(|_| SpiInitError::DeviceInitFailed)?;
            Ok(EmbassySpiDevice::new(bus, cs))
        }
        #[cfg(feature = "AtomicBus")]
        {
            AtomicDevice::new(bus, cs, delay).map_err(|_| SpiInitError::DeviceInitFailed)
        }
    }
}

pub fn init_spi_master(
    spi2: SPI2<'static>,
    sck: impl PeripheralOutput<'static>,
    mosi: impl PeripheralOutput<'static>,
    miso: impl PeripheralInput<'static>,
    frequency: fugit::HertzU32,
) -> Result<SpiBusInner, SpiInitError> {
    let spi = Spi::new(
        spi2,
        SpiConfig::default().with_frequency(Rate::from_hz(frequency.raw())),
    )
    .map_err(|_| SpiInitError::BusInitFailed)?
    .with_sck(sck)
    .with_mosi(mosi)
    .with_miso(miso);

    #[cfg(feature = "async")]
    let spi = spi.into_async();

    Ok(spi)
}