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 b7d2fb9..08f7163 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ mod log; pub mod prelude; pub mod series25; mod utils; +pub mod w25m; +pub mod w25n; use core::fmt::{self, Debug}; use embedded_hal::blocking::spi::Transfer; @@ -62,34 +64,36 @@ 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> { - /// Erases sectors from the memory chip. +pub trait BlockDevice, CS: OutputPin> { + /// Erases the smallest erasable 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, - /// the lower bits can be ignored in order to make it fit. - fn erase_sectors(&mut self, addr: Addr, amount: usize) -> Result<(), Error>; + /// * `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(&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. /// * `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..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, BlockDevice, Error, Read}; +use crate::{ + utils::{spi_command, HexSlice}, + BlockDevice, Error, Read, +}; use bitflags::bitflags; use core::convert::TryInto; use core::fmt; @@ -98,19 +101,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 +114,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(()) } @@ -138,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 @@ -171,8 +165,8 @@ impl, CS: OutputPin> Read for Flash { } } -impl, CS: OutputPin> BlockDevice for Flash { - fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error> { +impl, CS: OutputPin> BlockDevice for Flash { + fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error> { for c in 0..amount { self.write_enable()?; @@ -183,7 +177,7 @@ impl, CS: OutputPin> BlockDevice for Flash> 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 +211,7 @@ impl, CS: OutputPin> BlockDevice for Flash 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..03f9195 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,7 @@ +use crate::Error; use core::fmt; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; pub struct HexSlice(pub T) where @@ -16,3 +19,19 @@ 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 new file mode 100644 index 0000000..ca9b298 --- /dev/null +++ b/src/w25m.rs @@ -0,0 +1,157 @@ +//! 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 core::marker::PhantomData; +use core::mem; +use embedded_hal::blocking::spi::Transfer; +use embedded_hal::digital::v2::OutputPin; + +#[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 +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 { + inner: Inner, + _die: PhantomData, +} + +#[derive(Debug)] +enum Inner { + Die0(DIE0), + Die1(DIE1), + Dummy, +} + +impl Flash { + /// 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)?), + _die: PhantomData, + }) + } +} + +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::Die0(die) => die.free(), + _ => unreachable!(), + }; + let mut command = [0xC2, 0x01]; + spi_command(&mut spi, &mut cs, &mut command)?; + + Ok(Flash { + inner: Inner::Die1(DIE1::new(spi, cs)?), + _die: PhantomData, + }) + } +} + +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!(), + }; + + let mut command = [0xC2, 0x00]; + spi_command(&mut spi, &mut cs, &mut command)?; + + Ok(Flash { + inner: Inner::Die0(DIE0::new(spi, cs)?), + _die: PhantomData, + }) + } +} + +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!(), + } + } + + 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 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 { + Inner::Die0(die) => die.read(addr, buf), + Inner::Die1(die) => die.read(addr, buf), + _ => unreachable!(), + } + } +} diff --git a/src/w25n.rs b/src/w25n.rs new file mode 100644 index 0000000..4cf0c02 --- /dev/null +++ b/src/w25n.rs @@ -0,0 +1,211 @@ +use crate::utils::spi_command; +use crate::w25m::Stackable; +use crate::{BlockDevice, Error, Read}; +use bitflags::bitflags; +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 + 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 + LoadProgramData = 0x02, +} + +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 }; + 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. + 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 + 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]; + 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]; + spi_command(&mut self.spi, &mut self.cs, &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, + ]; + + spi_command(&mut self.spi, &mut self.cs, &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 + 64 * 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, + ]; + spi_command(&mut self.spi, &mut self.cs, &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; + assert!(data.len() % 2048 == 0); + data.reverse(); + for chunk in data.chunks_mut(2048).rev() { + chunk.reverse(); + self.write_enable()?; + 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); + 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, + ]; + spi_command(&mut self.spi, &mut self.cs, &mut cmd_buf)?; + self.wait_done()?; + current_addr += 1; + } + Ok(()) + } + + fn erase_all(&mut self) -> Result<(), Error> { + self.erase(0, 1024) + } +}