Skip to content

Commit 94f9b27

Browse files
authored
Merge pull request embassy-rs#4228 from okhsunrog/adding_eeprom
Adding EEPROM support to embassy-stm32
2 parents f740549 + c88bc97 commit 94f9b27

File tree

8 files changed

+390
-4
lines changed

8 files changed

+390
-4
lines changed

embassy-stm32/build.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1922,6 +1922,48 @@ fn main() {
19221922
pub const WRITE_SIZE: usize = #write_size;
19231923
));
19241924

1925+
// ========
1926+
// Generate EEPROM constants
1927+
1928+
cfgs.declare("eeprom");
1929+
1930+
let eeprom_memory_regions: Vec<&MemoryRegion> =
1931+
memory.iter().filter(|x| x.kind == MemoryRegionKind::Eeprom).collect();
1932+
1933+
if !eeprom_memory_regions.is_empty() {
1934+
cfgs.enable("eeprom");
1935+
1936+
let mut sorted_eeprom_regions = eeprom_memory_regions.clone();
1937+
sorted_eeprom_regions.sort_by_key(|r| r.address);
1938+
1939+
let first_eeprom_address = sorted_eeprom_regions[0].address;
1940+
let mut total_eeprom_size = 0;
1941+
let mut current_expected_address = first_eeprom_address;
1942+
1943+
for region in sorted_eeprom_regions.iter() {
1944+
if region.address != current_expected_address {
1945+
// For STM32L0 and STM32L1, EEPROM regions (if multiple) are expected to be contiguous.
1946+
// If they are not, this indicates an issue with the chip metadata or an unsupported configuration.
1947+
panic!(
1948+
"EEPROM regions for chip {} are not contiguous, which is unexpected for L0/L1 series. \
1949+
First region: '{}' at {:#X}. Found next non-contiguous region: '{}' at {:#X}. \
1950+
Please verify chip metadata. Embassy currently assumes contiguous EEPROM for these series.",
1951+
chip_name, sorted_eeprom_regions[0].name, first_eeprom_address, region.name, region.address
1952+
);
1953+
}
1954+
total_eeprom_size += region.size;
1955+
current_expected_address += region.size;
1956+
}
1957+
1958+
let eeprom_base_usize = first_eeprom_address as usize;
1959+
let total_eeprom_size_usize = total_eeprom_size as usize;
1960+
1961+
g.extend(quote! {
1962+
pub const EEPROM_BASE: usize = #eeprom_base_usize;
1963+
pub const EEPROM_SIZE: usize = #total_eeprom_size_usize;
1964+
});
1965+
}
1966+
19251967
// ========
19261968
// Generate macro-tables
19271969

embassy-stm32/src/flash/eeprom.rs

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
use embassy_hal_internal::drop::OnDrop;
2+
3+
use super::{family, Blocking, Error, Flash, EEPROM_BASE, EEPROM_SIZE};
4+
5+
#[cfg(eeprom)]
6+
impl<'d> Flash<'d, Blocking> {
7+
// --- Internal helpers ---
8+
9+
/// Checks if the given offset and size are within the EEPROM bounds.
10+
fn check_eeprom_offset(&self, offset: u32, size: u32) -> Result<(), Error> {
11+
if offset
12+
.checked_add(size)
13+
.filter(|&end| end <= EEPROM_SIZE as u32)
14+
.is_some()
15+
{
16+
Ok(())
17+
} else {
18+
Err(Error::Size)
19+
}
20+
}
21+
22+
// --- Unlocked (unsafe, internal) functions ---
23+
24+
/// Writes a slice of bytes to EEPROM at the given offset without locking.
25+
///
26+
/// # Safety
27+
/// Caller must ensure EEPROM is unlocked and offset is valid.
28+
unsafe fn eeprom_write_u8_slice_unlocked(&self, offset: u32, data: &[u8]) -> Result<(), Error> {
29+
for (i, &byte) in data.iter().enumerate() {
30+
let addr = EEPROM_BASE as u32 + offset + i as u32;
31+
core::ptr::write_volatile(addr as *mut u8, byte);
32+
family::wait_ready_blocking()?;
33+
family::clear_all_err();
34+
}
35+
Ok(())
36+
}
37+
38+
/// Writes a slice of u16 values to EEPROM at the given offset without locking.
39+
///
40+
/// # Safety
41+
/// Caller must ensure EEPROM is unlocked and offset is valid and aligned.
42+
unsafe fn eeprom_write_u16_slice_unlocked(&self, offset: u32, data: &[u16]) -> Result<(), Error> {
43+
for (i, &value) in data.iter().enumerate() {
44+
let addr = EEPROM_BASE as u32 + offset + i as u32 * 2;
45+
core::ptr::write_volatile(addr as *mut u16, value);
46+
family::wait_ready_blocking()?;
47+
family::clear_all_err();
48+
}
49+
Ok(())
50+
}
51+
52+
/// Writes a slice of u32 values to EEPROM at the given offset without locking.
53+
///
54+
/// # Safety
55+
/// Caller must ensure EEPROM is unlocked and offset is valid and aligned.
56+
unsafe fn eeprom_write_u32_slice_unlocked(&self, offset: u32, data: &[u32]) -> Result<(), Error> {
57+
for (i, &value) in data.iter().enumerate() {
58+
let addr = EEPROM_BASE as u32 + offset + i as u32 * 4;
59+
core::ptr::write_volatile(addr as *mut u32, value);
60+
family::wait_ready_blocking()?;
61+
family::clear_all_err();
62+
}
63+
Ok(())
64+
}
65+
66+
// --- Public, safe API ---
67+
68+
/// Writes a single byte to EEPROM at the given offset.
69+
pub fn eeprom_write_u8(&mut self, offset: u32, value: u8) -> Result<(), Error> {
70+
self.check_eeprom_offset(offset, 1)?;
71+
unsafe {
72+
family::unlock();
73+
let _on_drop = OnDrop::new(|| family::lock());
74+
self.eeprom_write_u8_slice_unlocked(offset, core::slice::from_ref(&value))?;
75+
}
76+
Ok(())
77+
}
78+
79+
/// Writes a single 16-bit value to EEPROM at the given offset.
80+
///
81+
/// Returns an error if the offset is not 2-byte aligned.
82+
pub fn eeprom_write_u16(&mut self, offset: u32, value: u16) -> Result<(), Error> {
83+
if offset % 2 != 0 {
84+
return Err(Error::Unaligned);
85+
}
86+
self.check_eeprom_offset(offset, 2)?;
87+
unsafe {
88+
family::unlock();
89+
let _on_drop = OnDrop::new(|| family::lock());
90+
self.eeprom_write_u16_slice_unlocked(offset, core::slice::from_ref(&value))?;
91+
}
92+
Ok(())
93+
}
94+
95+
/// Writes a single 32-bit value to EEPROM at the given offset.
96+
///
97+
/// Returns an error if the offset is not 4-byte aligned.
98+
pub fn eeprom_write_u32(&mut self, offset: u32, value: u32) -> Result<(), Error> {
99+
if offset % 4 != 0 {
100+
return Err(Error::Unaligned);
101+
}
102+
self.check_eeprom_offset(offset, 4)?;
103+
unsafe {
104+
family::unlock();
105+
let _on_drop = OnDrop::new(|| family::lock());
106+
self.eeprom_write_u32_slice_unlocked(offset, core::slice::from_ref(&value))?;
107+
}
108+
Ok(())
109+
}
110+
111+
/// Writes a slice of bytes to EEPROM at the given offset.
112+
pub fn eeprom_write_u8_slice(&mut self, offset: u32, data: &[u8]) -> Result<(), Error> {
113+
self.check_eeprom_offset(offset, data.len() as u32)?;
114+
unsafe {
115+
family::unlock();
116+
let _on_drop = OnDrop::new(|| family::lock());
117+
self.eeprom_write_u8_slice_unlocked(offset, data)?;
118+
}
119+
Ok(())
120+
}
121+
122+
/// Writes a slice of 16-bit values to EEPROM at the given offset.
123+
///
124+
/// Returns an error if the offset is not 2-byte aligned.
125+
pub fn eeprom_write_u16_slice(&mut self, offset: u32, data: &[u16]) -> Result<(), Error> {
126+
if offset % 2 != 0 {
127+
return Err(Error::Unaligned);
128+
}
129+
self.check_eeprom_offset(offset, data.len() as u32 * 2)?;
130+
unsafe {
131+
family::unlock();
132+
let _on_drop = OnDrop::new(|| family::lock());
133+
self.eeprom_write_u16_slice_unlocked(offset, data)?;
134+
}
135+
Ok(())
136+
}
137+
138+
/// Writes a slice of 32-bit values to EEPROM at the given offset.
139+
///
140+
/// Returns an error if the offset is not 4-byte aligned.
141+
pub fn eeprom_write_u32_slice(&mut self, offset: u32, data: &[u32]) -> Result<(), Error> {
142+
if offset % 4 != 0 {
143+
return Err(Error::Unaligned);
144+
}
145+
self.check_eeprom_offset(offset, data.len() as u32 * 4)?;
146+
unsafe {
147+
family::unlock();
148+
let _on_drop = OnDrop::new(|| family::lock());
149+
self.eeprom_write_u32_slice_unlocked(offset, data)?;
150+
}
151+
Ok(())
152+
}
153+
154+
/// Writes a byte slice to EEPROM at the given offset, handling alignment.
155+
///
156+
/// This method will write unaligned prefix and suffix as bytes, and aligned middle as u32.
157+
pub fn eeprom_write_slice(&mut self, offset: u32, data: &[u8]) -> Result<(), Error> {
158+
self.check_eeprom_offset(offset, data.len() as u32)?;
159+
let start = offset;
160+
let misalign = (start % 4) as usize;
161+
let prefix_len = if misalign == 0 {
162+
0
163+
} else {
164+
(4 - misalign).min(data.len())
165+
};
166+
let (prefix, rest) = data.split_at(prefix_len);
167+
let aligned_len = (rest.len() / 4) * 4;
168+
let (bytes_for_u32_write, suffix) = rest.split_at(aligned_len);
169+
170+
unsafe {
171+
family::unlock();
172+
let _on_drop = OnDrop::new(|| family::lock());
173+
174+
if !prefix.is_empty() {
175+
self.eeprom_write_u8_slice_unlocked(start, prefix)?;
176+
}
177+
if !bytes_for_u32_write.is_empty() {
178+
let aligned_eeprom_offset = start + prefix_len as u32;
179+
let base_eeprom_addr = EEPROM_BASE as u32 + aligned_eeprom_offset;
180+
for (i, chunk) in bytes_for_u32_write.chunks_exact(4).enumerate() {
181+
// Safely read a u32 from a potentially unaligned pointer into the chunk.
182+
let value = (chunk.as_ptr() as *const u32).read_unaligned();
183+
let current_eeprom_addr = base_eeprom_addr + (i * 4) as u32;
184+
core::ptr::write_volatile(current_eeprom_addr as *mut u32, value);
185+
family::wait_ready_blocking()?;
186+
family::clear_all_err();
187+
}
188+
}
189+
if !suffix.is_empty() {
190+
let suffix_offset = start + (prefix_len + aligned_len) as u32;
191+
self.eeprom_write_u8_slice_unlocked(suffix_offset, suffix)?;
192+
}
193+
}
194+
Ok(())
195+
}
196+
197+
/// Reads a single byte from EEPROM at the given offset.
198+
pub fn eeprom_read_u8(&self, offset: u32) -> Result<u8, Error> {
199+
self.check_eeprom_offset(offset, 1)?;
200+
let addr = EEPROM_BASE as u32 + offset;
201+
Ok(unsafe { core::ptr::read_volatile(addr as *const u8) })
202+
}
203+
204+
/// Reads a single 16-bit value from EEPROM at the given offset.
205+
///
206+
/// Returns an error if the offset is not 2-byte aligned.
207+
pub fn eeprom_read_u16(&self, offset: u32) -> Result<u16, Error> {
208+
if offset % 2 != 0 {
209+
return Err(Error::Unaligned);
210+
}
211+
self.check_eeprom_offset(offset, 2)?;
212+
let addr = EEPROM_BASE as u32 + offset;
213+
Ok(unsafe { core::ptr::read_volatile(addr as *const u16) })
214+
}
215+
216+
/// Reads a single 32-bit value from EEPROM at the given offset.
217+
///
218+
/// Returns an error if the offset is not 4-byte aligned.
219+
pub fn eeprom_read_u32(&self, offset: u32) -> Result<u32, Error> {
220+
if offset % 4 != 0 {
221+
return Err(Error::Unaligned);
222+
}
223+
self.check_eeprom_offset(offset, 4)?;
224+
let addr = EEPROM_BASE as u32 + offset;
225+
Ok(unsafe { core::ptr::read_volatile(addr as *const u32) })
226+
}
227+
228+
/// Reads a slice of bytes from EEPROM at the given offset into the provided buffer.
229+
pub fn eeprom_read_slice(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> {
230+
self.check_eeprom_offset(offset, buf.len() as u32)?;
231+
let addr = EEPROM_BASE as u32 + offset;
232+
let src = unsafe { core::slice::from_raw_parts(addr as *const u8, buf.len()) };
233+
buf.copy_from_slice(src);
234+
Ok(())
235+
}
236+
}

embassy-stm32/src/flash/l.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ pub(crate) unsafe fn clear_all_err() {
162162
pac::FLASH.nssr().modify(|_| {});
163163
}
164164

165-
unsafe fn wait_ready_blocking() -> Result<(), Error> {
165+
pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> {
166166
loop {
167167
#[cfg(not(flash_l5))]
168168
{

embassy-stm32/src/flash/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
55
mod asynch;
66
#[cfg(flash)]
77
mod common;
8+
#[cfg(eeprom)]
9+
mod eeprom;
810

911
#[cfg(flash_f4)]
1012
pub use asynch::InterruptHandler;
1113
#[cfg(flash)]
1214
pub use common::*;
15+
#[cfg(eeprom)]
16+
#[allow(unused_imports)]
17+
pub use eeprom::*;
1318

1419
pub use crate::_generated::flash_regions::*;
20+
#[cfg(eeprom)]
21+
pub use crate::_generated::{EEPROM_BASE, EEPROM_SIZE};
1522
pub use crate::_generated::{FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE};
1623

1724
/// Get all flash regions.
@@ -83,7 +90,8 @@ pub enum FlashBank {
8390
/// OTP region,
8491
Otp,
8592
}
86-
93+
#[cfg(all(eeprom, not(any(flash_l0, flash_l1))))]
94+
compile_error!("The 'eeprom' cfg is enabled for a non-L0/L1 chip family. This is an unsupported configuration.");
8795
#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb), path = "l.rs")]
8896
#[cfg_attr(flash_f0, path = "f0.rs")]
8997
#[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")]

examples/stm32l0/src/bin/eeprom.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use defmt::{info, unwrap};
5+
use embassy_executor::Spawner;
6+
use embassy_stm32::flash::{Flash, EEPROM_BASE, EEPROM_SIZE};
7+
use {defmt_rtt as _, panic_probe as _};
8+
9+
#[embassy_executor::main]
10+
async fn main(_spawner: Spawner) {
11+
let p = embassy_stm32::init(Default::default());
12+
13+
info!("Hello Eeprom! Start: {}, Size: {}", EEPROM_BASE, EEPROM_SIZE);
14+
15+
const ADDR: u32 = 0x0;
16+
17+
let mut f = Flash::new_blocking(p.FLASH);
18+
19+
info!("Reading...");
20+
let mut buf = [0u8; 8];
21+
unwrap!(f.eeprom_read_slice(ADDR, &mut buf));
22+
info!("Read: {=[u8]:x}", buf);
23+
24+
info!("Writing...");
25+
unwrap!(f.eeprom_write_slice(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8]));
26+
27+
info!("Reading...");
28+
let mut buf = [0u8; 8];
29+
unwrap!(f.eeprom_read_slice(ADDR, &mut buf));
30+
info!("Read: {=[u8]:x}", buf);
31+
assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]);
32+
}

examples/stm32l1/src/bin/eeprom.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use defmt::{info, unwrap};
5+
use embassy_executor::Spawner;
6+
use embassy_stm32::flash::{Flash, EEPROM_BASE, EEPROM_SIZE};
7+
use {defmt_rtt as _, panic_probe as _};
8+
9+
#[embassy_executor::main]
10+
async fn main(_spawner: Spawner) {
11+
let p = embassy_stm32::init(Default::default());
12+
13+
info!("Hello Eeprom! Start: {}, Size: {}", EEPROM_BASE, EEPROM_SIZE);
14+
15+
const ADDR: u32 = 0x0;
16+
17+
let mut f = Flash::new_blocking(p.FLASH);
18+
19+
info!("Reading...");
20+
let mut buf = [0u8; 8];
21+
unwrap!(f.eeprom_read_slice(ADDR, &mut buf));
22+
info!("Read: {=[u8]:x}", buf);
23+
24+
info!("Writing...");
25+
unwrap!(f.eeprom_write_slice(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8]));
26+
27+
info!("Reading...");
28+
let mut buf = [0u8; 8];
29+
unwrap!(f.eeprom_read_slice(ADDR, &mut buf));
30+
info!("Read: {=[u8]:x}", buf);
31+
assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]);
32+
}

0 commit comments

Comments
 (0)