From 6b914f7af7afd77d8fba584c8354b5fd8c2073ff Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 14 May 2021 19:03:12 +0900 Subject: [PATCH] sdcard: add support for fatfs --- Makefile | 2 + README.md | 3 +- examples/sdcard/console/console.go | 2 +- examples/sdcard/console/feather-m4.go | 14 +- examples/sdcard/console/grandcentral-m4.go | 17 + examples/sdcard/console/m0.go | 14 +- examples/sdcard/console/main.go | 5 +- examples/sdcard/console/p1am-100.go | 17 + examples/sdcard/console/pygamer.go | 17 + examples/sdcard/console/pyportal.go | 12 +- examples/sdcard/console/wioterminal.go | 12 +- examples/sdcard/tinyfs/console/console.go | 519 +++++++++++++++++++++ examples/sdcard/tinyfs/feather-m4.go | 17 + examples/sdcard/tinyfs/grandcentral-m4.go | 17 + examples/sdcard/tinyfs/m0.go | 17 + examples/sdcard/tinyfs/main.go | 49 ++ examples/sdcard/tinyfs/p1am-100.go | 17 + examples/sdcard/tinyfs/pygamer.go | 17 + examples/sdcard/tinyfs/pyportal.go | 17 + examples/sdcard/tinyfs/wioterminal.go | 17 + go.mod | 1 + go.sum | 5 + sdcard/README.md | 20 + sdcard/sdcard.go | 87 ++-- sdcard/timer.go | 22 + 25 files changed, 870 insertions(+), 67 deletions(-) create mode 100644 examples/sdcard/console/grandcentral-m4.go create mode 100644 examples/sdcard/console/p1am-100.go create mode 100644 examples/sdcard/console/pygamer.go create mode 100644 examples/sdcard/tinyfs/console/console.go create mode 100644 examples/sdcard/tinyfs/feather-m4.go create mode 100644 examples/sdcard/tinyfs/grandcentral-m4.go create mode 100644 examples/sdcard/tinyfs/m0.go create mode 100644 examples/sdcard/tinyfs/main.go create mode 100644 examples/sdcard/tinyfs/p1am-100.go create mode 100644 examples/sdcard/tinyfs/pygamer.go create mode 100644 examples/sdcard/tinyfs/pyportal.go create mode 100644 examples/sdcard/tinyfs/wioterminal.go create mode 100644 sdcard/README.md create mode 100644 sdcard/timer.go diff --git a/Makefile b/Makefile index 5ccd5ed..49d7100 100644 --- a/Makefile +++ b/Makefile @@ -195,6 +195,8 @@ endif @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/ @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/tinyfs/ + @md5sum ./build/test.hex DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ diff --git a/README.md b/README.md index 64eacd7..41658eb 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 65 devices are supported. +The following 66 devices are supported. | Device Name | Interface Type | |----------|-------------| @@ -106,6 +106,7 @@ The following 65 devices are supported. | [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO | | [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 | | [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI | +| [SPI SDCARD/MMC](https://en.wikipedia.org/wiki/SD_card) | SPI | | [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI | | [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI | | [SSD1351 OLED display](https://download.mikroe.com/documents/datasheets/ssd1351-revision-1.3.pdf) | SPI | diff --git a/examples/sdcard/console/console.go b/examples/sdcard/console/console.go index 4099a2d..0960afa 100644 --- a/examples/sdcard/console/console.go +++ b/examples/sdcard/console/console.go @@ -347,7 +347,7 @@ func xxdfprint(w io.Writer, offset uint32, b []byte) { } 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])) || b[i+j] >= 0xFE { + if j >= n || !strconv.IsPrint(rune(b[i+j])) || b[i+j] >= 0x80 { buf16[j] = '.' } else { buf16[j] = b[i+j] diff --git a/examples/sdcard/console/feather-m4.go b/examples/sdcard/console/feather-m4.go index 541c218..8ad3689 100644 --- a/examples/sdcard/console/feather-m4.go +++ b/examples/sdcard/console/feather-m4.go @@ -1,4 +1,4 @@ -// +build feather_m4 feather_m4_can +// +build feather_m4 feather_m4_can feather_nrf52840 package main @@ -8,15 +8,9 @@ import ( func init() { spi = machine.SPI0 - spi.Configure(machine.SPIConfig{ - SCK: machine.SPI0_SCK_PIN, - SDO: machine.SPI0_SDO_PIN, - SDI: machine.SPI0_SDI_PIN, - Frequency: 24000000, - LSBFirst: false, - Mode: 0, // phase=0, polarity=0 - }) - + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN csPin = machine.D10 ledPin = machine.LED diff --git a/examples/sdcard/console/grandcentral-m4.go b/examples/sdcard/console/grandcentral-m4.go new file mode 100644 index 0000000..8f1d6e0 --- /dev/null +++ b/examples/sdcard/console/grandcentral-m4.go @@ -0,0 +1,17 @@ +// +build grandcentral-m4 + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI1 + sckPin = machine.SDCARD_SCK_PIN + sdoPin = machine.SDCARD_SDO_PIN + sdiPin = machine.SDCARD_SDI_PIN + csPin = machine.SDCARD_CS_PIN + + ledPin = machine.LED +} diff --git a/examples/sdcard/console/m0.go b/examples/sdcard/console/m0.go index ddbad80..8571cc3 100644 --- a/examples/sdcard/console/m0.go +++ b/examples/sdcard/console/m0.go @@ -1,4 +1,4 @@ -// +build atsamd21 +// +build atsamd21,!p1am_100 package main @@ -8,15 +8,9 @@ import ( func init() { spi = machine.SPI0 - spi.Configure(machine.SPIConfig{ - SCK: machine.SPI0_SCK_PIN, - SDO: machine.SPI0_SDO_PIN, - SDI: machine.SPI0_SDI_PIN, - Frequency: 24000000, - LSBFirst: false, - Mode: 0, // phase=0, polarity=0 - }) - + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN csPin = machine.D2 ledPin = machine.LED diff --git a/examples/sdcard/console/main.go b/examples/sdcard/console/main.go index 171ac60..26f5eed 100644 --- a/examples/sdcard/console/main.go +++ b/examples/sdcard/console/main.go @@ -10,6 +10,9 @@ import ( var ( spi machine.SPI + sckPin machine.Pin + sdoPin machine.Pin + sdiPin machine.Pin csPin machine.Pin ledPin machine.Pin ) @@ -21,7 +24,7 @@ func main() { led := ledPin led.Configure(machine.PinConfig{Mode: machine.PinOutput}) - sd := sdcard.New(spi, csPin) + sd := sdcard.New(spi, sckPin, sdoPin, sdiPin, csPin) err := sd.Configure() if err != nil { fmt.Printf("%s\r\n", err.Error()) diff --git a/examples/sdcard/console/p1am-100.go b/examples/sdcard/console/p1am-100.go new file mode 100644 index 0000000..38268e7 --- /dev/null +++ b/examples/sdcard/console/p1am-100.go @@ -0,0 +1,17 @@ +// +build p1am_100 + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SDCARD_SPI + sckPin = machine.SDCARD_SCK_PIN + sdoPin = machine.SDCARD_SDO_PIN + sdiPin = machine.SDCARD_SDI_PIN + csPin = machine.SDCARD_SS_PIN + + ledPin = machine.LED +} diff --git a/examples/sdcard/console/pygamer.go b/examples/sdcard/console/pygamer.go new file mode 100644 index 0000000..217d3a5 --- /dev/null +++ b/examples/sdcard/console/pygamer.go @@ -0,0 +1,17 @@ +// +build pygamer + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D4 + + ledPin = machine.LED +} diff --git a/examples/sdcard/console/pyportal.go b/examples/sdcard/console/pyportal.go index 5949e35..0f27c3e 100644 --- a/examples/sdcard/console/pyportal.go +++ b/examples/sdcard/console/pyportal.go @@ -8,15 +8,9 @@ import ( func init() { spi = machine.SPI0 - spi.Configure(machine.SPIConfig{ - SCK: machine.SPI0_SCK_PIN, - SDO: machine.SPI0_SDO_PIN, - SDI: machine.SPI0_SDI_PIN, - Frequency: 24000000, - LSBFirst: false, - Mode: 0, // phase=0, polarity=0 - }) - + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN csPin = machine.D32 // SD_CS ledPin = machine.LED diff --git a/examples/sdcard/console/wioterminal.go b/examples/sdcard/console/wioterminal.go index 9ed7d29..0eef41b 100644 --- a/examples/sdcard/console/wioterminal.go +++ b/examples/sdcard/console/wioterminal.go @@ -8,15 +8,9 @@ import ( func init() { spi = machine.SPI2 - spi.Configure(machine.SPIConfig{ - SCK: machine.SCK2, - SDO: machine.SDO2, - SDI: machine.SDI2, - Frequency: 24000000, - LSBFirst: false, - Mode: 0, // phase=0, polarity=0 - }) - + sckPin = machine.SCK2 + sdoPin = machine.SDO2 + sdiPin = machine.SDI2 csPin = machine.SS2 ledPin = machine.LED diff --git a/examples/sdcard/tinyfs/console/console.go b/examples/sdcard/tinyfs/console/console.go new file mode 100644 index 0000000..d329cde --- /dev/null +++ b/examples/sdcard/tinyfs/console/console.go @@ -0,0 +1,519 @@ +// +build tinygo + +package console + +import ( + "fmt" + "io" + "machine" + "os" + "strconv" + "strings" + "time" + + "github.com/tinygo-org/tinyfs" +) + +const consoleBufLen = 64 + +const startBlock = 0 +const blockCount = 0 + +var ( + debug = false + + input [consoleBufLen]byte + + console = machine.Serial + readyLED = machine.LED + + flashdev tinyfs.BlockDevice + fs tinyfs.Filesystem + + currdir = "/" + + commands = map[string]cmdfunc{ + "": noop, + "dbg": dbg, + "lsblk": lsblk, + "mount": mount, + "umount": umount, + "format": format, + "xxd": xxd, + "ls": ls, + "samples": samples, + "mkdir": mkdir, + "cat": cat, + "create": create, + "write": write, + "rm": rm, + } +) + +type cmdfunc func(argv []string) + +const ( + StateInput = iota + StateEscape + StateEscBrc + StateCSI +) + +func RunFor(dev tinyfs.BlockDevice, filesys tinyfs.Filesystem) { + flashdev = dev + fs = filesys + + readyLED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + readyLED.High() + + readyLED.Low() + println("SPI Configured. Reading flash info") + + /* + lfsConfig := flashlfs.NewConfig() + if blockCount == 0 { + lfsConfig.BlockCount = (flashdev.Attrs().TotalSize / lfsConfig.BlockSize) - startBlock + } else { + lfsConfig.BlockCount = blockCount + } + println("Start block:", startBlock) + println("Block count:", lfsConfig.BlockCount) + + blockdev = flashlfs.NewBlockDevice(flashdev, startBlock, lfsConfig.BlockSize) + */ + + 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 + if console.Buffered() > 0 { + data, _ := console.ReadByte() + if data != 10 { + println("\r\nunexpected: \r", int(data)) + } + } + 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 dbg(argv []string) { + if debug { + debug = false + println("Console debugging off") + } else { + debug = true + println("Console debugging on") + } +} + +func lsblk(argv []string) { + fmt.Printf("lsblk : not implement\r\n") +} + +func mount(argv []string) { + if err := fs.Mount(); err != nil { + println("Could not mount LittleFS filesystem: " + err.Error() + "\r\n") + } else { + println("Successfully mounted LittleFS filesystem.\r\n") + } +} + +func format(argv []string) { + if err := fs.Format(); err != nil { + println("Could not format LittleFS filesystem: " + err.Error() + "\r\n") + } else { + println("Successfully formatted LittleFS filesystem.\r\n") + } +} + +func umount(argv []string) { + if err := fs.Unmount(); err != nil { + println("Could not unmount LittleFS filesystem: " + err.Error() + "\r\n") + } else { + println("Successfully unmounted LittleFS filesystem.\r\n") + } +} + +/* + var err error + if fatfs == nil { + fatfs, err = fat.New(fatdisk) + if err != nil { + fatfs = nil + println("could not load FAT filesystem: " + err.Error() + "\r\n") + } + fmt.Printf("loaded fs\r\n") + } + if rootdir == nil { + rootdir, err = fatfs.RootDir() + if err != nil { + rootdir = nil + println("could not load rootdir: " + err.Error() + "\r\n") + } + fmt.Printf("loaded rootdir\r\n") + } + if currdir == nil { + currdir = rootdir + } +*/ + +func ls(argv []string) { + path := "/" + if len(argv) > 1 { + path = strings.TrimSpace(argv[1]) + } + dir, err := fs.Open(path) + if err != nil { + fmt.Printf("Could not open directory %s: %v\r\n", path, err) + return + } + defer dir.Close() + infos, err := dir.Readdir(0) + _ = infos + if err != nil { + fmt.Printf("Could not read directory %s: %v\r\n", path, err) + return + } + for _, info := range infos { + s := "-rwxrwxrwx" + if info.IsDir() { + s = "drwxrwxrwx" + } + fmt.Printf("%s %5d %s\r\n", s, info.Size(), info.Name()) + } +} + +func mkdir(argv []string) { + tgt := "" + if len(argv) == 2 { + tgt = strings.TrimSpace(argv[1]) + } + if debug { + println("Trying mkdir to " + tgt) + } + if tgt == "" { + println("Usage: mkdir ") + return + } + err := fs.Mkdir(tgt, 0777) + if err != nil { + println("Could not mkdir " + tgt + ": " + err.Error()) + } +} + +func rm(argv []string) { + tgt := "" + if len(argv) == 2 { + tgt = strings.TrimSpace(argv[1]) + } + if debug { + println("Trying rm to " + tgt) + } + if tgt == "" { + println("Usage: rm ") + return + } + err := fs.Remove(tgt) + if err != nil { + println("Could not rm " + tgt + ": " + err.Error()) + } +} + +func samples(argv []string) { + buf := make([]byte, 90) + for i := 0; i < 5; i++ { + name := fmt.Sprintf("file%d.txt", i) + if bytes, err := createSampleFile(name, buf); err != nil { + fmt.Printf("%s\r\n", err) + return + } else { + fmt.Printf("wrote %d bytes to %s\r\n", bytes, name) + } + } +} + +func create(argv []string) { + tgt := "" + if len(argv) == 2 { + tgt = strings.TrimSpace(argv[1]) + } + if debug { + println("Trying create to " + tgt) + } + buf := make([]byte, 90) + if bytes, err := createSampleFile(tgt, buf); err != nil { + fmt.Printf("%s\r\n", err) + return + } else { + fmt.Printf("wrote %d bytes to %s\r\n", bytes, tgt) + } +} + +func write(argv []string) { + tgt := "" + if len(argv) == 2 { + tgt = strings.TrimSpace(argv[1]) + } + if debug { + println("Trying receive to " + tgt) + } + buf := make([]byte, 1) + f, err := fs.OpenFile(tgt, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) + if err != nil { + fmt.Printf("error opening %s: %s\r\n", tgt, err.Error()) + return + } + defer f.Close() + var n int + for { + if console.Buffered() > 0 { + data, _ := console.ReadByte() + switch data { + case 0x04: + fmt.Printf("wrote %d bytes to %s\r\n", n, tgt) + return + default: + // anything else, just echo the character if it is printable + if strconv.IsPrint(rune(data)) { + console.WriteByte(data) + } + buf[0] = data + if _, err := f.Write(buf); err != nil { + fmt.Printf("\nerror writing: %s\r\n", err) + return + } + n++ + } + } + } +} + +func createSampleFile(name string, buf []byte) (int, error) { + for j := uint8(0); j < uint8(len(buf)); j++ { + buf[j] = 0x20 + j + } + f, err := fs.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) + if err != nil { + return 0, fmt.Errorf("error opening %s: %s", name, err.Error()) + } + defer f.Close() + bytes, err := f.Write(buf) + if err != nil { + return 0, fmt.Errorf("error writing %s: %s", name, err.Error()) + } + return bytes, nil +} + +/* +func cd(argv []string) { + + if fatfs == nil || rootdir == nil { + mnt(nil) + } + if len(argv) == 1 { + currdir = rootdir + return + } + tgt := "" + if len(argv) == 2 { + tgt = strings.TrimSpace(argv[1]) + } + if debug { + println("Trying to cd to " + tgt) + } + if tgt == "" { + println("Usage: cd ") + return + } + if debug { + println("Getting entry") + } + entry := currdir.Entry(tgt) + if entry == nil { + println("File not found: " + tgt) + return + } + if !entry.IsDir() { + println("Not a directory: " + tgt) + return + } + if debug { + println("Getting dir") + } + cd, err := entry.Dir() + if err != nil { + println("Could not cd to " + tgt + ": " + err.Error()) + } + currdir = cd +} +*/ + +func cat(argv []string) { + tgt := "" + if len(argv) == 2 { + tgt = strings.TrimSpace(argv[1]) + } + if debug { + println("Trying to cat to " + tgt) + } + if tgt == "" { + println("Usage: cat ") + return + } + if debug { + println("Getting entry") + } + f, err := fs.Open(tgt) + if err != nil { + println("Could not open: " + err.Error()) + return + } + defer f.Close() + if f.IsDir() { + println("Not a file: " + tgt) + return + } + off := 0x0 + buf := make([]byte, 64) + for { + n, err := f.Read(buf) + if err != nil { + if err == io.EOF { + break + } + println("Error reading " + tgt + ": " + err.Error()) + time.Sleep(1 * time.Second) + } + xxdfprint(os.Stdout, uint32(off), buf[:n]) + off += 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 > 512 || size < 1 { + fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", 512) + return + } + fallthrough + case 2: + /* + if argv[1][:2] != "0x" { + println("Invalid hex address (should start with 0x)") + return + } + */ + 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 \r\n") + return + } + buf := make([]byte, size) + //bsz := uint64(flash.SectorSize) + //blockdev.ReadBlock(uint32(addr/bsz), uint32(addr%bsz), buf) + flashdev.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])) || b[i+j] >= 0x80 { + buf16[j] = '.' + } else { + buf16[j] = b[i+j] + } + } + console.Write(buf16) + println() + } +} + +func prompt() { + print("==> ") +} diff --git a/examples/sdcard/tinyfs/feather-m4.go b/examples/sdcard/tinyfs/feather-m4.go new file mode 100644 index 0000000..8ad3689 --- /dev/null +++ b/examples/sdcard/tinyfs/feather-m4.go @@ -0,0 +1,17 @@ +// +build feather_m4 feather_m4_can feather_nrf52840 + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D10 + + ledPin = machine.LED +} diff --git a/examples/sdcard/tinyfs/grandcentral-m4.go b/examples/sdcard/tinyfs/grandcentral-m4.go new file mode 100644 index 0000000..8f1d6e0 --- /dev/null +++ b/examples/sdcard/tinyfs/grandcentral-m4.go @@ -0,0 +1,17 @@ +// +build grandcentral-m4 + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI1 + sckPin = machine.SDCARD_SCK_PIN + sdoPin = machine.SDCARD_SDO_PIN + sdiPin = machine.SDCARD_SDI_PIN + csPin = machine.SDCARD_CS_PIN + + ledPin = machine.LED +} diff --git a/examples/sdcard/tinyfs/m0.go b/examples/sdcard/tinyfs/m0.go new file mode 100644 index 0000000..8571cc3 --- /dev/null +++ b/examples/sdcard/tinyfs/m0.go @@ -0,0 +1,17 @@ +// +build atsamd21,!p1am_100 + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D2 + + ledPin = machine.LED +} diff --git a/examples/sdcard/tinyfs/main.go b/examples/sdcard/tinyfs/main.go new file mode 100644 index 0000000..0acf38b --- /dev/null +++ b/examples/sdcard/tinyfs/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "machine" + "time" + + "github.com/tinygo-org/tinyfs/fatfs" + "tinygo.org/x/drivers/examples/sdcard/tinyfs/console" + "tinygo.org/x/drivers/sdcard" +) + +var ( + spi machine.SPI + sckPin machine.Pin + sdoPin machine.Pin + sdiPin machine.Pin + csPin machine.Pin + ledPin machine.Pin +) + +func main() { + waitSerial() + + sd := sdcard.New(spi, sckPin, sdoPin, sdiPin, csPin) + err := sd.Configure() + if err != nil { + fmt.Printf("%s\r\n", err.Error()) + for { + time.Sleep(time.Hour) + } + } + + filesystem := fatfs.New(&sd) + + // Configure FATFS with sector size (must match value in ff.h - use 512) + filesystem.Configure(&fatfs.Config{ + SectorSize: 512, + }) + + console.RunFor(&sd, filesystem) +} + +// Wait for user to open serial console +func waitSerial() { + for !machine.Serial.DTR() { + time.Sleep(100 * time.Millisecond) + } +} diff --git a/examples/sdcard/tinyfs/p1am-100.go b/examples/sdcard/tinyfs/p1am-100.go new file mode 100644 index 0000000..38268e7 --- /dev/null +++ b/examples/sdcard/tinyfs/p1am-100.go @@ -0,0 +1,17 @@ +// +build p1am_100 + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SDCARD_SPI + sckPin = machine.SDCARD_SCK_PIN + sdoPin = machine.SDCARD_SDO_PIN + sdiPin = machine.SDCARD_SDI_PIN + csPin = machine.SDCARD_SS_PIN + + ledPin = machine.LED +} diff --git a/examples/sdcard/tinyfs/pygamer.go b/examples/sdcard/tinyfs/pygamer.go new file mode 100644 index 0000000..217d3a5 --- /dev/null +++ b/examples/sdcard/tinyfs/pygamer.go @@ -0,0 +1,17 @@ +// +build pygamer + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D4 + + ledPin = machine.LED +} diff --git a/examples/sdcard/tinyfs/pyportal.go b/examples/sdcard/tinyfs/pyportal.go new file mode 100644 index 0000000..0f27c3e --- /dev/null +++ b/examples/sdcard/tinyfs/pyportal.go @@ -0,0 +1,17 @@ +// +build pyportal + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI0 + sckPin = machine.SPI0_SCK_PIN + sdoPin = machine.SPI0_SDO_PIN + sdiPin = machine.SPI0_SDI_PIN + csPin = machine.D32 // SD_CS + + ledPin = machine.LED +} diff --git a/examples/sdcard/tinyfs/wioterminal.go b/examples/sdcard/tinyfs/wioterminal.go new file mode 100644 index 0000000..0eef41b --- /dev/null +++ b/examples/sdcard/tinyfs/wioterminal.go @@ -0,0 +1,17 @@ +// +build wioterminal + +package main + +import ( + "machine" +) + +func init() { + spi = machine.SPI2 + sckPin = machine.SCK2 + sdoPin = machine.SDO2 + sdiPin = machine.SDI2 + csPin = machine.SS2 + + ledPin = machine.LED +} diff --git a/go.mod b/go.mod index edaa765..d08da0f 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.15 require ( github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/frankban/quicktest v1.10.2 + github.com/tinygo-org/tinyfs v0.0.0-20210514090915-924e60a7bcf8 ) diff --git a/go.sum b/go.sum index 2c57487..c049244 100644 --- a/go.sum +++ b/go.sum @@ -9,5 +9,10 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/tinygo-org/tinyfs v0.0.0-20210514004344-b02c062d12c9 h1:66or7MohOph3xr3gMGCXceLkd+MBoUbV/rPPjl8iQOU= +github.com/tinygo-org/tinyfs v0.0.0-20210514004344-b02c062d12c9/go.mod h1:HGuyo42bGd1zWSuS1gwgyyjN36ZZMlEpwM0vSrglmXc= +github.com/tinygo-org/tinyfs v0.0.0-20210514090915-924e60a7bcf8 h1:pYwuKe1XuNeJnGV1UjeZ0FhuZ5+lapIq1NUmGacbBwo= +github.com/tinygo-org/tinyfs v0.0.0-20210514090915-924e60a7bcf8/go.mod h1:HGuyo42bGd1zWSuS1gwgyyjN36ZZMlEpwM0vSrglmXc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= diff --git a/sdcard/README.md b/sdcard/README.md new file mode 100644 index 0000000..cd59aed --- /dev/null +++ b/sdcard/README.md @@ -0,0 +1,20 @@ +# SPI sdcard/mmc driver + +This package provides the driver for sdcard/mmc with SPI connection. +`examples/sdcard/console` shows a low-level access example. +`examples/sdcard/tinyfs` shows an example of using fatfs to read FAT32. + +If you use this package, you need to set `default-stack-size` in `targets/*.json`. +For example, `targets/wioterminal.json` has the following configuration. + +``` +{ + "inherits": ["atsamd51p19a"], + "build-tags": ["wioterminal"], + "flash-1200-bps-reset": "true", + "flash-method": "msd", + "msd-volume-name": "Arduino", + "msd-firmware-name": "firmware.uf2", + "default-stack-size": 2048 +} +``` diff --git a/sdcard/sdcard.go b/sdcard/sdcard.go index 902c63e..b8955e8 100644 --- a/sdcard/sdcard.go +++ b/sdcard/sdcard.go @@ -23,8 +23,15 @@ const ( SD_CARD_TYPE_SDHC = 3 // High Capacity SD card ) +var ( + dummy [512]byte +) + type Device struct { bus machine.SPI + sck machine.Pin + sdo machine.Pin + sdi machine.Pin cs machine.Pin cmdbuf []byte dummybuf []byte @@ -34,10 +41,13 @@ type Device struct { CSD *CSD } -func New(b machine.SPI, cs machine.Pin) Device { +func New(b machine.SPI, sck, sdo, sdi, cs machine.Pin) Device { return Device{ bus: b, cs: cs, + sck: sck, + sdo: sdo, + sdi: sdi, cmdbuf: make([]byte, 6), dummybuf: make([]byte, 512), tokenbuf: make([]byte, 1), @@ -50,26 +60,38 @@ func (d *Device) Configure() error { } func (d *Device) initCard() error { + d.bus.Configure(machine.SPIConfig{ + SCK: d.sck, + SDO: d.sdo, + SDI: d.sdi, + Frequency: 250000, + LSBFirst: false, + Mode: 0, // phase=0, polarity=0 + }) + // set pin modes d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput}) d.cs.High() - // clock card at least 100 cycles with cs high - for i := 0; i < 16; i++ { - d.bus.Transfer(byte(0xFF)) + for i := range dummy { + dummy[i] = 0xFF } + // clock card at least 100 cycles with cs high + d.bus.Tx(dummy[:10], nil) + d.cs.Low() + d.bus.Tx(dummy[:], nil) // CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts) ok := false - for i := 0; i < 2000; i++ { + tm := setTimeout(0, 2*time.Second) + for !tm.expired() { // Wait up to 2 seconds to be the same as the Arduino if d.cmd(CMD0_GO_IDLE_STATE, 0, 0x95) == _R1_IDLE_STATE { ok = true break } - time.Sleep(1 * time.Millisecond) } if !ok { return fmt.Errorf("no SD card") @@ -115,20 +137,21 @@ func (d *Device) initCard() error { // check for timeout ok = false - for i := 0; i < 2000; i++ { + tm = setTimeout(0, 2*time.Second) + for !tm.expired() { if d.acmd(ACMD41_SD_APP_OP_COND, arg) == 0 { ok = true break } - time.Sleep(1 * time.Millisecond) } + if !ok { return fmt.Errorf("SD_CARD_ERROR_ACMD41") } // if SD2 read OCR register to check for SDHC card if d.sdCardType == SD_CARD_TYPE_SD2 { - if d.cmd(CMD58_READ_OCR, 0, 0) != 0 { + if d.cmd(CMD58_READ_OCR, 0, 0xFF) != 0 { return fmt.Errorf("SD_CARD_ERROR_CMD58") } @@ -145,6 +168,10 @@ func (d *Device) initCard() error { } } + if d.cmd(CMD16_SET_BLOCKLEN, 0x0200, 0xFF) != 0 { + return fmt.Errorf("SD_CARD_ERROR_CMD16") + } + var buf [16]byte // read CID err := d.ReadCID(buf[:]) @@ -162,19 +189,28 @@ func (d *Device) initCard() error { d.cs.High() + d.bus.Configure(machine.SPIConfig{ + SCK: d.sck, + SDO: d.sdo, + SDI: d.sdi, + Frequency: 4000000, + LSBFirst: false, + Mode: 0, // phase=0, polarity=0 + }) + return nil } func (d Device) acmd(cmd byte, arg uint32) byte { - d.cmd(CMD55_APP_CMD, 0, 0) - return d.cmd(cmd, arg, 0) + d.cmd(CMD55_APP_CMD, 0, 0xFF) + return d.cmd(cmd, arg, 0xFF) } func (d Device) cmd(cmd byte, arg uint32, crc byte) byte { d.cs.Low() if cmd != 12 { - d.waitNotBusy(300) + d.waitNotBusy(300 * time.Millisecond) } // create and send the command @@ -209,8 +245,9 @@ func (d Device) cmd(cmd byte, arg uint32, crc byte) byte { return 0xFF // -1 } -func (d Device) waitNotBusy(timeoutMs int) error { - for i := 0; i < timeoutMs; i++ { +func (d Device) waitNotBusy(timeout time.Duration) error { + tm := setTimeout(1, timeout) + for !tm.expired() { r, err := d.bus.Transfer(byte(0xFF)) if err != nil { return err @@ -218,8 +255,6 @@ func (d Device) waitNotBusy(timeoutMs int) error { if r == 0xFF { return nil } - - time.Sleep(1 * time.Millisecond) } return nil } @@ -227,7 +262,8 @@ func (d Device) waitNotBusy(timeoutMs int) error { func (d Device) waitStartBlock() error { status := byte(0xFF) - for i := 0; i < 3000; i++ { + tm := setTimeout(0, 300*time.Millisecond) + for !tm.expired() { var err error status, err = d.bus.Transfer(byte(0xFF)) if err != nil { @@ -237,7 +273,6 @@ func (d Device) waitStartBlock() error { if status != 0xFF { break } - time.Sleep(100 * time.Microsecond) } if status != 254 { @@ -259,7 +294,7 @@ func (d Device) ReadCID(csd []byte) error { } func (d Device) readRegister(cmd uint8, dst []byte) error { - if d.cmd(cmd, 0, 0) != 0 { + if d.cmd(cmd, 0, 0xFF) != 0 { return fmt.Errorf("SD_CARD_ERROR_READ_REG") } if err := d.waitStartBlock(); err != nil { @@ -290,14 +325,14 @@ func (d Device) ReadData(block uint32, dst []byte) error { if d.sdCardType != SD_CARD_TYPE_SDHC { block <<= 9 } - if d.cmd(CMD17_READ_SINGLE_BLOCK, block, 0) != 0 { + if d.cmd(CMD17_READ_SINGLE_BLOCK, block, 0xFF) != 0 { return fmt.Errorf("CMD17 error") } if err := d.waitStartBlock(); err != nil { return fmt.Errorf("waitStartBlock()") } - err := d.bus.Tx(nil, dst) + err := d.bus.Tx(dummy[:512], dst) if err != nil { return err } @@ -318,7 +353,7 @@ func (d Device) WriteMultiStart(block uint32) error { if d.sdCardType != SD_CARD_TYPE_SDHC { block <<= 9 } - if d.cmd(CMD25_WRITE_MULTIPLE_BLOCK, block, 0) != 0 { + if d.cmd(CMD25_WRITE_MULTIPLE_BLOCK, block, 0xFF) != 0 { return fmt.Errorf("CMD25 error") } @@ -355,7 +390,7 @@ func (d Device) WriteMulti(buf []byte) error { } // wait no busy - err = d.waitNotBusy(600) + err = d.waitNotBusy(600 * time.Millisecond) if err != nil { return fmt.Errorf("SD_CARD_ERROR_WRITE_TIMEOUT") } @@ -373,7 +408,7 @@ func (d Device) WriteMultiStop() error { // skip 1 byte d.bus.Transfer(byte(0xFF)) - err := d.waitNotBusy(600) + err := d.waitNotBusy(600 * time.Millisecond) if err != nil { return nil } @@ -391,7 +426,7 @@ func (d Device) WriteData(block uint32, src []byte) error { if d.sdCardType != SD_CARD_TYPE_SDHC { block <<= 9 } - if d.cmd(CMD24_WRITE_BLOCK, block, 0) != 0 { + if d.cmd(CMD24_WRITE_BLOCK, block, 0xFF) != 0 { return fmt.Errorf("CMD24 error") } @@ -418,7 +453,7 @@ func (d Device) WriteData(block uint32, src []byte) error { } // wait no busy - err = d.waitNotBusy(600) + err = d.waitNotBusy(600 * time.Millisecond) if err != nil { return fmt.Errorf("SD_CARD_ERROR_WRITE_TIMEOUT") } diff --git a/sdcard/timer.go b/sdcard/timer.go new file mode 100644 index 0000000..6019a9a --- /dev/null +++ b/sdcard/timer.go @@ -0,0 +1,22 @@ +package sdcard + +import ( + "time" +) + +var timeoutTimer [2]timer + +type timer struct { + start int64 + timeout int64 +} + +func setTimeout(timerID int, timeout time.Duration) *timer { + timeoutTimer[timerID].start = time.Now().UnixNano() + timeoutTimer[timerID].timeout = timeout.Nanoseconds() + return &timeoutTimer[timerID] +} + +func (t timer) expired() bool { + return time.Now().UnixNano() > (t.start + t.timeout) +}