Skip to main content

oxstack_cardputer/
sd.rs

1//! microSD card on the shared SPI bus (SPI2: SCK 40, MOSI 14, MISO 39,
2//! CS 12), with FAT through `embedded-sdmmc` (re-exported as [`fs`]).
3//!
4//! [`SpiBusResources::sd`](crate::SpiBusResources::sd) takes the whole bus
5//! for the card and holds the EXT header's chip-select (GPIO5) high, so a
6//! module on the header (e.g. the LoRa cap) can never answer on the bus.
7//! [`Sd`] implements [`fs::BlockDevice`], so the guard lives exactly as long
8//! as the filesystem.
9//!
10//! **The card is read-only until you say otherwise.** `embedded-sdmmc`
11//! updates FAT bookkeeping (the FSInfo sector) even when a program only
12//! lists a directory, which is an unwelcome surprise on a card full of
13//! personal files. Every write returns [`Error::ReadOnly`] until
14//! [`Sd::set_writable`]`(true)`.
15//!
16//! `embedded-sdmmc` is blocking: card I/O holds the CPU for its duration
17//! (~25 µs per 512-byte block at 20 MHz, plus card latency). An async
18//! block layer is future work.
19
20use core::fmt;
21
22use embassy_time::Delay;
23use embedded_hal_bus::spi::ExclusiveDevice;
24pub use embedded_sdmmc as fs;
25use embedded_sdmmc::{
26    Block, BlockCount, BlockDevice, BlockIdx, SdCard, SdCardError, TimeSource, Timestamp,
27};
28use esp_hal::Blocking;
29use esp_hal::gpio::{Level, Output, OutputConfig};
30use esp_hal::spi::Mode;
31use esp_hal::spi::master::{Config, Spi};
32use esp_hal::time::Rate;
33
34use crate::SpiBusResources;
35
36/// Clock while the card initialises (the SD spec caps it at 400 kHz).
37pub const INIT_FREQUENCY: Rate = Rate::from_khz(400);
38/// Clock after initialisation. Cards allow 25 MHz in SPI mode; 20 MHz
39/// leaves margin for the GPIO-matrix routing of these pins.
40pub const FREQUENCY: Rate = Rate::from_mhz(20);
41
42/// The SPI device the card is driven through.
43pub type SdSpi<'d> = ExclusiveDevice<Spi<'d, Blocking>, Output<'d>, Delay>;
44/// The card driver.
45pub type Card<'d> = SdCard<SdSpi<'d>, Delay>;
46
47/// SD errors.
48#[derive(Debug, Clone)]
49#[cfg_attr(feature = "defmt", derive(defmt::Format))]
50pub enum Error {
51    /// The card failed or did not respond.
52    Card(SdCardError),
53    /// A write was attempted while the card is read-only (the default); see
54    /// [`Sd::set_writable`].
55    ReadOnly,
56}
57
58impl From<SdCardError> for Error {
59    fn from(e: SdCardError) -> Self {
60        Error::Card(e)
61    }
62}
63
64impl fmt::Display for Error {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            Error::Card(e) => write!(f, "SD card error: {e:?}"),
68            Error::ReadOnly => f.write_str("SD card is read-only"),
69        }
70    }
71}
72
73impl core::error::Error for Error {}
74
75fn spi_config(frequency: Rate) -> Config {
76    Config::default()
77        .with_frequency(frequency)
78        .with_mode(Mode::_0)
79}
80
81impl<'d> SpiBusResources<'d> {
82    /// Initialises the microSD card, giving it the shared bus exclusively,
83    /// **read-only**. Fails if no card is inserted or it doesn't respond.
84    ///
85    /// The returned [`Sd`] borrows the bus; drop it and the bus is free for
86    /// the EXT header or another `sd` call.
87    pub fn sd(&mut self) -> Result<Sd<'_>, Error> {
88        let out = OutputConfig::default();
89        // Deselect both devices before the bus starts clocking.
90        let ext_cs = Output::new(self.ext_cs.reborrow(), Level::High, out);
91        let sd_cs = Output::new(self.sd_cs.reborrow(), Level::High, out);
92        let mut spi = Spi::new(self.spi.reborrow(), spi_config(INIT_FREQUENCY))
93            .expect("400 kHz mode 0 is a valid SPI configuration")
94            .with_sck(self.sck.reborrow())
95            .with_mosi(self.mosi.reborrow())
96            .with_miso(self.miso.reborrow());
97
98        // The card enters SPI mode after ≥74 clocks with CS high (sent
99        // here, outside any device transaction, so CS stays deasserted).
100        spi.write(&[0xFF; 10]).map_err(|_| SdCardError::Transport)?;
101
102        let Ok(dev) = ExclusiveDevice::new(spi, sd_cs, Delay);
103        let card = SdCard::new(dev, Delay);
104        card.num_bytes()?; // forces initialisation at the slow clock
105        card.spi(|dev| dev.bus_mut().apply_config(&spi_config(FREQUENCY)))
106            .expect("20 MHz mode 0 is a valid SPI configuration");
107        Ok(Sd {
108            card,
109            writable: false,
110            _ext_cs: ext_cs,
111        })
112    }
113}
114
115/// An initialised microSD card.
116pub struct Sd<'d> {
117    card: Card<'d>,
118    writable: bool,
119    /// Holds the EXT header deselected while the card owns the bus.
120    _ext_cs: Output<'d>,
121}
122
123impl<'d> Sd<'d> {
124    /// Allows (or forbids) writes. Decide before handing the card to a
125    /// `VolumeManager`; with writes forbidden, FAT bookkeeping writes fail
126    /// harmlessly and reads keep working.
127    pub fn set_writable(&mut self, writable: bool) {
128        self.writable = writable;
129    }
130
131    /// Whether writes are allowed.
132    pub fn is_writable(&self) -> bool {
133        self.writable
134    }
135
136    /// Card capacity in bytes.
137    pub fn size_bytes(&self) -> Result<u64, Error> {
138        Ok(self.card.num_bytes()?)
139    }
140
141    /// Card generation (SD1, SD2, SDHC/SDXC).
142    pub fn card_type(&self) -> Option<embedded_sdmmc::embedded_sdmmc_types::sdcard::CardType> {
143        self.card.get_card_type()
144    }
145
146    /// The underlying driver. Writes through it bypass the read-only guard.
147    pub fn card(&self) -> &Card<'d> {
148        &self.card
149    }
150}
151
152impl BlockDevice for Sd<'_> {
153    type Error = Error;
154
155    fn read(&self, blocks: &mut [Block], start: BlockIdx) -> Result<(), Error> {
156        Ok(self.card.read(blocks, start)?)
157    }
158
159    fn write(&self, blocks: &[Block], start: BlockIdx) -> Result<(), Error> {
160        if !self.writable {
161            #[cfg(feature = "defmt")]
162            defmt::warn!(
163                "blocked a write to SD block {} (card is read-only; see Sd::set_writable)",
164                start.0
165            );
166            return Err(Error::ReadOnly);
167        }
168        Ok(self.card.write(blocks, start)?)
169    }
170
171    fn num_blocks(&self) -> Result<BlockCount, Error> {
172        Ok(self.card.num_blocks()?)
173    }
174}
175
176/// A [`TimeSource`] for boards without a real-time clock: every timestamp
177/// is 2026-01-01 00:00:00. Swap in a real clock (e.g. after network time
178/// sync) for meaningful file dates.
179#[derive(Clone, Copy, Debug, Default)]
180pub struct FixedTime;
181
182impl TimeSource for FixedTime {
183    fn get_timestamp(&self) -> Timestamp {
184        Timestamp::from_calendar(2026, 1, 1, 0, 0, 0).expect("valid date")
185    }
186}