aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRon Evans <[email protected]>2023-02-27 13:55:38 +0100
committerGitHub <[email protected]>2023-02-27 13:55:38 +0100
commit6e1b8a54aaa4e181e814c7c3925220a26753751e (patch)
treedd700208e4fd5080c9eaeb6189e44c0c02ce64ae
parent8bf94b92316c44a3110667d52bcb92f22f6c2923 (diff)
downloadtinygo-6e1b8a54aaa4e181e814c7c3925220a26753751e.tar.gz
tinygo-6e1b8a54aaa4e181e814c7c3925220a26753751e.zip
machine/stm32, nrf: flash API (#3472)
machine/stm32, nrf: implement machine.Flash Implements the machine.Flash interface using the same definition as the tinyfs BlockDevice. This implementation covers the stm32f4, stm32l4, stm32wlx, nrf51, nrf52, and nrf528xx processors.
-rw-r--r--src/examples/flash/main.go57
-rw-r--r--src/machine/flash.go144
-rw-r--r--src/machine/machine_nrf.go108
-rw-r--r--src/machine/machine_nrf51.go6
-rw-r--r--src/machine/machine_nrf52.go6
-rw-r--r--src/machine/machine_nrf52833.go6
-rw-r--r--src/machine/machine_nrf52840.go6
-rw-r--r--src/machine/machine_stm32_flash.go122
-rw-r--r--src/machine/machine_stm32f4.go141
-rw-r--r--src/machine/machine_stm32l4.go103
-rw-r--r--src/machine/machine_stm32wlx.go114
-rw-r--r--targets/arm.ld4
12 files changed, 817 insertions, 0 deletions
diff --git a/src/examples/flash/main.go b/src/examples/flash/main.go
new file mode 100644
index 000000000..923a6f877
--- /dev/null
+++ b/src/examples/flash/main.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+ "machine"
+ "time"
+)
+
+var (
+ err error
+ message = "1234567887654321123456788765432112345678876543211234567887654321"
+)
+
+func main() {
+ time.Sleep(3 * time.Second)
+
+ // Print out general information
+ println("Flash data start: ", machine.FlashDataStart())
+ println("Flash data end: ", machine.FlashDataEnd())
+ println("Flash data size, bytes:", machine.Flash.Size())
+ println("Flash write block size:", machine.Flash.WriteBlockSize())
+ println("Flash erase block size:", machine.Flash.EraseBlockSize())
+ println()
+
+ flash := machine.OpenFlashBuffer(machine.Flash, machine.FlashDataStart())
+ original := make([]byte, len(message))
+ saved := make([]byte, len(message))
+
+ // Read flash contents on start (data shall survive power off)
+ print("Reading data from flash: ")
+ _, err = flash.Read(original)
+ checkError(err)
+ println(string(original))
+
+ // Write the message to flash
+ print("Writing data to flash: ")
+ flash.Seek(0, 0) // rewind back to beginning
+ _, err = flash.Write([]byte(message))
+ checkError(err)
+ println(string(message))
+
+ // Read back flash contents after write (verify data is the same as written)
+ print("Reading data back from flash: ")
+ flash.Seek(0, 0) // rewind back to beginning
+ _, err = flash.Read(saved)
+ checkError(err)
+ println(string(saved))
+ println()
+}
+
+func checkError(err error) {
+ if err != nil {
+ for {
+ println(err.Error())
+ time.Sleep(time.Second)
+ }
+ }
+}
diff --git a/src/machine/flash.go b/src/machine/flash.go
new file mode 100644
index 000000000..59c3e077e
--- /dev/null
+++ b/src/machine/flash.go
@@ -0,0 +1,144 @@
+//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx
+
+package machine
+
+import (
+ "errors"
+ "io"
+ "unsafe"
+)
+
+//go:extern __flash_data_start
+var flashDataStart [0]byte
+
+//go:extern __flash_data_end
+var flashDataEnd [0]byte
+
+// Return the start of the writable flash area, aligned on a page boundary. This
+// is usually just after the program and static data.
+func FlashDataStart() uintptr {
+ pagesize := uintptr(eraseBlockSize())
+ return (uintptr(unsafe.Pointer(&flashDataStart)) + pagesize - 1) &^ (pagesize - 1)
+}
+
+// Return the end of the writable flash area. Usually this is the address one
+// past the end of the on-chip flash.
+func FlashDataEnd() uintptr {
+ return uintptr(unsafe.Pointer(&flashDataEnd))
+}
+
+var (
+ errFlashCannotErasePage = errors.New("cannot erase flash page")
+ errFlashInvalidWriteLength = errors.New("write flash data must align to correct number of bits")
+ errFlashNotAllowedWriteData = errors.New("not allowed to write flash data")
+ errFlashCannotWriteData = errors.New("cannot write flash data")
+ errFlashCannotReadPastEOF = errors.New("cannot read beyond end of flash data")
+ errFlashCannotWritePastEOF = errors.New("cannot write beyond end of flash data")
+)
+
+// BlockDevice is the raw device that is meant to store flash data.
+type BlockDevice interface {
+ // ReadAt reads the given number of bytes from the block device.
+ io.ReaderAt
+
+ // WriteAt writes the given number of bytes to the block device.
+ io.WriterAt
+
+ // Size returns the number of bytes in this block device.
+ Size() int64
+
+ // 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.
+ WriteBlockSize() int64
+
+ // 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.
+ EraseBlockSize() int64
+
+ // 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.
+ EraseBlocks(start, len int64) error
+}
+
+// FlashBuffer implements the ReadWriteCloser interface using the BlockDevice interface.
+type FlashBuffer struct {
+ b BlockDevice
+ start uintptr
+ current uintptr
+}
+
+// OpenFlashBuffer opens a FlashBuffer.
+func OpenFlashBuffer(b BlockDevice, address uintptr) *FlashBuffer {
+ return &FlashBuffer{b: b, start: address, current: address}
+}
+
+// Read data from a FlashBuffer.
+func (fl *FlashBuffer) Read(p []byte) (n int, err error) {
+ fl.b.ReadAt(p, int64(fl.current))
+
+ fl.current += uintptr(len(p))
+
+ return len(p), nil
+}
+
+// Write data to a FlashBuffer.
+func (fl *FlashBuffer) Write(p []byte) (n int, err error) {
+ // any new pages needed?
+ // NOTE probably will not work as expected if you try to write over page boundary
+ // of pages with different sizes.
+ pagesize := uintptr(fl.b.EraseBlockSize())
+ currentPageCount := (fl.current - fl.start + pagesize - 1) / pagesize
+ totalPagesNeeded := (fl.current - fl.start + uintptr(len(p)) + pagesize - 1) / pagesize
+ if currentPageCount == totalPagesNeeded {
+ // just write the data
+ n, err := fl.b.WriteAt(p, int64(fl.current))
+ if err != nil {
+ return 0, err
+ }
+ fl.current += uintptr(n)
+ return n, nil
+ }
+
+ // erase enough blocks to hold the data
+ page := fl.flashPageFromAddress(fl.start + (currentPageCount * pagesize))
+ fl.b.EraseBlocks(page, int64(totalPagesNeeded-currentPageCount))
+
+ // write the data
+ for i := 0; i < len(p); i += int(pagesize) {
+ var last int = i + int(pagesize)
+ if i+int(pagesize) > len(p) {
+ last = len(p)
+ }
+
+ _, err := fl.b.WriteAt(p[i:last], int64(fl.current))
+ if err != nil {
+ return 0, err
+ }
+ fl.current += uintptr(last - i)
+ }
+
+ return len(p), nil
+}
+
+// Close the FlashBuffer.
+func (fl *FlashBuffer) Close() error {
+ return nil
+}
+
+// Seek implements io.Seeker interface, but with limitations.
+// You can only seek relative to the start.
+// Also, you cannot use seek before write operations, only read.
+func (fl *FlashBuffer) Seek(offset int64, whence int) (int64, error) {
+ fl.current = fl.start + uintptr(offset)
+
+ return offset, nil
+}
+
+// calculate page number from address
+func (fl *FlashBuffer) flashPageFromAddress(address uintptr) int64 {
+ return int64(address-memoryStart) / fl.b.EraseBlockSize()
+}
diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go
index f7f1d5926..3b07a897c 100644
--- a/src/machine/machine_nrf.go
+++ b/src/machine/machine_nrf.go
@@ -3,7 +3,9 @@
package machine
import (
+ "bytes"
"device/nrf"
+ "encoding/binary"
"runtime/interrupt"
"unsafe"
)
@@ -382,3 +384,109 @@ func ReadTemperature() int32 {
nrf.TEMP.EVENTS_DATARDY.Set(0)
return temp
}
+
+const memoryStart = 0x0
+
+// 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 double-word (64 bits) length data can be programmed. See rm0461 page 78.
+// If the length of p is not long enough it will be padded with 0xFF 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
+ }
+
+ address := FlashDataStart() + uintptr(off)
+ padded := f.pad(p)
+
+ waitWhileFlashBusy()
+
+ nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen)
+ defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)
+
+ for j := 0; j < len(padded); j += int(f.WriteBlockSize()) {
+ // write word
+ *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(padded[j : j+int(f.WriteBlockSize())])
+ address += uintptr(f.WriteBlockSize())
+ waitWhileFlashBusy()
+ }
+
+ return len(padded), nil
+}
+
+// Size returns the number of bytes in this block device.
+func (f flashBlockDevice) Size() int64 {
+ return int64(FlashDataEnd() - FlashDataStart())
+}
+
+const writeBlockSize = 4
+
+// 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 writeBlockSize
+}
+
+// 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.
+func (f flashBlockDevice) EraseBlocks(start, len int64) error {
+ address := FlashDataStart() + uintptr(start*f.EraseBlockSize())
+ waitWhileFlashBusy()
+
+ nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Een)
+ defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)
+
+ for i := start; i < start+len; i++ {
+ nrf.NVMC.ERASEPAGE.Set(uint32(address))
+ waitWhileFlashBusy()
+ address += uintptr(f.EraseBlockSize())
+ }
+
+ return nil
+}
+
+// pad data if needed so it is long enough for correct byte alignment on writes.
+func (f flashBlockDevice) pad(p []byte) []byte {
+ paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
+ if paddingNeeded == 0 {
+ return p
+ }
+
+ padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
+ return append(p, padding...)
+}
+
+func waitWhileFlashBusy() {
+ for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready {
+ }
+}
diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go
index 0fd04f776..95723cfd0 100644
--- a/src/machine/machine_nrf51.go
+++ b/src/machine/machine_nrf51.go
@@ -6,6 +6,12 @@ import (
"device/nrf"
)
+const eraseBlockSizeValue = 1024
+
+func eraseBlockSize() int64 {
+ return eraseBlockSizeValue
+}
+
// Get peripheral and pin number for this GPIO pin.
func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) {
return nrf.GPIO, uint32(p)
diff --git a/src/machine/machine_nrf52.go b/src/machine/machine_nrf52.go
index 06ca6e01c..71c534325 100644
--- a/src/machine/machine_nrf52.go
+++ b/src/machine/machine_nrf52.go
@@ -63,3 +63,9 @@ var (
PWM1 = &PWM{PWM: nrf.PWM1}
PWM2 = &PWM{PWM: nrf.PWM2}
)
+
+const eraseBlockSizeValue = 4096
+
+func eraseBlockSize() int64 {
+ return eraseBlockSizeValue
+}
diff --git a/src/machine/machine_nrf52833.go b/src/machine/machine_nrf52833.go
index ded3c90cf..60558eb0e 100644
--- a/src/machine/machine_nrf52833.go
+++ b/src/machine/machine_nrf52833.go
@@ -84,3 +84,9 @@ var (
PWM2 = &PWM{PWM: nrf.PWM2}
PWM3 = &PWM{PWM: nrf.PWM3}
)
+
+const eraseBlockSizeValue = 4096
+
+func eraseBlockSize() int64 {
+ return eraseBlockSizeValue
+}
diff --git a/src/machine/machine_nrf52840.go b/src/machine/machine_nrf52840.go
index d38ebbe56..21a436780 100644
--- a/src/machine/machine_nrf52840.go
+++ b/src/machine/machine_nrf52840.go
@@ -102,3 +102,9 @@ func (pdm *PDM) Read(buf []int16) (uint32, error) {
return uint32(len(buf)), nil
}
+
+const eraseBlockSizeValue = 4096
+
+func eraseBlockSize() int64 {
+ return eraseBlockSizeValue
+}
diff --git a/src/machine/machine_stm32_flash.go b/src/machine/machine_stm32_flash.go
new file mode 100644
index 000000000..00ec718c1
--- /dev/null
+++ b/src/machine/machine_stm32_flash.go
@@ -0,0 +1,122 @@
+//go:build stm32f4 || stm32l4 || stm32wlx
+
+package machine
+
+import (
+ "device/stm32"
+
+ "bytes"
+ "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 double-word (64 bits) length data can be programmed. See rm0461 page 78.
+// If the length of p is not long enough it will be padded with 0xFF 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
+ }
+
+ unlockFlash()
+ defer lockFlash()
+
+ return writeFlashData(FlashDataStart()+uintptr(off), f.pad(p))
+}
+
+// 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 writeBlockSize
+}
+
+func eraseBlockSize() int64 {
+ return eraseBlockSizeValue
+}
+
+// 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.
+// TODO: correctly handle processors that have differently sized blocks
+// in different areas of memory like the STM32F40x and STM32F1x.
+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.
+func (f flashBlockDevice) EraseBlocks(start, len int64) error {
+ unlockFlash()
+ defer lockFlash()
+
+ for i := start; i < start+len; i++ {
+ if err := eraseBlock(uint32(i)); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// pad data if needed so it is long enough for correct byte alignment on writes.
+func (f flashBlockDevice) pad(p []byte) []byte {
+ paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
+ if paddingNeeded == 0 {
+ return p
+ }
+
+ padded := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
+ return append(p, padded...)
+}
+
+const memoryStart = 0x08000000
+
+func unlockFlash() {
+ // keys as described rm0461 page 76
+ var fkey1 uint32 = 0x45670123
+ var fkey2 uint32 = 0xCDEF89AB
+
+ // Wait for the flash memory not to be busy
+ for stm32.FLASH.GetSR_BSY() != 0 {
+ }
+
+ // Check if the controller is unlocked already
+ if stm32.FLASH.GetCR_LOCK() != 0 {
+ // Write the first key
+ stm32.FLASH.SetKEYR(fkey1)
+ // Write the second key
+ stm32.FLASH.SetKEYR(fkey2)
+ }
+}
+
+func lockFlash() {
+ stm32.FLASH.SetCR_LOCK(1)
+}
diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go
index 829dc7f85..016c1019f 100644
--- a/src/machine/machine_stm32f4.go
+++ b/src/machine/machine_stm32f4.go
@@ -6,6 +6,8 @@ package machine
import (
"device/stm32"
+ "encoding/binary"
+ "errors"
"math/bits"
"runtime/interrupt"
"runtime/volatile"
@@ -791,3 +793,142 @@ func (i2c *I2C) getSpeed(config I2CConfig) uint32 {
}
}
}
+
+//---------- Flash related code
+
+// the block size actually depends on the sector.
+// TODO: handle this correctly for sectors > 3
+const eraseBlockSizeValue = 16384
+
+// see RM0090 page 75
+func sectorNumber(address uintptr) uint32 {
+ switch {
+ // 0x0800 0000 - 0x0800 3FFF
+ case address >= 0x08000000 && address <= 0x08003FFF:
+ return 0
+ // 0x0800 4000 - 0x0800 7FFF
+ case address >= 0x08004000 && address <= 0x08007FFF:
+ return 1
+ // 0x0800 8000 - 0x0800 BFFF
+ case address >= 0x08008000 && address <= 0x0800BFFF:
+ return 2
+ // 0x0800 C000 - 0x0800 FFFF
+ case address >= 0x0800C000 && address <= 0x0800FFFF:
+ return 3
+ // 0x0801 0000 - 0x0801 FFFF
+ case address >= 0x08010000 && address <= 0x0801FFFF:
+ return 4
+ // 0x0802 0000 - 0x0803 FFFF
+ case address >= 0x08020000 && address <= 0x0803FFFF:
+ return 5
+ // 0x0804 0000 - 0x0805 FFFF
+ case address >= 0x08040000 && address <= 0x0805FFFF:
+ return 6
+ case address >= 0x08060000 && address <= 0x0807FFFF:
+ return 7
+ case address >= 0x08080000 && address <= 0x0809FFFF:
+ return 8
+ case address >= 0x080A0000 && address <= 0x080BFFFF:
+ return 9
+ case address >= 0x080C0000 && address <= 0x080DFFFF:
+ return 10
+ case address >= 0x080E0000 && address <= 0x080FFFFF:
+ return 11
+ default:
+ return 0
+ }
+}
+
+// calculate sector number from address
+// var sector uint32 = sectorNumber(address)
+
+// see RM0090 page 85
+// eraseBlock at the passed in block number
+func eraseBlock(block uint32) error {
+ waitUntilFlashDone()
+
+ // clear any previous errors
+ stm32.FLASH.SR.SetBits(0xF0)
+
+ // set SER bit
+ stm32.FLASH.SetCR_SER(1)
+ defer stm32.FLASH.SetCR_SER(0)
+
+ // set the block (aka sector) to be erased
+ stm32.FLASH.SetCR_SNB(block)
+ defer stm32.FLASH.SetCR_SNB(0)
+
+ // start the page erase
+ stm32.FLASH.SetCR_STRT(1)
+
+ waitUntilFlashDone()
+
+ if err := checkError(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+const writeBlockSize = 2
+
+// see RM0090 page 86
+// must write data in word-length
+func writeFlashData(address uintptr, data []byte) (int, error) {
+ if len(data)%writeBlockSize != 0 {
+ return 0, errFlashInvalidWriteLength
+ }
+
+ waitUntilFlashDone()
+
+ // clear any previous errors
+ stm32.FLASH.SR.SetBits(0xF0)
+
+ // set parallelism to x32
+ stm32.FLASH.SetCR_PSIZE(2)
+
+ for i := 0; i < len(data); i += writeBlockSize {
+ // start write operation
+ stm32.FLASH.SetCR_PG(1)
+
+ *(*uint16)(unsafe.Pointer(address)) = binary.BigEndian.Uint16(data[i : i+writeBlockSize])
+
+ waitUntilFlashDone()
+
+ if err := checkError(); err != nil {
+ return i, err
+ }
+
+ // end write operation
+ stm32.FLASH.SetCR_PG(0)
+ }
+
+ return len(data), nil
+}
+
+func waitUntilFlashDone() {
+ for stm32.FLASH.GetSR_BSY() != 0 {
+ }
+}
+
+var (
+ errFlashPGS = errors.New("errFlashPGS")
+ errFlashPGP = errors.New("errFlashPGP")
+ errFlashPGA = errors.New("errFlashPGA")
+ errFlashWRP = errors.New("errFlashWRP")
+)
+
+func checkError() error {
+ switch {
+ case stm32.FLASH.GetSR_PGSERR() != 0:
+ return errFlashPGS
+ case stm32.FLASH.GetSR_PGPERR() != 0:
+ return errFlashPGP
+ case stm32.FLASH.GetSR_PGAERR() != 0:
+ return errFlashPGA
+ case stm32.FLASH.GetSR_WRPERR() != 0:
+ return errFlashWRP
+ }
+
+ return nil
+}
diff --git a/src/machine/machine_stm32l4.go b/src/machine/machine_stm32l4.go
index b6da1b4db..2ac00200e 100644
--- a/src/machine/machine_stm32l4.go
+++ b/src/machine/machine_stm32l4.go
@@ -4,6 +4,8 @@ package machine
import (
"device/stm32"
+ "encoding/binary"
+ "errors"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
@@ -543,3 +545,104 @@ func initRNG() {
stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN)
stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN)
}
+
+//---------- Flash related code
+
+const eraseBlockSizeValue = 2048
+
+// see RM0394 page 83
+// eraseBlock of the passed in block number
+func eraseBlock(block uint32) error {
+ waitUntilFlashDone()
+
+ // clear any previous errors
+ stm32.FLASH.SR.SetBits(0x3FA)
+
+ // page erase operation
+ stm32.FLASH.SetCR_PER(1)
+ defer stm32.FLASH.SetCR_PER(0)
+
+ // set the page to be erased
+ stm32.FLASH.SetCR_PNB(block)
+
+ // start the page erase
+ stm32.FLASH.SetCR_START(1)
+
+ waitUntilFlashDone()
+
+ if err := checkError(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+const writeBlockSize = 8
+
+// see RM0394 page 84
+// It is only possible to program double word (2 x 32-bit data).
+func writeFlashData(address uintptr, data []byte) (int, error) {
+ if len(data)%writeBlockSize != 0 {
+ return 0, errFlashInvalidWriteLength
+ }
+
+ waitUntilFlashDone()
+
+ // clear any previous errors
+ stm32.FLASH.SR.SetBits(0x3FA)
+
+ for j := 0; j < len(data); j += writeBlockSize {
+ // start page write operation
+ stm32.FLASH.SetCR_PG(1)
+
+ // write first word using double-word low order word
+ *(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize])
+
+ address += writeBlockSize / 2
+
+ // write second word using double-word high order word
+ *(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j : j+writeBlockSize/2])
+
+ waitUntilFlashDone()
+
+ if err := checkError(); err != nil {
+ return j, err
+ }
+
+ // end flash write
+ stm32.FLASH.SetCR_PG(0)
+ address += writeBlockSize / 2
+ }
+
+ return len(data), nil
+}
+
+func waitUntilFlashDone() {
+ for stm32.FLASH.GetSR_BSY() != 0 {
+ }
+}
+
+var (
+ errFlashPGS = errors.New("errFlashPGS")
+ errFlashSIZE = errors.New("errFlashSIZE")
+ errFlashPGA = errors.New("errFlashPGA")
+ errFlashWRP = errors.New("errFlashWRP")
+ errFlashPROG = errors.New("errFlashPROG")
+)
+
+func checkError() error {
+ switch {
+ case stm32.FLASH.GetSR_PGSERR() != 0:
+ return errFlashPGS
+ case stm32.FLASH.GetSR_SIZERR() != 0:
+ return errFlashSIZE
+ case stm32.FLASH.GetSR_PGAERR() != 0:
+ return errFlashPGA
+ case stm32.FLASH.GetSR_WRPERR() != 0:
+ return errFlashWRP
+ case stm32.FLASH.GetSR_PROGERR() != 0:
+ return errFlashPROG
+ }
+
+ return nil
+}
diff --git a/src/machine/machine_stm32wlx.go b/src/machine/machine_stm32wlx.go
index 32b3b5823..7971d7a32 100644
--- a/src/machine/machine_stm32wlx.go
+++ b/src/machine/machine_stm32wlx.go
@@ -6,6 +6,8 @@ package machine
import (
"device/stm32"
+ "encoding/binary"
+ "errors"
"math/bits"
"runtime/interrupt"
"runtime/volatile"
@@ -424,3 +426,115 @@ const (
ARR_MAX = 0x10000
PSC_MAX = 0x10000
)
+
+//---------- Flash related code
+
+const eraseBlockSizeValue = 2048
+
+// eraseBlock of the passed in block number
+func eraseBlock(block uint32) error {
+ waitUntilFlashDone()
+
+ // check if operation is allowed.
+ if stm32.FLASH.GetSR_PESD() != 0 {
+ return errFlashCannotErasePage
+ }
+
+ // clear any previous errors
+ stm32.FLASH.SR.SetBits(0x3FA)
+
+ // page erase operation
+ stm32.FLASH.SetCR_PER(1)
+ defer stm32.FLASH.SetCR_PER(0)
+
+ // set the address to the page to be written
+ stm32.FLASH.SetCR_PNB(block)
+ defer stm32.FLASH.SetCR_PNB(0)
+
+ // start the page erase
+ stm32.FLASH.SetCR_STRT(1)
+
+ waitUntilFlashDone()
+
+ if err := checkError(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+const writeBlockSize = 8
+
+func writeFlashData(address uintptr, data []byte) (int, error) {
+ if len(data)%writeBlockSize != 0 {
+ return 0, errFlashInvalidWriteLength
+ }
+
+ waitUntilFlashDone()
+
+ // check if operation is allowed
+ if stm32.FLASH.GetSR_PESD() != 0 {
+ return 0, errFlashNotAllowedWriteData
+ }
+
+ // clear any previous errors
+ stm32.FLASH.SR.SetBits(0x3FA)
+
+ for j := 0; j < len(data); j += writeBlockSize {
+ // start page write operation
+ stm32.FLASH.SetCR_PG(1)
+
+ // write first word using double-word low order word
+ *(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize])
+
+ address += writeBlockSize / 2
+
+ // write second word using double-word high order word
+ *(*uint32)(unsafe.Pointer(address)) = binary.BigEndian.Uint32(data[j : j+writeBlockSize/2])
+
+ waitUntilFlashDone()
+
+ if err := checkError(); err != nil {
+ return j, err
+ }
+
+ // end flash write
+ stm32.FLASH.SetCR_PG(0)
+ address += writeBlockSize / 2
+ }
+
+ return len(data), nil
+}
+
+func waitUntilFlashDone() {
+ for stm32.FLASH.GetSR_BSY() != 0 {
+ }
+
+ for stm32.FLASH.GetSR_CFGBSY() != 0 {
+ }
+}
+
+var (
+ errFlashPGS = errors.New("errFlashPGS")
+ errFlashSIZE = errors.New("errFlashSIZE")
+ errFlashPGA = errors.New("errFlashPGA")
+ errFlashWRP = errors.New("errFlashWRP")
+ errFlashPROG = errors.New("errFlashPROG")
+)
+
+func checkError() error {
+ switch {
+ case stm32.FLASH.GetSR_PGSERR() != 0:
+ return errFlashPGS
+ case stm32.FLASH.GetSR_SIZERR() != 0:
+ return errFlashSIZE
+ case stm32.FLASH.GetSR_PGAERR() != 0:
+ return errFlashPGA
+ case stm32.FLASH.GetSR_WRPERR() != 0:
+ return errFlashWRP
+ case stm32.FLASH.GetSR_PROGERR() != 0:
+ return errFlashPROG
+ }
+
+ return nil
+}
diff --git a/targets/arm.ld b/targets/arm.ld
index e4155b909..ad6c541a1 100644
--- a/targets/arm.ld
+++ b/targets/arm.ld
@@ -69,3 +69,7 @@ _heap_start = _ebss;
_heap_end = ORIGIN(RAM) + LENGTH(RAM);
_globals_start = _sdata;
_globals_end = _ebss;
+
+/* For the flash API */
+__flash_data_start = LOADADDR(.data) + SIZEOF(.data);
+__flash_data_end = ORIGIN(FLASH_TEXT) + LENGTH(FLASH_TEXT);