Skip to content
This repository was archived by the owner on Oct 18, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
06d9b55
adding the FlashWrite, EepromWrite and Read traits
hargoniX Aug 18, 2019
08197ad
reworking the new traits
hargoniX Aug 19, 2019
cbd5159
adding erase_all and write_bytes implementations
hargoniX Aug 20, 2019
233effe
finalizing the trait impl on series25 chips, adding a prelude file wh…
hargoniX Aug 21, 2019
e28690d
finalizing the trait impl on series25 chips, adding a prelude file wh…
hargoniX Aug 21, 2019
e07ac1d
Merge branch 'master' of github.com:hargoniX/spi-memory
hargoniX Aug 21, 2019
ceda1d6
addressing review points
hargoniX Aug 21, 2019
4bfdf97
Update comment on erase times
hargoniX Aug 21, 2019
a3422c2
Update src/lib.rs
hargoniX Aug 21, 2019
7a4755a
more review points addressed
hargoniX Aug 22, 2019
7e41934
Merge branch 'master' of github.com:hargoniX/spi-memory
hargoniX Aug 22, 2019
8a518a0
commented the wrong addr
hargoniX Aug 22, 2019
e82af20
Nit 1
hargoniX Aug 22, 2019
a2e816e
Nit 2
hargoniX Aug 22, 2019
fd9b3bc
adding an (undocumented) implementation of the w25m concept
hargoniX Aug 25, 2019
fa2488a
merged conflicts
hargoniX Aug 25, 2019
a6d2db2
adding BlockDevice and Read impls for w25m
hargoniX Aug 25, 2019
816d9f1
read, write and reset working roughly for w25n, more testing to be d…
hargoniX Aug 31, 2019
8127c02
working API for the w25n series, everything tested
hargoniX Sep 29, 2019
33834ca
adding support for spi stack chips
hargoniX Oct 3, 2019
4e3b729
Resolve merge conflicts
hargoniX Oct 3, 2019
e72f625
Update src/lib.rs
hargoniX Oct 6, 2019
eebfde2
increasing code reusage by only having one central spi_command function
hargoniX Oct 6, 2019
a446e71
formatting and a bit of docs
hargoniX Oct 6, 2019
1474850
Merge branch 'master' of github.com:hargoniX/spi-memory
hargoniX Oct 6, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
24 changes: 14 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,34 +64,36 @@ where
}

/// A trait for reading operations from a memory chip.
pub trait Read<Addr, SPI: Transfer<u8>, CS: OutputPin> {
pub trait Read<SPI: Transfer<u8>, 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<SPI, CS>>;
fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error<SPI, CS>>;
}

/// A trait for writing and erasing operations on a memory chip.
pub trait BlockDevice<Addr, SPI: Transfer<u8>, CS: OutputPin> {
/// Erases sectors from the memory chip.
pub trait BlockDevice<SPI: Transfer<u8>, 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<SPI, CS>>;
/// * `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<SPI, CS>>;

/// 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<SPI, CS>>;
/// 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<SPI, CS>>;
fn write_bytes(&mut self, addr: u32, data: &mut [u8]) -> Result<(), Error<SPI, CS>>;
}
30 changes: 12 additions & 18 deletions src/series25.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -98,19 +101,10 @@ impl<SPI: Transfer<u8>, CS: OutputPin> Flash<SPI, CS> {
Ok(this)
}

