From 510d50ed347beca3d85332b0d04ba2376c361ea1 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 6 Jul 2024 23:37:44 +0100 Subject: [PATCH] Use SpiDevice to control the chip select. Closes #126 --- Cargo.toml | 2 +- README.md | 2 +- examples/readme_test.rs | 23 +++- src/lib.rs | 5 +- src/sdcard/mod.rs | 234 ++++++++++++++-------------------------- tests/utils/mod.rs | 1 + 6 files changed, 102 insertions(+), 165 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd6549c..1fa09ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ log = {version = "0.4", default-features = false, optional = true} [dev-dependencies] chrono = "0.4" -embedded-hal-bus = "0.1.0" +embedded-hal-bus = "0.2.0" env_logger = "0.10.0" flate2 = "1.0" hex-literal = "0.4.1" diff --git a/README.md b/README.md index 9b881a2..1e3194c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ You will need something that implements the `BlockDevice` trait, which can read ```rust // Build an SD Card interface out of an SPI device, a chip-select pin and the delay object -let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delay); +let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, delay); // Get the card size (this also triggers card initialisation because it's not been done yet) println!("Card size is {} bytes", sdcard.num_bytes()?); // Now let's look for volumes (also known as partitions) on our block device. diff --git a/examples/readme_test.rs b/examples/readme_test.rs index ed4b146..7ae7384 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -7,7 +7,23 @@ use core::cell::RefCell; -use embedded_sdmmc::sdcard::DummyCsPin; +pub struct DummyCsPin; + +impl embedded_hal::digital::ErrorType for DummyCsPin { + type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::OutputPin for DummyCsPin { + #[inline(always)] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + #[inline(always)] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} struct FakeSpiBus(); @@ -99,13 +115,12 @@ fn main() -> Result<(), Error> { // BEGIN Fake stuff that will be replaced with real peripherals let spi_bus = RefCell::new(FakeSpiBus()); let delay = FakeDelayer(); - let sdmmc_spi = embedded_hal_bus::spi::RefCellDevice::new(&spi_bus, DummyCsPin, delay); - let sdmmc_cs = FakeCs(); + let sdmmc_spi = embedded_hal_bus::spi::RefCellDevice::new(&spi_bus, DummyCsPin, delay).unwrap(); let time_source = FakeTimesource(); // END Fake stuff that will be replaced with real peripherals // Build an SD Card interface out of an SPI device, a chip-select pin and the delay object - let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delay); + let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, delay); // Get the card size (this also triggers card initialisation because it's not been done yet) println!("Card size is {} bytes", sdcard.num_bytes()?); // Now let's look for volumes (also known as partitions) on our block device. diff --git a/src/lib.rs b/src/lib.rs index 1bcd429..6466102 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,14 +19,13 @@ //! ```rust //! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager}; //! -//! fn example(spi: S, cs: CS, delay: D, ts: T) -> Result<(), Error> +//! fn example(spi: S, delay: D, ts: T) -> Result<(), Error> //! where //! S: embedded_hal::spi::SpiDevice, -//! CS: embedded_hal::digital::OutputPin, //! D: embedded_hal::delay::DelayNs, //! T: TimeSource, //! { -//! let sdcard = SdCard::new(spi, cs, delay); +//! let sdcard = SdCard::new(spi, delay); //! println!("Card size is {} bytes", sdcard.num_bytes()?); //! let mut volume_mgr = VolumeManager::new(sdcard, ts); //! let mut volume0 = volume_mgr.open_volume(VolumeIdx(0))?; diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 8f0dbb9..d53a15a 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -21,80 +21,43 @@ use crate::{debug, warn}; // Types and Implementations // **************************************************************************** -/// A dummy "CS pin" that does nothing when set high or low. -/// -/// Should be used when constructing an [`SpiDevice`] implementation for use with [`SdCard`]. -/// -/// Let the [`SpiDevice`] use this dummy CS pin that does not actually do anything, and pass the -/// card's real CS pin to [`SdCard`]'s constructor. This allows the driver to have more -/// fine-grained control of how the CS pin is managed than is allowed by default using the -/// [`SpiDevice`] trait, which is needed to implement the SD/MMC SPI communication spec correctly. -/// -/// If you're not sure how to get a [`SpiDevice`], you may use one of the implementations -/// in the [`embedded-hal-bus`] crate, providing a wrapped version of your platform's HAL-provided -/// [`SpiBus`] and [`DelayNs`] as well as our [`DummyCsPin`] in the constructor. -/// -/// [`SpiDevice`]: embedded_hal::spi::SpiDevice -/// [`SpiBus`]: embedded_hal::spi::SpiBus -/// [`DelayNs`]: embedded_hal::delay::DelayNs -/// [`embedded-hal-bus`]: https://docs.rs/embedded-hal-bus -pub struct DummyCsPin; - -impl embedded_hal::digital::ErrorType for DummyCsPin { - type Error = core::convert::Infallible; -} - -impl embedded_hal::digital::OutputPin for DummyCsPin { - #[inline(always)] - fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - - #[inline(always)] - fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - /// Represents an SD Card on an SPI bus. /// /// Built from an [`SpiDevice`] implementation and a Chip Select pin. -/// Unfortunately, We need control of the chip select pin separately from the [`SpiDevice`] -/// implementation so we can clock out some bytes without Chip Select asserted -/// (which is necessary to make the SD card actually release the Spi bus after performing -/// operations on it, according to the spec). To support this, we provide [`DummyCsPin`] -/// which should be provided to your chosen [`SpiDevice`] implementation rather than the card's -/// actual CS pin. Then provide the actual CS pin to [`SdCard`]'s constructor. +/// +/// Before talking to the SD Card, the caller needs to send 74 clocks cycles on +/// the SPI Clock line, at 400 kHz, with no chip-select asserted (or at least, +/// not the chip-select of the SD Card). +/// +/// This kind of breaks the embedded-hal model, so how to do this is left to +/// the caller. You could drive the SpiBus directly, or use an SpiDevice with +/// a dummy chip-select pin. Or you could try just not doing the 74 clocks and +/// see if your card works anyway - some do, some don't. /// /// All the APIs take `&self` - mutability is handled using an inner `RefCell`. /// /// [`SpiDevice`]: embedded_hal::spi::SpiDevice -pub struct SdCard +pub struct SdCard where SPI: embedded_hal::spi::SpiDevice, - CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayNs, { - inner: RefCell>, + inner: RefCell>, } -impl SdCard +impl SdCard where SPI: embedded_hal::spi::SpiDevice, - CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayNs, { /// Create a new SD/MMC Card driver using a raw SPI interface. /// - /// See the docs of the [`SdCard`] struct for more information about - /// how to construct the needed `SPI` and `CS` types. - /// /// The card will not be initialised at this time. Initialisation is /// deferred until a method is called on the object. /// /// Uses the default options. - pub fn new(spi: SPI, cs: CS, delayer: DELAYER) -> SdCard { - Self::new_with_options(spi, cs, delayer, AcquireOpts::default()) + pub fn new(spi: SPI, delayer: DELAYER) -> SdCard { + Self::new_with_options(spi, delayer, AcquireOpts::default()) } /// Construct a new SD/MMC Card driver, using a raw SPI interface and the given options. @@ -106,14 +69,12 @@ where /// deferred until a method is called on the object. pub fn new_with_options( spi: SPI, - cs: CS, delayer: DELAYER, options: AcquireOpts, - ) -> SdCard { + ) -> SdCard { SdCard { inner: RefCell::new(SdCardInner { spi, - cs, delayer, card_type: None, options, @@ -193,10 +154,9 @@ where } } -impl BlockDevice for SdCard +impl BlockDevice for SdCard where SPI: embedded_hal::spi::SpiDevice, - CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayNs, { type Error = Error; @@ -244,23 +204,20 @@ where /// Represents an SD Card on an SPI bus. /// /// All the APIs required `&mut self`. -struct SdCardInner +struct SdCardInner where SPI: embedded_hal::spi::SpiDevice, - CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayNs, { spi: SPI, - cs: CS, delayer: DELAYER, card_type: Option, options: AcquireOpts, } -impl SdCardInner +impl SdCardInner where SPI: embedded_hal::spi::SpiDevice, - CS: embedded_hal::digital::OutputPin, DELAYER: embedded_hal::delay::DelayNs, { /// Read one or more blocks, starting at the given block index. @@ -270,22 +227,21 @@ where Some(CardType::SDHC) => start_block_idx.0, None => return Err(Error::CardNotFound), }; - self.with_chip_select(|s| { - if blocks.len() == 1 { - // Start a single-block read - s.card_command(CMD17, start_idx)?; - s.read_data(&mut blocks[0].contents)?; - } else { - // Start a multi-block read - s.card_command(CMD18, start_idx)?; - for block in blocks.iter_mut() { - s.read_data(&mut block.contents)?; - } - // Stop the read - s.card_command(CMD12, 0)?; + + if blocks.len() == 1 { + // Start a single-block read + self.card_command(CMD17, start_idx)?; + self.read_data(&mut blocks[0].contents)?; + } else { + // Start a multi-block read + self.card_command(CMD18, start_idx)?; + for block in blocks.iter_mut() { + self.read_data(&mut block.contents)?; } - Ok(()) - }) + // Stop the read + self.card_command(CMD12, 0)?; + } + Ok(()) } /// Write one or more blocks, starting at the given block index. @@ -295,74 +251,66 @@ where Some(CardType::SDHC) => start_block_idx.0, None => return Err(Error::CardNotFound), }; - self.with_chip_select(|s| { - if blocks.len() == 1 { - // Start a single-block write - s.card_command(CMD24, start_idx)?; - s.write_data(DATA_START_BLOCK, &blocks[0].contents)?; - s.wait_not_busy(Delay::new_write())?; - if s.card_command(CMD13, 0)? != 0x00 { - return Err(Error::WriteError); - } - if s.read_byte()? != 0x00 { - return Err(Error::WriteError); - } - } else { - // > It is recommended using this command preceding CMD25, some of the cards will be faster for Multiple - // > Write Blocks operation. Note that the host should send ACMD23 just before WRITE command if the host - // > wants to use the pre-erased feature - s.card_acmd(ACMD23, blocks.len() as u32)?; - // wait for card to be ready before sending the next command - s.wait_not_busy(Delay::new_write())?; - - // Start a multi-block write - s.card_command(CMD25, start_idx)?; - for block in blocks.iter() { - s.wait_not_busy(Delay::new_write())?; - s.write_data(WRITE_MULTIPLE_TOKEN, &block.contents)?; - } - // Stop the write - s.wait_not_busy(Delay::new_write())?; - s.write_byte(STOP_TRAN_TOKEN)?; + if blocks.len() == 1 { + // Start a single-block write + self.card_command(CMD24, start_idx)?; + self.write_data(DATA_START_BLOCK, &blocks[0].contents)?; + self.wait_not_busy(Delay::new_write())?; + if self.card_command(CMD13, 0)? != 0x00 { + return Err(Error::WriteError); } - Ok(()) - }) + if self.read_byte()? != 0x00 { + return Err(Error::WriteError); + } + } else { + // > It is recommended using this command preceding CMD25, some of the cards will be faster for Multiple + // > Write Blocks operation. Note that the host should send ACMD23 just before WRITE command if the host + // > wants to use the pre-erased feature + self.card_acmd(ACMD23, blocks.len() as u32)?; + // wait for card to be ready before sending the next command + self.wait_not_busy(Delay::new_write())?; + + // Start a multi-block write + self.card_command(CMD25, start_idx)?; + for block in blocks.iter() { + self.wait_not_busy(Delay::new_write())?; + self.write_data(WRITE_MULTIPLE_TOKEN, &block.contents)?; + } + // Stop the write + self.wait_not_busy(Delay::new_write())?; + self.write_byte(STOP_TRAN_TOKEN)?; + } + Ok(()) } /// Determine how many blocks this device can hold. fn num_blocks(&mut self) -> Result { - let num_blocks = self.with_chip_select(|s| { - let csd = s.read_csd()?; - debug!("CSD: {:?}", csd); - match csd { - Csd::V1(ref contents) => Ok(contents.card_capacity_blocks()), - Csd::V2(ref contents) => Ok(contents.card_capacity_blocks()), - } - })?; + let csd = self.read_csd()?; + debug!("CSD: {:?}", csd); + let num_blocks = match csd { + Csd::V1(ref contents) => contents.card_capacity_blocks(), + Csd::V2(ref contents) => contents.card_capacity_blocks(), + }; Ok(BlockCount(num_blocks)) } /// Return the usable size of this SD card in bytes. fn num_bytes(&mut self) -> Result { - self.with_chip_select(|s| { - let csd = s.read_csd()?; - debug!("CSD: {:?}", csd); - match csd { - Csd::V1(ref contents) => Ok(contents.card_capacity_bytes()), - Csd::V2(ref contents) => Ok(contents.card_capacity_bytes()), - } - }) + let csd = self.read_csd()?; + debug!("CSD: {:?}", csd); + match csd { + Csd::V1(ref contents) => Ok(contents.card_capacity_bytes()), + Csd::V2(ref contents) => Ok(contents.card_capacity_bytes()), + } } /// Can this card erase single blocks? pub fn erase_single_block_enabled(&mut self) -> Result { - self.with_chip_select(|s| { - let csd = s.read_csd()?; - match csd { - Csd::V1(ref contents) => Ok(contents.erase_single_block_enabled()), - Csd::V2(ref contents) => Ok(contents.erase_single_block_enabled()), - } - }) + let csd = self.read_csd()?; + match csd { + Csd::V1(ref contents) => Ok(contents.erase_single_block_enabled()), + Csd::V2(ref contents) => Ok(contents.erase_single_block_enabled()), + } } /// Read the 'card specific data' block. @@ -447,14 +395,6 @@ where } } - fn cs_high(&mut self) -> Result<(), Error> { - self.cs.set_high().map_err(|_| Error::GpioError) - } - - fn cs_low(&mut self) -> Result<(), Error> { - self.cs.set_low().map_err(|_| Error::GpioError) - } - /// Check the card is initialised. fn check_init(&mut self) -> Result<(), Error> { if self.card_type.is_none() { @@ -473,11 +413,6 @@ where // Assume it hasn't worked let mut card_type; trace!("Reset card.."); - // Supply minimum of 74 clock cycles without CS asserted. - s.cs_high()?; - s.write_bytes(&[0xFF; 10])?; - // Assert CS - s.cs_low()?; // Enter SPI mode. let mut delay = Delay::new(s.options.acquire_retries); for _attempts in 1.. { @@ -551,23 +486,10 @@ where Ok(()) }; let result = f(self); - self.cs_high()?; let _ = self.read_byte(); result } - /// Perform a function that might error with the chipselect low. - /// Always releases the chipselect, even if the function errors. - fn with_chip_select(&mut self, func: F) -> Result - where - F: FnOnce(&mut Self) -> Result, - { - self.cs_low()?; - let result = func(self); - self.cs_high()?; - result - } - /// Perform an application-specific command. fn card_acmd(&mut self, command: u8, arg: u32) -> Result { self.card_command(CMD55, 0)?; diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 9c371a4..22ed2ea 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -41,6 +41,7 @@ use embedded_sdmmc::{Block, BlockCount, BlockDevice, BlockIdx}; pub static DISK_SOURCE: &[u8] = include_bytes!("../disk.img.gz"); #[derive(Debug)] +#[allow(dead_code)] pub enum Error { /// Failed to read the source image Io(std::io::Error),