use core::fmt;
use embassy_time::Delay;
use embedded_hal_bus::spi::ExclusiveDevice;
pub use embedded_sdmmc as fs;
use embedded_sdmmc::{
Block, BlockCount, BlockDevice, BlockIdx, SdCard, SdCardError, TimeSource, Timestamp,
};
use esp_hal::Blocking;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::spi::Mode;
use esp_hal::spi::master::{Config, Spi};
use esp_hal::time::Rate;
use crate::SpiBusResources;
pub const INIT_FREQUENCY: Rate = Rate::from_khz(400);
pub const FREQUENCY: Rate = Rate::from_mhz(20);
pub type SdSpi<'d> = ExclusiveDevice<Spi<'d, Blocking>, Output<'d>, Delay>;
pub type Card<'d> = SdCard<SdSpi<'d>, Delay>;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
Card(SdCardError),
ReadOnly,
}
impl From<SdCardError> for Error {
fn from(e: SdCardError) -> Self {
Error::Card(e)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Card(e) => write!(f, "SD card error: {e:?}"),
Error::ReadOnly => f.write_str("SD card is read-only"),
}
}
}
impl core::error::Error for Error {}
fn spi_config(frequency: Rate) -> Config {
Config::default()
.with_frequency(frequency)
.with_mode(Mode::_0)
}
impl<'d> SpiBusResources<'d> {
pub fn sd(&mut self) -> Result<Sd<'_>, Error> {
let out = OutputConfig::default();
let ext_cs = Output::new(self.ext_cs.reborrow(), Level::High, out);
let sd_cs = Output::new(self.sd_cs.reborrow(), Level::High, out);
let mut spi = Spi::new(self.spi.reborrow(), spi_config(INIT_FREQUENCY))
.expect("400 kHz mode 0 is a valid SPI configuration")
.with_sck(self.sck.reborrow())
.with_mosi(self.mosi.reborrow())
.with_miso(self.miso.reborrow());
spi.write(&[0xFF; 10]).map_err(|_| SdCardError::Transport)?;
let Ok(dev) = ExclusiveDevice::new(spi, sd_cs, Delay);
let card = SdCard::new(dev, Delay);
card.num_bytes()?; card.spi(|dev| dev.bus_mut().apply_config(&spi_config(FREQUENCY)))
.expect("20 MHz mode 0 is a valid SPI configuration");
Ok(Sd {
card,
writable: false,
_ext_cs: ext_cs,
})
}
}
pub struct Sd<'d> {
card: Card<'d>,
writable: bool,
_ext_cs: Output<'d>,
}
impl<'d> Sd<'d> {
pub fn set_writable(&mut self, writable: bool) {
self.writable = writable;
}
pub fn is_writable(&self) -> bool {
self.writable
}
pub fn size_bytes(&self) -> Result<u64, Error> {
Ok(self.card.num_bytes()?)
}
pub fn card_type(&self) -> Option<embedded_sdmmc::embedded_sdmmc_types::sdcard::CardType> {
self.card.get_card_type()
}
pub fn card(&self) -> &Card<'d> {
&self.card
}
}
impl BlockDevice for Sd<'_> {
type Error = Error;
fn read(&self, blocks: &mut [Block], start: BlockIdx) -> Result<(), Error> {
Ok(self.card.read(blocks, start)?)
}
fn write(&self, blocks: &[Block], start: BlockIdx) -> Result<(), Error> {
if !self.writable {
#[cfg(feature = "defmt")]
defmt::warn!(
"blocked a write to SD block {} (card is read-only; see Sd::set_writable)",
start.0
);
return Err(Error::ReadOnly);
}
Ok(self.card.write(blocks, start)?)
}
fn num_blocks(&self) -> Result<BlockCount, Error> {
Ok(self.card.num_blocks()?)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FixedTime;
impl TimeSource for FixedTime {
fn get_timestamp(&self) -> Timestamp {
Timestamp::from_calendar(2026, 1, 1, 0, 0, 0).expect("valid date")
}
}