Skip to content

Commit 6e1b8a5

Browse files
authored
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.
1 parent 8bf94b9 commit 6e1b8a5

File tree

12 files changed

+817
-0
lines changed

12 files changed

+817
-0
lines changed

src/examples/flash/main.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package main
2+
3+
import (
4+
"machine"
5+
"time"
6+
)
7+
8+
var (
9+
err error
10+
message = "1234567887654321123456788765432112345678876543211234567887654321"
11+
)
12+
13+
func main() {
14+
time.Sleep(3 * time.Second)
15+
16+
// Print out general information
17+
println("Flash data start: ", machine.FlashDataStart())
18+
println("Flash data end: ", machine.FlashDataEnd())
19+
println("Flash data size, bytes:", machine.Flash.Size())
20+
println("Flash write block size:", machine.Flash.WriteBlockSize())
21+
println("Flash erase block size:", machine.Flash.EraseBlockSize())
22+
println()
23+
24+
flash := machine.OpenFlashBuffer(machine.Flash, machine.FlashDataStart())
25+
original := make([]byte, len(message))
26+
saved := make([]byte, len(message))
27+
28+
// Read flash contents on start (data shall survive power off)
29+
print("Reading data from flash: ")
30+
_, err = flash.Read(original)
31+
checkError(err)
32+
println(string(original))
33+
34+
// Write the message to flash
35+
print("Writing data to flash: ")
36+
flash.Seek(0, 0) // rewind back to beginning
37+
_, err = flash.Write([]byte(message))
38+
checkError(err)
39+
println(string(message))
40+
41+
// Read back flash contents after write (verify data is the same as written)
42+
print("Reading data back from flash: ")
43+
flash.Seek(0, 0) // rewind back to beginning
44+
_, err = flash.Read(saved)
45+
checkError(err)
46+
println(string(saved))
47+
println()
48+
}
49+
50+
func checkError(err error) {
51+
if err != nil {
52+
for {
53+
println(err.Error())
54+
time.Sleep(time.Second)
55+
}
56+
}
57+
}

