Skip to content

Commit b1529dc

Browse files
authored
Low-level IO driver for serial flash memory via SPI and QSPI (#124)
* QSPI/SPI: flash memory functions
1 parent 1987f42 commit b1529dc

File tree

9 files changed

+1559
-0
lines changed

9 files changed

+1559
-0
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ smoke-test:
4141
@md5sum ./build/test.hex
4242
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espstation/main.go
4343
@md5sum ./build/test.hex
44+
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi
45+
@md5sum ./build/test.hex
46+
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
47+
@md5sum ./build/test.hex
4448
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/i2c/main.go
4549
@md5sum ./build/test.hex
4650
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/uart/main.go

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ The following 46 devices are supported.
9191
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
9292
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
9393
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
94+
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
9495
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
9596
| [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI |
9697
| [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI |
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package console_example
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"machine"
7+
"os"
8+
"strconv"
9+
"strings"
10+
11+
"tinygo.org/x/drivers/flash"
12+
)
13+
14+
const consoleBufLen = 64
15+
const storageBufLen = 512
16+
17+
var (
18+
debug = false
19+
20+
input [consoleBufLen]byte
21+
store [storageBufLen]byte
22+
23+
console = machine.UART0
24+
25+
dev *flash.Device
26+
27+
commands map[string]cmdfunc = map[string]cmdfunc{
28+
"": cmdfunc(noop),
29+
"erase": cmdfunc(erase),
30+
"lsblk": cmdfunc(lsblk),
31+
"write": cmdfunc(write),
32+
"xxd": cmdfunc(xxd),
33+
}
34+
)
35+
36+
type cmdfunc func(argv []string)
37+
38+
const (
39+
StateInput = iota
40+
StateEscape
41+
StateEscBrc
42+
StateCSI
43+
)
44+
45+
func RunFor(device *flash.Device) {
46+
47+
dev = device
48+
dev.Configure(&flash.DeviceConfig{
49+
Identifier: flash.DefaultDeviceIdentifier,
50+
})
51+
52+
prompt()
53+
54+
var state = StateInput
55+
56+
for i := 0; ; {
57+
if console.Buffered() > 0 {
58+
data, _ := console.ReadByte()
59+
if debug {
60+
fmt.Printf("\rdata: %x\r\n\r", data)
61+
prompt()
62+
console.Write(input[:i])
63+
}
64+
switch state {
65+
case StateInput:
66+
switch data {
67+
case 0x8:
68+
fallthrough
69+
case 0x7f: // this is probably wrong... works on my machine tho :)
70+
// backspace
71+
if i > 0 {
72+
i -= 1
73+
console.Write([]byte{0x8, 0x20, 0x8})
74+
}
75+
case 13:
76+
// return key
77+
console.Write([]byte("\r\n"))
78+
runCommand(string(input[:i]))
79+
prompt()
80+
81+
i = 0
82+
continue
83+
case 27:
84+
// escape
85+
state = StateEscape
86+
default:
87+
// anything else, just echo the character if it is printable
88+
if strconv.IsPrint(rune(data)) {
89+
if i < (consoleBufLen - 1) {
90+
console.WriteByte(data)
91+
input[i] = data
92+
i++
93+
}
94+
}
95+
}
96+
case StateEscape:
97+
switch data {
98+
case 0x5b:
99+
state = StateEscBrc
100+
default:
101+
state = StateInput
102+
}
103+
default:
104+
// TODO: handle escape sequences
105+
state = StateInput
106+
}
107+
}
108+
}
109+
}
110+
111+
func runCommand(line string) {
112+
argv := strings.SplitN(strings.TrimSpace(line), " ", -1)
113+
cmd := argv[0]
114+
cmdfn, ok := commands[cmd]
115+
if !ok {
116+
println("unknown command: " + line)
117+
return
118+
}
119+
cmdfn(argv)
120+
}
121+
122+
func noop(argv []string) {}
123+
124+
func lsblk(argv []string) {
125+
attrs := dev.Attrs()
126+
status1, _ := dev.ReadStatus()
127+
status2, _ := dev.ReadStatus2()
128+
serialNumber1, _ := dev.ReadSerialNumber()
129+
fmt.Printf(
130+
"\n-------------------------------------\r\n"+
131+
" Device Information: \r\n"+
132+
"-------------------------------------\r\n"+
133+
" JEDEC ID: %v\r\n"+
134+
" Serial: %v\r\n"+
135+
" Status 1: %02x\r\n"+
136+
" Status 2: %02x\r\n"+
137+
" \r\n"+
138+
" Max clock speed (MHz): %d\r\n"+
139+
" Has Sector Protection: %t\r\n"+
140+
" Supports Fast Reads: %t\r\n"+
141+
" Supports QSPI Reads: %t\r\n"+
142+
" Supports QSPI Write: %t\r\n"+
143+
" Write Status Split: %t\r\n"+
144+
" Single Status Byte: %t\r\n"+
145+
"-------------------------------------\r\n\r\n",
146+
attrs.JedecID,
147+
serialNumber1,
148+
status1,
149+
status2,
150+
attrs.MaxClockSpeedMHz,
151+
attrs.HasSectorProtection,
152+
attrs.SupportsFastRead,
153+
attrs.SupportsQSPI,
154+
attrs.SupportsQSPIWrites,
155+
attrs.WriteStatusSplit,
156+
attrs.SingleStatusByte,
157+
)
158+
}
159+
160+
func erase(argv []string) {
161+
if len(argv) < 3 {
162+
println("usage: erase <chip|block|sector> <bytes>")
163+
return
164+
}
165+
var err error
166+
var addr uint64 = 0x0
167+
if addr, err = strconv.ParseUint(argv[2], 16, 32); err != nil {
168+
println("Invalid address: " + err.Error() + "\r\n")
169+
return
170+
}
171+
if argv[1] == "block" {
172+
if err = dev.EraseBlock(uint32(addr)); err != nil {
173+
println("Block erase error: " + err.Error() + "\r\n")
174+
}
175+
} else if argv[1] == "sector" {
176+
if err = dev.EraseSector(uint32(addr)); err != nil {
177+
println("Sector erase error: " + err.Error() + "\r\n")
178+
}
179+
} else if argv[1] == "chip" {
180+
if err = dev.EraseAll(); err != nil {
181+
println("Chip erase error: " + err.Error() + "\r\n")
182+
}
183+
} else {
184+
println("usage: erase <chip|block|sector> <bytes>")
185+
}
186+
}
187+
188+
func write(argv []string) {
189+
if len(argv) < 3 {
190+
println("usage: write <hex offset> <bytes>")
191+
}
192+
var err error
193+
var addr uint64 = 0x0
194+
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
195+
println("Invalid address: " + err.Error() + "\r\n")
196+
return
197+
}
198+
buf := []byte(argv[2])
199+
if _, err = dev.WriteAt(buf, int64(addr)); err != nil {
200+
println("Write error: " + err.Error() + "\r\n")
201+
}
202+
}
203+
204+
func xxd(argv []string) {
205+
var err error
206+
var addr uint64 = 0x0
207+
var size int = 64
208+
switch len(argv) {
209+
case 3:
210+
if size, err = strconv.Atoi(argv[2]); err != nil {
211+
println("Invalid size argument: " + err.Error() + "\r\n")
212+
return
213+
}
214+
if size > storageBufLen || size < 1 {
215+
fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", storageBufLen)
216+
return
217+
}
218+
fallthrough
219+
case 2:
220+
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
221+
println("Invalid address: " + err.Error() + "\r\n")
222+
return
223+
}
224+
fallthrough
225+
case 1:
226+
// no args supplied, so nothing to do here, just use the defaults
227+
default:
228+
println("usage: xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
229+
return
230+
}
231+
buf := store[0:size]
232+
dev.ReadAt(buf, int64(addr))
233+
xxdfprint(os.Stdout, uint32(addr), buf)
234+
}
235+
236+
func xxdfprint(w io.Writer, offset uint32, b []byte) {
237+
var l int
238+
var buf16 = make([]byte, 16)
239+
for i, c := 0, len(b); i < c; i += 16 {
240+
l = i + 16
241+
if l >= c {
242+
l = c
243+
}
244+
fmt.Fprintf(w, "%08x: % x ", offset+uint32(i), b[i:l])
245+
for j, n := 0, l-i; j < 16; j++ {
246+
if j >= n || !strconv.IsPrint(rune(b[i+j])) {
247+
buf16[j] = '.'
248+
} else {
249+
buf16[j] = b[i+j]
250+
}
251+
}
252+
console.Write(buf16)
253+
println()
254+
}
255+
}
256+
257+
func prompt() {
258+
print("==> ")
259+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"machine"
5+
6+
"tinygo.org/x/drivers/examples/flash/console"
7+
"tinygo.org/x/drivers/flash"
8+
)
9+
10+
func main() {
11+
console_example.RunFor(
12+
flash.NewQSPI(
13+
machine.QSPI_CS,
14+
machine.QSPI_SCK,
15+
machine.QSPI_DATA0,
16+
machine.QSPI_DATA1,
17+
machine.QSPI_DATA2,
18+
machine.QSPI_DATA3,
19+
),
20+
)
21+
}

examples/flash/console/spi/main.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"machine"
5+
6+
"tinygo.org/x/drivers/examples/flash/console"
7+
"tinygo.org/x/drivers/flash"
8+
)
9+
10+
func main() {
11+
console_example.RunFor(
12+
flash.NewSPI(
13+
&machine.SPI1,
14+
machine.SPI1_MOSI_PIN,
15+
machine.SPI1_MISO_PIN,
16+
machine.SPI1_SCK_PIN,
17+
machine.SPI1_CS_PIN,
18+
),
19+
)
20+
}

0 commit comments

Comments
 (0)