diff --git a/Cargo.toml b/Cargo.toml index e94d79e2..20322ce2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,16 +13,17 @@ version = "0.6.0" [dependencies] byteorder = {version = "1", default-features = false} defmt = {version = "0.3", optional = true} -embedded-hal = "0.2.3" +embedded-hal = "1.0.0" heapless = "0.7" log = {version = "0.4", default-features = false, optional = true} [dev-dependencies] -env_logger = "0.9" -hex-literal = "0.3" +env_logger = "0.10.0" +hex-literal = "0.4.1" flate2 = "1.0" sha2 = "0.10" chrono = "0.4" +embedded-hal-bus = "0.1.0" [features] default = ["log"] diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 9fffca1d..153b7581 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -3,26 +3,45 @@ //! We add enough stuff to make it compile, but it won't run because our fake //! SPI doesn't do any replies. -struct FakeSpi(); +use core::cell::RefCell; -impl embedded_hal::blocking::spi::Transfer for FakeSpi { +use embedded_sdmmc::sdcard::DummyCsPin; + +struct FakeSpiBus(); + +impl embedded_hal::spi::ErrorType for FakeSpiBus { type Error = core::convert::Infallible; - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { - Ok(words) - } } -impl embedded_hal::blocking::spi::Write for FakeSpi { - type Error = core::convert::Infallible; - fn write(&mut self, _words: &[u8]) -> Result<(), Self::Error> { +impl embedded_hal::spi::SpiBus for FakeSpiBus { + fn read(&mut self, _: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn write(&mut self, _: &[u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn transfer(&mut self, _: &mut [u8], _: &[u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn transfer_in_place(&mut self, _: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } } struct FakeCs(); -impl embedded_hal::digital::v2::OutputPin for FakeCs { +impl embedded_hal::digital::ErrorType for FakeCs { type Error = core::convert::Infallible; +} + +impl embedded_hal::digital::OutputPin for FakeCs { fn set_low(&mut self) -> Result<(), Self::Error> { Ok(()) } @@ -32,11 +51,12 @@ impl embedded_hal::digital::v2::OutputPin for FakeCs { } } +#[derive(Clone, Copy)] struct FakeDelayer(); -impl embedded_hal::blocking::delay::DelayUs for FakeDelayer { - fn delay_us(&mut self, us: u8) { - std::thread::sleep(std::time::Duration::from_micros(u64::from(us))); +impl embedded_hal::delay::DelayNs for FakeDelayer { + fn delay_ns(&mut self, ns: u32) { + std::thread::sleep(std::time::Duration::from_nanos(u64::from(ns))); } } @@ -74,10 +94,14 @@ impl From for Error { } fn main() -> Result<(), Error> { - let sdmmc_spi = FakeSpi(); - let sdmmc_cs = FakeCs(); + // 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 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); // Get the card size (this also triggers card initialisation because it's not been done yet) diff --git a/src/lib.rs b/src/lib.rs index 630d09fd..11db6e2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,55 +16,32 @@ //! couldn't work with a USB Thumb Drive, but we only supply a `BlockDevice` //! suitable for reading SD and SDHC cards over SPI. //! -//! ```rust,no_run -//! # struct DummySpi; -//! # struct DummyCsPin; -//! # struct DummyUart; -//! # struct DummyTimeSource; -//! # struct DummyDelayer; -//! # impl embedded_hal::blocking::spi::Transfer for DummySpi { -//! # type Error = (); -//! # fn transfer<'w>(&mut self, data: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { Ok(&[0]) } -//! # } -//! # impl embedded_hal::blocking::spi::Write for DummySpi { -//! # type Error = (); -//! # fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { Ok(()) } -//! # } -//! # impl embedded_hal::digital::v2::OutputPin for DummyCsPin { -//! # type Error = (); -//! # fn set_low(&mut self) -> Result<(), ()> { Ok(()) } -//! # fn set_high(&mut self) -> Result<(), ()> { Ok(()) } -//! # } -//! # impl embedded_sdmmc::TimeSource for DummyTimeSource { -//! # fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { embedded_sdmmc::Timestamp::from_fat(0, 0) } -//! # } -//! # impl embedded_hal::blocking::delay::DelayUs for DummyDelayer { -//! # fn delay_us(&mut self, us: u8) {} -//! # } -//! # impl std::fmt::Write for DummyUart { fn write_str(&mut self, s: &str) -> std::fmt::Result { Ok(()) } } -//! # use std::fmt::Write; -//! # use embedded_sdmmc::VolumeManager; -//! # fn main() -> Result<(), embedded_sdmmc::Error> { -//! # let mut sdmmc_spi = DummySpi; -//! # let mut sdmmc_cs = DummyCsPin; -//! # let time_source = DummyTimeSource; -//! # let delayer = DummyDelayer; -//! let sdcard = embedded_sdmmc::SdCard::new(sdmmc_spi, sdmmc_cs, delayer); -//! println!("Card size is {} bytes", sdcard.num_bytes()?); -//! let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, time_source); -//! let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0))?; -//! println!("Volume 0: {:?}", volume0); -//! let mut root_dir = volume0.open_root_dir()?; -//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly)?; -//! while !my_file.is_eof() { -//! let mut buffer = [0u8; 32]; -//! let num_read = my_file.read(&mut buffer)?; -//! for b in &buffer[0..num_read] { -//! print!("{}", *b as char); +//! ```rust +//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager}; +//! +//! fn example(spi: S, cs: CS, 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); +//! 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))?; +//! println!("Volume 0: {:?}", volume0); +//! let mut root_dir = volume0.open_root_dir()?; +//! let mut my_file = root_dir.open_file_in_dir("MY_FILE.TXT", Mode::ReadOnly)?; +//! while !my_file.is_eof() { +//! let mut buffer = [0u8; 32]; +//! let num_read = my_file.read(&mut buffer)?; +//! for b in &buffer[0..num_read] { +//! print!("{}", *b as char); +//! } //! } +//! Ok(()) //! } -//! # Ok(()) -//! # } //! ``` //! //! ## Features diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 517e19ce..8f0dbb9c 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -21,34 +21,74 @@ 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 SPI peripheral and a Chip Select pin. We need Chip Select to -/// be separate so we can clock out some bytes without Chip Select asserted -/// (which "flushes the SD cards registers" according to the spec). +/// 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. /// /// All the APIs take `&self` - mutability is handled using an inner `RefCell`. +/// +/// [`SpiDevice`]: embedded_hal::spi::SpiDevice pub struct SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayNs, { inner: RefCell>, } impl SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + 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. /// @@ -59,6 +99,9 @@ where /// Construct a new SD/MMC Card driver, using a raw SPI interface and the given options. /// + /// 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. pub fn new_with_options( @@ -152,11 +195,9 @@ where impl BlockDevice for SdCard where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayNs, { type Error = Error; @@ -205,11 +246,9 @@ where /// All the APIs required `&mut self`. struct SdCardInner where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + SPI: embedded_hal::spi::SpiDevice, + CS: embedded_hal::digital::OutputPin, + DELAYER: embedded_hal::delay::DelayNs, { spi: SPI, cs: CS, @@ -220,11 +259,9 @@ where impl SdCardInner where - SPI: embedded_hal::blocking::spi::Transfer + embedded_hal::blocking::spi::Write, - CS: embedded_hal::digital::v2::OutputPin, - >::Error: core::fmt::Debug, - >::Error: core::fmt::Debug, - DELAYER: embedded_hal::blocking::delay::DelayUs, + 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. fn read(&mut self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), Error> { @@ -583,13 +620,14 @@ where /// Send one byte and receive one byte over the SPI bus. fn transfer_byte(&mut self, out: u8) -> Result { + let mut read_buf = [0u8; 1]; self.spi - .transfer(&mut [out]) - .map(|b| b[0]) - .map_err(|_e| Error::Transport) + .transfer(&mut read_buf, &[out]) + .map_err(|_| Error::Transport)?; + Ok(read_buf[0]) } - /// Send mutiple bytes and ignore what comes back over the SPI bus. + /// Send multiple bytes and ignore what comes back over the SPI bus. fn write_bytes(&mut self, out: &[u8]) -> Result<(), Error> { self.spi.write(out).map_err(|_e| Error::Transport)?; Ok(()) @@ -597,7 +635,9 @@ where /// Send multiple bytes and replace them with what comes back over the SPI bus. fn transfer_bytes(&mut self, in_out: &mut [u8]) -> Result<(), Error> { - self.spi.transfer(in_out).map_err(|_e| Error::Transport)?; + self.spi + .transfer_in_place(in_out) + .map_err(|_e| Error::Transport)?; Ok(()) } @@ -689,7 +729,7 @@ pub enum CardType { /// Uses byte-addressing internally, so limited to 2GiB in size. SD2, /// An high-capacity 'SDHC' Card. - /// + /// /// Uses block-addressing internally to support capacities above 2GiB. SDHC, } @@ -753,7 +793,7 @@ impl Delay { /// `Ok(())`. fn delay(&mut self, delayer: &mut T, err: Error) -> Result<(), Error> where - T: embedded_hal::blocking::delay::DelayUs, + T: embedded_hal::delay::DelayNs, { if self.retries_left == 0 { Err(err)