stm32l0: add flash support

Flash on the STM32L0 series of chips works a bit different and needs a
slightly different implementation compared to other STM32 chips.
This commit is contained in:
Ayke van Laethem
2026-01-05 12:07:15 +01:00
committed by Ron Evans
parent 53c1ccaa42
commit 42fed60e61
3 changed files with 169 additions and 1 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
package machine
+3
View File
@@ -2,6 +2,9 @@
package machine
// Flash support for STM32 chips, except for STM32L0 which have a different type
// of flash.
import (
"device/stm32"
+165
View File
@@ -0,0 +1,165 @@
//go:build stm32l0
package machine
// The STM32L0 series of MCUs has a different type of flash than other STM32
// series chips. The programming interface is different, and the flash is erased
// to zero bits instead of one bits as on most flash. So this requires a
// different implementation.
import (
"device/stm32"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
// compile-time check for ensuring we fulfill BlockDevice interface
var _ BlockDevice = flashBlockDevice{}
var Flash flashBlockDevice
type flashBlockDevice struct {
}
// ReadAt reads the given number of bytes from the block device.
func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotReadPastEOF
}
data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p))
copy(p, data)
return len(p), nil
}
// WriteAt writes the given number of bytes to the block device.
// Only word-sized (32 bits) length data can be programmed.
// If the length of p is not long enough it will be padded with zero bytes.
// This method assumes that the destination is already erased.
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotWritePastEOF
}
if uintptr(off)%4 != 0 {
// Offset must be aligned on a word boundary.
return 0, errFlashCannotWriteData
}
unlockFlash()
defer lockFlash()
// Write words in this area.
for i := 0; i < len(p); i += 4 {
// Construct the word to write.
word := uint32(p[i])
if i+1 < len(p) {
word |= uint32(p[i+1]) << 8
}
if i+2 < len(p) {
word |= uint32(p[i+2]) << 16
}
if i+3 < len(p) {
word |= uint32(p[i+3]) << 24
}
// Find the pointer address to write.
address := FlashDataStart() + uintptr(off) + uintptr(i)
// Write the word to flash.
(*volatile.Register32)(unsafe.Pointer(address)).Set(word)
// Check for any errors.
if stm32.FLASH.SR.Get()&(stm32.Flash_SR_WRPERR|stm32.Flash_SR_NOTZEROERR|stm32.Flash_SR_SIZERR) != 0 {
return i, errFlashCannotWriteData
}
}
return len(p), nil
}
// Size returns the number of bytes in this block device.
func (f flashBlockDevice) Size() int64 {
return int64(FlashDataEnd() - FlashDataStart())
}
// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
// should always work correctly.
func (f flashBlockDevice) WriteBlockSize() int64 {
return 4
}
func eraseBlockSize() int64 {
return 128
}
// EraseBlockSize returns the smallest erasable area on this particular chip
// in bytes. This is used for the block size in EraseBlocks.
// It must be a power of two, and may be as small as 1. A typical size is 4096.
func (f flashBlockDevice) EraseBlockSize() int64 {
return eraseBlockSize()
}
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
// Note that block 0 should map to the address of FlashDataStart().
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
// Flash needs to be unlocked to be able to erase it.
unlockFlash()
defer lockFlash()
// Set the flash programming mode to erase a page.
// Note: lockFlash() will reset these flags to 0 so we don't need to
// explicitly set them to 0.
stm32.FLASH.PECR.Set(stm32.Flash_PECR_ERASE | stm32.Flash_PECR_PROG)
// Erase all pages in this range.
for i := uintptr(start); i < uintptr(start)+uintptr(len); i++ {
// Find the pointer address somewhere in the page to erase.
address := FlashDataStart() + i*uintptr(eraseBlockSize())
// To erase, write any value to that address.
(*volatile.Register32)(unsafe.Pointer(address)).Set(uint32(address))
// Check for any errors.
// The only error (that is not a programming error) that could happen is
// if a row is in a protected sector.
if stm32.FLASH.SR.Get()&(stm32.Flash_SR_WRPERR|stm32.Flash_SR_SIZERR) != 0 {
return errFlashCannotErasePage
}
}
return nil
}
func unlockFlash() {
// Make sure the flash peripheral clock is enabled.
stm32.RCC.AHBENR.SetBits(stm32.RCC_AHBENR_MIFEN)
// Wait for the flash memory not to be busy.
for stm32.FLASH.GetSR_BSY() != 0 {
}
// Disable interrupts while writing, since no memory operations may happen
// while the unlock sequence is ongoing.
mask := interrupt.Disable()
// Remove PELOCK bit.
stm32.FLASH.PEKEYR.Set(0x89ABCDEF)
stm32.FLASH.PEKEYR.Set(0x02030405)
// Remove PRGLOCK bit.
stm32.FLASH.PRGKEYR.Set(0x8C9DAEBF)
stm32.FLASH.PRGKEYR.Set(0x13141516)
interrupt.Restore(mask)
}
func lockFlash() {
// Set PELOCK to 1, which also automatically sets PRGLOCK to 1.
stm32.FLASH.PECR.Set(stm32.Flash_PECR_PELOCK)
}