src/machine/flash.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx
2+
3+
package machine
4+
5+
import (
6+
"errors"
7+
"io"
8+
"unsafe"
9+
)
10+
11+
//go:extern __flash_data_start
12+
var flashDataStart [0]byte
13+
14+
//go:extern __flash_data_end
15+
var flashDataEnd [0]byte
16+
17+
// Return the start of the writable flash area, aligned on a page boundary. This
18+
// is usually just after the program and static data.
19+
func FlashDataStart() uintptr {
20+
pagesize := uintptr(eraseBlockSize())
21+
return (uintptr(unsafe.Pointer(&flashDataStart)) + pagesize - 1) &^ (pagesize - 1)
22+
}
23+
24+
// Return the end of the writable flash area. Usually this is the address one
25+
// past the end of the on-chip flash.
26+
func FlashDataEnd() uintptr {
27+
return uintptr(unsafe.Pointer(&flashDataEnd))
28+
}
29+
30+
var (
31+
errFlashCannotErasePage = errors.New("cannot erase flash page")
32+
errFlashInvalidWriteLength = errors.New("write flash data must align to correct number of bits")
33+
errFlashNotAllowedWriteData = errors.New("not allowed to write flash data")
34+
errFlashCannotWriteData = errors.New("cannot write flash data")
35+
errFlashCannotReadPastEOF = errors.New("cannot read beyond end of flash data")
36+
errFlashCannotWritePastEOF = errors.New("cannot write beyond end of flash data")
37+
)
38+
39+
// BlockDevice is the raw device that is meant to store flash data.
40+
type BlockDevice interface {
41+
// ReadAt reads the given number of bytes from the block device.
42+
io.ReaderAt
43+
44+
// WriteAt writes the given number of bytes to the block device.
45+
io.WriterAt
46+
47+
// Size returns the number of bytes in this block device.
48+
Size() int64
49+
50+
// WriteBlockSize returns the block size in which data can be written to
51+
// memory. It can be used by a client to optimize writes, non-aligned writes
52+
// should always work correctly.
53+
WriteBlockSize() int64
54+
55+
// EraseBlockSize returns the smallest erasable area on this particular chip
56+
// in bytes. This is used for the block size in EraseBlocks.
57+
// It must be a power of two, and may be as small as 1. A typical size is 4096.
58+
EraseBlockSize() int64
59+
60+
// EraseBlocks erases the given number of blocks. An implementation may
61+
// transparently coalesce ranges of blocks into larger bundles if the chip
62+
// supports this. The start and len parameters are in block numbers, use
63+
// EraseBlockSize to map addresses to blocks.
64+
EraseBlocks(start, len int64) error
65+
}
66+
67+
// FlashBuffer implements the ReadWriteCloser interface using the BlockDevice interface.
68+
type FlashBuffer struct {
69+
b BlockDevice
70+
start uintptr
71+
current uintptr
72+
}
73+
74+
// OpenFlashBuffer opens a FlashBuffer.
75+
func OpenFlashBuffer(b BlockDevice, address uintptr) *FlashBuffer {
76+
return &FlashBuffer{b: b, start: address, current: address}
77+
}
78+
79+
// Read data from a FlashBuffer.
80+
func (fl *FlashBuffer) Read(p []byte) (n int, err error) {
81+
fl.b.ReadAt(p, int64(fl.current))
82+
83+
fl.current += uintptr(len(p))
84+
85+
return len(p), nil
86+
}
87+
88+
// Write data to a FlashBuffer.
89+
func (fl *FlashBuffer) Write(p []byte) (n int, err error) {
90+
// any new pages needed?
91+
// NOTE probably will not work as expected if you try to write over page boundary
92+
// of pages with different sizes.
93+
pagesize := uintptr(fl.b.EraseBlockSize())
94+
currentPageCount := (fl.current - fl.start + pagesize - 1) / pagesize
95+
totalPagesNeeded := (fl.current - fl.start + uintptr(len(p)) + pagesize - 1) / pagesize
96+
if currentPageCount == totalPagesNeeded {
97+
// just write the data
98+
n, err := fl.b.WriteAt(p, int64(fl.current))
99+
if err != nil {
100+
return 0, err
101+
}
102+
fl.current += uintptr(n)
103+
return n, nil
104+
}
105+
106+
// erase enough blocks to hold the data
107+
page := fl.flashPageFromAddress(fl.start + (currentPageCount * pagesize))
108+
fl.b.EraseBlocks(page, int64(totalPagesNeeded-currentPageCount))
109+
110+
// write the data
111+
for i := 0; i < len(p); i += int(pagesize) {
112+
var last int = i + int(pagesize)
113+
if i+int(pagesize) > len(p) {
114+
last = len(p)
115+
}
116+
117+
_, err := fl.b.WriteAt(p[i:last], int64(fl.current))
118+
if err != nil {
119+
return 0, err
120+
}
121+
fl.current += uintptr(last - i)
122+
}
123+
124+
return len(p), nil
125+
}
126+
127+
// Close the FlashBuffer.
128+
func (fl *FlashBuffer) Close() error {
129+
return nil
130+
}
131+
132+
// Seek implements io.Seeker interface, but with limitations.
133+
// You can only seek relative to the start.
134+
// Also, you cannot use seek before write operations, only read.
135+
func (fl *FlashBuffer) Seek(offset int64, whence int) (int64, error) {
136+
fl.current = fl.start + uintptr(offset)
137+
138+
return offset, nil
139+
}
140+
141+
// calculate page number from address
142+
func (fl *FlashBuffer) flashPageFromAddress(address uintptr) int64 {
143+
return int64(address-memoryStart) / fl.b.EraseBlockSize()
144+
}

src/machine/machine_nrf.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
package machine
44

