mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-08 08:53:40 +00:00
Low-level IO driver for serial flash memory via SPI and QSPI (#124)
* QSPI/SPI: flash memory functions
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
package console_example
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"machine"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/drivers/flash"
|
||||
)
|
||||
|
||||
const consoleBufLen = 64
|
||||
const storageBufLen = 512
|
||||
|
||||
var (
|
||||
debug = false
|
||||
|
||||
input [consoleBufLen]byte
|
||||
store [storageBufLen]byte
|
||||
|
||||
console = machine.UART0
|
||||
|
||||
dev *flash.Device
|
||||
|
||||
commands map[string]cmdfunc = map[string]cmdfunc{
|
||||
"": cmdfunc(noop),
|
||||
"erase": cmdfunc(erase),
|
||||
"lsblk": cmdfunc(lsblk),
|
||||
"write": cmdfunc(write),
|
||||
"xxd": cmdfunc(xxd),
|
||||
}
|
||||
)
|
||||
|
||||
type cmdfunc func(argv []string)
|
||||
|
||||
const (
|
||||
StateInput = iota
|
||||
StateEscape
|
||||
StateEscBrc
|
||||
StateCSI
|
||||
)
|
||||
|
||||
func RunFor(device *flash.Device) {
|
||||
|
||||
dev = device
|
||||
dev.Configure(&flash.DeviceConfig{
|
||||
Identifier: flash.DefaultDeviceIdentifier,
|
||||
})
|
||||
|
||||
prompt()
|
||||
|
||||
var state = StateInput
|
||||
|
||||
for i := 0; ; {
|
||||
if console.Buffered() > 0 {
|
||||
data, _ := console.ReadByte()
|
||||
if debug {
|
||||
fmt.Printf("\rdata: %x\r\n\r", data)
|
||||
prompt()
|
||||
console.Write(input[:i])
|
||||
}
|
||||
switch state {
|
||||
case StateInput:
|
||||
switch data {
|
||||
case 0x8:
|
||||
fallthrough
|
||||
case 0x7f: // this is probably wrong... works on my machine tho :)
|
||||
// backspace
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
console.Write([]byte{0x8, 0x20, 0x8})
|
||||
}
|
||||
case 13:
|
||||
// return key
|
||||
console.Write([]byte("\r\n"))
|
||||
runCommand(string(input[:i]))
|
||||
prompt()
|
||||
|
||||
i = 0
|
||||
continue
|
||||
case 27:
|
||||
// escape
|
||||
state = StateEscape
|
||||
default:
|
||||
// anything else, just echo the character if it is printable
|
||||
if strconv.IsPrint(rune(data)) {
|
||||
if i < (consoleBufLen - 1) {
|
||||
console.WriteByte(data)
|
||||
input[i] = data
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
case StateEscape:
|
||||
switch data {
|
||||
case 0x5b:
|
||||
state = StateEscBrc
|
||||
default:
|
||||
state = StateInput
|
||||
}
|
||||
default:
|
||||
// TODO: handle escape sequences
|
||||
state = StateInput
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runCommand(line string) {
|
||||
argv := strings.SplitN(strings.TrimSpace(line), " ", -1)
|
||||
cmd := argv[0]
|
||||
cmdfn, ok := commands[cmd]
|
||||
if !ok {
|
||||
println("unknown command: " + line)
|
||||
return
|
||||
}
|
||||
cmdfn(argv)
|
||||
}
|
||||
|
||||
func noop(argv []string) {}
|
||||
|
||||
func lsblk(argv []string) {
|
||||
attrs := dev.Attrs()
|
||||
status1, _ := dev.ReadStatus()
|
||||
status2, _ := dev.ReadStatus2()
|
||||
serialNumber1, _ := dev.ReadSerialNumber()
|
||||
fmt.Printf(
|
||||
"\n-------------------------------------\r\n"+
|
||||
" Device Information: \r\n"+
|
||||
"-------------------------------------\r\n"+
|
||||
" JEDEC ID: %v\r\n"+
|
||||
" Serial: %v\r\n"+
|
||||
" Status 1: %02x\r\n"+
|
||||
" Status 2: %02x\r\n"+
|
||||
" \r\n"+
|
||||
" Max clock speed (MHz): %d\r\n"+
|
||||
" Has Sector Protection: %t\r\n"+
|
||||
" Supports Fast Reads: %t\r\n"+
|
||||
" Supports QSPI Reads: %t\r\n"+
|
||||
" Supports QSPI Write: %t\r\n"+
|
||||
" Write Status Split: %t\r\n"+
|
||||
" Single Status Byte: %t\r\n"+
|
||||
"-------------------------------------\r\n\r\n",
|
||||
attrs.JedecID,
|
||||
serialNumber1,
|
||||
status1,
|
||||
status2,
|
||||
attrs.MaxClockSpeedMHz,
|
||||
attrs.HasSectorProtection,
|
||||
attrs.SupportsFastRead,
|
||||
attrs.SupportsQSPI,
|
||||
attrs.SupportsQSPIWrites,
|
||||
attrs.WriteStatusSplit,
|
||||
attrs.SingleStatusByte,
|
||||
)
|
||||
}
|
||||
|
||||
func erase(argv []string) {
|
||||
if len(argv) < 3 {
|
||||
println("usage: erase <chip|block|sector> <bytes>")
|
||||
return
|
||||
}
|
||||
var err error
|
||||
var addr uint64 = 0x0
|
||||
if addr, err = strconv.ParseUint(argv[2], 16, 32); err != nil {
|
||||
println("Invalid address: " + err.Error() + "\r\n")
|
||||
return
|
||||
}
|
||||
if argv[1] == "block" {
|
||||
if err = dev.EraseBlock(uint32(addr)); err != nil {
|
||||
println("Block erase error: " + err.Error() + "\r\n")
|
||||
}
|
||||
} else if argv[1] == "sector" {
|
||||
if err = dev.EraseSector(uint32(addr)); err != nil {
|
||||
println("Sector erase error: " + err.Error() + "\r\n")
|
||||
}
|
||||
} else if argv[1] == "chip" {
|
||||
if err = dev.EraseAll(); err != nil {
|
||||
println("Chip erase error: " + err.Error() + "\r\n")
|
||||
}
|
||||
} else {
|
||||
println("usage: erase <chip|block|sector> <bytes>")
|
||||
}
|
||||
}
|
||||
|
||||
func write(argv []string) {
|
||||
if len(argv) < 3 {
|
||||
println("usage: write <hex offset> <bytes>")
|
||||
}
|
||||
var err error
|
||||
var addr uint64 = 0x0
|
||||
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
|
||||
println("Invalid address: " + err.Error() + "\r\n")
|
||||
return
|
||||
}
|
||||
buf := []byte(argv[2])
|
||||
if _, err = dev.WriteAt(buf, int64(addr)); err != nil {
|
||||
println("Write error: " + err.Error() + "\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
func xxd(argv []string) {
|
||||
var err error
|
||||
var addr uint64 = 0x0
|
||||
var size int = 64
|
||||
switch len(argv) {
|
||||
case 3:
|
||||
if size, err = strconv.Atoi(argv[2]); err != nil {
|
||||
println("Invalid size argument: " + err.Error() + "\r\n")
|
||||
return
|
||||
}
|
||||
if size > storageBufLen || size < 1 {
|
||||
fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", storageBufLen)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 2:
|
||||
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
|
||||
println("Invalid address: " + err.Error() + "\r\n")
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 1:
|
||||
// no args supplied, so nothing to do here, just use the defaults
|
||||
default:
|
||||
println("usage: xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
|
||||
return
|
||||
}
|
||||
buf := store[0:size]
|
||||
dev.ReadAt(buf, int64(addr))
|
||||
xxdfprint(os.Stdout, uint32(addr), buf)
|
||||
}
|
||||
|
||||
func xxdfprint(w io.Writer, offset uint32, b []byte) {
|
||||
var l int
|
||||
var buf16 = make([]byte, 16)
|
||||
for i, c := 0, len(b); i < c; i += 16 {
|
||||
l = i + 16
|
||||
if l >= c {
|
||||
l = c
|
||||
}
|
||||
fmt.Fprintf(w, "%08x: % x ", offset+uint32(i), b[i:l])
|
||||
for j, n := 0, l-i; j < 16; j++ {
|
||||
if j >= n || !strconv.IsPrint(rune(b[i+j])) {
|
||||
buf16[j] = '.'
|
||||
} else {
|
||||
buf16[j] = b[i+j]
|
||||
}
|
||||
}
|
||||
console.Write(buf16)
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
func prompt() {
|
||||
print("==> ")
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/examples/flash/console"
|
||||
"tinygo.org/x/drivers/flash"
|
||||
)
|
||||
|
||||
func main() {
|
||||
console_example.RunFor(
|
||||
flash.NewQSPI(
|
||||
machine.QSPI_CS,
|
||||
machine.QSPI_SCK,
|
||||
machine.QSPI_DATA0,
|
||||
machine.QSPI_DATA1,
|
||||
machine.QSPI_DATA2,
|
||||
machine.QSPI_DATA3,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/examples/flash/console"
|
||||
"tinygo.org/x/drivers/flash"
|
||||
)
|
||||
|
||||
func main() {
|
||||
console_example.RunFor(
|
||||
flash.NewSPI(
|
||||
&machine.SPI1,
|
||||
machine.SPI1_MOSI_PIN,
|
||||
machine.SPI1_MISO_PIN,
|
||||
machine.SPI1_SCK_PIN,
|
||||
machine.SPI1_CS_PIN,
|
||||
),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user