From 06d9b55741f2ef184181d3cbd8e0bf1a0b7edcbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sun, 18 Aug 2019 15:28:35 +0200 Subject: [PATCH 01/20] adding the FlashWrite, EepromWrite and Read traits --- src/lib.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a35c1f1..4560bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,10 @@ pub enum Error, GPIO: OutputPin> { /// A GPIO could not be set. Gpio(GPIO::Error), + /// The data submitted for a write onto a Flash chip did not match its + /// block length + BlockLength, + /// Status register contained unexpected flags. /// /// This can happen when the chip is faulty, incorrectly connected, or the @@ -55,7 +59,47 @@ where Error::Spi(spi) => write!(f, "Error::Spi({:?})", spi), Error::Gpio(gpio) => write!(f, "Error::Gpio({:?})", gpio), Error::UnexpectedStatus => f.write_str("Error::UnexpectedStatus"), + Error::BlockLength => f.write_str("Error::BlockLength"), Error::__NonExhaustive(_) => unreachable!(), } } } + +pub trait Read, CS: OutputPin> { + /// Reads flash contents into `buf`, starting at `addr`. + /// + /// This will always read `buf.len()` worth of bytes, filling up `buf` + /// completely. + fn read(&mut self, addr: Addr, buf: &mut [u8]) -> Result<(), Error>; +} + +pub trait FlashWrite, CS: OutputPin> { + const BLOCK_LENGTH: usize; + + /// Writes a block of data onto a flash memory chip, this function checks if the + /// block is the length set in the associated constant [BLOCK_LENGTH](FlashWrite.BLOCK_LENGTH) + /// and will throw an error if it isn't. + fn write(&mut self, addr: Addr, block: &[u8]) -> Result<(), Error> { + if block.len() == Self::BLOCK_LENGTH { + unsafe { + self.write_block_unchecked(addr, block)?; + } + Ok(()) + } + else { + Err(Error::BlockLength) + } + } + + /// Writes a block without checking wether it fits the block size + /// This function should never be used directly, instead the [write](FlashWrite.write) should be used + /// as it provides a safe frontend for this function + unsafe fn write_block_unchecked(&mut self, addr: Addr, block: &[u8]) -> Result<(), Error>; +} + +pub trait EepromWrite, CS: OutputPin> { + type Error; + + /// Writes a block of data towards addr onto an EEPROM chip + fn write(&mut self, addr: Addr, block: &[u8]) -> Result<(), Error>; +} From 08197ade142f513aacb3ea81a2a07f2e28ad5b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Mon, 19 Aug 2019 21:37:53 +0200 Subject: [PATCH 02/20] reworking the new traits --- src/lib.rs | 60 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4560bde..509d0d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,41 +65,49 @@ where } } +/// A trait for reading operations from a memory chip pub trait Read, CS: OutputPin> { - /// Reads flash contents into `buf`, starting at `addr`. + /// Reads bytes from a memory chip /// - /// This will always read `buf.len()` worth of bytes, filling up `buf` - /// completely. + /// # Parameters + /// * `addr`: The address to start reading at + /// * `buf`: The buffer to read buf.len() bytes into fn read(&mut self, addr: Addr, buf: &mut [u8]) -> Result<(), Error>; } -pub trait FlashWrite, CS: OutputPin> { +/// A trait for writing and erasing operations on a memory chip +pub trait BlockDevice, CS: OutputPin> { + /// The block length in bytes, should be set to 1 for EEPROM implementations const BLOCK_LENGTH: usize; - /// Writes a block of data onto a flash memory chip, this function checks if the - /// block is the length set in the associated constant [BLOCK_LENGTH](FlashWrite.BLOCK_LENGTH) - /// and will throw an error if it isn't. - fn write(&mut self, addr: Addr, block: &[u8]) -> Result<(), Error> { - if block.len() == Self::BLOCK_LENGTH { - unsafe { - self.write_block_unchecked(addr, block)?; - } - Ok(()) - } - else { + /// Erases bytes from the memory chip + /// + /// This function will return a `BlockLength` error if `amount` is not a multiple + /// of [BLOCK_LENGTH](BlockDevice::BLOCK_LENGTH) + /// + /// # Parameters + /// * `addr`: The address to start erasing at + /// * `amount`: The amount of bytes to erase, starting at `addr` + fn erase_bytes(&mut self, addr: Addr, amount: Addr) -> Result<(), Error> { + if amount < Self::BLOCK_LENGTH || amount % Self::BLOCK_LENGTH != 0 { Err(Error::BlockLength) } + unsafe { + erase_unchecked(addr, amount) + } } - /// Writes a block without checking wether it fits the block size - /// This function should never be used directly, instead the [write](FlashWrite.write) should be used - /// as it provides a safe frontend for this function - unsafe fn write_block_unchecked(&mut self, addr: Addr, block: &[u8]) -> Result<(), Error>; -} - -pub trait EepromWrite, CS: OutputPin> { - type Error; + /// The "internal" method called by [erase_bytes](BlockDevice::erase_bytes), this function doesn't + /// need to perform the checks regarding [BLOCK_LENGTH](BlockDevice::BLOCK_LENGTH) and is not supposed + /// to be called by the end user of this library (which is the reason it is marked unsafe) + unsafe fn erase_bytes_unchecked(&mut self, addr: Addr, amount: Addr) -> Result<(), Error>; - /// Writes a block of data towards addr onto an EEPROM chip - fn write(&mut self, addr: Addr, block: &[u8]) -> Result<(), Error>; -} + /// Erases the memory chip fully + fn erase_all(&mut self) -> Result<(), Error>; + /// Writes bytes onto the memory chip + /// + /// # Parameters + /// * `addr`: The address to write to + /// * `data`: The bytes to write to `addr` + fn write_block(&mut self, addr: Addr, data: &[u8]) -> Result<(), Error>; +} \ No newline at end of file From cbd515994b945885f2f1ea839815f2d47c653c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Wed, 21 Aug 2019 01:59:35 +0200 Subject: [PATCH 03/20] adding erase_all and write_bytes implementations --- src/lib.rs | 35 ++++++++++++----------- src/series25.rs | 74 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 509d0d7..dfab696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ mod utils; use core::fmt::{self, Debug}; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; +use core::convert::TryInto; mod private { #[derive(Debug)] @@ -34,9 +35,9 @@ pub enum Error, GPIO: OutputPin> { /// A GPIO could not be set. Gpio(GPIO::Error), - /// The data submitted for a write onto a Flash chip did not match its - /// block length - BlockLength, + /// The data or the address submitted for a write onto a Flash chip did not match its + /// sector length + SectorLength, /// Status register contained unexpected flags. /// @@ -59,7 +60,7 @@ where Error::Spi(spi) => write!(f, "Error::Spi({:?})", spi), Error::Gpio(gpio) => write!(f, "Error::Gpio({:?})", gpio), Error::UnexpectedStatus => f.write_str("Error::UnexpectedStatus"), - Error::BlockLength => f.write_str("Error::BlockLength"), + Error::SectorLength => f.write_str("Error::SectorLength"), Error::__NonExhaustive(_) => unreachable!(), } } @@ -76,31 +77,33 @@ pub trait Read, CS: OutputPin> { } /// A trait for writing and erasing operations on a memory chip -pub trait BlockDevice, CS: OutputPin> { - /// The block length in bytes, should be set to 1 for EEPROM implementations - const BLOCK_LENGTH: usize; +pub trait BlockDevice + Copy, SPI: Transfer, CS: OutputPin> + where >::Error : core::fmt::Debug, + { + /// The sector length in bytes, should be set to 1 for EEPROM implementations + const SECTOR_LENGTH: usize; /// Erases bytes from the memory chip /// - /// This function will return a `BlockLength` error if `amount` is not a multiple - /// of [BLOCK_LENGTH](BlockDevice::BLOCK_LENGTH) + /// This function will return a `SectorLength` error if `amount` is not a multiple + /// of [SECTOR_LENGTH](BlockDevice::SECTOR_LENGTH) /// /// # Parameters /// * `addr`: The address to start erasing at /// * `amount`: The amount of bytes to erase, starting at `addr` - fn erase_bytes(&mut self, addr: Addr, amount: Addr) -> Result<(), Error> { - if amount < Self::BLOCK_LENGTH || amount % Self::BLOCK_LENGTH != 0 { - Err(Error::BlockLength) + fn erase_bytes(&mut self, addr: Addr, amount: usize) -> Result<(), Error> { + if amount < Self::SECTOR_LENGTH || amount % Self::SECTOR_LENGTH != 0 || addr.try_into().unwrap() % Self::SECTOR_LENGTH != 0 { + return Err(Error::SectorLength); } unsafe { - erase_unchecked(addr, amount) + self.erase_bytes_unchecked(addr, amount) } } /// The "internal" method called by [erase_bytes](BlockDevice::erase_bytes), this function doesn't - /// need to perform the checks regarding [BLOCK_LENGTH](BlockDevice::BLOCK_LENGTH) and is not supposed + /// need to perform the checks regarding [SECTOR_LENGTH](BlockDevice::SECTOR_LENGTH) and is not supposed /// to be called by the end user of this library (which is the reason it is marked unsafe) - unsafe fn erase_bytes_unchecked(&mut self, addr: Addr, amount: Addr) -> Result<(), Error>; + unsafe fn erase_bytes_unchecked(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; /// Erases the memory chip fully fn erase_all(&mut self) -> Result<(), Error>; @@ -109,5 +112,5 @@ pub trait BlockDevice, CS: OutputPin> { /// # Parameters /// * `addr`: The address to write to /// * `data`: The bytes to write to `addr` - fn write_block(&mut self, addr: Addr, data: &[u8]) -> Result<(), Error>; + fn write_bytes(&mut self, addr: Addr, data: &mut [u8]) -> Result<(), Error>; } \ No newline at end of file diff --git a/src/series25.rs b/src/series25.rs index 6dcdbd6..e0d4617 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -1,8 +1,9 @@ //! Driver for 25-series SPI Flash and EEPROM chips. -use crate::{utils::HexSlice, Error}; +use crate::{utils::HexSlice, Error, Read, BlockDevice}; use bitflags::bitflags; use core::fmt; +use core::convert::TryInto; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; @@ -50,8 +51,8 @@ enum Opcode { bitflags! { /// Status register bits. pub struct Status: u8 { - /// **W**rite **I**n **P**rogress bit. - const WIP = 1 << 0; + /// Erase or write in progress + const BUSY = 1 << 0; /// Status of the **W**rite **E**nable **L**atch. const WEL = 1 << 1; /// The 3 protection region bits. @@ -90,7 +91,7 @@ impl, CS: OutputPin> Flash { // Here we don't expect any writes to be in progress, and the latch must // also be deasserted. - if !(status & (Status::WIP | Status::WEL)).is_empty() { + if !(status & (Status::BUSY | Status::WEL)).is_empty() { return Err(Error::UnexpectedStatus); } @@ -124,11 +125,16 @@ impl, CS: OutputPin> Flash { Ok(Status::from_bits_truncate(buf[1])) } + fn write_enable(&mut self) -> Result<(), Error> { + let mut cmd_buf = [Opcode::WriteEnable as u8]; + self.command(&mut cmd_buf)?; + Ok(()) + } +} + +impl, CS: OutputPin> Read for Flash{ /// Reads flash contents into `buf`, starting at `addr`. /// - /// This will always read `buf.len()` worth of bytes, filling up `buf` - /// completely. - /// /// Note that `addr` is not fully decoded: Flash chips will typically only /// look at the lowest `N` bits needed to encode their size, which means /// that the contents are "mirrored" to addresses that are a multiple of the @@ -137,9 +143,9 @@ impl, CS: OutputPin> Flash { /// /// # Parameters /// - /// * **`addr`**: 24-bit address to start reading at. - /// * **`buf`**: Destination buffer to fill. - pub fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error> { + /// * `addr`: 24-bit address to start reading at. + /// * `buf`: Destination buffer to fill. + fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error> { // TODO what happens if `buf` is empty? let mut cmd_buf = [ @@ -158,3 +164,51 @@ impl, CS: OutputPin> Flash { spi_result.map(|_| ()).map_err(Error::Spi) } } + +impl, CS: OutputPin> BlockDevice for Flash { + const SECTOR_LENGTH: usize = 4096; + + unsafe fn erase_bytes_unchecked(&mut self, addr: u32, amount: usize) -> Result<(), Error> { + Ok(()) + } + + fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error> { + let mut current_addr = addr; + for (c, chunk) in data.chunks_mut(256).enumerate() { + self.write_enable()?; + + current_addr = (addr as usize + c * 256).try_into().unwrap(); + let mut cmd_buf = [ + Opcode::PageProg as u8, + (current_addr >> 16) as u8, + (current_addr >> 8) as u8, + current_addr as u8, + ]; + + self.cs.set_low().map_err(Error::Gpio)?; + let mut spi_result = self.spi.transfer(&mut cmd_buf); + if spi_result.is_ok() { + spi_result = self.spi.transfer(chunk); + } + self.cs.set_high().map_err(Error::Gpio)?; + spi_result.map(|_| ()).map_err(Error::Spi)?; + } + Ok(()) + } + + fn erase_all(&mut self) -> Result<(), Error> { + self.write_enable()?; + let mut cmd_buf = [Opcode::ChipErase as u8]; + self.command(&mut cmd_buf)?; + + let mut done = false; + // Wait until the erase is done + // TODO: maybe exchange this with a delay + while !done { + let status = self.read_status()?; + done = (status & Status::BUSY).is_empty(); + } + + Ok(()) + } +} From 233effefb90fddf42d6c218fafc3cfead3639c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Wed, 21 Aug 2019 15:56:09 +0200 Subject: [PATCH 04/20] finalizing the trait impl on series25 chips, adding a prelude file which exports the BlockDevice and Read trait and formatting the crate with cargo fmt --- examples/dump.rs | 28 +++++++++++++++++----------- src/lib.rs | 29 ++++++++++++++++++----------- src/prelude.rs | 1 + src/series25.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 src/prelude.rs diff --git a/examples/dump.rs b/examples/dump.rs index a16401c..275bf32 100644 --- a/examples/dump.rs +++ b/examples/dump.rs @@ -15,18 +15,19 @@ extern crate panic_semihosting; use cortex_m_rt::entry; -use stm32f4xx_hal::spi::Spi; -use stm32f4xx_hal::stm32 as pac; +use cortex_m_semihosting::hprintln; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal::serial::Write; +use embedded_hal::spi::MODE_0; use stm32f4xx_hal::gpio::GpioExt; -use stm32f4xx_hal::time::{Bps, MegaHertz}; use stm32f4xx_hal::rcc::RccExt; use stm32f4xx_hal::serial::{self, Serial}; -use embedded_hal::spi::MODE_0; -use embedded_hal::digital::v2::OutputPin; -use embedded_hal::serial::Write; -use cortex_m_semihosting::hprintln; +use stm32f4xx_hal::spi::Spi; +use stm32f4xx_hal::stm32 as pac; +use stm32f4xx_hal::time::{Bps, MegaHertz}; use spi_memory::series25::Flash; +use spi_memory::prelude::*; use core::fmt::Write as _; @@ -39,8 +40,7 @@ const BAUDRATE: u32 = 912600; /// Size of the flash chip in bytes. const SIZE_IN_BYTES: u32 = (MEGABITS * 1024 * 1024) / 8; - -fn print<'a, E>(buf: &[u8], w: &'a mut (dyn Write + 'static)) { +fn print<'a, E>(buf: &[u8], w: &'a mut (dyn Write + 'static)) { for c in buf { write!(w, "{:02X}", c).unwrap(); } @@ -55,7 +55,7 @@ fn main() -> ! { let cs = { let mut cs = gpioa.pa9.into_push_pull_output(); - cs.set_high().unwrap(); // deselect + cs.set_high().unwrap(); // deselect cs }; @@ -64,7 +64,13 @@ fn main() -> ! { let miso = gpioa.pa6.into_alternate_af5(); let mosi = gpioa.pa7.into_alternate_af5(); - Spi::spi1(periph.SPI1, (sck, miso, mosi), MODE_0, MegaHertz(1).into(), clocks) + Spi::spi1( + periph.SPI1, + (sck, miso, mosi), + MODE_0, + MegaHertz(1).into(), + clocks, + ) }; let mut serial = { diff --git a/src/lib.rs b/src/lib.rs index dfab696..64fcc9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,13 +11,14 @@ #[macro_use] mod log; +pub mod prelude; pub mod series25; mod utils; +use core::convert::TryInto; use core::fmt::{self, Debug}; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; -use core::convert::TryInto; mod private { #[derive(Debug)] @@ -35,7 +36,7 @@ pub enum Error, GPIO: OutputPin> { /// A GPIO could not be set. Gpio(GPIO::Error), - /// The data or the address submitted for a write onto a Flash chip did not match its + /// The data or the address submitted for a write onto a Flash chip did not match its /// sector length SectorLength, @@ -77,9 +78,10 @@ pub trait Read, CS: OutputPin> { } /// A trait for writing and erasing operations on a memory chip -pub trait BlockDevice + Copy, SPI: Transfer, CS: OutputPin> - where >::Error : core::fmt::Debug, - { +pub trait BlockDevice + Copy, SPI: Transfer, CS: OutputPin> +where + >::Error: core::fmt::Debug, +{ /// The sector length in bytes, should be set to 1 for EEPROM implementations const SECTOR_LENGTH: usize; @@ -92,18 +94,23 @@ pub trait BlockDevice + Copy, SPI: Transfer, CS: Output /// * `addr`: The address to start erasing at /// * `amount`: The amount of bytes to erase, starting at `addr` fn erase_bytes(&mut self, addr: Addr, amount: usize) -> Result<(), Error> { - if amount < Self::SECTOR_LENGTH || amount % Self::SECTOR_LENGTH != 0 || addr.try_into().unwrap() % Self::SECTOR_LENGTH != 0 { + if amount < Self::SECTOR_LENGTH + || amount % Self::SECTOR_LENGTH != 0 + || addr.try_into().unwrap() % Self::SECTOR_LENGTH != 0 + { return Err(Error::SectorLength); } - unsafe { - self.erase_bytes_unchecked(addr, amount) - } + unsafe { self.erase_bytes_unchecked(addr, amount) } } /// The "internal" method called by [erase_bytes](BlockDevice::erase_bytes), this function doesn't /// need to perform the checks regarding [SECTOR_LENGTH](BlockDevice::SECTOR_LENGTH) and is not supposed /// to be called by the end user of this library (which is the reason it is marked unsafe) - unsafe fn erase_bytes_unchecked(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; + unsafe fn erase_bytes_unchecked( + &mut self, + addr: Addr, + amount: usize, + ) -> Result<(), Error>; /// Erases the memory chip fully fn erase_all(&mut self) -> Result<(), Error>; @@ -113,4 +120,4 @@ pub trait BlockDevice + Copy, SPI: Transfer, CS: Output /// * `addr`: The address to write to /// * `data`: The bytes to write to `addr` fn write_bytes(&mut self, addr: Addr, data: &mut [u8]) -> Result<(), Error>; -} \ No newline at end of file +} diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..11de49f --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::{BlockDevice, Read}; diff --git a/src/series25.rs b/src/series25.rs index e0d4617..6f5f4b0 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -1,9 +1,9 @@ //! Driver for 25-series SPI Flash and EEPROM chips. -use crate::{utils::HexSlice, Error, Read, BlockDevice}; +use crate::{utils::HexSlice, BlockDevice, Error, Read}; use bitflags::bitflags; -use core::fmt; use core::convert::TryInto; +use core::fmt; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; @@ -43,7 +43,7 @@ enum Opcode { WriteStatus = 0x01, Read = 0x03, PageProg = 0x02, // directly writes to EEPROMs too - SectorErase = 0xD7, + SectorErase = 0x20, BlockErase = 0xD8, ChipErase = 0xC7, } @@ -132,7 +132,7 @@ impl, CS: OutputPin> Flash { } } -impl, CS: OutputPin> Read for Flash{ +impl, CS: OutputPin> Read for Flash { /// Reads flash contents into `buf`, starting at `addr`. /// /// Note that `addr` is not fully decoded: Flash chips will typically only @@ -168,16 +168,41 @@ impl, CS: OutputPin> Read for Flash{ impl, CS: OutputPin> BlockDevice for Flash { const SECTOR_LENGTH: usize = 4096; - unsafe fn erase_bytes_unchecked(&mut self, addr: u32, amount: usize) -> Result<(), Error> { + unsafe fn erase_bytes_unchecked( + &mut self, + addr: u32, + amount: usize, + ) -> Result<(), Error> { + let amount = amount / Self::SECTOR_LENGTH; + for c in 0..amount { + self.write_enable()?; + + let current_addr: u32 = (addr as usize + c * 256).try_into().unwrap(); + let mut cmd_buf = [ + Opcode::SectorErase as u8, + (current_addr >> 16) as u8, + (current_addr >> 8) as u8, + current_addr as u8, + ]; + self.command(&mut cmd_buf)?; + + let mut done = false; + // Wait until the erase is done + // TODO: maybe exchange this with a delay + while !done { + let status = self.read_status()?; + done = (status & Status::BUSY).is_empty(); + } + } + Ok(()) } fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error> { - let mut current_addr = addr; for (c, chunk) in data.chunks_mut(256).enumerate() { self.write_enable()?; - current_addr = (addr as usize + c * 256).try_into().unwrap(); + let current_addr: u32 = (addr as usize + c * 256).try_into().unwrap(); let mut cmd_buf = [ Opcode::PageProg as u8, (current_addr >> 16) as u8, @@ -192,6 +217,14 @@ impl, CS: OutputPin> BlockDevice for Flash Date: Wed, 21 Aug 2019 15:56:09 +0200 Subject: [PATCH 05/20] finalizing the trait impl on series25 chips, adding a prelude file which exports the BlockDevice and Read trait and formatting the crate with cargo fmt --- Cargo.toml | 2 +- examples/dump.rs | 28 +++++++++++++++++----------- src/lib.rs | 29 ++++++++++++++++++----------- src/prelude.rs | 1 + src/series25.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 src/prelude.rs diff --git a/Cargo.toml b/Cargo.toml index a10ac4c..8bbee60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "spi-memory" version = "0.1.0" -authors = ["Jonas Schievink "] +authors = ["Jonas Schievink ", "Henrik Böving "] edition = "2018" description = "A generic driver for different SPI Flash and EEPROM chips" documentation = "https://docs.rs/spi-memory/" diff --git a/examples/dump.rs b/examples/dump.rs index a16401c..275bf32 100644 --- a/examples/dump.rs +++ b/examples/dump.rs @@ -15,18 +15,19 @@ extern crate panic_semihosting; use cortex_m_rt::entry; -use stm32f4xx_hal::spi::Spi; -use stm32f4xx_hal::stm32 as pac; +use cortex_m_semihosting::hprintln; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal::serial::Write; +use embedded_hal::spi::MODE_0; use stm32f4xx_hal::gpio::GpioExt; -use stm32f4xx_hal::time::{Bps, MegaHertz}; use stm32f4xx_hal::rcc::RccExt; use stm32f4xx_hal::serial::{self, Serial}; -use embedded_hal::spi::MODE_0; -use embedded_hal::digital::v2::OutputPin; -use embedded_hal::serial::Write; -use cortex_m_semihosting::hprintln; +use stm32f4xx_hal::spi::Spi; +use stm32f4xx_hal::stm32 as pac; +use stm32f4xx_hal::time::{Bps, MegaHertz}; use spi_memory::series25::Flash; +use spi_memory::prelude::*; use core::fmt::Write as _; @@ -39,8 +40,7 @@ const BAUDRATE: u32 = 912600; /// Size of the flash chip in bytes. const SIZE_IN_BYTES: u32 = (MEGABITS * 1024 * 1024) / 8; - -fn print<'a, E>(buf: &[u8], w: &'a mut (dyn Write + 'static)) { +fn print<'a, E>(buf: &[u8], w: &'a mut (dyn Write + 'static)) { for c in buf { write!(w, "{:02X}", c).unwrap(); } @@ -55,7 +55,7 @@ fn main() -> ! { let cs = { let mut cs = gpioa.pa9.into_push_pull_output(); - cs.set_high().unwrap(); // deselect + cs.set_high().unwrap(); // deselect cs }; @@ -64,7 +64,13 @@ fn main() -> ! { let miso = gpioa.pa6.into_alternate_af5(); let mosi = gpioa.pa7.into_alternate_af5(); - Spi::spi1(periph.SPI1, (sck, miso, mosi), MODE_0, MegaHertz(1).into(), clocks) + Spi::spi1( + periph.SPI1, + (sck, miso, mosi), + MODE_0, + MegaHertz(1).into(), + clocks, + ) }; let mut serial = { diff --git a/src/lib.rs b/src/lib.rs index dfab696..64fcc9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,13 +11,14 @@ #[macro_use] mod log; +pub mod prelude; pub mod series25; mod utils; +use core::convert::TryInto; use core::fmt::{self, Debug}; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; -use core::convert::TryInto; mod private { #[derive(Debug)] @@ -35,7 +36,7 @@ pub enum Error, GPIO: OutputPin> { /// A GPIO could not be set. Gpio(GPIO::Error), - /// The data or the address submitted for a write onto a Flash chip did not match its + /// The data or the address submitted for a write onto a Flash chip did not match its /// sector length SectorLength, @@ -77,9 +78,10 @@ pub trait Read, CS: OutputPin> { } /// A trait for writing and erasing operations on a memory chip -pub trait BlockDevice + Copy, SPI: Transfer, CS: OutputPin> - where >::Error : core::fmt::Debug, - { +pub trait BlockDevice + Copy, SPI: Transfer, CS: OutputPin> +where + >::Error: core::fmt::Debug, +{ /// The sector length in bytes, should be set to 1 for EEPROM implementations const SECTOR_LENGTH: usize; @@ -92,18 +94,23 @@ pub trait BlockDevice + Copy, SPI: Transfer, CS: Output /// * `addr`: The address to start erasing at /// * `amount`: The amount of bytes to erase, starting at `addr` fn erase_bytes(&mut self, addr: Addr, amount: usize) -> Result<(), Error> { - if amount < Self::SECTOR_LENGTH || amount % Self::SECTOR_LENGTH != 0 || addr.try_into().unwrap() % Self::SECTOR_LENGTH != 0 { + if amount < Self::SECTOR_LENGTH + || amount % Self::SECTOR_LENGTH != 0 + || addr.try_into().unwrap() % Self::SECTOR_LENGTH != 0 + { return Err(Error::SectorLength); } - unsafe { - self.erase_bytes_unchecked(addr, amount) - } + unsafe { self.erase_bytes_unchecked(addr, amount) } } /// The "internal" method called by [erase_bytes](BlockDevice::erase_bytes), this function doesn't /// need to perform the checks regarding [SECTOR_LENGTH](BlockDevice::SECTOR_LENGTH) and is not supposed /// to be called by the end user of this library (which is the reason it is marked unsafe) - unsafe fn erase_bytes_unchecked(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; + unsafe fn erase_bytes_unchecked( + &mut self, + addr: Addr, + amount: usize, + ) -> Result<(), Error>; /// Erases the memory chip fully fn erase_all(&mut self) -> Result<(), Error>; @@ -113,4 +120,4 @@ pub trait BlockDevice + Copy, SPI: Transfer, CS: Output /// * `addr`: The address to write to /// * `data`: The bytes to write to `addr` fn write_bytes(&mut self, addr: Addr, data: &mut [u8]) -> Result<(), Error>; -} \ No newline at end of file +} diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..11de49f --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::{BlockDevice, Read}; diff --git a/src/series25.rs b/src/series25.rs index e0d4617..6f5f4b0 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -1,9 +1,9 @@ //! Driver for 25-series SPI Flash and EEPROM chips. -use crate::{utils::HexSlice, Error, Read, BlockDevice}; +use crate::{utils::HexSlice, BlockDevice, Error, Read}; use bitflags::bitflags; -use core::fmt; use core::convert::TryInto; +use core::fmt; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; @@ -43,7 +43,7 @@ enum Opcode { WriteStatus = 0x01, Read = 0x03, PageProg = 0x02, // directly writes to EEPROMs too - SectorErase = 0xD7, + SectorErase = 0x20, BlockErase = 0xD8, ChipErase = 0xC7, } @@ -132,7 +132,7 @@ impl, CS: OutputPin> Flash { } } -impl, CS: OutputPin> Read for Flash{ +impl, CS: OutputPin> Read for Flash { /// Reads flash contents into `buf`, starting at `addr`. /// /// Note that `addr` is not fully decoded: Flash chips will typically only @@ -168,16 +168,41 @@ impl, CS: OutputPin> Read for Flash{ impl, CS: OutputPin> BlockDevice for Flash { const SECTOR_LENGTH: usize = 4096; - unsafe fn erase_bytes_unchecked(&mut self, addr: u32, amount: usize) -> Result<(), Error> { + unsafe fn erase_bytes_unchecked( + &mut self, + addr: u32, + amount: usize, + ) -> Result<(), Error> { + let amount = amount / Self::SECTOR_LENGTH; + for c in 0..amount { + self.write_enable()?; + + let current_addr: u32 = (addr as usize + c * 256).try_into().unwrap(); + let mut cmd_buf = [ + Opcode::SectorErase as u8, + (current_addr >> 16) as u8, + (current_addr >> 8) as u8, + current_addr as u8, + ]; + self.command(&mut cmd_buf)?; + + let mut done = false; + // Wait until the erase is done + // TODO: maybe exchange this with a delay + while !done { + let status = self.read_status()?; + done = (status & Status::BUSY).is_empty(); + } + } + Ok(()) } fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error> { - let mut current_addr = addr; for (c, chunk) in data.chunks_mut(256).enumerate() { self.write_enable()?; - current_addr = (addr as usize + c * 256).try_into().unwrap(); + let current_addr: u32 = (addr as usize + c * 256).try_into().unwrap(); let mut cmd_buf = [ Opcode::PageProg as u8, (current_addr >> 16) as u8, @@ -192,6 +217,14 @@ impl, CS: OutputPin> BlockDevice for Flash Date: Wed, 21 Aug 2019 20:42:25 +0200 Subject: [PATCH 06/20] addressing review points --- .travis.yml | 2 +- examples/dump.rs | 2 +- src/lib.rs | 52 ++++++++++++++---------------------------------- src/prelude.rs | 1 + src/series25.rs | 45 ++++++++++------------------------------- 5 files changed, 29 insertions(+), 73 deletions(-) diff --git a/.travis.yml b/.travis.yml index 321a6dd..9f3a038 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: rust rust: - - 1.33.0 + - 1.35.0 - stable - nightly cache: cargo diff --git a/examples/dump.rs b/examples/dump.rs index 275bf32..d78692e 100644 --- a/examples/dump.rs +++ b/examples/dump.rs @@ -26,8 +26,8 @@ use stm32f4xx_hal::spi::Spi; use stm32f4xx_hal::stm32 as pac; use stm32f4xx_hal::time::{Bps, MegaHertz}; -use spi_memory::series25::Flash; use spi_memory::prelude::*; +use spi_memory::series25::Flash; use core::fmt::Write as _; diff --git a/src/lib.rs b/src/lib.rs index 64fcc9d..014467d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ pub enum Error, GPIO: OutputPin> { Gpio(GPIO::Error), /// The data or the address submitted for a write onto a Flash chip did not match its - /// sector length + /// sector length. SectorLength, /// Status register contained unexpected flags. @@ -67,57 +67,35 @@ where } } -/// A trait for reading operations from a memory chip +/// A trait for reading operations from a memory chip. pub trait Read, CS: OutputPin> { - /// Reads bytes from a memory chip + /// Reads bytes from a memory chip. /// /// # Parameters - /// * `addr`: The address to start reading at - /// * `buf`: The buffer to read buf.len() bytes into + /// * `addr`: The address to start reading at. + /// * `buf`: The buffer to read buf.len() bytes into. fn read(&mut self, addr: Addr, buf: &mut [u8]) -> Result<(), Error>; } -/// A trait for writing and erasing operations on a memory chip +/// A trait for writing and erasing operations on a memory chip. pub trait BlockDevice + Copy, SPI: Transfer, CS: OutputPin> where >::Error: core::fmt::Debug, { - /// The sector length in bytes, should be set to 1 for EEPROM implementations - const SECTOR_LENGTH: usize; - - /// Erases bytes from the memory chip - /// - /// This function will return a `SectorLength` error if `amount` is not a multiple - /// of [SECTOR_LENGTH](BlockDevice::SECTOR_LENGTH) + /// Erases sectors from the memory chip. /// /// # Parameters - /// * `addr`: The address to start erasing at - /// * `amount`: The amount of bytes to erase, starting at `addr` - fn erase_bytes(&mut self, addr: Addr, amount: usize) -> Result<(), Error> { - if amount < Self::SECTOR_LENGTH - || amount % Self::SECTOR_LENGTH != 0 - || addr.try_into().unwrap() % Self::SECTOR_LENGTH != 0 - { - return Err(Error::SectorLength); - } - unsafe { self.erase_bytes_unchecked(addr, amount) } - } - - /// The "internal" method called by [erase_bytes](BlockDevice::erase_bytes), this function doesn't - /// need to perform the checks regarding [SECTOR_LENGTH](BlockDevice::SECTOR_LENGTH) and is not supposed - /// to be called by the end user of this library (which is the reason it is marked unsafe) - unsafe fn erase_bytes_unchecked( - &mut self, - addr: Addr, - amount: usize, - ) -> Result<(), Error>; + /// * `addr`: The address to start erasing at. + fn erase_sectors(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; - /// Erases the memory chip fully + /// Erases the memory chip fully. + /// Warning: Full erase operations do take some time usually fn erase_all(&mut self) -> Result<(), Error>; - /// Writes bytes onto the memory chip + /// Writes bytes onto the memory chip. This method is supposed to assume that the sectors + /// it is writing to have already been erased and should not do any erasing themselves. /// /// # Parameters - /// * `addr`: The address to write to - /// * `data`: The bytes to write to `addr` + /// * `addr`: The address to write to. + /// * `data`: The bytes to write to `addr`. fn write_bytes(&mut self, addr: Addr, data: &mut [u8]) -> Result<(), Error>; } diff --git a/src/prelude.rs b/src/prelude.rs index 11de49f..157d3a2 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1 +1,2 @@ +//! Automatically loads in the BlockDevice and Read trait so the user doesn't have to do that all the time. pub use crate::{BlockDevice, Read}; diff --git a/src/series25.rs b/src/series25.rs index 6f5f4b0..53816ac 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -51,7 +51,7 @@ enum Opcode { bitflags! { /// Status register bits. pub struct Status: u8 { - /// Erase or write in progress + /// Erase or write in progress. const BUSY = 1 << 0; /// Status of the **W**rite **E**nable **L**atch. const WEL = 1 << 1; @@ -130,6 +130,12 @@ impl, CS: OutputPin> Flash { self.command(&mut cmd_buf)?; Ok(()) } + + fn wait_done(&mut self) -> Result<(), Error> { + // TODO: Consider changing this to a delay based pattern + while self.read_status()?.contains(Status::BUSY) {} + Ok(()) + } } impl, CS: OutputPin> Read for Flash { @@ -166,14 +172,7 @@ impl, CS: OutputPin> Read for Flash { } impl, CS: OutputPin> BlockDevice for Flash { - const SECTOR_LENGTH: usize = 4096; - - unsafe fn erase_bytes_unchecked( - &mut self, - addr: u32, - amount: usize, - ) -> Result<(), Error> { - let amount = amount / Self::SECTOR_LENGTH; + fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error> { for c in 0..amount { self.write_enable()?; @@ -185,14 +184,7 @@ impl, CS: OutputPin> BlockDevice for Flash, CS: OutputPin> BlockDevice for Flash, CS: OutputPin> BlockDevice for Flash Date: Wed, 21 Aug 2019 22:25:05 +0200 Subject: [PATCH 07/20] Update comment on erase times Co-Authored-By: Jonas Schievink --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 014467d..951182c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,9 @@ where fn erase_sectors(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; /// Erases the memory chip fully. - /// Warning: Full erase operations do take some time usually + + /// Warning: Full erase operations can take a significant amount of time. + /// Check your device's datasheet for precise numbers. fn erase_all(&mut self) -> Result<(), Error>; /// Writes bytes onto the memory chip. This method is supposed to assume that the sectors /// it is writing to have already been erased and should not do any erasing themselves. From a3422c2c27fbe0ed609989e92a113ec0627d94a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Wed, 21 Aug 2019 22:25:16 +0200 Subject: [PATCH 08/20] Update src/lib.rs Co-Authored-By: Jonas Schievink --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 951182c..1cc5517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ pub trait Read, CS: OutputPin> { /// /// # Parameters /// * `addr`: The address to start reading at. - /// * `buf`: The buffer to read buf.len() bytes into. + /// * `buf`: The buffer to read `buf.len()` bytes into. fn read(&mut self, addr: Addr, buf: &mut [u8]) -> Result<(), Error>; } From 7a4755a6120a4e8205461df6f08eb60a14d52b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Thu, 22 Aug 2019 13:46:30 +0200 Subject: [PATCH 09/20] more review points addressed --- src/lib.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 014467d..5b7aef3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,6 @@ pub mod prelude; pub mod series25; mod utils; -use core::convert::TryInto; use core::fmt::{self, Debug}; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; @@ -36,10 +35,6 @@ pub enum Error, GPIO: OutputPin> { /// A GPIO could not be set. Gpio(GPIO::Error), - /// The data or the address submitted for a write onto a Flash chip did not match its - /// sector length. - SectorLength, - /// Status register contained unexpected flags. /// /// This can happen when the chip is faulty, incorrectly connected, or the @@ -61,7 +56,6 @@ where Error::Spi(spi) => write!(f, "Error::Spi({:?})", spi), Error::Gpio(gpio) => write!(f, "Error::Gpio({:?})", gpio), Error::UnexpectedStatus => f.write_str("Error::UnexpectedStatus"), - Error::SectorLength => f.write_str("Error::SectorLength"), Error::__NonExhaustive(_) => unreachable!(), } } @@ -78,10 +72,7 @@ pub trait Read, CS: OutputPin> { } /// A trait for writing and erasing operations on a memory chip. -pub trait BlockDevice + Copy, SPI: Transfer, CS: OutputPin> -where - >::Error: core::fmt::Debug, -{ +pub trait BlockDevice, CS: OutputPin> { /// Erases sectors from the memory chip. /// /// # Parameters From 8a518a0c3b7d87972998d5d4925e31934ca1b4e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Thu, 22 Aug 2019 13:54:58 +0200 Subject: [PATCH 10/20] commented the wrong addr --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a6776a9..4c0308d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,8 @@ pub trait BlockDevice, CS: OutputPin> { /// Erases sectors from the memory chip. /// /// # Parameters - /// * `addr`: The address to start erasing at. + /// * `addr`: The address to start erasing at. If the address is not on a sector boundary, + /// the lower bits can be ignored in order to make it fit fn erase_sectors(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; /// Erases the memory chip fully. @@ -88,8 +89,7 @@ pub trait BlockDevice, CS: OutputPin> { /// it is writing to have already been erased and should not do any erasing themselves. /// /// # Parameters - /// * `addr`: The address to write to. If the address is not on a sector boundary, - /// the lower bits can be ignored in order to make it fit + /// * `addr`: The address to write to. /// * `data`: The bytes to write to `addr`. fn write_bytes(&mut self, addr: Addr, data: &mut [u8]) -> Result<(), Error>; } From e82af20a34242f79809d273805e28a7c317998e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Thu, 22 Aug 2019 14:10:26 +0200 Subject: [PATCH 11/20] Nit 1 Co-Authored-By: Jonas Schievink --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4c0308d..25e4dc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ pub trait BlockDevice, CS: OutputPin> { fn erase_sectors(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; /// Erases the memory chip fully. - + /// /// Warning: Full erase operations can take a significant amount of time. /// Check your device's datasheet for precise numbers. fn erase_all(&mut self) -> Result<(), Error>; From a2e816ebfee9a40ac4be349129ba82485b4aa97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Thu, 22 Aug 2019 14:10:35 +0200 Subject: [PATCH 12/20] Nit 2 Co-Authored-By: Jonas Schievink --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 25e4dc9..b7d2fb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,7 +77,7 @@ pub trait BlockDevice, CS: OutputPin> { /// /// # Parameters /// * `addr`: The address to start erasing at. If the address is not on a sector boundary, - /// the lower bits can be ignored in order to make it fit + /// the lower bits can be ignored in order to make it fit. fn erase_sectors(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; /// Erases the memory chip fully. From fd9b3bc0de1e1182cdb5f5699a05d89d5a091b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sun, 25 Aug 2019 22:56:04 +0200 Subject: [PATCH 13/20] adding an (undocumented) implementation of the w25m concept --- src/lib.rs | 11 ++--- src/series25.rs | 4 +- src/w25m.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 src/w25m.rs diff --git a/src/lib.rs b/src/lib.rs index 4c0308d..032a340 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ mod log; pub mod prelude; pub mod series25; +pub mod w25m; mod utils; use core::fmt::{self, Debug}; @@ -62,23 +63,23 @@ where } /// A trait for reading operations from a memory chip. -pub trait Read, CS: OutputPin> { +pub trait Read, CS: OutputPin> { /// Reads bytes from a memory chip. /// /// # Parameters /// * `addr`: The address to start reading at. /// * `buf`: The buffer to read `buf.len()` bytes into. - fn read(&mut self, addr: Addr, buf: &mut [u8]) -> Result<(), Error>; + fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error>; } /// A trait for writing and erasing operations on a memory chip. -pub trait BlockDevice, CS: OutputPin> { +pub trait BlockDevice, CS: OutputPin> { /// Erases sectors from the memory chip. /// /// # Parameters /// * `addr`: The address to start erasing at. If the address is not on a sector boundary, /// the lower bits can be ignored in order to make it fit - fn erase_sectors(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; + fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error>; /// Erases the memory chip fully. @@ -91,5 +92,5 @@ pub trait BlockDevice, CS: OutputPin> { /// # Parameters /// * `addr`: The address to write to. /// * `data`: The bytes to write to `addr`. - fn write_bytes(&mut self, addr: Addr, data: &mut [u8]) -> Result<(), Error>; + fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error>; } diff --git a/src/series25.rs b/src/series25.rs index 53816ac..0a51919 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -138,7 +138,7 @@ impl, CS: OutputPin> Flash { } } -impl, CS: OutputPin> Read for Flash { +impl, CS: OutputPin> Read for Flash { /// Reads flash contents into `buf`, starting at `addr`. /// /// Note that `addr` is not fully decoded: Flash chips will typically only @@ -171,7 +171,7 @@ impl, CS: OutputPin> Read for Flash { } } -impl, CS: OutputPin> BlockDevice for Flash { +impl, CS: OutputPin> BlockDevice for Flash { fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error> { for c in 0..amount { self.write_enable()?; diff --git a/src/w25m.rs b/src/w25m.rs new file mode 100644 index 0000000..05a0773 --- /dev/null +++ b/src/w25m.rs @@ -0,0 +1,114 @@ +//! Provides an implementation for switching between the two dies stacked upon each other inside the W25M series +use crate::{BlockDevice, Error, Read}; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; +use core::marker::PhantomData; +use core::mem; + +pub struct Die0; +pub struct Die1; + +/// All dies which are supposed to be supported by the W25M struct have to implement this trait +pub trait Stackable, CS: OutputPin>: BlockDevice + Read { + fn new(spi: SPI, cs: CS) -> Self; + /// Returns the SPI and chip select objects so they can be used elsewhere + fn free(self) -> (SPI, CS); +} + +#[derive(Debug)] +pub struct W25M +{ + inner: Inner, + spi: Option, + cs: Option, + _die: PhantomData, +} + +#[derive(Debug)] +enum Inner { + Die0(DIE0), + Die1(DIE1), + Dummy, +} + +impl W25M + where DIE0: Stackable, + DIE1: Stackable, + SPI: Transfer, + CS: OutputPin +{ + pub fn new(spi: SPI, cs: CS) -> W25M { + W25M{ + inner: Inner::Die0(DIE0::new(spi, cs)), + spi: None, + cs: None, + _die: PhantomData + } + } + + // TODO: This is a duplicate from the series25 implementation, deduplicate this + fn command(&mut self, bytes: &mut [u8]) -> Result<(), Error> { + // If the SPI transfer fails, make sure to disable CS anyways + self.cs.as_mut().unwrap().set_low().map_err(Error::Gpio)?; + let spi_result = self.spi.as_mut().unwrap().transfer(bytes).map_err(Error::Spi); + self.cs.as_mut().unwrap().set_high().map_err(Error::Gpio)?; + spi_result?; + Ok(()) + } +} + +impl W25M + where DIE0: Stackable, + DIE1: Stackable, + SPI: Transfer, + CS: OutputPin +{ + pub fn switch_die(mut self) -> Result, Error> { + let (spi, cs) = match mem::replace(&mut self.inner, Inner::Dummy) { + Inner::Die0(die) => die.free(), + _ => unreachable!() + }; + mem::replace(&mut self.spi, Some(spi)); + mem::replace(&mut self.cs, Some(cs)); + + self.command(&mut [0xC2, 0x01])?; + + let spi = mem::replace(&mut self.spi, None).unwrap(); + let cs = mem::replace(&mut self.cs, None).unwrap(); + + Ok(W25M{ + inner: Inner::Die1(DIE1::new(spi, cs)), + spi: None, + cs: None, + _die: PhantomData + }) + } +} + +impl W25M + where DIE0: Stackable, + DIE1: Stackable, + SPI: Transfer, + CS: OutputPin +{ + pub fn switch_die(mut self) -> Result, Error> { + let (spi, cs) = match mem::replace(&mut self.inner, Inner::Dummy) { + Inner::Die1(die) => die.free(), + _ => unreachable!() + }; + mem::replace(&mut self.spi, Some(spi)); + mem::replace(&mut self.cs, Some(cs)); + + self.command(&mut [0xC2, 0x00])?; + + let spi = mem::replace(&mut self.spi, None).unwrap(); + let cs = mem::replace(&mut self.cs, None).unwrap(); + + Ok(W25M{ + inner: Inner::Die0(DIE0::new(spi, cs)), + spi: None, + cs: None, + _die: PhantomData + }) + } +} \ No newline at end of file From a6d2db2967aa9a4e57b5441829a224c0810c8e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sun, 25 Aug 2019 23:20:35 +0200 Subject: [PATCH 14/20] adding BlockDevice and Read impls for w25m --- src/w25m.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/w25m.rs b/src/w25m.rs index 05a0773..3c1091e 100644 --- a/src/w25m.rs +++ b/src/w25m.rs @@ -111,4 +111,50 @@ impl W25M _die: PhantomData }) } +} + +impl BlockDevice for W25M +where DIE0: Stackable, +DIE1: Stackable, +SPI: Transfer, +CS: OutputPin +{ + fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error> { + match &mut self.inner { + Inner::Die0(die) => die.erase_sectors(addr, amount), + Inner::Die1(die) => die.erase_sectors(addr, amount), + _ => unreachable!() + } + } + + fn erase_all(&mut self) -> Result<(), Error> { + match &mut self.inner { + Inner::Die0(die) => die.erase_all(), + Inner::Die1(die) => die.erase_all(), + _ => unreachable!() + } + } + + fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error> { + match &mut self.inner { + Inner::Die0(die) => die.write_bytes(addr, data), + Inner::Die1(die) => die.write_bytes(addr, data), + _ => unreachable!() + } + } +} + +impl Read for W25M +where DIE0: Stackable, +DIE1: Stackable, +SPI: Transfer, +CS: OutputPin +{ + fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error> { + match &mut self.inner { + Inner::Die0(die) => die.read(addr, buf), + Inner::Die1(die) => die.read(addr, buf), + _ => unreachable!() + } + } } \ No newline at end of file From 816d9f12f98e8b02b36fd9b3dad5a1d951eb68a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sat, 31 Aug 2019 23:49:33 +0200 Subject: [PATCH 15/20] read, write and reset working roughly for w25n, more testing to be done to verify --- Cargo.toml | 2 +- src/lib.rs | 13 +-- src/series25.rs | 2 +- src/w25m.rs | 42 ++++----- src/w25n.rs | 229 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 260 insertions(+), 28 deletions(-) create mode 100644 src/w25n.rs diff --git a/Cargo.toml b/Cargo.toml index 8bbee60..0590bb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,9 +44,9 @@ bitflags = "1.0.4" [dev-dependencies] cortex-m = "0.6.0" cortex-m-rt = "0.6.8" -cortex-m-semihosting = "0.3.3" stm32f4xx-hal = { version = "0.5.0", features = ["stm32f401"] } panic-semihosting = "0.5.2" +cortex-m-semihosting = "0.3.4" [profile.dev] opt-level = "z" diff --git a/src/lib.rs b/src/lib.rs index f254077..d660d5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ mod log; pub mod prelude; pub mod series25; pub mod w25m; +pub mod w25n; mod utils; use core::fmt::{self, Debug}; @@ -74,20 +75,22 @@ pub trait Read, CS: OutputPin> { /// A trait for writing and erasing operations on a memory chip. pub trait BlockDevice, CS: OutputPin> { - /// Erases sectors from the memory chip. + /// Erases the smallest available unit from the memory chip. For Flash this should be + /// blocks or sectors, for EEPROM single bytes /// /// # Parameters - /// * `addr`: The address to start erasing at. If the address is not on a sector boundary, + /// * `addr`: The address to start erasing at. If the address is not on a block boundary, /// the lower bits can be ignored in order to make it fit - fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error>; + fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error>; /// Erases the memory chip fully. /// /// Warning: Full erase operations can take a significant amount of time. /// Check your device's datasheet for precise numbers. fn erase_all(&mut self) -> Result<(), Error>; - /// Writes bytes onto the memory chip. This method is supposed to assume that the sectors - /// it is writing to have already been erased and should not do any erasing themselves. + /// Writes bytes onto the smallest writable unit of the memory chip. This method is + /// supposed to assume that the sectors it is writing to have already been erased and + /// should not do any erasing themselves. /// /// # Parameters /// * `addr`: The address to write to. diff --git a/src/series25.rs b/src/series25.rs index 0a51919..fb1208a 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -172,7 +172,7 @@ impl, CS: OutputPin> Read for Flash { } impl, CS: OutputPin> BlockDevice for Flash { - fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error> { + fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error> { for c in 0..amount { self.write_enable()?; diff --git a/src/w25m.rs b/src/w25m.rs index 3c1091e..99e4a46 100644 --- a/src/w25m.rs +++ b/src/w25m.rs @@ -9,14 +9,14 @@ pub struct Die0; pub struct Die1; /// All dies which are supposed to be supported by the W25M struct have to implement this trait -pub trait Stackable, CS: OutputPin>: BlockDevice + Read { - fn new(spi: SPI, cs: CS) -> Self; +pub trait Stackable, CS: OutputPin>: BlockDevice + Read + Sized { + fn new(spi: SPI, cs: CS) -> Result>; /// Returns the SPI and chip select objects so they can be used elsewhere fn free(self) -> (SPI, CS); } #[derive(Debug)] -pub struct W25M +pub struct Flash { inner: Inner, spi: Option, @@ -31,19 +31,19 @@ enum Inner { Dummy, } -impl W25M +impl Flash where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, CS: OutputPin { - pub fn new(spi: SPI, cs: CS) -> W25M { - W25M{ - inner: Inner::Die0(DIE0::new(spi, cs)), + pub fn new(spi: SPI, cs: CS) -> Result, Error> { + Ok(Flash{ + inner: Inner::Die0(DIE0::new(spi, cs)?), spi: None, cs: None, _die: PhantomData - } + }) } // TODO: This is a duplicate from the series25 implementation, deduplicate this @@ -57,13 +57,13 @@ impl W25M } } -impl W25M +impl Flash where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, CS: OutputPin { - pub fn switch_die(mut self) -> Result, Error> { + pub fn switch_die(mut self) -> Result, Error> { let (spi, cs) = match mem::replace(&mut self.inner, Inner::Dummy) { Inner::Die0(die) => die.free(), _ => unreachable!() @@ -76,8 +76,8 @@ impl W25M let spi = mem::replace(&mut self.spi, None).unwrap(); let cs = mem::replace(&mut self.cs, None).unwrap(); - Ok(W25M{ - inner: Inner::Die1(DIE1::new(spi, cs)), + Ok(Flash{ + inner: Inner::Die1(DIE1::new(spi, cs)?), spi: None, cs: None, _die: PhantomData @@ -85,13 +85,13 @@ impl W25M } } -impl W25M +impl Flash where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, CS: OutputPin { - pub fn switch_die(mut self) -> Result, Error> { + pub fn switch_die(mut self) -> Result, Error> { let (spi, cs) = match mem::replace(&mut self.inner, Inner::Dummy) { Inner::Die1(die) => die.free(), _ => unreachable!() @@ -104,8 +104,8 @@ impl W25M let spi = mem::replace(&mut self.spi, None).unwrap(); let cs = mem::replace(&mut self.cs, None).unwrap(); - Ok(W25M{ - inner: Inner::Die0(DIE0::new(spi, cs)), + Ok(Flash{ + inner: Inner::Die0(DIE0::new(spi, cs)?), spi: None, cs: None, _die: PhantomData @@ -113,16 +113,16 @@ impl W25M } } -impl BlockDevice for W25M +impl BlockDevice for Flash where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, CS: OutputPin { - fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error> { + fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error> { match &mut self.inner { - Inner::Die0(die) => die.erase_sectors(addr, amount), - Inner::Die1(die) => die.erase_sectors(addr, amount), + Inner::Die0(die) => die.erase(addr, amount), + Inner::Die1(die) => die.erase(addr, amount), _ => unreachable!() } } @@ -144,7 +144,7 @@ CS: OutputPin } } -impl Read for W25M +impl Read for Flash where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, diff --git a/src/w25n.rs b/src/w25n.rs new file mode 100644 index 0000000..94906ae --- /dev/null +++ b/src/w25n.rs @@ -0,0 +1,229 @@ +use crate::{BlockDevice, Error, Read}; +use crate::w25m::Stackable; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; +use bitflags::bitflags; +use core::fmt::Debug; +use core::convert::TryInto; + +enum Opcode { + // Read one of the 3 8 bit status registers + ReadStatus = 0x05, + // Set the write enable latch. + WriteEnable = 0x06, + // Write to one of the three status registers + WriteStatus = 0x1F, + // Erase a 128 kb block + BlockErase = 0xD8, + // Read one page of data into the buffer + PageDataRead = 0x13, + // Read data from the buffer + ReadData = 0x03, + // Write a page of data from the buffer into a memory region + ProgramExecute = 0x10, + // Write a page of data into the buffer + RandomLoadProgramData = 0x84, +} + +bitflags! { + /// Status register bits. + pub struct Status3: u8 { + /// Erase or write in progress. + const BUSY = 1 << 0; + /// Status of the **W**rite **E**nable **L**atch. + const WEL = 1 << 1; + } +} + +/// Driver for W25N series SPI Flash chips. +/// +/// # Type Parameters +/// +/// * **`SPI`**: The SPI master to which the flash chip is attached. +/// * **`CS`**: The **C**hip-**S**elect line attached to the `\CS`/`\CE` pin of +/// the flash chip. +#[derive(Debug)] +pub struct Flash, CS: OutputPin> { + spi: SPI, + cs: CS, +} + +impl, CS: OutputPin> Flash { + /// Creates a new 25-series flash driver. + /// + /// # Parameters + /// + /// * **`spi`**: An SPI master. Must be configured to operate in the correct + /// mode for the device. + /// * **`cs`**: The **C**hip-**S**elect Pin connected to the `\CS`/`\CE` pin + /// of the flash chip. Will be driven low when accessing the device. + pub fn init(spi: SPI, cs: CS) -> Result> { + let mut this = Self { spi, cs }; + let status = this.read_status_3()?; + info!("Flash::init: status = {:?}", status); + // Here we don't expect any writes to be in progress, and the latch must + // also be deasserted. + if !(status & (Status3::BUSY | Status3::WEL)).is_empty() { + return Err(Error::UnexpectedStatus); + } + + // write to config register 2, set BUF=0 (continious mode) and everything else on reset + this.command(&mut [Opcode::WriteStatus as u8, 0xA0, 0b00000010])?; + this.command(&mut [Opcode::WriteStatus as u8, 0xB0, 0b00010000])?; + Ok(this) + } + + fn command(&mut self, bytes: &mut [u8]) -> Result<(), Error> { + // If the SPI transfer fails, make sure to disable CS anyways + self.cs.set_low().map_err(Error::Gpio)?; + let spi_result = self.spi.transfer(bytes).map_err(Error::Spi); + self.cs.set_high().map_err(Error::Gpio)?; + spi_result?; + Ok(()) + } + + /// Reads status register 3 + pub fn read_status_3(&mut self) -> Result> { + let mut buf = [Opcode::ReadStatus as u8, 0xC0, 0]; + self.command(&mut buf)?; + Ok(Status3::from_bits_truncate(buf[2])) + } + + fn write_enable(&mut self) -> Result<(), Error> { + let mut cmd_buf = [Opcode::WriteEnable as u8]; + self.command(&mut cmd_buf)?; + Ok(()) + } + + fn wait_done(&mut self) -> Result<(), Error> { + // TODO: Consider changing this to a delay based pattern + while self.read_status_3()?.contains(Status3::BUSY) {} + Ok(()) + } +} + +impl, CS: OutputPin> Stackable for Flash +where + SPI::Error: Debug, + CS::Error: Debug, +{ + fn new(spi: SPI, cs: CS) -> Result> { + Flash::init(spi, cs) + } + + fn free(self) -> (SPI, CS) { + (self.spi, self.cs) + } +} + +impl, CS: OutputPin> Read for Flash { + fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error> { + let start_addr: u16 = (addr / 2048).try_into().unwrap(); // page address = addr / 2048 byte + let mut cmd_buf = [ + Opcode::PageDataRead as u8, + 0, // dummy cycles + (start_addr >> 8) as u8, + start_addr as u8 + ]; + + self.command(&mut cmd_buf)?; + self.wait_done()?; + + let mut cmd_buf = [ + Opcode::ReadData as u8, + 0, // 24 dummy cycles + 0, + 0, + ]; + + self.cs.set_low().map_err(Error::Gpio)?; + let mut spi_result = self.spi.transfer(&mut cmd_buf); + if spi_result.is_ok() { + spi_result = self.spi.transfer(buf); + } + self.cs.set_high().map_err(Error::Gpio)?; + self.wait_done()?; + spi_result.map(|_| ()).map_err(Error::Spi) + } +} + +impl, CS: OutputPin> BlockDevice for Flash { + fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error> { + let start_addr: u16 = (addr / 2048).try_into().unwrap(); // page address = addr / 2048 byte + for c in 0..amount { + self.write_enable()?; + + let current_addr: u16 = (start_addr as usize + c).try_into().unwrap(); + let mut cmd_buf = [ + Opcode::BlockErase as u8, + 0, // 8 dummy cycles + (current_addr >> 8) as u8, + current_addr as u8, + ]; + self.command(&mut cmd_buf)?; + self.wait_done()?; + } + + Ok(()) + } + + fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error> { + let start_addr: u16 = (addr / 2048).try_into().unwrap(); // page address = addr / 2048 byte + let mut current_addr = start_addr; + data.reverse(); + for chunk in data.chunks_mut(2048).rev() { + chunk.reverse(); + self.write_enable()?; + let column_addr: u16 = current_addr % 2048; + let mut cmd_buf = [ + Opcode::RandomLoadProgramData as u8, + (column_addr >> 8) as u8, + column_addr as u8, + ]; + + self.cs.set_low().map_err(Error::Gpio)?; + let mut spi_result = self.spi.transfer(&mut cmd_buf); + if spi_result.is_ok() { + spi_result = self.spi.transfer(chunk); + } + self.cs.set_high().map_err(Error::Gpio)?; + spi_result.map(|_| ()).map_err(Error::Spi)?; + + self.wait_done()?; + + let mut cmd_buf = [ + Opcode::ProgramExecute as u8, + 0, // 8 dummy cycles + (current_addr >> 8) as u8, + current_addr as u8, + ]; + self.command(&mut cmd_buf)?; + self.wait_done()?; + current_addr = current_addr + chunk.len() as u16; + } + Ok(()) + } + + fn erase_all(&mut self) -> Result<(), Error> { + self.erase(0, 1024) + } +} + + + + + + + + + + + + + + + + + + + From 8127c021e5e6a5b8f970a93120363732f5531280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sun, 29 Sep 2019 15:12:57 +0200 Subject: [PATCH 16/20] working API for the w25n series, everything tested --- src/w25n.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/w25n.rs b/src/w25n.rs index 94906ae..8a14e4e 100644 --- a/src/w25n.rs +++ b/src/w25n.rs @@ -22,7 +22,7 @@ enum Opcode { // Write a page of data from the buffer into a memory region ProgramExecute = 0x10, // Write a page of data into the buffer - RandomLoadProgramData = 0x84, + LoadProgramData = 0x02, } bitflags! { @@ -153,7 +153,7 @@ impl, CS: OutputPin> BlockDevice for Flash { for c in 0..amount { self.write_enable()?; - let current_addr: u16 = (start_addr as usize + c).try_into().unwrap(); + let current_addr: u16 = (start_addr as usize + 64 * c).try_into().unwrap(); let mut cmd_buf = [ Opcode::BlockErase as u8, 0, // 8 dummy cycles @@ -170,15 +170,15 @@ impl, CS: OutputPin> BlockDevice for Flash { fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error> { let start_addr: u16 = (addr / 2048).try_into().unwrap(); // page address = addr / 2048 byte let mut current_addr = start_addr; + assert!(data.len() % 2048 == 0); data.reverse(); for chunk in data.chunks_mut(2048).rev() { chunk.reverse(); self.write_enable()?; - let column_addr: u16 = current_addr % 2048; let mut cmd_buf = [ - Opcode::RandomLoadProgramData as u8, - (column_addr >> 8) as u8, - column_addr as u8, + Opcode::LoadProgramData as u8, + 0, + 0 ]; self.cs.set_low().map_err(Error::Gpio)?; @@ -199,7 +199,7 @@ impl, CS: OutputPin> BlockDevice for Flash { ]; self.command(&mut cmd_buf)?; self.wait_done()?; - current_addr = current_addr + chunk.len() as u16; + current_addr += 1; } Ok(()) } From 33834ca6fef4f24a91ebd0d88dda15f4d64a5ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Thu, 3 Oct 2019 19:17:30 +0200 Subject: [PATCH 17/20] adding support for spi stack chips --- src/w25m.rs | 94 +++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/src/w25m.rs b/src/w25m.rs index 99e4a46..c15f8b0 100644 --- a/src/w25m.rs +++ b/src/w25m.rs @@ -5,7 +5,9 @@ use embedded_hal::digital::v2::OutputPin; use core::marker::PhantomData; use core::mem; +#[allow(missing_debug_implementations)] pub struct Die0; +#[allow(missing_debug_implementations)] pub struct Die1; /// All dies which are supposed to be supported by the W25M struct have to implement this trait @@ -16,11 +18,9 @@ pub trait Stackable, CS: OutputPin>: BlockDevice + Re } #[derive(Debug)] -pub struct Flash +pub struct Flash { inner: Inner, - spi: Option, - cs: Option, _die: PhantomData, } @@ -31,89 +31,83 @@ enum Inner { Dummy, } -impl Flash - where DIE0: Stackable, - DIE1: Stackable, - SPI: Transfer, - CS: OutputPin +impl Flash { - pub fn new(spi: SPI, cs: CS) -> Result, Error> { + /// Creates a new W25M device + /// + /// At + /// the moment the only way to call this function is sadly + /// ``` + /// let mut flash: Flash, W25N<_, _>, _> = Flash::init(spi, cs).unwrap(); + /// ``` + /// TODO: Improve this API, its not very convenient + pub fn init(spi: SPI, cs: CS) -> Result, Error> + where + SPI: Transfer, + CS: OutputPin, + DIE0: Stackable, + DIE1: Stackable, + { Ok(Flash{ inner: Inner::Die0(DIE0::new(spi, cs)?), - spi: None, - cs: None, _die: PhantomData }) } - - // TODO: This is a duplicate from the series25 implementation, deduplicate this - fn command(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - // If the SPI transfer fails, make sure to disable CS anyways - self.cs.as_mut().unwrap().set_low().map_err(Error::Gpio)?; - let spi_result = self.spi.as_mut().unwrap().transfer(bytes).map_err(Error::Spi); - self.cs.as_mut().unwrap().set_high().map_err(Error::Gpio)?; - spi_result?; - Ok(()) - } } -impl Flash +impl Flash + +{ + pub fn switch_die(mut self) -> Result, Error> where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, - CS: OutputPin -{ - pub fn switch_die(mut self) -> Result, Error> { - let (spi, cs) = match mem::replace(&mut self.inner, Inner::Dummy) { + CS: OutputPin { + let (mut spi, mut cs) = match mem::replace(&mut self.inner, Inner::Dummy) { Inner::Die0(die) => die.free(), _ => unreachable!() }; - mem::replace(&mut self.spi, Some(spi)); - mem::replace(&mut self.cs, Some(cs)); - - self.command(&mut [0xC2, 0x01])?; - - let spi = mem::replace(&mut self.spi, None).unwrap(); - let cs = mem::replace(&mut self.cs, None).unwrap(); + let mut command = [0xC2, 0x01]; + cs.set_low().map_err(Error::Gpio)?; + let spi_result = spi.transfer(&mut command).map_err(Error::Spi); + cs.set_high().map_err(Error::Gpio)?; + spi_result?; Ok(Flash{ inner: Inner::Die1(DIE1::new(spi, cs)?), - spi: None, - cs: None, _die: PhantomData }) } } -impl Flash - where DIE0: Stackable, +impl Flash +{ + pub fn switch_die(mut self) -> Result, Error> + where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, CS: OutputPin -{ - pub fn switch_die(mut self) -> Result, Error> { - let (spi, cs) = match mem::replace(&mut self.inner, Inner::Dummy) { + { + let (mut spi, mut cs) = match mem::replace(&mut self.inner, Inner::Dummy) { Inner::Die1(die) => die.free(), _ => unreachable!() }; - mem::replace(&mut self.spi, Some(spi)); - mem::replace(&mut self.cs, Some(cs)); - - self.command(&mut [0xC2, 0x00])?; - let spi = mem::replace(&mut self.spi, None).unwrap(); - let cs = mem::replace(&mut self.cs, None).unwrap(); + let mut command = [0xC2, 0x00]; + cs.set_low().map_err(Error::Gpio)?; + let spi_result = spi.transfer(&mut command).map_err(Error::Spi); + cs.set_high().map_err(Error::Gpio)?; + spi_result?; + Ok(Flash{ inner: Inner::Die0(DIE0::new(spi, cs)?), - spi: None, - cs: None, _die: PhantomData }) } } -impl BlockDevice for Flash +impl BlockDevice for Flash where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, @@ -144,7 +138,7 @@ CS: OutputPin } } -impl Read for Flash +impl Read for Flash where DIE0: Stackable, DIE1: Stackable, SPI: Transfer, From e72f625d7fd7f5c913225a563e05cf93d3375395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sun, 6 Oct 2019 15:22:04 +0200 Subject: [PATCH 18/20] Update src/lib.rs Co-Authored-By: Jonas Schievink --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d660d5e..ef440ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ pub trait Read, CS: OutputPin> { /// A trait for writing and erasing operations on a memory chip. pub trait BlockDevice, CS: OutputPin> { - /// Erases the smallest available unit from the memory chip. For Flash this should be + /// Erases the smallest erasable unit from the memory chip. For Flash this should be /// blocks or sectors, for EEPROM single bytes /// /// # Parameters From eebfde2ebdbe17448d03063ce34de9b437679789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sun, 6 Oct 2019 15:40:12 +0200 Subject: [PATCH 19/20] increasing code reusage by only having one central spi_command function --- src/series25.rs | 21 ++++++--------------- src/utils.rs | 14 ++++++++++++++ src/w25m.rs | 12 +++--------- src/w25n.rs | 32 +++++++++++++++----------------- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/series25.rs b/src/series25.rs index fb1208a..c0a2bb6 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -1,6 +1,6 @@ //! Driver for 25-series SPI Flash and EEPROM chips. -use crate::{utils::HexSlice, BlockDevice, Error, Read}; +use crate::{utils::{HexSlice, spi_command}, BlockDevice, Error, Read}; use bitflags::bitflags; use core::convert::TryInto; use core::fmt; @@ -98,19 +98,10 @@ impl, CS: OutputPin> Flash { Ok(this) } - fn command(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - // If the SPI transfer fails, make sure to disable CS anyways - self.cs.set_low().map_err(Error::Gpio)?; - let spi_result = self.spi.transfer(bytes).map_err(Error::Spi); - self.cs.set_high().map_err(Error::Gpio)?; - spi_result?; - Ok(()) - } - /// Reads the JEDEC manufacturer/device identification. pub fn read_jedec_id(&mut self) -> Result> { let mut buf = [Opcode::ReadJedecId as u8, 0, 0, 0]; - self.command(&mut buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut buf)?; Ok(Identification { bytes: [buf[1], buf[2], buf[3]], @@ -120,14 +111,14 @@ impl, CS: OutputPin> Flash { /// Reads the status register. pub fn read_status(&mut self) -> Result> { let mut buf = [Opcode::ReadStatus as u8, 0]; - self.command(&mut buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut buf)?; Ok(Status::from_bits_truncate(buf[1])) } fn write_enable(&mut self) -> Result<(), Error> { let mut cmd_buf = [Opcode::WriteEnable as u8]; - self.command(&mut cmd_buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; Ok(()) } @@ -183,7 +174,7 @@ impl, CS: OutputPin> BlockDevice for Flash { (current_addr >> 8) as u8, current_addr as u8, ]; - self.command(&mut cmd_buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; self.wait_done()?; } @@ -217,7 +208,7 @@ impl, CS: OutputPin> BlockDevice for Flash { fn erase_all(&mut self) -> Result<(), Error> { self.write_enable()?; let mut cmd_buf = [Opcode::ChipErase as u8]; - self.command(&mut cmd_buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; self.wait_done()?; Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index 45abc88..1b84fa2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,7 @@ use core::fmt; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; +use crate::Error; pub struct HexSlice(pub T) where @@ -16,3 +19,14 @@ impl> fmt::Debug for HexSlice { f.write_str("]") } } + +pub (crate) fn spi_command(spi: &mut SPI, cs: &mut CS, command: &mut [u8]) -> Result<(), Error> +where + SPI: Transfer, + CS: OutputPin, { + cs.set_low().map_err(Error::Gpio)?; + let spi_result = spi.transfer(command).map_err(Error::Spi); + cs.set_high().map_err(Error::Gpio)?; + spi_result?; + Ok(()) +} diff --git a/src/w25m.rs b/src/w25m.rs index c15f8b0..c4daae3 100644 --- a/src/w25m.rs +++ b/src/w25m.rs @@ -4,6 +4,7 @@ use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; use core::marker::PhantomData; use core::mem; +use crate::utils::spi_command; #[allow(missing_debug_implementations)] pub struct Die0; @@ -68,10 +69,7 @@ impl Flash _ => unreachable!() }; let mut command = [0xC2, 0x01]; - cs.set_low().map_err(Error::Gpio)?; - let spi_result = spi.transfer(&mut command).map_err(Error::Spi); - cs.set_high().map_err(Error::Gpio)?; - spi_result?; + spi_command(&mut spi, &mut cs, &mut command)?; Ok(Flash{ inner: Inner::Die1(DIE1::new(spi, cs)?), @@ -94,11 +92,7 @@ impl Flash }; let mut command = [0xC2, 0x00]; - cs.set_low().map_err(Error::Gpio)?; - let spi_result = spi.transfer(&mut command).map_err(Error::Spi); - cs.set_high().map_err(Error::Gpio)?; - spi_result?; - + spi_command(&mut spi, &mut cs, &mut command)?; Ok(Flash{ inner: Inner::Die0(DIE0::new(spi, cs)?), diff --git a/src/w25n.rs b/src/w25n.rs index 8a14e4e..21fb976 100644 --- a/src/w25n.rs +++ b/src/w25n.rs @@ -1,5 +1,6 @@ use crate::{BlockDevice, Error, Read}; use crate::w25m::Stackable; +use crate::utils::spi_command; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; use bitflags::bitflags; @@ -59,7 +60,12 @@ impl, CS: OutputPin> Flash { /// of the flash chip. Will be driven low when accessing the device. pub fn init(spi: SPI, cs: CS) -> Result> { let mut this = Self { spi, cs }; - let status = this.read_status_3()?; + this.setup()?; + Ok(this) + } + + fn setup(&mut self) -> Result<(), Error> { + let status = self.read_status_3()?; info!("Flash::init: status = {:?}", status); // Here we don't expect any writes to be in progress, and the latch must // also be deasserted. @@ -68,30 +74,22 @@ impl, CS: OutputPin> Flash { } // write to config register 2, set BUF=0 (continious mode) and everything else on reset - this.command(&mut [Opcode::WriteStatus as u8, 0xA0, 0b00000010])?; - this.command(&mut [Opcode::WriteStatus as u8, 0xB0, 0b00010000])?; - Ok(this) - } - - fn command(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - // If the SPI transfer fails, make sure to disable CS anyways - self.cs.set_low().map_err(Error::Gpio)?; - let spi_result = self.spi.transfer(bytes).map_err(Error::Spi); - self.cs.set_high().map_err(Error::Gpio)?; - spi_result?; + spi_command(&mut self.spi, &mut self.cs, &mut [Opcode::WriteStatus as u8, 0xA0, 0b00000010])?; + spi_command(&mut self.spi, &mut self.cs, &mut [Opcode::WriteStatus as u8, 0xB0, 0b00010000])?; Ok(()) } + /// Reads status register 3 pub fn read_status_3(&mut self) -> Result> { let mut buf = [Opcode::ReadStatus as u8, 0xC0, 0]; - self.command(&mut buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut buf)?; Ok(Status3::from_bits_truncate(buf[2])) } fn write_enable(&mut self) -> Result<(), Error> { let mut cmd_buf = [Opcode::WriteEnable as u8]; - self.command(&mut cmd_buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; Ok(()) } @@ -126,7 +124,7 @@ impl, CS: OutputPin> Read for Flash { start_addr as u8 ]; - self.command(&mut cmd_buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; self.wait_done()?; let mut cmd_buf = [ @@ -160,7 +158,7 @@ impl, CS: OutputPin> BlockDevice for Flash { (current_addr >> 8) as u8, current_addr as u8, ]; - self.command(&mut cmd_buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; self.wait_done()?; } @@ -197,7 +195,7 @@ impl, CS: OutputPin> BlockDevice for Flash { (current_addr >> 8) as u8, current_addr as u8, ]; - self.command(&mut cmd_buf)?; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; self.wait_done()?; current_addr += 1; } From a446e71ac16fd2f1a8285f5b3260194570ec94a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20B=C3=B6ving?= Date: Sun, 6 Oct 2019 15:45:23 +0200 Subject: [PATCH 20/20] formatting and a bit of docs --- src/lib.rs | 2 +- src/series25.rs | 5 ++- src/utils.rs | 13 ++++-- src/w25m.rs | 109 ++++++++++++++++++++++++++---------------------- src/w25n.rs | 54 +++++++++--------------- 5 files changed, 92 insertions(+), 91 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d660d5e..e8f9254 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,9 +13,9 @@ mod log; pub mod prelude; pub mod series25; +mod utils; pub mod w25m; pub mod w25n; -mod utils; use core::fmt::{self, Debug}; use embedded_hal::blocking::spi::Transfer; diff --git a/src/series25.rs b/src/series25.rs index c0a2bb6..0b515a3 100644 --- a/src/series25.rs +++ b/src/series25.rs @@ -1,6 +1,9 @@ //! Driver for 25-series SPI Flash and EEPROM chips. -use crate::{utils::{HexSlice, spi_command}, BlockDevice, Error, Read}; +use crate::{ + utils::{spi_command, HexSlice}, + BlockDevice, Error, Read, +}; use bitflags::bitflags; use core::convert::TryInto; use core::fmt; diff --git a/src/utils.rs b/src/utils.rs index 1b84fa2..03f9195 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ +use crate::Error; use core::fmt; use embedded_hal::blocking::spi::Transfer; use embedded_hal::digital::v2::OutputPin; -use crate::Error; pub struct HexSlice(pub T) where @@ -20,10 +20,15 @@ impl> fmt::Debug for HexSlice { } } -pub (crate) fn spi_command(spi: &mut SPI, cs: &mut CS, command: &mut [u8]) -> Result<(), Error> -where +pub(crate) fn spi_command( + spi: &mut SPI, + cs: &mut CS, + command: &mut [u8], +) -> Result<(), Error> +where SPI: Transfer, - CS: OutputPin, { + CS: OutputPin, +{ cs.set_low().map_err(Error::Gpio)?; let spi_result = spi.transfer(command).map_err(Error::Spi); cs.set_high().map_err(Error::Gpio)?; diff --git a/src/w25m.rs b/src/w25m.rs index c4daae3..ca9b298 100644 --- a/src/w25m.rs +++ b/src/w25m.rs @@ -1,10 +1,10 @@ //! Provides an implementation for switching between the two dies stacked upon each other inside the W25M series +use crate::utils::spi_command; use crate::{BlockDevice, Error, Read}; -use embedded_hal::blocking::spi::Transfer; -use embedded_hal::digital::v2::OutputPin; use core::marker::PhantomData; use core::mem; -use crate::utils::spi_command; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; #[allow(missing_debug_implementations)] pub struct Die0; @@ -12,15 +12,23 @@ pub struct Die0; pub struct Die1; /// All dies which are supposed to be supported by the W25M struct have to implement this trait -pub trait Stackable, CS: OutputPin>: BlockDevice + Read + Sized { +pub trait Stackable, CS: OutputPin>: + BlockDevice + Read + Sized +{ fn new(spi: SPI, cs: CS) -> Result>; /// Returns the SPI and chip select objects so they can be used elsewhere fn free(self) -> (SPI, CS); } +/// Driver for W25M SPI Flash chips. +/// +/// # Type Parameters +/// +/// * **`DIE0`**: The type of one of the two dies inside the W25M package +/// * **`DIE0`**: The type of the other of the two dies inside the W25M package +/// * **`DIE`**: A type state, used to indicate which of the two die's is the currently active one #[derive(Debug)] -pub struct Flash -{ +pub struct Flash { inner: Inner, _die: PhantomData, } @@ -32,86 +40,86 @@ enum Inner { Dummy, } -impl Flash -{ +impl Flash { /// Creates a new W25M device - /// - /// At + /// + /// At /// the moment the only way to call this function is sadly /// ``` /// let mut flash: Flash, W25N<_, _>, _> = Flash::init(spi, cs).unwrap(); /// ``` /// TODO: Improve this API, its not very convenient - pub fn init(spi: SPI, cs: CS) -> Result, Error> - where + pub fn init(spi: SPI, cs: CS) -> Result, Error> + where SPI: Transfer, CS: OutputPin, DIE0: Stackable, DIE1: Stackable, { - Ok(Flash{ + Ok(Flash { inner: Inner::Die0(DIE0::new(spi, cs)?), - _die: PhantomData + _die: PhantomData, }) } } -impl Flash - -{ +impl Flash { pub fn switch_die(mut self) -> Result, Error> - where DIE0: Stackable, - DIE1: Stackable, - SPI: Transfer, - CS: OutputPin { + where + DIE0: Stackable, + DIE1: Stackable, + SPI: Transfer, + CS: OutputPin, + { let (mut spi, mut cs) = match mem::replace(&mut self.inner, Inner::Dummy) { Inner::Die0(die) => die.free(), - _ => unreachable!() + _ => unreachable!(), }; let mut command = [0xC2, 0x01]; spi_command(&mut spi, &mut cs, &mut command)?; - Ok(Flash{ + Ok(Flash { inner: Inner::Die1(DIE1::new(spi, cs)?), - _die: PhantomData + _die: PhantomData, }) } } -impl Flash -{ - pub fn switch_die(mut self) -> Result, Error> - where DIE0: Stackable, - DIE1: Stackable, - SPI: Transfer, - CS: OutputPin +impl Flash { + pub fn switch_die(mut self) -> Result, Error> + where + DIE0: Stackable, + DIE1: Stackable, + SPI: Transfer, + CS: OutputPin, { let (mut spi, mut cs) = match mem::replace(&mut self.inner, Inner::Dummy) { Inner::Die1(die) => die.free(), - _ => unreachable!() + _ => unreachable!(), }; let mut command = [0xC2, 0x00]; spi_command(&mut spi, &mut cs, &mut command)?; - Ok(Flash{ + Ok(Flash { inner: Inner::Die0(DIE0::new(spi, cs)?), - _die: PhantomData + _die: PhantomData, }) } } -impl BlockDevice for Flash -where DIE0: Stackable, -DIE1: Stackable, -SPI: Transfer, -CS: OutputPin +impl BlockDevice for Flash +where + DIE0: Stackable, + DIE1: Stackable, + SPI: Transfer, + CS: OutputPin, { fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error> { match &mut self.inner { Inner::Die0(die) => die.erase(addr, amount), Inner::Die1(die) => die.erase(addr, amount), - _ => unreachable!() + _ => unreachable!(), } } @@ -119,7 +127,7 @@ CS: OutputPin match &mut self.inner { Inner::Die0(die) => die.erase_all(), Inner::Die1(die) => die.erase_all(), - _ => unreachable!() + _ => unreachable!(), } } @@ -127,22 +135,23 @@ CS: OutputPin match &mut self.inner { Inner::Die0(die) => die.write_bytes(addr, data), Inner::Die1(die) => die.write_bytes(addr, data), - _ => unreachable!() + _ => unreachable!(), } } } -impl Read for Flash -where DIE0: Stackable, -DIE1: Stackable, -SPI: Transfer, -CS: OutputPin +impl Read for Flash +where + DIE0: Stackable, + DIE1: Stackable, + SPI: Transfer, + CS: OutputPin, { fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error> { - match &mut self.inner { + match &mut self.inner { Inner::Die0(die) => die.read(addr, buf), Inner::Die1(die) => die.read(addr, buf), - _ => unreachable!() + _ => unreachable!(), } } -} \ No newline at end of file +} diff --git a/src/w25n.rs b/src/w25n.rs index 21fb976..4cf0c02 100644 --- a/src/w25n.rs +++ b/src/w25n.rs @@ -1,11 +1,11 @@ -use crate::{BlockDevice, Error, Read}; -use crate::w25m::Stackable; use crate::utils::spi_command; -use embedded_hal::blocking::spi::Transfer; -use embedded_hal::digital::v2::OutputPin; +use crate::w25m::Stackable; +use crate::{BlockDevice, Error, Read}; use bitflags::bitflags; -use core::fmt::Debug; use core::convert::TryInto; +use core::fmt::Debug; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; enum Opcode { // Read one of the 3 8 bit status registers @@ -63,7 +63,7 @@ impl, CS: OutputPin> Flash { this.setup()?; Ok(this) } - + fn setup(&mut self) -> Result<(), Error> { let status = self.read_status_3()?; info!("Flash::init: status = {:?}", status); @@ -74,12 +74,19 @@ impl, CS: OutputPin> Flash { } // write to config register 2, set BUF=0 (continious mode) and everything else on reset - spi_command(&mut self.spi, &mut self.cs, &mut [Opcode::WriteStatus as u8, 0xA0, 0b00000010])?; - spi_command(&mut self.spi, &mut self.cs, &mut [Opcode::WriteStatus as u8, 0xB0, 0b00010000])?; + spi_command( + &mut self.spi, + &mut self.cs, + &mut [Opcode::WriteStatus as u8, 0xA0, 0b00000010], + )?; + spi_command( + &mut self.spi, + &mut self.cs, + &mut [Opcode::WriteStatus as u8, 0xB0, 0b00010000], + )?; Ok(()) } - /// Reads status register 3 pub fn read_status_3(&mut self) -> Result> { let mut buf = [Opcode::ReadStatus as u8, 0xC0, 0]; @@ -100,7 +107,7 @@ impl, CS: OutputPin> Flash { } } -impl, CS: OutputPin> Stackable for Flash +impl, CS: OutputPin> Stackable for Flash where SPI::Error: Debug, CS::Error: Debug, @@ -121,7 +128,7 @@ impl, CS: OutputPin> Read for Flash { Opcode::PageDataRead as u8, 0, // dummy cycles (start_addr >> 8) as u8, - start_addr as u8 + start_addr as u8, ]; spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; @@ -173,11 +180,7 @@ impl, CS: OutputPin> BlockDevice for Flash { for chunk in data.chunks_mut(2048).rev() { chunk.reverse(); self.write_enable()?; - let mut cmd_buf = [ - Opcode::LoadProgramData as u8, - 0, - 0 - ]; + let mut cmd_buf = [Opcode::LoadProgramData as u8, 0, 0]; self.cs.set_low().map_err(Error::Gpio)?; let mut spi_result = self.spi.transfer(&mut cmd_buf); @@ -206,22 +209,3 @@ impl, CS: OutputPin> BlockDevice for Flash { self.erase(0, 1024) } } - - - - - - - - - - - - - - - - - - -