sdcard: add support for fatfs

This commit is contained in:
sago35
2021-05-14 19:03:12 +09:00
committed by Ron Evans
parent 5a02fe068b
commit 6b914f7af7
25 changed files with 870 additions and 67 deletions
+2
View File
@@ -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 \
+2 -1
View File
@@ -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 |
+1 -1
View File
@@ -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]
+4 -10
View File
@@ -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
@@ -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
}
+4 -10
View File
@@ -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
+4 -1
View File
@@ -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())
+17
View File
@@ -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
}
+17
View File
@@ -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
}
+3 -9
View File
@@ -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
+3 -9
View File
@@ -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
+519
View File
@@ -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 <target dir>")
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 <target dir>")
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 <target dir>")
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 <target dir>")
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 <hex address, ex: 0xA0> <size of hexdump in bytes>\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("==> ")
}
+17
View File
@@ -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
}
+17
View File
@@ -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
}
+17
View File
@@ -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
}
+49
View File
@@ -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)
}
}
+17
View File
@@ -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
}
+17
View File
@@ -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
}
+17
View File
@@ -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
}
+17
View File
@@ -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
}
+1
View File
@@ -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
)
+5
View File
@@ -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=
+20
View File
@@ -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
}
```
+61 -26
View File
@@ -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")
}
+22
View File
@@ -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)
}