diff options
| author | Ulf Lilleengen <[email protected]> | 2025-05-23 14:15:15 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-05-23 14:15:15 +0200 |
| commit | 94f9b2707486ca3eade5bf4b237edf3d6aa90f35 (patch) | |
| tree | ed91d32d5c462bc919957bc6ea3eccccd9c0d260 /embassy-stm32 | |
| parent | f7405493c184ce453ac3f7ba97f7f2689f978194 (diff) | |
| parent | c88bc972316634da586589adcefa49bb02a2cf0f (diff) | |
Merge pull request #4228 from okhsunrog/adding_eeprom
Adding EEPROM support to embassy-stm32
Diffstat (limited to 'embassy-stm32')
| -rw-r--r-- | embassy-stm32/build.rs | 42 | ||||
| -rw-r--r-- | embassy-stm32/src/flash/eeprom.rs | 236 | ||||
| -rw-r--r-- | embassy-stm32/src/flash/l.rs | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/flash/mod.rs | 10 |
4 files changed, 288 insertions, 2 deletions
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index b00b6a7ac..bb5ef53d7 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs | |||
| @@ -1923,6 +1923,48 @@ fn main() { | |||
| 1923 | )); | 1923 | )); |
| 1924 | 1924 | ||
| 1925 | // ======== | 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 | |||
| 1967 | // ======== | ||
| 1926 | // Generate macro-tables | 1968 | // Generate macro-tables |
| 1927 | 1969 | ||
| 1928 | for irq in METADATA.interrupts { | 1970 | for irq in METADATA.interrupts { |
diff --git a/embassy-stm32/src/flash/eeprom.rs b/embassy-stm32/src/flash/eeprom.rs new file mode 100644 index 000000000..cc3529eb9 --- /dev/null +++ b/embassy-stm32/src/flash/eeprom.rs | |||
| @@ -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 | } | ||
diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs index 65cea005c..1b82704ec 100644 --- a/embassy-stm32/src/flash/l.rs +++ b/embassy-stm32/src/flash/l.rs | |||
| @@ -162,7 +162,7 @@ pub(crate) unsafe fn clear_all_err() { | |||
| 162 | pac::FLASH.nssr().modify(|_| {}); | 162 | pac::FLASH.nssr().modify(|_| {}); |
| 163 | } | 163 | } |
| 164 | 164 | ||
| 165 | unsafe fn wait_ready_blocking() -> Result<(), Error> { | 165 | pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> { |
| 166 | loop { | 166 | loop { |
| 167 | #[cfg(not(flash_l5))] | 167 | #[cfg(not(flash_l5))] |
| 168 | { | 168 | { |
diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index adc45db9c..a3f9e00f7 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs | |||
| @@ -5,13 +5,20 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | |||
| 5 | mod asynch; | 5 | mod asynch; |
| 6 | #[cfg(flash)] | 6 | #[cfg(flash)] |
| 7 | mod common; | 7 | mod common; |
| 8 | #[cfg(eeprom)] | ||
| 9 | mod eeprom; | ||
| 8 | 10 | ||
| 9 | #[cfg(flash_f4)] | 11 | #[cfg(flash_f4)] |
| 10 | pub use asynch::InterruptHandler; | 12 | pub use asynch::InterruptHandler; |
| 11 | #[cfg(flash)] | 13 | #[cfg(flash)] |
| 12 | pub use common::*; | 14 | pub use common::*; |
| 15 | #[cfg(eeprom)] | ||
| 16 | #[allow(unused_imports)] | ||
| 17 | pub use eeprom::*; | ||
| 13 | 18 | ||
| 14 | pub use crate::_generated::flash_regions::*; | 19 | pub use crate::_generated::flash_regions::*; |
| 20 | #[cfg(eeprom)] | ||
| 21 | pub use crate::_generated::{EEPROM_BASE, EEPROM_SIZE}; | ||
| 15 | pub use crate::_generated::{FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE}; | 22 | pub use crate::_generated::{FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE}; |
| 16 | 23 | ||
| 17 | /// Get all flash regions. | 24 | /// Get all flash regions. |
| @@ -83,7 +90,8 @@ pub enum FlashBank { | |||
| 83 | /// OTP region, | 90 | /// OTP region, |
| 84 | Otp, | 91 | Otp, |
| 85 | } | 92 | } |
| 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."); | ||
| 87 | #[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb), path = "l.rs")] | 95 | #[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb), path = "l.rs")] |
| 88 | #[cfg_attr(flash_f0, path = "f0.rs")] | 96 | #[cfg_attr(flash_f0, path = "f0.rs")] |
| 89 | #[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] | 97 | #[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] |
