diff options
author | Ron Evans <[email protected]> | 2023-02-27 13:55:38 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2023-02-27 13:55:38 +0100 |
commit | 6e1b8a54aaa4e181e814c7c3925220a26753751e (patch) | |
tree | dd700208e4fd5080c9eaeb6189e44c0c02ce64ae | |
parent | 8bf94b92316c44a3110667d52bcb92f22f6c2923 (diff) | |
download | tinygo-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.go | 57 | ||||
-rw-r--r-- | src/machine/flash.go | 144 | ||||
-rw-r--r-- | src/machine/machine_nrf.go | 108 | ||||
-rw-r--r-- | src/machine/machine_nrf51.go | 6 | ||||
-rw-r--r-- | src/machine/machine_nrf52.go | 6 | ||||
-rw-r--r-- | src/machine/machine_nrf52833.go | 6 | ||||
-rw-r--r-- | src/machine/machine_nrf52840.go | 6 | ||||
-rw-r--r-- | src/machine/machine_stm32_flash.go | 122 | ||||
-rw-r--r-- | src/machine/machine_stm32f4.go | 141 | ||||
-rw-r--r-- | src/machine/machine_stm32l4.go | 103 | ||||
-rw-r--r-- | src/machine/machine_stm32wlx.go | 114 | ||||
-rw-r--r-- | targets/arm.ld | 4 |
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); |