55
import (
6+
"bytes"
67
"device/nrf"
8+
"encoding/binary"
79
"runtime/interrupt"
810
"unsafe"
911
)
@@ -382,3 +384,109 @@ func ReadTemperature() int32 {
382384
nrf.TEMP.EVENTS_DATARDY.Set(0)
383385
return temp
384386
}
387+
388+
const memoryStart = 0x0
389+
390+
// compile-time check for ensuring we fulfill BlockDevice interface
391+
var _ BlockDevice = flashBlockDevice{}
392+
393+
var Flash flashBlockDevice
394+
395+
type flashBlockDevice struct {
396+
}
397+
398+
// ReadAt reads the given number of bytes from the block device.
399+
func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
400+
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
401+
return 0, errFlashCannotReadPastEOF
402+
}
403+
404+
data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p))
405+
copy(p, data)
406+
407+
return len(p), nil
408+
}
409+
410+
// WriteAt writes the given number of bytes to the block device.
411+
// Only double-word (64 bits) length data can be programmed. See rm0461 page 78.
412+
// If the length of p is not long enough it will be padded with 0xFF bytes.
413+
// This method assumes that the destination is already erased.
414+
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
415+
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
416+
return 0, errFlashCannotWritePastEOF
417+
}
418+
419+
address := FlashDataStart() + uintptr(off)
420+
padded := f.pad(p)
421+
422+
waitWhileFlashBusy()
423+
424+
nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen)
425+
defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)
426+
427+
for j := 0; j < len(padded); j += int(f.WriteBlockSize()) {
428+
// write word
429+
*(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(padded[j : j+int(f.WriteBlockSize())])
430+
address += uintptr(f.WriteBlockSize())
431+
waitWhileFlashBusy()
432+
}
433+
434+
return len(padded), nil
435+
}
436+
437+
// Size returns the number of bytes in this block device.
438+
func (f flashBlockDevice) Size() int64 {
439+
return int64(FlashDataEnd() - FlashDataStart())
440+
}
441+
442+
const writeBlockSize = 4
443+
444+
// WriteBlockSize returns the block size in which data can be written to
445+
// memory. It can be used by a client to optimize writes, non-aligned writes
446+
// should always work correctly.
447+
func (f flashBlockDevice) WriteBlockSize() int64 {
448+
return writeBlockSize
449+
}
450+
451+
// EraseBlockSize returns the smallest erasable area on this particular chip
452+
// in bytes. This is used for the block size in EraseBlocks.
453+
// It must be a power of two, and may be as small as 1. A typical size is 4096.
454+
func (f flashBlockDevice) EraseBlockSize() int64 {
455+
return eraseBlockSize()
456+
}
457+
458+
// EraseBlocks erases the given number of blocks. An implementation may
459+
// transparently coalesce ranges of blocks into larger bundles if the chip
460+
// supports this. The start and len parameters are in block numbers, use
461+
// EraseBlockSize to map addresses to blocks.
462+
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
463+
address := FlashDataStart() + uintptr(start*f.EraseBlockSize())
464+
waitWhileFlashBusy()
465+
466+
nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Een)
467+
defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)
468+
469+
for i := start; i < start+len; i++ {
470+
nrf.NVMC.ERASEPAGE.Set(uint32(address))
471+
waitWhileFlashBusy()
472+
address += uintptr(f.EraseBlockSize())
473+
}
474+
475+
return nil
476+
}
477+
478+
// pad data if needed so it is long enough for correct byte alignment on writes.
479+
func (f flashBlockDevice) pad(p []byte) []byte {
480+
paddingNeeded := f.WriteBlockSize() - (int64(len(p)) % f.WriteBlockSize())
481+
if paddingNeeded == 0 {
482+
return p
483+
}
484+
485+
padding := bytes.Repeat([]byte{0xff}, int(paddingNeeded))
486+
return append(p, padding...)
487+
}
488+
489+
func waitWhileFlashBusy() {
490+
for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready {
491+
}
492+
}

src/machine/machine_nrf51.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import (
66
"device/nrf"
77
)
88

9+
const eraseBlockSizeValue = 1024
10+
11+
func eraseBlockSize() int64 {
12+
return eraseBlockSizeValue
13+
}
14+
915
// Get peripheral and pin number for this GPIO pin.
1016
func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) {
1117
return nrf.GPIO, uint32(p)

src/machine/machine_nrf52.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,9 @@ var (
6363
PWM1 = &PWM{PWM: nrf.PWM1}
6464
PWM2 = &PWM{PWM: nrf.PWM2}
6565
)
66+
67+
const eraseBlockSizeValue = 4096
68+
69+
func eraseBlockSize() int64 {
70+
return eraseBlockSizeValue
71+
}

src/machine/machine_nrf52833.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,9 @@ var (
8484
PWM2 = &PWM{PWM: nrf.PWM2}
8585
PWM3 = &PWM{PWM: nrf.PWM3}
8686
)
87+
88+
const eraseBlockSizeValue = 4096
89+
90+
func eraseBlockSize() int64 {
91+
return eraseBlockSizeValue
92+
}

src/machine/machine_nrf52840.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,9 @@ func (pdm *PDM) Read(buf []int16) (uint32, error) {
102102

103103
return uint32(len(buf)), nil
104104
}
105+
106+
const eraseBlockSizeValue = 4096
107+
108+
func eraseBlockSize() int64 {
109+
return eraseBlockSizeValue
110+
}

0 commit comments

Comments
 (0)