fn command(&mut self, bytes: &mut [u8]) -> Result<(), Error<SPI, CS>> {
// 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<Identification, Error<SPI, CS>> {
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]],
Expand All @@ -120,14 +114,14 @@ impl<SPI: Transfer<u8>, CS: OutputPin> Flash<SPI, CS> {
/// Reads the status register.
pub fn read_status(&mut self) -> Result<Status, Error<SPI, CS>> {
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<SPI, CS>> {
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(())
}

Expand All @@ -138,7 +132,7 @@ impl<SPI: Transfer<u8>, CS: OutputPin> Flash<SPI, CS> {
}
}

impl<SPI: Transfer<u8>, CS: OutputPin> Read<u32, SPI, CS> for Flash<SPI, CS> {
impl<SPI: Transfer<u8>, CS: OutputPin> Read<SPI, CS> for Flash<SPI, CS> {
/// Reads flash contents into `buf`, starting at `addr`.
///
/// Note that `addr` is not fully decoded: Flash chips will typically only
Expand Down Expand Up @@ -171,8 +165,8 @@ impl<SPI: Transfer<u8>, CS: OutputPin> Read<u32, SPI, CS> for Flash<SPI, CS> {
}
}

impl<SPI: Transfer<u8>, CS: OutputPin> BlockDevice<u32, SPI, CS> for Flash<SPI, CS> {
fn erase_sectors(&mut self, addr: u32, amount: usize) -> Result<(), Error<SPI, CS>> {
impl<SPI: Transfer<u8>, CS: OutputPin> BlockDevice<SPI, CS> for Flash<SPI, CS> {
fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error<SPI, CS>> {
for c in 0..amount {
self.write_enable()?;

Expand All @@ -183,7 +177,7 @@ impl<SPI: Transfer<u8>, CS: OutputPin> BlockDevice<u32, SPI, CS> for Flash<SPI,
(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()?;
}

Expand Down Expand Up @@ -217,7 +211,7 @@ impl<SPI: Transfer<u8>, CS: OutputPin> BlockDevice<u32, SPI, CS> for Flash<SPI,
fn erase_all(&mut self) -> Result<(), Error<SPI, CS>> {
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(())
}
Expand Down
19 changes: 19 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<T>(pub T)
where
Expand All @@ -16,3 +19,19 @@ impl<T: AsRef<[u8]>> fmt::Debug for HexSlice<T> {
f.write_str("]")
}
}

pub(crate) fn spi_command<SPI, CS>(
spi: &mut SPI,
cs: &mut CS,
command: &mut [u8],
) -> Result<(), Error<SPI, CS>>
where
SPI: Transfer<u8>,
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(())
}
157 changes: 157 additions & 0 deletions src/w25m.rs
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can just #[derive(Debug)], there shouldn't be any harm in that

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also make them enums instead of structs since they're just used as type markers


/// All dies which are supposed to be supported by the W25M struct have to implement this trait
pub trait Stackable<SPI: Transfer<u8>, CS: OutputPin>:
BlockDevice<SPI, CS> + Read<SPI, CS> + Sized
{
fn new(spi: SPI, cs: CS) -> Result<Self, Error<SPI, CS>>;
/// 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<DIE0, DIE1, DIE> {
inner: Inner<DIE0, DIE1>,
_die: PhantomData<DIE>,
}

#[derive(Debug)]
enum Inner<DIE0, DIE1> {
Die0(DIE0),
Die1(DIE1),
Dummy,
}

impl<DIE0, DIE1> Flash<DIE0, DIE1, Die0> {
/// Creates a new W25M device
///
/// At
/// the moment the only way to call this function is sadly
/// ```
/// let mut flash: Flash<W25N<_, _>, W25N<_, _>, _> = Flash::init(spi, cs).unwrap();
/// ```
/// TODO: Improve this API, its not very convenient
pub fn init<SPI, CS>(spi: SPI, cs: CS) -> Result<Flash<DIE0, DIE1, Die0>, Error<SPI, CS>>
where
SPI: Transfer<u8>,
CS: OutputPin,
DIE0: Stackable<SPI, CS>,
DIE1: Stackable<SPI, CS>,
{
Ok(Flash {
inner: Inner::Die0(DIE0::new(spi, cs)?),
_die: PhantomData,
})
}
}

impl<DIE0, DIE1> Flash<DIE0, DIE1, Die0> {
pub fn switch_die<SPI, CS>(mut self) -> Result<Flash<DIE0, DIE1, Die1>, Error<SPI, CS>>
where
DIE0: Stackable<SPI, CS>,
DIE1: Stackable<SPI, CS>,
SPI: Transfer<u8>,
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<DIE0, DIE1> Flash<DIE0, DIE1, Die1> {
pub fn switch_die<SPI, CS>(mut self) -> Result<Flash<DIE0, DIE1, Die0>, Error<SPI, CS>>
where
DIE0: Stackable<SPI, CS>,
DIE1: Stackable<SPI, CS>,
SPI: Transfer<u8>,
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<DIE0, DIE1, SPI, CS, DIE> BlockDevice<SPI, CS> for Flash<DIE0, DIE1, DIE>
where
DIE0: Stackable<SPI, CS>,
DIE1: Stackable<SPI, CS>,
SPI: Transfer<u8>,
CS: OutputPin,
{
fn erase(&mut self, addr: u32, amount: usize) -> Result<(), Error<SPI, CS>> {
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<SPI, CS>> {
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<SPI, CS>> {
match &mut self.inner {
Inner::Die0(die) => die.write_bytes(addr, data),
Inner::Die1(die) => die.write_bytes(addr, data),
_ => unreachable!(),
}
}
}

impl<DIE0, DIE1, SPI, CS, DIE> Read<SPI, CS> for Flash<DIE0, DIE1, DIE>
where
DIE0: Stackable<SPI, CS>,
DIE1: Stackable<SPI, CS>,
SPI: Transfer<u8>,
CS: OutputPin,
{
fn read(&mut self, addr: u32, buf: &mut [u8]) -> Result<(), Error<SPI, CS>> {
match &mut self.inner {
Inner::Die0(die) => die.read(addr, buf),
Inner::Die1(die) => die.read(addr, buf),
_ => unreachable!(),
}
}
}
Loading