use core::{cell::Cell, fmt};
use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::{Block, BlockCount, BlockDevice, BlockIdx, SdCardError};
use esp_hal::{
Blocking,
delay::Delay,
gpio::{Level, Output, OutputConfig},
peripherals::{GPIO12, GPIO14, GPIO39, GPIO40, SPI3},
spi::{
Mode,
master::{Config as SpiConfig, Spi},
},
time::Rate,
};
use crate::Error;
type Card =
embedded_sdmmc::SdCard<ExclusiveDevice<Spi<'static, Blocking>, Output<'static>, Delay>, Delay>;
#[expect(missing_docs, reason = "the fields are the pins they're named after")]
#[derive(Debug)]
pub struct SdCardPins {
pub spi: SPI3<'static>,
pub sck: GPIO40<'static>,
pub mosi: GPIO14<'static>,
pub miso: GPIO39<'static>,
pub cs: GPIO12<'static>,
}
const SD_INIT_CLOCK: Rate = Rate::from_khz(400);
const SD_CLOCK: Rate = Rate::from_mhz(20);
pub struct SdCard {
card: Card,
started: Cell<bool>,
}
impl SdCard {
fn access<T>(
&self,
access: impl FnOnce(&Card) -> Result<T, SdCardError>,
) -> Result<T, SdCardError> {
if !self.started.get() {
self.start()?;
}
access(&self.card).inspect_err(|_| self.started.set(false))
}
fn start(&self) -> Result<(), SdCardError> {
self.set_clock(SD_INIT_CLOCK)?;
self.card
.spi(|device| device.bus_mut().write(&[0xFF; 10]))
.map_err(|_| SdCardError::Transport)?;
self.card.mark_card_uninit();
self.card.num_bytes()?;
self.set_clock(SD_CLOCK)?;
self.started.set(true);
Ok(())
}
fn set_clock(&self, clock: Rate) -> Result<(), SdCardError> {
self.card
.spi(|device| device.bus_mut().apply_config(&spi_config(clock)))
.map_err(|_| SdCardError::Transport)
}
}
impl BlockDevice for SdCard {
type Error = SdCardError;
fn read(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> {
self.access(|card| card.read(blocks, start_block_idx))
}
fn write(&self, blocks: &[Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> {
self.access(|card| card.write(blocks, start_block_idx))
}
fn num_blocks(&self) -> Result<BlockCount, Self::Error> {
self.access(BlockDevice::num_blocks)
}
}
impl fmt::Debug for SdCard {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SdCard")
.field("started", &self.started.get())
.finish_non_exhaustive()
}
}
fn spi_config(clock: Rate) -> SpiConfig {
SpiConfig::default()
.with_frequency(clock)
.with_mode(Mode::_0)
}
pub fn sd_card(pins: SdCardPins) -> Result<SdCard, Error> {
let spi = Spi::new(pins.spi, spi_config(SD_INIT_CLOCK))
.map_err(|_| Error::SdCard)?
.with_sck(pins.sck)
.with_mosi(pins.mosi)
.with_miso(pins.miso);
let cs = Output::new(pins.cs, Level::High, OutputConfig::default());
let device = ExclusiveDevice::new(spi, cs, Delay::new()).map_err(|_| Error::SdCard)?;
Ok(SdCard {
card: Card::new(device, Delay::new()),
started: Cell::new(false),
})
}