Skip to content

Commit

Permalink
Merge #429
Browse files Browse the repository at this point in the history
429: Implement embedded-storage traits r=burrbull a=chemicstry

This PR implements [embedded-storage](https://github.com/rust-embedded-community/embedded-storage) traits for flash.

One major headache with F4 series is dual-bank flash and non-uniform sector sizes, which required quite a bit of code to abstract away. I went through all of the F4 reference manuals and believe that `flash_sectors(...)` function should be correct for all variants. AFAIK, there is no single source about flash layout for all the chips. Moreover, ST [lists](https://www.st.com/en/microcontrollers-microprocessors/stm32f4-series.html#products) F429 and F469 with 512 KB of flash as dual-bank, but there is no information about it in the reference manual. I believe this could be an error in the website and only 1 MB chips have dual-bank capabilities.

The PAC crate is also missing `DB1M` field for some of the dual-bank capable chips, so for now I hardcoded the bit position in `OPTCR` register.

The writable `NorFlash` trait was implemented for the largest sector size of 128 KB, because `embedded-storage` does not intend to supprot non-uniform sectors (see [comment](rust-embedded-community/embedded-storage#23 (comment))). Smaller sectors are erased together in the larger 128 KB group. There was a suggestion to add different types for the smaller sector ranges, but I'm not sure if that is useful?

Co-authored-by: chemicstry <chemicstry@gmail.com>
  • Loading branch information
bors[bot] and chemicstry committed Feb 3, 2022
2 parents 41d8dd9 + c519799 commit 6613316
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `Pwm` struct with `split` method and implementation of embedded-hal::Pwm (similar to f1xx-hal) [#425]
- VSCode setting file
- Add CAN1 PB8/PB9 and SPI3 MOSI PC1 pin mappings for F446 [#421]
- Add embedded-storage traits for flash [#429]

[#421]: https://github.com/stm32-rs/stm32f4xx-hal/pull/421
[#422]: https://github.com/stm32-rs/stm32f4xx-hal/pull/422
[#423]: https://github.com/stm32-rs/stm32f4xx-hal/pull/423
[#428]: https://github.com/stm32-rs/stm32f4xx-hal/pull/428
[#425]: https://github.com/stm32-rs/stm32f4xx-hal/pull/425
[#429]: https://github.com/stm32-rs/stm32f4xx-hal/pull/429

### Changed

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ fugit = "0.3.3"
fugit-timer = "0.1.3"
rtic-monotonic = { version = "1.0", optional = true }
bitflags = "1.3.2"
embedded-storage = "0.2"

[dependencies.stm32_i2s_v12x]
version = "0.2.0"
Expand Down
251 changes: 251 additions & 0 deletions src/flash.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use embedded_storage::nor_flash::{MultiwriteNorFlash, NorFlash, ReadNorFlash};

use crate::pac::FLASH;
use crate::signature::FlashSize;
use core::{ptr, slice};
Expand Down Expand Up @@ -46,6 +48,10 @@ pub trait FlashExt {
/// Unlock flash for erasing/programming until this method's
/// result is dropped
fn unlocked(&mut self) -> UnlockedFlash;
// Returns true if flash is in dual bank organization
fn dual_bank(&self) -> bool;
/// Returns flash memory sector of a given offset. Returns none if offset is out of range.
fn sector(&self, offset: usize) -> Option<FlashSector>;
}

impl FlashExt for FLASH {
Expand All @@ -61,11 +67,114 @@ impl FlashExt for FLASH {
unlock(self);
UnlockedFlash { flash: self }
}

fn dual_bank(&self) -> bool {
match self.len() / 1024 {
// 1 MB devices depend on configuration
1024 => {
if cfg!(any(
feature = "stm32f427",
feature = "stm32f429",
feature = "stm32f437",
feature = "stm32f439",
feature = "stm32f469",
feature = "stm32f479",
)) {
// DB1M bit is not present in all SVDs
// self.optcr.read().db1m().bit_is_set()
self.optcr.read().bits() & (1 << 30) != 0
} else {
false
}
}
// 2 MB devices are always dual bank
2048 => true,
// All other devices are single bank
_ => false,
}
}

fn sector(&self, offset: usize) -> Option<FlashSector> {
flash_sectors(self.len(), self.dual_bank()).find(|s| s.contains(offset))
}
}

const PSIZE_X8: u8 = 0b00;

/// Read-only flash
///
/// # Examples
///
/// ```
/// use stm32f4xx_hal::pac::Peripherals;
/// use stm32f4xx_hal::flash::LockedFlash;
/// use embedded_storage::nor_flash::ReadNorFlash;
///
/// let dp = Peripherals::take().unwrap();
/// let mut flash = LockedFlash::new(dp.FLASH);
/// println!("Flash capacity: {}", ReadNorFlash::capacity(&flash));
///
/// let mut buf = [0u8; 64];
/// ReadNorFlash::read(&mut flash, 0x0, &mut buf).unwrap();
/// println!("First 64 bytes of flash memory: {:?}", buf);
/// ```
pub struct LockedFlash {
flash: FLASH,
}

impl LockedFlash {
pub fn new(flash: FLASH) -> Self {
Self { flash }
}
}

impl FlashExt for LockedFlash {
fn address(&self) -> usize {
self.flash.address()
}

fn len(&self) -> usize {
self.flash.len()
}

fn unlocked(&mut self) -> UnlockedFlash {
self.flash.unlocked()
}

fn dual_bank(&self) -> bool {
self.flash.dual_bank()
}

fn sector(&self, offset: usize) -> Option<FlashSector> {
self.flash.sector(offset)
}
}

/// Result of `FlashExt::unlocked()`
///
/// # Examples
///
/// ```
/// use stm32f4xx_hal::pac::Peripherals;
/// use stm32f4xx_hal::flash::{FlashExt, LockedFlash, UnlockedFlash};
/// use embedded_storage::nor_flash::NorFlash;
///
/// let dp = Peripherals::take().unwrap();
/// let mut flash = LockedFlash::new(dp.FLASH);
///
/// // Unlock flash for writing
/// let mut unlocked_flash = flash.unlocked();
///
/// // Erase the second 128 KB sector.
/// NorFlash::erase(&mut unlocked_flash, 128 * 1024, 256 * 1024).unwrap();
///
/// // Write some data at the start of the second 128 KB sector.
/// let buf = [0u8; 64];
/// NorFlash::write(&mut unlocked_flash, 128 * 1024, &buf).unwrap();
///
/// // Lock flash by dropping
/// drop(unlocked_flash);
/// ```
pub struct UnlockedFlash<'a> {
flash: &'a mut FLASH,
}
Expand Down Expand Up @@ -166,3 +275,145 @@ fn unlock(flash: &FLASH) {
fn lock(flash: &FLASH) {
flash.cr.modify(|_, w| w.lock().set_bit());
}

/// Flash memory sector
pub struct FlashSector {
/// Sector number
pub number: u8,
/// Offset from base memory address
pub offset: usize,
/// Sector size in bytes
pub size: usize,
}

impl FlashSector {
/// Returns true if given offset belongs to this sector
pub fn contains(&self, offset: usize) -> bool {
self.offset <= offset && offset < self.offset + self.size
}
}

/// Iterator of flash memory sectors in a single bank.
/// Yields a size sequence of [16, 16, 16, 64, 128, 128, ..]
pub struct FlashSectorIterator {
index: u8,
start_sector: u8,
start_offset: usize,
end_offset: usize,
}

impl FlashSectorIterator {
fn new(start_sector: u8, start_offset: usize, end_offset: usize) -> Self {
Self {
index: 0,
start_sector,
start_offset,
end_offset,
}
}
}

impl Iterator for FlashSectorIterator {
type Item = FlashSector;

fn next(&mut self) -> Option<Self::Item> {
if self.start_offset >= self.end_offset {
None
} else {
// First 4 sectors are 16 KB, then one 64 KB and the rest are 128 KB
let size = match self.index {
0..=3 => 16 * 1024,
4 => 64 * 1024,
_ => 128 * 1024,
};

let sector = FlashSector {
number: self.start_sector + self.index,
offset: self.start_offset,
size,
};

self.index += 1;
self.start_offset += size;

Some(sector)
}
}
}

/// Returns iterator of flash memory sectors for single and dual bank flash.
/// Sectors are returned in continuous memory order, while sector numbers can have spaces between banks.
pub fn flash_sectors(flash_size: usize, dual_bank: bool) -> impl Iterator<Item = FlashSector> {
if dual_bank {
// Second memory bank always starts from sector 12
FlashSectorIterator::new(0, 0, flash_size / 2).chain(FlashSectorIterator::new(
12,
flash_size / 2,
flash_size,
))
} else {
// Chain an empty iterator to match types
FlashSectorIterator::new(0, 0, flash_size).chain(FlashSectorIterator::new(0, 0, 0))
}
}

impl ReadNorFlash for LockedFlash {
type Error = Error;

const READ_SIZE: usize = 1;

fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let offset = offset as usize;
Ok(bytes.copy_from_slice(&self.flash.read()[offset..offset + bytes.len()]))
}

fn capacity(&self) -> usize {
self.flash.len()
}
}

impl<'a> ReadNorFlash for UnlockedFlash<'a> {
type Error = Error;

const READ_SIZE: usize = 1;

fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
let offset = offset as usize;
Ok(bytes.copy_from_slice(&self.flash.read()[offset..offset + bytes.len()]))
}

fn capacity(&self) -> usize {
self.flash.len()
}
}

impl<'a> NorFlash for UnlockedFlash<'a> {
const WRITE_SIZE: usize = 1;

// Use largest sector size of 128 KB. All smaller sectors will be erased together.
const ERASE_SIZE: usize = 128 * 1024;

fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
let mut current = from as usize;

for sector in flash_sectors(self.flash.len(), self.flash.dual_bank()) {
if sector.contains(current) {
UnlockedFlash::erase(self, sector.number)?;
current += sector.size;
}

if current >= to as usize {
break;
}
}

Ok(())
}

fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.program(offset as usize, bytes.iter())
}
}

// STM32F4 supports multiple writes
impl<'a> MultiwriteNorFlash for UnlockedFlash<'a> {}

0 comments on commit 6613316

Please sign in to comment.