use esp_hal::{
Async,
dma::{DmaRxBuf, DmaTxBuf},
gpio::{AnyPin, Level, Output, OutputConfig},
spi::{
Mode,
master::{AnySpi, Config as SpiConfig, ConfigError, Spi, SpiDmaBus},
},
time::Rate,
};
pub type SpiBusType = SpiDmaBus<'static, Async>;
#[cfg(feature = "display")]
pub struct Spi2Bus {
inner: SpiBusType,
card_configured: bool,
}
#[cfg(feature = "display")]
impl Spi2Bus {
fn new(inner: SpiBusType) -> Self {
Self {
inner,
card_configured: false,
}
}
fn bus(&mut self) -> &mut SpiBusType {
&mut self.inner
}
async fn configure_for_card(&mut self, config: &SpiConfig) -> Result<(), ConfigError> {
if self.card_configured {
return Ok(());
}
AsyncSpiBus::flush(&mut self.inner).await.ok();
self.inner.apply_config(config)?;
self.card_configured = true;
Ok(())
}
fn apply_for_card(&mut self, config: &SpiConfig) -> Result<(), ConfigError> {
self.inner.apply_config(config)?;
self.card_configured = true;
Ok(())
}
fn invalidate(&mut self) {
self.card_configured = false;
}
}
#[cfg(feature = "display")]
impl embassy_embedded_hal::SetConfig for Spi2Bus {
type Config = SpiConfig;
type ConfigError = ConfigError;
fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
self.inner.apply_config(config)?;
self.card_configured = false;
Ok(())
}
}
#[cfg(feature = "display")]
use embedded_hal_async::spi::SpiBus as AsyncSpiBus;
#[cfg(feature = "display")]
impl embedded_hal_async::spi::ErrorType for Spi2Bus {
type Error = <SpiBusType as embedded_hal_async::spi::ErrorType>::Error;
}
#[cfg(feature = "display")]
impl embedded_hal_async::spi::SpiBus for Spi2Bus {
async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
AsyncSpiBus::read(&mut self.inner, words).await
}
async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
AsyncSpiBus::write(&mut self.inner, words).await
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
AsyncSpiBus::transfer(&mut self.inner, read, write).await
}
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
AsyncSpiBus::transfer_in_place(&mut self.inner, words).await
}
async fn flush(&mut self) -> Result<(), Self::Error> {
AsyncSpiBus::flush(&mut self.inner).await
}
}
pub fn sd_init_config() -> SpiConfig {
SpiConfig::default()
.with_frequency(Rate::from_khz(400))
.with_mode(Mode::_0)
}
pub fn display_config() -> SpiConfig {
sd_init_config().with_frequency(Rate::from_khz(40_000))
}
#[cfg(feature = "cores3")]
pub struct Spi2Resources<'a> {
pub spi2: AnySpi<'a>,
pub spi2_dma: esp_hal::dma::AnyGdmaChannel<'a>,
pub sck: AnyPin<'a>,
pub mosi: AnyPin<'a>,
pub miso_dc: AnyPin<'a>,
pub display_cs: AnyPin<'a>,
pub card_cs: AnyPin<'a>,
}
#[cfg(feature = "fire27")]
pub struct Spi2Resources<'a> {
pub spi2: AnySpi<'a>,
pub spi2_dma: esp_hal::dma::AnySpiDmaChannel<'a>,
pub sck: AnyPin<'a>,
pub mosi: AnyPin<'a>,
pub miso: AnyPin<'a>,
pub display_cs: AnyPin<'a>,
pub display_dc: AnyPin<'a>,
pub display_rst: AnyPin<'a>,
pub display_bl: AnyPin<'a>,
pub card_cs: AnyPin<'a>,
}
#[cfg_attr(not(feature = "display"), allow(dead_code))]
pub struct Spi2Parts {
pub bus: SpiBusType,
display_cs: Output<'static>,
#[cfg(feature = "fire27")]
display_dc: Output<'static>,
#[cfg(feature = "fire27")]
display_rst: Output<'static>,
#[cfg(feature = "fire27")]
display_bl: Output<'static>,
}
#[cfg(feature = "cores3")]
impl Spi2Resources<'static> {
pub fn into_parts(
self,
dma_rx_buf: DmaRxBuf,
dma_tx_buf: DmaTxBuf,
) -> Result<(Spi2Parts, Output<'static>), ConfigError> {
let bus = Spi::new(self.spi2, sd_init_config())?
.with_sck(self.sck)
.with_mosi(self.mosi)
.with_miso(self.miso_dc)
.with_dma(self.spi2_dma)
.with_buffers(dma_rx_buf, dma_tx_buf)
.into_async();
let display_cs = Output::new(self.display_cs, Level::High, OutputConfig::default());
let card_cs = Output::new(self.card_cs, Level::High, OutputConfig::default());
Ok((Spi2Parts { bus, display_cs }, card_cs))
}
}
#[cfg(feature = "fire27")]
impl Spi2Resources<'static> {
pub fn into_parts(
self,
dma_rx_buf: DmaRxBuf,
dma_tx_buf: DmaTxBuf,
) -> Result<(Spi2Parts, Output<'static>), ConfigError> {
let bus = Spi::new(self.spi2, sd_init_config())?
.with_sck(self.sck)
.with_mosi(self.mosi)
.with_miso(self.miso)
.with_dma(self.spi2_dma)
.with_buffers(dma_rx_buf, dma_tx_buf)
.into_async();
let display_cs = Output::new(self.display_cs, Level::High, OutputConfig::default());
let card_cs = Output::new(self.card_cs, Level::High, OutputConfig::default());
let display_dc = Output::new(self.display_dc, Level::Low, OutputConfig::default());
let display_rst = Output::new(self.display_rst, Level::Low, OutputConfig::default());
let display_bl = Output::new(self.display_bl, Level::Low, OutputConfig::default());
Ok((
Spi2Parts {
bus,
display_cs,
display_dc,
display_rst,
display_bl,
},
card_cs,
))
}
}
#[cfg(feature = "display")]
pub use devices::*;
#[cfg(feature = "display")]
mod devices {
use super::Spi2Bus;
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDeviceWithConfig;
use embassy_sync::mutex::Mutex;
use embassy_sync::semaphore::{FairSemaphore, Semaphore};
use embedded_hal::digital::{ErrorType, OutputPin};
use esp_hal::{
dma::{DmaRxBuf, DmaTxBuf},
gpio::{Level, Output, OutputConfig},
spi::master::Spi,
};
use esp_sync::RawMutex;
use lcd_async::interface::{Interface, SpiInterface};
use static_cell::StaticCell;
use super::{
ConfigError, Spi2Parts, Spi2Resources, SpiBusType, SpiConfig, display_config,
sd_init_config,
};
use crate::board::display::{self, Ili9342c};
pub type SpiDeviceType<'a> =
FairSpiDevice<SpiDeviceWithConfig<'a, RawMutex, Spi2Bus, Output<'a>>>;
pub type CardSpiDevice<CS> = SpiDeviceWithConfig<'static, RawMutex, Spi2Bus, CS>;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CardPresence {
Detect,
ForceAbsent,
}
pub struct PresenceCs<CS> {
pin: CS,
frozen: bool,
}
impl<CS: ErrorType> ErrorType for PresenceCs<CS> {
type Error = CS::Error;
}
impl<CS: OutputPin> OutputPin for PresenceCs<CS> {
fn set_low(&mut self) -> Result<(), Self::Error> {
if self.frozen {
Ok(()) } else {
self.pin.set_low()
}
}
fn set_high(&mut self) -> Result<(), Self::Error> {
self.pin.set_high() }
}
pub struct PreparedCard<CS: OutputPin> {
bus: &'static Mutex<RawMutex, Spi2Bus>,
cs: PresenceCs<CS>,
config: SpiConfig,
}
pub struct FairSpiDevice<D> {
inner: D,
applies_dc: bool,
}
impl<D> FairSpiDevice<D> {
pub fn new(inner: D) -> Self {
Self {
inner,
applies_dc: false,
}
}
pub fn new_display(inner: D) -> Self {
Self {
inner,
applies_dc: true,
}
}
}
impl<'a, M, BUS, CS> FairSpiDevice<SpiDeviceWithConfig<'a, M, BUS, CS>>
where
M: embassy_sync::blocking_mutex::raw::RawMutex,
BUS: embassy_embedded_hal::SetConfig,
{
pub fn set_config(&mut self, config: BUS::Config) {
self.inner.set_config(config)
}
}
impl<D: embedded_hal_async::spi::ErrorType> embedded_hal_async::spi::ErrorType
for FairSpiDevice<D>
{
type Error = D::Error;
}
impl<D: embedded_hal_async::spi::SpiDevice> embedded_hal_async::spi::SpiDevice
for FairSpiDevice<D>
{
async fn transaction(
&mut self,
operations: &mut [embedded_hal_async::spi::Operation<'_, u8>],
) -> Result<(), Self::Error> {
let queued = embassy_time::Instant::now();
permits::DISP_ENTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let Ok(_permit) = SPI2_FAIR.acquire(1).await else {
warn!("SPI2: waiter queue full, display dropped a frame");
return Ok(());
};
permits::DISP_GOT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
struct RelOnDrop;
impl Drop for RelOnDrop {
fn drop(&mut self) {
permits::DISP_REL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
let _rel = RelOnDrop;
let waited = queued.elapsed();
#[cfg(feature = "cores3")]
if self.applies_dc {
crate::board::cores3::apply_pending_dc();
}
#[cfg(not(feature = "cores3"))]
let _ = self.applies_dc;
let r = self.inner.transaction(operations).await;
let total = queued.elapsed();
if total.as_millis() > 100 {
warn!(
"SPI2: display transaction {} ms ({} ms of it waiting for the bus)",
total.as_millis(),
waited.as_millis()
);
}
r
}
}
pub struct Spi2CardGuard<'a, CS: OutputPin> {
bus: embassy_sync::mutex::MutexGuard<'a, RawMutex, Spi2Bus>,
cs: &'a mut PresenceCs<CS>,
_permit:
embassy_sync::semaphore::SemaphoreReleaser<'a, FairSemaphore<RawMutex, SPI2_WAITERS>>,
}
impl<CS: OutputPin> Spi2CardGuard<'_, CS> {
pub fn split(&mut self) -> (&mut SpiBusType, &mut PresenceCs<CS>) {
(self.bus.bus(), self.cs)
}
pub fn apply_config(&mut self, config: &SpiConfig) -> Result<(), ConfigError> {
self.bus.apply_for_card(config)
}
}
impl<CS: OutputPin> PreparedCard<CS> {
pub async fn set_config(&mut self, config: SpiConfig) {
self.config = config;
self.bus.lock().await.invalidate();
}
}
impl<CS: OutputPin> Drop for Spi2CardGuard<'_, CS> {
fn drop(&mut self) {
let _ = self.cs.set_high();
permits::CARD_REL.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
impl<CS: OutputPin> PreparedCard<CS> {
pub async fn acquire(&mut self) -> Option<Spi2CardGuard<'_, CS>> {
let cfg = self.config;
permits::CARD_ENTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let permit = match SPI2_FAIR.acquire(1).await {
Ok(p) => p,
Err(_) => {
warn!("SPI2: waiter queue full, card could not take the bus");
return None;
}
};
permits::CARD_GOT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let mut bus = self.bus.lock().await;
let was_foreign = !bus.card_configured;
if let Err(e) = bus.configure_for_card(&cfg).await {
warn!("SPI2: card bus config rejected: {e:?}");
}
if was_foreign {
#[cfg(feature = "cores3")]
crate::board::cores3::gpio35_disable_output();
}
Some(Spi2CardGuard {
bus,
cs: &mut self.cs,
_permit: permit,
})
}
pub fn into_inner(self) -> FairSpiDevice<CardSpiDevice<PresenceCs<CS>>> {
FairSpiDevice::new(SpiDeviceWithConfig::new(
self.bus,
self.cs,
sd_init_config(),
))
}
}
#[cfg(feature = "cores3")]
pub type DisplayInterface =
SpiInterface<SpiDeviceType<'static>, crate::board::cores3::Gpio35Dc>;
#[cfg(feature = "fire27")]
pub type DisplayInterface = SpiInterface<SpiDeviceType<'static>, Output<'static>>;
#[cfg(feature = "cores3")]
pub type DisplayType = Ili9342c<DisplayInterface>;
#[cfg(feature = "fire27")]
pub type DisplayType = Ili9342c<DisplayInterface, Output<'static>>;
pub type DisplayInitError =
lcd_async::InitError<<DisplayInterface as Interface>::Error, core::convert::Infallible>;
pub type DisplayOnlyInterface = SpiInterface<SpiDeviceType<'static>, Output<'static>>;
#[cfg(feature = "cores3")]
pub type DisplayOnlyType = Ili9342c<DisplayOnlyInterface>;
#[cfg(feature = "fire27")]
pub type DisplayOnlyType = Ili9342c<DisplayOnlyInterface, Output<'static>>;
pub type DisplayOnlyInitError =
lcd_async::InitError<<DisplayOnlyInterface as Interface>::Error, core::convert::Infallible>;
#[derive(Debug)]
pub enum DisplayOnlyError {
Config(ConfigError),
Init(DisplayOnlyInitError),
}
impl From<ConfigError> for DisplayOnlyError {
fn from(e: ConfigError) -> Self {
Self::Config(e)
}
}
impl From<DisplayOnlyInitError> for DisplayOnlyError {
fn from(e: DisplayOnlyInitError) -> Self {
Self::Init(e)
}
}
pub struct DisplayBus {
pub display: DisplayOnlyType,
#[cfg(feature = "fire27")]
pub backlight: Output<'static>,
}
pub struct DisplayDriver {
pub display: DisplayType,
#[cfg(feature = "fire27")]
bl: Output<'static>,
}
#[cfg(feature = "fire27")]
impl DisplayDriver {
pub fn bl_on(&mut self) {
self.bl.set_high();
}
pub fn bl_off(&mut self) {
self.bl.set_low();
}
}
static SPI_BUS: StaticCell<Mutex<RawMutex, Spi2Bus>> = StaticCell::new();
const SPI2_WAITERS: usize = 4;
static SPI2_FAIR: FairSemaphore<RawMutex, SPI2_WAITERS> = FairSemaphore::new(1);
mod permits {
use core::sync::atomic::AtomicU32;
pub(super) static CARD_ENTER: AtomicU32 = AtomicU32::new(0);
pub(super) static CARD_GOT: AtomicU32 = AtomicU32::new(0);
pub(super) static CARD_REL: AtomicU32 = AtomicU32::new(0);
pub(super) static DISP_ENTER: AtomicU32 = AtomicU32::new(0);
pub(super) static DISP_GOT: AtomicU32 = AtomicU32::new(0);
pub(super) static DISP_REL: AtomicU32 = AtomicU32::new(0);
}
#[derive(Debug, Clone, Copy)]
pub struct PermitStats {
pub card_enter: u32,
pub card_got: u32,
pub card_rel: u32,
pub disp_enter: u32,
pub disp_got: u32,
pub disp_rel: u32,
}
pub fn permit_stats() -> PermitStats {
use core::sync::atomic::Ordering::Relaxed;
PermitStats {
card_enter: permits::CARD_ENTER.load(Relaxed),
card_got: permits::CARD_GOT.load(Relaxed),
card_rel: permits::CARD_REL.load(Relaxed),
disp_enter: permits::DISP_ENTER.load(Relaxed),
disp_got: permits::DISP_GOT.load(Relaxed),
disp_rel: permits::DISP_REL.load(Relaxed),
}
}
impl Spi2Parts {
pub async fn finish<CS>(
self,
card_cs: CS,
) -> Result<(DisplayDriver, CardSpiDevice<CS>), DisplayInitError> {
let (driver, bus) = self.finish_bus().await?;
Ok((
driver,
SpiDeviceWithConfig::new(bus, card_cs, sd_init_config()),
))
}
async fn finish_bus(
self,
) -> Result<(DisplayDriver, &'static Mutex<RawMutex, Spi2Bus>), DisplayInitError> {
let bus = SPI_BUS.init(Mutex::new(Spi2Bus::new(self.bus)));
let display_device = FairSpiDevice::new_display(SpiDeviceWithConfig::new(
bus,
self.display_cs,
display_config(),
));
#[cfg(feature = "cores3")]
let driver = {
let di = SpiInterface::new(display_device, crate::board::cores3::Gpio35Dc);
let display = display::init_ili9342c(di).await?;
crate::board::cores3::gpio35_disable_output();
DisplayDriver { display }
};
#[cfg(feature = "fire27")]
let driver = {
let di = SpiInterface::new(display_device, self.display_dc);
let display = display::init_ili9342c_with_reset(di, self.display_rst).await?;
let mut driver = DisplayDriver {
display,
bl: self.display_bl,
};
driver.bl_on();
driver
};
Ok((driver, bus))
}
pub async fn finish_sd<CS>(
mut self,
card_cs: CS,
presence: CardPresence,
) -> Result<(DisplayDriver, PreparedCard<CS>), DisplayInitError>
where
CS: OutputPin,
{
if let Err(e) = self.bus.write(&[0xFF; 10]) {
warn!("SD power-up idle clock failed: {e:?}");
}
let card_cs = PresenceCs {
pin: card_cs,
frozen: matches!(presence, CardPresence::ForceAbsent),
};
let (driver, bus) = self.finish_bus().await?;
Ok((
driver,
PreparedCard {
bus,
cs: card_cs,
config: sd_init_config(),
},
))
}
}
#[cfg(feature = "cores3")]
impl Spi2Resources<'static> {
pub async fn into_display_only(
self,
dma_rx_buf: DmaRxBuf,
dma_tx_buf: DmaTxBuf,
) -> Result<DisplayBus, DisplayOnlyError> {
let spi = Spi::new(self.spi2, display_config())?
.with_sck(self.sck)
.with_mosi(self.mosi)
.with_dma(self.spi2_dma)
.with_buffers(dma_rx_buf, dma_tx_buf)
.into_async();
let bus = SPI_BUS.init(Mutex::new(Spi2Bus::new(spi)));
let display_cs = Output::new(self.display_cs, Level::High, OutputConfig::default());
let dc = Output::new(self.miso_dc, Level::Low, OutputConfig::default());
let device = FairSpiDevice::new_display(SpiDeviceWithConfig::new(
bus,
display_cs,
display_config(),
));
let di = SpiInterface::new(device, dc);
let display = display::init_ili9342c(di).await?;
Ok(DisplayBus { display })
}
}
#[cfg(feature = "fire27")]
impl Spi2Resources<'static> {
pub async fn into_display_only(
self,
dma_rx_buf: DmaRxBuf,
dma_tx_buf: DmaTxBuf,
) -> Result<DisplayBus, DisplayOnlyError> {
let spi = Spi::new(self.spi2, display_config())?
.with_sck(self.sck)
.with_mosi(self.mosi)
.with_miso(self.miso)
.with_dma(self.spi2_dma)
.with_buffers(dma_rx_buf, dma_tx_buf)
.into_async();
let bus = SPI_BUS.init(Mutex::new(Spi2Bus::new(spi)));
let display_cs = Output::new(self.display_cs, Level::High, OutputConfig::default());
let dc = Output::new(self.display_dc, Level::Low, OutputConfig::default());
let rst = Output::new(self.display_rst, Level::Low, OutputConfig::default());
let mut backlight = Output::new(self.display_bl, Level::Low, OutputConfig::default());
let device = FairSpiDevice::new_display(SpiDeviceWithConfig::new(
bus,
display_cs,
display_config(),
));
let di = SpiInterface::new(device, dc);
let display = display::init_ili9342c_with_reset(di, rst).await?;
backlight.set_high();
Ok(DisplayBus { display, backlight })
}
}
}