From 59da6d78812c5c05457a032180957a5f7d2429b0 Mon Sep 17 00:00:00 2001 From: Janek Date: Fri, 17 Nov 2023 11:22:51 +0100 Subject: [PATCH 01/11] Implement signature module Can be used to read the flash size and device UID --- src/lib.rs | 2 ++ src/signature.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/signature.rs diff --git a/src/lib.rs b/src/lib.rs index 8370ed8..0986abd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,8 @@ pub mod spi; pub mod time; #[cfg(feature = "device-selected")] pub mod timers; +#[cfg(feature = "device-selected")] +pub mod signature; #[cfg(any( feature = "stm32f031", feature = "stm32f051", diff --git a/src/signature.rs b/src/signature.rs new file mode 100644 index 0000000..b129e84 --- /dev/null +++ b/src/signature.rs @@ -0,0 +1,80 @@ +//! Device electronic signature +//! +//! (stored in flash memory) + +use core::str::from_utf8_unchecked; + +/// This is the test voltage in millivolts of the calibration done at the factory +pub const VDDA_CALIB: u32 = 3300; + +macro_rules! define_ptr_type { + ($name: ident, $ptr: expr) => { + impl $name { + fn ptr() -> *const Self { + $ptr as *const _ + } + + /// Returns a wrapped reference to the value in flash memory + pub fn get() -> &'static Self { + unsafe { &*Self::ptr() } + } + } + }; +} + +// f030 and f070 don't have a UID in ROM +#[cfg(not(any(feature = "stm32f030", feature = "stm32f070")))] +#[derive(Hash, Debug)] +#[repr(C)] +pub struct Uid { + x: u16, + y: u16, + waf_lot: [u8; 8], +} +#[cfg(not(any(feature = "stm32f030", feature = "stm32f070")))] +define_ptr_type!(Uid, 0x1FFF_F7AC); + +/// Device UID from ROM. See the [reference manual](https://www.st.com/content/ccc/resource/technical/document/reference_manual/c2/f8/8a/f2/18/e6/43/96/DM00031936.pdf/files/DM00031936.pdf/jcr:content/translations/en.DM00031936.pdf#%5B%7B%22num%22%3A1575%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C67%2C755%2Cnull%5D) for more info. +#[cfg(not(any(feature = "stm32f030", feature = "stm32f070")))] +impl Uid { + /// X coordinate on wafer + pub fn x(&self) -> u16 { + self.x + } + + /// Y coordinate on wafer + pub fn y(&self) -> u16 { + self.y + } + + /// Wafer number + pub fn waf_num(&self) -> u8 { + self.waf_lot[0] + } + + /// Lot number + pub fn lot_num(&self) -> &str { + unsafe { from_utf8_unchecked(&self.waf_lot[1..]) } + } +} + +/// Size of integrated flash +#[derive(Debug)] +#[repr(C)] +pub struct FlashSize(u16); +#[cfg(not(any(feature = "stm32f030", feature = "stm32f070")))] +define_ptr_type!(FlashSize, 0x1FFF_F7CC); +#[cfg(any(feature = "stm32f030", feature = "stm32f070"))] +define_ptr_type!(FlashSize, 0x1FFF_0000); + +impl FlashSize { + /// Read flash size in kilobytes + pub fn kilo_bytes(&self) -> u16 { + self.0 + } + + /// Read flash size in bytes + pub fn bytes(&self) -> usize { + usize::from(self.kilo_bytes()) * 1024 + } +} From 7a6c0bc3df57ceae4374d6b6bcfcc559beac5ddd Mon Sep 17 00:00:00 2001 From: Janek Date: Wed, 22 Nov 2023 16:31:59 +0100 Subject: [PATCH 02/11] WIP: Initial implementation of the flash module - Interface is mostly copied and adapted from the F4-HAL - Hardfaults while programming flash --- Cargo.toml | 1 + src/flash.rs | 395 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 3 files changed, 398 insertions(+) create mode 100644 src/flash.rs diff --git a/Cargo.toml b/Cargo.toml index d72313a..393a9c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ nb = "1" void = { version = "1.0", default-features = false } stm32-usbd = { version = "0.6", optional = true } bxcan = "0.6.0" +embedded-storage = "0.3.0" [dev-dependencies] cortex-m-rt = "0.7" diff --git a/src/flash.rs b/src/flash.rs new file mode 100644 index 0000000..574c161 --- /dev/null +++ b/src/flash.rs @@ -0,0 +1,395 @@ +use core::convert::TryInto; +use core::{ptr, slice}; + +use embedded_storage::nor_flash::{ + ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, +}; + +use crate::pac::FLASH; +use crate::signature::FlashSize; + +/// Flash erase/program error +#[derive(Debug, Clone, Copy)] +pub enum Error { + Programming, + WriteProtection, +} + +impl Error { + fn read(flash: &FLASH) -> Option { + let sr = flash.sr.read(); + if sr.pgerr().bit() { + Some(Error::Programming) + } else if sr.wrprt().bit() { + Some(Error::WriteProtection) + } else { + None + } + } +} + +/// Flash methods implemented for `pac::FLASH` +#[allow(clippy::len_without_is_empty)] +pub trait FlashExt { + /// Memory-mapped address + fn address(&self) -> usize; + /// Size in bytes + fn len(&self) -> usize; + /// Returns a read-only view of flash memory + fn read(&self) -> &[u8] { + let ptr = self.address() as *const _; + unsafe { slice::from_raw_parts(ptr, self.len()) } + } + /// Unlock flash for erasing/programming until this method's + /// result is dropped + fn unlocked(&mut self) -> UnlockedFlash; + /// Returns flash memory sector of a given offset. Returns none if offset is out of range. + fn sector(&self, offset: usize) -> Option; +} + +impl FlashExt for FLASH { + fn address(&self) -> usize { + 0x0800_0000 + } + + fn len(&self) -> usize { + FlashSize::get().bytes() + } + + fn unlocked(&mut self) -> UnlockedFlash { + unlock(self); + UnlockedFlash { flash: self } + } + + fn sector(&self, offset: usize) -> Option { + flash_sectors(self.len()).find(|s| s.contains(offset)) + } +} + +/// Read-only flash +/// +/// # Examples +/// +/// ``` +/// use stm32f0xx_hal::pac::Peripherals; +/// use stm32f0xx_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 sector(&self, offset: usize) -> Option { + self.flash.sector(offset) + } +} + +/// Result of `FlashExt::unlocked()` +/// +/// # Examples +/// +/// ``` +/// use stm32f0xx_hal::pac::Peripherals; +/// use stm32f0xx_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, +} + +/// Automatically lock flash erase/program when leaving scope +impl Drop for UnlockedFlash<'_> { + fn drop(&mut self) { + lock(self.flash); + } +} + +impl UnlockedFlash<'_> { + /// Erase a flash page at offset + /// + /// Refer to the reference manual to see which sector corresponds + /// to which memory address. + pub fn erase(&mut self, offset: u32) -> Result<(), Error> { + // Write address into the AR register + self.flash + .ar + .write(|w| w.far().bits(self.flash.address() as u32 + offset)); + #[rustfmt::skip] + self.flash.cr.modify(|_, w| + w + // page erase + .per().set_bit() + // start + .strt().set_bit() + ); + self.wait_ready(); + // Clear PER bit after operation is finished + self.flash.cr.modify(|_, w| w.per().clear_bit()); + self.ok() + } + + /// Program bytes with offset into flash memory + pub fn program<'a, I>(&mut self, mut offset: usize, mut bytes: I) -> Result<(), Error> + where + I: Iterator, + { + if self.flash.cr.read().lock().bit_is_set() { + return Err(Error::Programming); + } + let ptr = self.flash.address() as *mut u8; + let mut bytes_written = 1; + while bytes_written > 0 { + bytes_written = 0; + let amount = 2 - (offset % 2); + + #[allow(unused_unsafe)] + self.flash.cr.modify(|_, w| unsafe { + // programming + w.pg().set_bit() + }); + for _ in 0..amount { + match bytes.next() { + Some(byte) => { + unsafe { + ptr::write_volatile(ptr.add(offset), *byte); + } + offset += 1; + bytes_written += 1; + } + None => break, + } + } + self.wait_ready(); + self.ok()?; + } + self.flash.cr.modify(|_, w| w.pg().clear_bit()); + + Ok(()) + } + + fn ok(&self) -> Result<(), Error> { + Error::read(self.flash).map(Err).unwrap_or(Ok(())) + } + + fn wait_ready(&self) { + while self.flash.sr.read().bsy().bit() {} + } +} + +const UNLOCK_KEY1: u32 = 0x45670123; +const UNLOCK_KEY2: u32 = 0xCDEF89AB; + +#[allow(unused_unsafe)] +fn unlock(flash: &FLASH) { + flash.keyr.write(|w| unsafe { w.fkeyr().bits(UNLOCK_KEY1) }); + flash.keyr.write(|w| unsafe { w.fkeyr().bits(UNLOCK_KEY2) }); + assert!(!flash.cr.read().lock().bit()) +} + +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 { + if self.start_offset >= self.end_offset { + None + } else { + // F03x, F04x and F05x sectors are 1K long + #[cfg(any( + feature = "stm32f030", + feature = "stm32f031", + feature = "stm32f038", + feature = "stm32f042", + feature = "stm32f048", + feature = "stm32f051", + feature = "stm32f058", + ))] + let size = 1024; + + // F07x and F09x sectors are 2K long + #[cfg(any( + feature = "stm32f070", + feature = "stm32f071", + feature = "stm32f072", + feature = "stm32f078", + feature = "stm32f091", + feature = "stm32f098", + ))] + let size = 2048; + + 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) -> impl Iterator { + // Chain an empty iterator to match types + FlashSectorIterator::new(0, 0, flash_size).chain(FlashSectorIterator::new(0, 0, 0)) +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + NorFlashErrorKind::Other + } +} + +impl ErrorType for LockedFlash { + type Error = Error; +} + +impl ErrorType for UnlockedFlash<'_> { + type Error = Error; +} + +impl ReadNorFlash for LockedFlash { + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let offset = offset as usize; + bytes.copy_from_slice(&self.flash.read()[offset..offset + bytes.len()]); + Ok(()) + } + + fn capacity(&self) -> usize { + self.flash.len() + } +} + +impl<'a> ReadNorFlash for UnlockedFlash<'a> { + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let offset = offset as usize; + bytes.copy_from_slice(&self.flash.read()[offset..offset + bytes.len()]); + Ok(()) + } + + 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()) { + if sector.contains(current) { + UnlockedFlash::erase(self, current as u32)?; + 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> {} diff --git a/src/lib.rs b/src/lib.rs index 0986abd..a78005d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,8 @@ pub mod time; #[cfg(feature = "device-selected")] pub mod timers; #[cfg(feature = "device-selected")] +pub mod flash; +#[cfg(feature = "device-selected")] pub mod signature; #[cfg(any( feature = "stm32f031", From 6a9d68e1e594b25e7638583b3abe937aa3f291e1 Mon Sep 17 00:00:00 2001 From: Janek Date: Wed, 22 Nov 2023 16:32:52 +0100 Subject: [PATCH 03/11] Add flash read/write example --- examples/flash_read_write.rs | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/flash_read_write.rs diff --git a/examples/flash_read_write.rs b/examples/flash_read_write.rs new file mode 100644 index 0000000..64fea69 --- /dev/null +++ b/examples/flash_read_write.rs @@ -0,0 +1,51 @@ +#![no_main] +#![no_std] + +use cortex_m::Peripherals; +use panic_halt as _; +use stm32f0xx_hal as hal; + +use crate::hal::{ + pac::{self, flash}, + prelude::*, +}; + +use cortex_m_rt::entry; +use embedded_storage::nor_flash::NorFlash; +use stm32f0xx_hal::flash::FlashExt; +use stm32f0xx_hal::prelude::_stm32f0xx_hal_rcc_RccExt; + +#[entry] +fn main() -> ! { + if let Some(mut p) = pac::Peripherals::take() { + let _ = p.RCC.configure().freeze(&mut p.FLASH); + + const OFFSET_START: u32 = 32 * 1024; + const OFFSET_END: u32 = 33 * 1024; + // Unlock flash before writing + let mut unlocked_flash = p.FLASH.unlocked(); + + // All examples use the first 16K of flash for the program so we use the first page after that + NorFlash::erase(&mut unlocked_flash, OFFSET_START, OFFSET_END).unwrap(); + + // Write some data to the start of that page + let write_data = [0xC0_u8, 0xFF_u8, 0xEE_u8, 0x00_u8]; + NorFlash::write(&mut unlocked_flash, OFFSET_START, &write_data).unwrap(); + + // Lock flash by dropping it + drop(unlocked_flash); + + // Read back the slice from flash + let read_data = unsafe { + core::slice::from_raw_parts( + (p.FLASH.address() + 16 * 1024) as *const u8, + write_data.len(), + ) + }; + + assert_eq!(write_data, *read_data); + } + loop { + continue; + } +} From 85afc7d716a98d262f00819b27f993640c1da93c Mon Sep 17 00:00:00 2001 From: Janek Date: Wed, 22 Nov 2023 17:07:14 +0100 Subject: [PATCH 04/11] Add flash read/write example --- examples/flash_read_write.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/flash_read_write.rs b/examples/flash_read_write.rs index 64fea69..661b0ae 100644 --- a/examples/flash_read_write.rs +++ b/examples/flash_read_write.rs @@ -20,12 +20,12 @@ fn main() -> ! { if let Some(mut p) = pac::Peripherals::take() { let _ = p.RCC.configure().freeze(&mut p.FLASH); - const OFFSET_START: u32 = 32 * 1024; - const OFFSET_END: u32 = 33 * 1024; + // All examples use the first 16K of flash for the program so we use the first page after that + const OFFSET_START: u32 = 16 * 1024; + const OFFSET_END: u32 = OFFSET_START + 1024; // Unlock flash before writing let mut unlocked_flash = p.FLASH.unlocked(); - // All examples use the first 16K of flash for the program so we use the first page after that NorFlash::erase(&mut unlocked_flash, OFFSET_START, OFFSET_END).unwrap(); // Write some data to the start of that page From 754b443a3bbabde42cce98d77f4b29c9900c2b72 Mon Sep 17 00:00:00 2001 From: Janek Date: Mon, 4 Dec 2023 09:10:34 +0100 Subject: [PATCH 05/11] WIP: Minor fixes to flash.rs --- examples/flash_read_write.rs | 19 ++-- src/flash.rs | 205 ++++++++++++++++++++++++----------- 2 files changed, 152 insertions(+), 72 deletions(-) diff --git a/examples/flash_read_write.rs b/examples/flash_read_write.rs index 661b0ae..8c51ab1 100644 --- a/examples/flash_read_write.rs +++ b/examples/flash_read_write.rs @@ -1,19 +1,13 @@ #![no_main] #![no_std] -use cortex_m::Peripherals; use panic_halt as _; use stm32f0xx_hal as hal; -use crate::hal::{ - pac::{self, flash}, - prelude::*, -}; +use crate::hal::{flash::FlashExt, pac, prelude::*}; use cortex_m_rt::entry; use embedded_storage::nor_flash::NorFlash; -use stm32f0xx_hal::flash::FlashExt; -use stm32f0xx_hal::prelude::_stm32f0xx_hal_rcc_RccExt; #[entry] fn main() -> ! { @@ -21,16 +15,23 @@ fn main() -> ! { let _ = p.RCC.configure().freeze(&mut p.FLASH); // All examples use the first 16K of flash for the program so we use the first page after that - const OFFSET_START: u32 = 16 * 1024; + const OFFSET_START: u32 = 32 * 1024; const OFFSET_END: u32 = OFFSET_START + 1024; // Unlock flash before writing let mut unlocked_flash = p.FLASH.unlocked(); NorFlash::erase(&mut unlocked_flash, OFFSET_START, OFFSET_END).unwrap(); + NorFlash::erase(&mut unlocked_flash, 0x10000, 0x10000 + 1024).unwrap(); // Write some data to the start of that page let write_data = [0xC0_u8, 0xFF_u8, 0xEE_u8, 0x00_u8]; - NorFlash::write(&mut unlocked_flash, OFFSET_START, &write_data).unwrap(); + match NorFlash::write(&mut unlocked_flash, OFFSET_START, &write_data) { + Err(e) => { + let err = e; + loop {} + } + Ok(_) => (), + } // Lock flash by dropping it drop(unlocked_flash); diff --git a/src/flash.rs b/src/flash.rs index 574c161..b92f099 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -1,5 +1,5 @@ use core::convert::TryInto; -use core::{ptr, slice}; +use core::{mem, ptr, slice}; use embedded_storage::nor_flash::{ ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, @@ -8,11 +8,60 @@ use embedded_storage::nor_flash::{ use crate::pac::FLASH; use crate::signature::FlashSize; +/// First address of the flash memory +pub const FLASH_START: usize = 0x0800_0000; + +// F03x, F04x and F05x pages are 1K long +#[cfg(any( + feature = "stm32f030", + feature = "stm32f031", + feature = "stm32f038", + feature = "stm32f042", + feature = "stm32f048", + feature = "stm32f051", + feature = "stm32f058", +))] +pub const PAGE_SIZE: u32 = 1024; +// F03x, F04x and F05x have 64 flash pages +#[cfg(any( + feature = "stm32f030", + feature = "stm32f031", + feature = "stm32f038", + feature = "stm32f042", + feature = "stm32f048", + feature = "stm32f051", + feature = "stm32f058", +))] +pub const NUM_PAGES: u32 = 64; + +// F07x and F09x pages are 2K long +#[cfg(any( + feature = "stm32f070", + feature = "stm32f071", + feature = "stm32f072", + feature = "stm32f078", + feature = "stm32f091", + feature = "stm32f098", +))] +pub const PAGE_SIZE: u32 = 2048; +// F07x and F09x have 128 flash pages +#[cfg(any( + feature = "stm32f070", + feature = "stm32f071", + feature = "stm32f072", + feature = "stm32f078", + feature = "stm32f091", + feature = "stm32f098", +))] +pub const NUM_PAGES: u32 = 128; + /// Flash erase/program error #[derive(Debug, Clone, Copy)] pub enum Error { Programming, WriteProtection, + /// STM32F0 can only write Half Words (16 Bit) to flash. Can not write to addresses not aligned to that. + Alignment, } impl Error { @@ -43,13 +92,11 @@ pub trait FlashExt { /// Unlock flash for erasing/programming until this method's /// result is dropped fn unlocked(&mut self) -> UnlockedFlash; - /// Returns flash memory sector of a given offset. Returns none if offset is out of range. - fn sector(&self, offset: usize) -> Option; } impl FlashExt for FLASH { fn address(&self) -> usize { - 0x0800_0000 + FLASH_START } fn len(&self) -> usize { @@ -60,10 +107,6 @@ impl FlashExt for FLASH { unlock(self); UnlockedFlash { flash: self } } - - fn sector(&self, offset: usize) -> Option { - flash_sectors(self.len()).find(|s| s.contains(offset)) - } } /// Read-only flash @@ -105,10 +148,6 @@ impl FlashExt for LockedFlash { fn unlocked(&mut self) -> UnlockedFlash { self.flash.unlocked() } - - fn sector(&self, offset: usize) -> Option { - self.flash.sector(offset) - } } /// Result of `FlashExt::unlocked()` @@ -147,69 +186,110 @@ impl Drop for UnlockedFlash<'_> { } } +pub trait WriteErase { + /// Native type for the flash for writing with the correct alignment and size + /// + /// Can be `u8`, `u16`, `u32`, ... (`u16` for STM32F0xx devices) + type NativeType; + + /// The smallest possible write, depends on the platform + fn program_native(&mut self, offset: usize, data: &[Self::NativeType]) -> Result<(), Error>; + + /// Write a buffer of bytes to memory and use native writes internally. + /// If it is not the same length as a set of native writes the write will be padded to fill the + /// native write. + fn program(&mut self, offset: usize, data: &[u8]) -> Result<(), Error>; +} + +impl WriteErase for UnlockedFlash<'_> { + type NativeType = u16; + + fn program_native( + &mut self, + mut offset: usize, + data: &[Self::NativeType], + ) -> Result<(), Error> { + // Wait for ready bit + self.wait_ready(); + + let addr = self.flash.address() as *mut u16; + + // Write the data to flash + for &half_word in data { + self.flash.cr.modify(|_, w| w.pg().set_bit()); + unsafe { + ptr::write_volatile(addr.add(offset), half_word); + } + offset += mem::size_of::(); + } + + self.wait_ready(); + + // Clear programming bit + self.flash.cr.modify(|_, w| w.pg().clear_bit()); + + self.ok() + } + + fn program(&mut self, mut offset: usize, data: &[u8]) -> Result<(), Error> { + if offset % mem::align_of::() != 0 { + return Err(Error::Alignment); + } + + let mut chunks = data.chunks_exact(mem::size_of::()); + + for exact_chunk in &mut chunks { + let native = &[Self::NativeType::from_ne_bytes( + exact_chunk.try_into().unwrap(), + )]; + self.program_native(offset, native)?; + offset += mem::size_of::(); + } + + let remainder = chunks.remainder(); + + if !remainder.is_empty() { + let mut data = Self::NativeType::MAX; + + for b in remainder.iter().rev() { + data = (data << 8) | *b as Self::NativeType; + } + + let native = &[data]; + self.program_native(offset, native)?; + } + + self.ok() + } +} + impl UnlockedFlash<'_> { /// Erase a flash page at offset /// /// Refer to the reference manual to see which sector corresponds /// to which memory address. pub fn erase(&mut self, offset: u32) -> Result<(), Error> { + // Wait for ready bit + self.wait_ready(); + + // Set the PER (page erase) bit in CR register + self.flash.cr.modify(|_, w| w.per().set_bit()); + // Write address into the AR register self.flash .ar .write(|w| w.far().bits(self.flash.address() as u32 + offset)); - #[rustfmt::skip] - self.flash.cr.modify(|_, w| - w - // page erase - .per().set_bit() - // start - .strt().set_bit() - ); + // Set the STRT (start) Bit in CR register + self.flash.cr.modify(|_, w| w.strt().set_bit()); + + // Wait for the operation to finish self.wait_ready(); + // Clear PER bit after operation is finished self.flash.cr.modify(|_, w| w.per().clear_bit()); self.ok() } - /// Program bytes with offset into flash memory - pub fn program<'a, I>(&mut self, mut offset: usize, mut bytes: I) -> Result<(), Error> - where - I: Iterator, - { - if self.flash.cr.read().lock().bit_is_set() { - return Err(Error::Programming); - } - let ptr = self.flash.address() as *mut u8; - let mut bytes_written = 1; - while bytes_written > 0 { - bytes_written = 0; - let amount = 2 - (offset % 2); - - #[allow(unused_unsafe)] - self.flash.cr.modify(|_, w| unsafe { - // programming - w.pg().set_bit() - }); - for _ in 0..amount { - match bytes.next() { - Some(byte) => { - unsafe { - ptr::write_volatile(ptr.add(offset), *byte); - } - offset += 1; - bytes_written += 1; - } - None => break, - } - } - self.wait_ready(); - self.ok()?; - } - self.flash.cr.modify(|_, w| w.pg().clear_bit()); - - Ok(()) - } - fn ok(&self) -> Result<(), Error> { Error::read(self.flash).map(Err).unwrap_or(Ok(())) } @@ -364,10 +444,9 @@ impl<'a> ReadNorFlash for UnlockedFlash<'a> { } impl<'a> NorFlash for UnlockedFlash<'a> { - const WRITE_SIZE: usize = 1; + const WRITE_SIZE: usize = 2; - // Use largest sector size of 128 KB. All smaller sectors will be erased together. - const ERASE_SIZE: usize = 128 * 1024; + const ERASE_SIZE: usize = PAGE_SIZE as usize; fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { let mut current = from as usize; @@ -387,7 +466,7 @@ impl<'a> NorFlash for UnlockedFlash<'a> { } fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.program(offset as usize, bytes.iter()) + self.program(offset as usize, bytes) } } From 5524712a5c3b4627ca525be0a9bc68b0f67b4c2e Mon Sep 17 00:00:00 2001 From: Janek Date: Fri, 8 Dec 2023 10:56:27 +0100 Subject: [PATCH 06/11] Fix bugs in flash interface --- src/flash.rs | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/flash.rs b/src/flash.rs index b92f099..3c0003d 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -2,7 +2,7 @@ use core::convert::TryInto; use core::{mem, ptr, slice}; use embedded_storage::nor_flash::{ - ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, + ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, }; use crate::pac::FLASH; @@ -85,7 +85,7 @@ pub trait FlashExt { /// Size in bytes fn len(&self) -> usize; /// Returns a read-only view of flash memory - fn read(&self) -> &[u8] { + fn read_all(&self) -> &[u8] { let ptr = self.address() as *const _; unsafe { slice::from_raw_parts(ptr, self.len()) } } @@ -204,23 +204,19 @@ pub trait WriteErase { impl WriteErase for UnlockedFlash<'_> { type NativeType = u16; - fn program_native( - &mut self, - mut offset: usize, - data: &[Self::NativeType], - ) -> Result<(), Error> { + fn program_native(&mut self, address: usize, data: &[Self::NativeType]) -> Result<(), Error> { // Wait for ready bit self.wait_ready(); - let addr = self.flash.address() as *mut u16; + let mut addr = address as *mut Self::NativeType; // Write the data to flash for &half_word in data { self.flash.cr.modify(|_, w| w.pg().set_bit()); unsafe { - ptr::write_volatile(addr.add(offset), half_word); + ptr::write_volatile(addr, half_word); + addr = addr.add(mem::size_of::()); } - offset += mem::size_of::(); } self.wait_ready(); @@ -231,8 +227,8 @@ impl WriteErase for UnlockedFlash<'_> { self.ok() } - fn program(&mut self, mut offset: usize, data: &[u8]) -> Result<(), Error> { - if offset % mem::align_of::() != 0 { + fn program(&mut self, mut address: usize, data: &[u8]) -> Result<(), Error> { + if address % mem::align_of::() != 0 { return Err(Error::Alignment); } @@ -242,8 +238,8 @@ impl WriteErase for UnlockedFlash<'_> { let native = &[Self::NativeType::from_ne_bytes( exact_chunk.try_into().unwrap(), )]; - self.program_native(offset, native)?; - offset += mem::size_of::(); + self.program_native(address, native)?; + address += mem::size_of::(); } let remainder = chunks.remainder(); @@ -256,7 +252,7 @@ impl WriteErase for UnlockedFlash<'_> { } let native = &[data]; - self.program_native(offset, native)?; + self.program_native(address, native)?; } self.ok() @@ -420,7 +416,7 @@ impl ReadNorFlash for LockedFlash { fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { let offset = offset as usize; - bytes.copy_from_slice(&self.flash.read()[offset..offset + bytes.len()]); + bytes.copy_from_slice(&self.flash.read_all()[offset..offset + bytes.len()]); Ok(()) } @@ -434,7 +430,7 @@ impl<'a> ReadNorFlash for UnlockedFlash<'a> { fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { let offset = offset as usize; - bytes.copy_from_slice(&self.flash.read()[offset..offset + bytes.len()]); + bytes.copy_from_slice(&self.flash.read_all()[offset..offset + bytes.len()]); Ok(()) } @@ -466,9 +462,6 @@ impl<'a> NorFlash for UnlockedFlash<'a> { } fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.program(offset as usize, bytes) + self.program(self.flash.address() + offset as usize, bytes) } } - -// STM32F4 supports multiple writes -impl<'a> MultiwriteNorFlash for UnlockedFlash<'a> {} From c86ea9f0a39e01444c44d964b1a0362f516335dc Mon Sep 17 00:00:00 2001 From: Janek Date: Fri, 8 Dec 2023 10:56:42 +0100 Subject: [PATCH 07/11] Clean up flash read write example --- examples/flash_read_write.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/examples/flash_read_write.rs b/examples/flash_read_write.rs index 8c51ab1..3deb69d 100644 --- a/examples/flash_read_write.rs +++ b/examples/flash_read_write.rs @@ -4,10 +4,14 @@ use panic_halt as _; use stm32f0xx_hal as hal; -use crate::hal::{flash::FlashExt, pac, prelude::*}; +use crate::hal::{ + flash::{FlashExt, LockedFlash}, + pac, + prelude::*, +}; use cortex_m_rt::entry; -use embedded_storage::nor_flash::NorFlash; +use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; #[entry] fn main() -> ! { @@ -15,36 +19,46 @@ fn main() -> ! { let _ = p.RCC.configure().freeze(&mut p.FLASH); // All examples use the first 16K of flash for the program so we use the first page after that - const OFFSET_START: u32 = 32 * 1024; + const OFFSET_START: u32 = 16 * 1024; const OFFSET_END: u32 = OFFSET_START + 1024; // Unlock flash before writing let mut unlocked_flash = p.FLASH.unlocked(); NorFlash::erase(&mut unlocked_flash, OFFSET_START, OFFSET_END).unwrap(); - NorFlash::erase(&mut unlocked_flash, 0x10000, 0x10000 + 1024).unwrap(); // Write some data to the start of that page let write_data = [0xC0_u8, 0xFF_u8, 0xEE_u8, 0x00_u8]; match NorFlash::write(&mut unlocked_flash, OFFSET_START, &write_data) { - Err(e) => { - let err = e; - loop {} - } + Err(_) => panic!(), Ok(_) => (), } + // Read back the written data from flash + let mut read_buffer: [u8; 4] = [0; 4]; + unlocked_flash.read(OFFSET_START, &mut read_buffer).unwrap(); + assert_eq!(write_data, read_buffer); + // Lock flash by dropping it drop(unlocked_flash); - // Read back the slice from flash + // It is also possible to read "manually" using core functions let read_data = unsafe { core::slice::from_raw_parts( - (p.FLASH.address() + 16 * 1024) as *const u8, + (p.FLASH.address() + OFFSET_START as usize) as *const u8, write_data.len(), ) }; + for (i, d) in read_data.iter().enumerate() { + read_buffer[i] = *d; + } + + assert_eq!(write_data, read_buffer); + + // Reading is also possible on locked flash + let mut locked_flash = LockedFlash::new(p.FLASH); + locked_flash.read(OFFSET_START, &mut read_buffer).unwrap(); - assert_eq!(write_data, *read_data); + assert_eq!(write_data, read_buffer); } loop { continue; From 6557c1e23411cefb3ccd249c1ceebe3194087a18 Mon Sep 17 00:00:00 2001 From: Janek Date: Fri, 8 Dec 2023 11:00:43 +0100 Subject: [PATCH 08/11] Add note for flash size to example --- examples/flash_read_write.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/flash_read_write.rs b/examples/flash_read_write.rs index 3deb69d..0c2f3e7 100644 --- a/examples/flash_read_write.rs +++ b/examples/flash_read_write.rs @@ -13,11 +13,17 @@ use crate::hal::{ use cortex_m_rt::entry; use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; +/// # NOTE +/// This example assumes a flash size of more than 16K. If your MCU has less or equal than 16K Bytes +/// of flash memory, adjust the `memory.x` file and `OFFSET_START` + `OFFSET_END` constants accordingly. #[entry] fn main() -> ! { if let Some(mut p) = pac::Peripherals::take() { let _ = p.RCC.configure().freeze(&mut p.FLASH); + // Check that flash is big enough for this example + assert!(p.FLASH.len() > 16 * 1024); + // All examples use the first 16K of flash for the program so we use the first page after that const OFFSET_START: u32 = 16 * 1024; const OFFSET_END: u32 = OFFSET_START + 1024; From 2769e82dfa8e42b74f7e367601d5bc5d2873d36d Mon Sep 17 00:00:00 2001 From: Janek Date: Fri, 8 Dec 2023 11:02:21 +0100 Subject: [PATCH 09/11] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d688384..c8501dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Provide getters to serial status flags idle/txe/rxne/tc. - Provide ability to reset timer UIF interrupt flag - PWM complementary output capability for TIM1 with new example to demonstrate +- Implement interface for reading and writing to the internal flash memory and an example for demonstration. ### Fixed From eb45dd35c3a2cb9a9ff1348a88d2a2df7ac05ceb Mon Sep 17 00:00:00 2001 From: Janek Date: Tue, 12 Dec 2023 16:57:54 +0100 Subject: [PATCH 10/11] Fix clippy error in flash.rs --- src/flash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flash.rs b/src/flash.rs index 3c0003d..9bf320d 100644 --- a/src/flash.rs +++ b/src/flash.rs @@ -215,7 +215,7 @@ impl WriteErase for UnlockedFlash<'_> { self.flash.cr.modify(|_, w| w.pg().set_bit()); unsafe { ptr::write_volatile(addr, half_word); - addr = addr.add(mem::size_of::()); + addr = addr.add(1); } } From baed308ed28329b86d4cb6f8500a53a208ebba94 Mon Sep 17 00:00:00 2001 From: Janek Date: Tue, 12 Dec 2023 17:26:20 +0100 Subject: [PATCH 11/11] Fix rustfmt failing --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a78005d..623847c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,8 @@ pub mod dac; #[cfg(feature = "device-selected")] pub mod delay; #[cfg(feature = "device-selected")] +pub mod flash; +#[cfg(feature = "device-selected")] pub mod gpio; #[cfg(feature = "device-selected")] pub mod i2c; @@ -53,15 +55,13 @@ pub mod rcc; #[cfg(feature = "device-selected")] pub mod serial; #[cfg(feature = "device-selected")] +pub mod signature; +#[cfg(feature = "device-selected")] pub mod spi; #[cfg(feature = "device-selected")] pub mod time; #[cfg(feature = "device-selected")] pub mod timers; -#[cfg(feature = "device-selected")] -pub mod flash; -#[cfg(feature = "device-selected")] -pub mod signature; #[cfg(any( feature = "stm32f031", feature = "stm32f051",