From d07ad40373f473886d64d09a6f7a73801cb8613d Mon Sep 17 00:00:00 2001 From: sago35 Date: Fri, 14 May 2021 18:40:29 +0900 Subject: [PATCH] sdcard: add support for spi sdcard driver --- Makefile | 4 +- examples/sdcard/console/console.go | 363 +++++++++++++++ examples/sdcard/console/feather-m4.go | 23 + examples/sdcard/console/m0.go | 23 + examples/sdcard/console/main.go | 48 ++ examples/sdcard/console/pyportal.go | 23 + examples/sdcard/console/wioterminal.go | 23 + sdcard/cid.go | 49 ++ sdcard/const.go | 52 +++ sdcard/csd.go | 106 +++++ sdcard/sdcard.go | 606 +++++++++++++++++++++++++ 11 files changed, 1319 insertions(+), 1 deletion(-) create mode 100644 examples/sdcard/console/console.go create mode 100644 examples/sdcard/console/feather-m4.go create mode 100644 examples/sdcard/console/m0.go create mode 100644 examples/sdcard/console/main.go create mode 100644 examples/sdcard/console/pyportal.go create mode 100644 examples/sdcard/console/wioterminal.go create mode 100644 sdcard/cid.go create mode 100644 sdcard/const.go create mode 100644 sdcard/csd.go create mode 100644 sdcard/sdcard.go diff --git a/Makefile b/Makefile index 104734a..5ccd5ed 100644 --- a/Makefile +++ b/Makefile @@ -193,12 +193,14 @@ endif @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/ + @md5sum ./build/test.hex DRIVERS = $(wildcard */) NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \ hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \ hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x dht keypad4x4 max72xx p1am tone tm1637 \ - pcf8563 mcp2515 servo + pcf8563 mcp2515 servo sdcard TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS)) unit-test: diff --git a/examples/sdcard/console/console.go b/examples/sdcard/console/console.go new file mode 100644 index 0000000..4099a2d --- /dev/null +++ b/examples/sdcard/console/console.go @@ -0,0 +1,363 @@ +package main + +import ( + "fmt" + "io" + "machine" + "os" + "strconv" + "strings" + "time" + + "tinygo.org/x/drivers/sdcard" +) + +const consoleBufLen = 64 +const storageBufLen = 1024 + +var ( + debug = false + + input [consoleBufLen]byte + store [storageBufLen]byte + + console = machine.Serial + + dev *sdcard.Device + + commands map[string]cmdfunc = map[string]cmdfunc{ + "": cmdfunc(noop), + "help": cmdfunc(help), + "dbg": cmdfunc(dbg), + "erase": cmdfunc(erase), + "lsblk": cmdfunc(lsblk), + "write": cmdfunc(write), + "xxd": cmdfunc(xxd), + } + + his history +) + +type history struct { + buf [32]string + wp int + idx int +} + +func (h *history) Add(cmd string) { + if len(cmd) == 0 { + h.idx = h.wp + return + } + + if h.wp == len(h.buf)-1 { + for i := 1; i < len(h.buf); i++ { + h.buf[i-1] = h.buf[i] + } + h.wp-- + } + + h.buf[h.wp] = cmd + h.wp++ + h.idx = h.wp +} + +func (h *history) PeekPrev() string { + if h.idx > 0 { + h.idx-- + } + return h.buf[h.idx] +} + +func (h *history) PeekNext() string { + if h.idx < h.wp { + h.idx++ + } + return h.buf[h.idx] +} + +type cmdfunc func(argv []string) + +const ( + StateInput = iota + StateEscape + StateEscBrc + StateCSI +) + +func RunFor(device *sdcard.Device) { + + dev = device + + prompt() + + var state = StateInput + + for i := 0; ; { + if console.Buffered() > 0 { + data, _ := console.ReadByte() + if debug { + fmt.Printf("\rdata: %x, his.idx: %d\r\n\r", data, his.idx) + 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])) + his.Add(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 + } + case StateEscBrc: + switch data { + case 0x41: + // up + println() + prompt() + cmd := his.PeekPrev() + i = len(cmd) + copy(input[:i], []byte(cmd)) + console.Write(input[:i]) + state = StateInput + case 0x42: + //down + println() + prompt() + cmd := his.PeekNext() + i = len(cmd) + copy(input[:i], []byte(cmd)) + console.Write(input[:i]) + state = StateInput + default: + // TODO: handle escape sequences + state = StateInput + } + default: + // TODO: handle escape sequences + state = StateInput + } + } else { + time.Sleep(1 * time.Millisecond) + } + } +} + +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 help(argv []string) { + fmt.Printf("help\r\n") + fmt.Printf("dbg\r\n") + fmt.Printf("erase\r\n") + fmt.Printf("lsblk\r\n") + fmt.Printf("write \r\n") + fmt.Printf("xxd \r\n") +} + +func dbg(argv []string) { + if debug { + debug = false + println("Console debbuging off") + } else { + debug = true + println("Console debbuging on") + } +} + +func lsblk(argv []string) { + csd := dev.CSD + sectors, err := csd.Sectors() + if err != nil { + fmt.Printf("%s\r\n", err.Error()) + return + } + cid := dev.CID + + fmt.Printf( + "\r\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"+ + "-Sectors: %d\r\n"+ + "-Bytes (Sectors * 512) %d\r\n"+ + "-ManufacturerID %02X\r\n"+ + "-OEMApplicationID %04X\r\n"+ + "-ProductName %s\r\n"+ + "-ProductVersion %s\r\n"+ + "-ProductSerialNumber %08X\r\n"+ + "-ManufacturingYear %02X\r\n"+ + "-ManufacturingMonth %02X\r\n"+ + "-Always1 %d\r\n"+ + "-CRC %02X\r\n"+ + "-------------------------------------\r\n\r\n", + "attrs.JedecID", // attrs.JedecID, + cid.ProductSerialNumber, // serialNumber1, + 0, // status1, + 0, // status2, + csd.TRAN_SPEED, // attrs.MaxClockSpeedMHz, + false, // attrs.HasSectorProtection, + false, // attrs.SupportsFastRead, + false, // attrs.SupportsQSPI, + false, // attrs.SupportsQSPIWrites, + false, // attrs.WriteStatusSplit, + false, // attrs.SingleStatusByte, + sectors, + csd.Size(), + cid.ManufacturerID, + cid.OEMApplicationID, + cid.ProductName, + cid.ProductVersion, + cid.ProductSerialNumber, + cid.ManufacturingYear, + cid.ManufacturingMonth, + cid.Always1, + cid.CRC, + ) +} + +func erase(argv []string) { + fmt.Printf("erase - not impl\r\n") +} + +var writeBuf [256]byte + +func write(argv []string) { + if len(argv) < 3 { + println("usage: write ") + return + } + 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 := writeBuf[:0] + for i := 0; i < len(argv[2]); i += 2 { + var b uint64 + if b, err = strconv.ParseUint(argv[2][i:i+2], 16, 8); err != nil { + println("Invalid bytes: " + err.Error() + "\r\n") + return + } + buf = append(buf, byte(b)) + } + + 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 \r\n") + return + } + buf := store[0:size] + _, err = dev.ReadAt(buf, int64(addr)) + if err != nil { + fmt.Printf("xxd err : %s\r\n", err.Error()) + } + 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] >= 0xFE { + buf16[j] = '.' + } else { + buf16[j] = b[i+j] + } + } + console.Write(buf16) + println() + } +} + +func prompt() { + print("==> ") +} diff --git a/examples/sdcard/console/feather-m4.go b/examples/sdcard/console/feather-m4.go new file mode 100644 index 0000000..541c218 --- /dev/null +++ b/examples/sdcard/console/feather-m4.go @@ -0,0 +1,23 @@ +// +build feather_m4 feather_m4_can + +package main + +import ( + "machine" +) + +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 + }) + + csPin = machine.D10 + + ledPin = machine.LED +} diff --git a/examples/sdcard/console/m0.go b/examples/sdcard/console/m0.go new file mode 100644 index 0000000..ddbad80 --- /dev/null +++ b/examples/sdcard/console/m0.go @@ -0,0 +1,23 @@ +// +build atsamd21 + +package main + +import ( + "machine" +) + +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 + }) + + csPin = machine.D2 + + ledPin = machine.LED +} diff --git a/examples/sdcard/console/main.go b/examples/sdcard/console/main.go new file mode 100644 index 0000000..171ac60 --- /dev/null +++ b/examples/sdcard/console/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/sdcard" +) + +var ( + spi machine.SPI + csPin machine.Pin + ledPin machine.Pin +) + +func main() { + waitSerial() + fmt.Printf("sdcard console\r\n") + + led := ledPin + led.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + sd := sdcard.New(spi, csPin) + err := sd.Configure() + if err != nil { + fmt.Printf("%s\r\n", err.Error()) + for { + time.Sleep(time.Hour) + } + } + + go RunFor(&sd) + + for { + led.High() + time.Sleep(200 * time.Millisecond) + led.Low() + time.Sleep(200 * time.Millisecond) + } +} + +// Wait for user to open serial console +func waitSerial() { + for !machine.Serial.DTR() { + time.Sleep(100 * time.Millisecond) + } +} diff --git a/examples/sdcard/console/pyportal.go b/examples/sdcard/console/pyportal.go new file mode 100644 index 0000000..5949e35 --- /dev/null +++ b/examples/sdcard/console/pyportal.go @@ -0,0 +1,23 @@ +// +build pyportal + +package main + +import ( + "machine" +) + +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 + }) + + csPin = machine.D32 // SD_CS + + ledPin = machine.LED +} diff --git a/examples/sdcard/console/wioterminal.go b/examples/sdcard/console/wioterminal.go new file mode 100644 index 0000000..9ed7d29 --- /dev/null +++ b/examples/sdcard/console/wioterminal.go @@ -0,0 +1,23 @@ +// +build wioterminal + +package main + +import ( + "machine" +) + +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 + }) + + csPin = machine.SS2 + + ledPin = machine.LED +} diff --git a/sdcard/cid.go b/sdcard/cid.go new file mode 100644 index 0000000..7a36471 --- /dev/null +++ b/sdcard/cid.go @@ -0,0 +1,49 @@ +package sdcard + +import "fmt" + +type CID struct { + //// byte 0 + ManufacturerID byte + //uint8_t mid; // Manufacturer ID + //// byte 1-2 + OEMApplicationID uint16 + //char oid[2]; // OEM/Application ID + //// byte 3-7 + ProductName string + //char pnm[5]; // Product name + //// byte 8 + ProductVersion string + //unsigned prv_m : 4; // Product revision n.m + //unsigned prv_n : 4; + //// byte 9-12 + ProductSerialNumber uint32 + //uint32_t psn; // Product serial number + //// byte 13 + ManufacturingYear byte + ManufacturingMonth byte + //unsigned mdt_year_high : 4; // Manufacturing date + //unsigned reserved : 4; + //// byte 14 + //unsigned mdt_month : 4; + //unsigned mdt_year_low : 4; + //// byte 15 + Always1 byte + //unsigned always1 : 1; + CRC byte + //unsigned crc : 7; +} + +func NewCID(buf []byte) *CID { + return &CID{ + ManufacturerID: buf[0], + OEMApplicationID: (uint16(buf[0]) << 8) | uint16(buf[1]), + ProductName: string(buf[3:8]), + ProductVersion: fmt.Sprintf("%d.%d", (buf[8]&0xF0)>>4, buf[8]&0x0F), + ProductSerialNumber: (uint32(buf[9]) << 24) | (uint32(buf[10]) << 16) | (uint32(buf[11]) << 8) | uint32(buf[12]), + ManufacturingYear: (buf[13] & 0xF0) | (buf[14] & 0x0F), + ManufacturingMonth: (buf[14] & 0xF0) >> 4, + Always1: (buf[15] & 0x80) >> 7, + CRC: buf[15] & 0x7F, + } +} diff --git a/sdcard/const.go b/sdcard/const.go new file mode 100644 index 0000000..66a1410 --- /dev/null +++ b/sdcard/const.go @@ -0,0 +1,52 @@ +package sdcard + +const ( + CMD0_GO_IDLE_STATE = 0 + CMD1_SEND_OP_CND = 1 + CMD2_ALL_SEND_CID = 2 + CMD3_SEND_RELATIVE_ADDR = 3 + CMD4_SET_DSR = 4 + CMD6_SWITCH_FUNC = 6 + CMD7_SELECT_DESELECT_CARD = 7 + CMD8_SEND_IF_COND = 8 + CMD9_SEND_CSD = 9 + CMD10_SEND_CID = 10 + CMD12_STOP_TRANSMISSION = 12 + CMD13_SEND_STATUS = 13 + CMD15_GO_INACTIVE_STATE = 15 + CMD16_SET_BLOCKLEN = 16 + CMD17_READ_SINGLE_BLOCK = 17 + CMD18_READ_MULTIPLE_BLOCK = 18 + CMD24_WRITE_BLOCK = 24 + CMD25_WRITE_MULTIPLE_BLOCK = 25 + CMD27_PROGRAM_CSD = 27 + CMD28_SET_WRITE_PROT = 28 + CMD29_CLR_WRITE_PROT = 29 + CMD30_SEND_WRITE_PROT = 30 + CMD32_ERASE_WR_BLK_START_ADDR = 32 + CMD33_ERASE_WR_BLK_END_ADDR = 33 + CMD38_ERASE = 38 + CMD42_LOCK_UNLOCK = 42 + CMD55_APP_CMD = 55 + CMD56_GEN_CMD = 56 + CMD58_READ_OCR = 58 + CMD59_CRC_ON_OFF = 59 + ACMD6_SET_BUS_WIDTH = 6 + ACMD13_SD_STATUS = 13 + ACMD22_SEND_NUM_WR_BLOCKS = 22 + ACMD23_SET_WR_BLK_ERASE_COUNT = 23 + ACMD41_SD_APP_OP_COND = 41 + ACMD42_SET_CLR_CARD_DETECT = 42 + ACMD51_SEND_SCR = 51 + ACMD18_SECURE_READ_MULTI_BLOCK = 18 + ACMD25_SECURE_WRITE_MULTI_BLOCK = 25 + ACMD26_SECURE_WRITE_MKB = 26 + ACMD38_SECURE_ERASE = 38 + ACMD43_GET_MKB = 43 + ACMD44_GET_MID = 44 + ACMD45_SET_CER_RN1 = 45 + ACMD46_SET_CER_RN2 = 46 + ACMD47_SET_CER_RES2 = 47 + ACMD48_SET_CER_RES1 = 48 + ACMD49_CHANGE_SECURE_AREA = 49 +) diff --git a/sdcard/csd.go b/sdcard/csd.go new file mode 100644 index 0000000..aa9a30d --- /dev/null +++ b/sdcard/csd.go @@ -0,0 +1,106 @@ +package sdcard + +import ( + "fmt" +) + +type CSD struct { + CSD_STRUCTURE byte // 2 R [127:126] 0x01 : CSD Structure + TAAC byte // 8 R [119:112] 0x0E : Data Read Access-Time-1 + NSAC byte // 8 R [111:104] 0x00 : Data Read Access-Time-2 in CLK Cycles (NSAC*100) + TRAN_SPEED byte // 8 R [103:96] 0x5A : Max. Data Transfer Rate + CCC uint16 // 12 R [95:84] 0x5B5 : Card Command Classes + READ_BL_LEN byte // 4 R [83:80] 0x09 : Max. Read Data Block Length + READ_BL_PARTIAL byte // 1 R [79:79] 0x00 : Partial Blocks for Read Allowed + WRITE_BLK_MISALIGN byte // 1 R [78:78] 0x00 : Write Block Misalignment + READ_BLK_MISALIGN byte // 1 R [77:77] 0x00 : Read Block Misalignment + DSR_IMP byte // 1 R [76:76] 0x00 : DSR Implemented + C_SIZE uint32 // 22 R [69:48] 0xXXXXXX : Device Size + ERASE_BLK_EN byte // 1 R [46:46] 0x01 : Erase Single Block Enable + SECTOR_SIZE byte // 7 R [45:39] 0x7F : Erase Sector Size + WP_GRP_SIZE byte // 7 R [38:32] 0x00 : Write Protect Group Size + WP_GRP_ENABLE byte // 1 R [31:31] 0x00 : Write Protect Group Enable + R2W_FACTOR byte // 3 R [28:26] 0x02 : Write Speed Factor + WRITE_BL_LEN byte // 4 R [25:22] 0x09 : Max. Write data Block Length + WRITE_BL_PARTIAL byte // 1 R [21:21] 0x00 : Partial Blocks for Write Allowed + FILE_FORMAT_GRP byte // 1 R [15:15] 0x00 : File Format Group + COPY byte // 1 RW [14:14] 0x00 :Copy Flag + PERM_WRITE_PROTECT byte // 1 RW [13:13] 0x00 : Permanent Write Protection + TMP_WRITE_PROTECT byte // 1 RW [12:12] 0x00 : Temporary Write Protection + FILE_FORMAT byte // 2 R [11:10] 0x00 : File Format + CRC byte // 7 RW [7:1] 0xXX : CRC +} + +func NewCSD(buf []byte) *CSD { + return &CSD{ + CSD_STRUCTURE: (buf[0] & 0xC0) >> 6, + TAAC: buf[1], + NSAC: buf[2], + TRAN_SPEED: buf[3], + CCC: uint16(buf[4])<<4 | uint16(buf[5])>>4, + READ_BL_LEN: buf[5] & 0x0F, + READ_BL_PARTIAL: (buf[6] & 0x80) >> 7, + WRITE_BLK_MISALIGN: (buf[6] & 0x40) >> 6, + READ_BLK_MISALIGN: (buf[6] & 0x20) >> 5, + DSR_IMP: (buf[6] & 0x10) >> 4, + C_SIZE: uint32(buf[7]&0x3F)<<16 | uint32(buf[8])<<8 | uint32(buf[9]), + ERASE_BLK_EN: (buf[10] & 0x40) >> 6, + SECTOR_SIZE: (buf[10]&0x3F)<<1 | (buf[11]&0x80)>>7, + WP_GRP_SIZE: buf[11] & 0x7F, + WP_GRP_ENABLE: (buf[12] & 0x80) >> 7, + R2W_FACTOR: (buf[12] & 0x1C) >> 2, + WRITE_BL_LEN: (buf[12]&0x03)<<2 | (buf[13]&0xC0)>>6, + WRITE_BL_PARTIAL: (buf[13] & 0x20) >> 5, + FILE_FORMAT_GRP: (buf[14] & 0x80) >> 7, + COPY: (buf[14] & 0x40) >> 6, + PERM_WRITE_PROTECT: (buf[14] & 0x20) >> 5, + TMP_WRITE_PROTECT: (buf[14] & 0x10) >> 4, + FILE_FORMAT: (buf[14] & 0x0C) >> 2, + CRC: (buf[15] & 0xFE) >> 1, + } +} + +func (c *CSD) Dump() { + fmt.Printf("CSD_STRUCTURE: %X\r\n", c.CSD_STRUCTURE) + fmt.Printf("TAAC: %X\r\n", c.TAAC) + fmt.Printf("NSAC: %X\r\n", c.NSAC) + fmt.Printf("TRAN_SPEED: %X\r\n", c.TRAN_SPEED) + fmt.Printf("CCC: %X\r\n", c.CCC) + fmt.Printf("READ_BL_LEN: %X\r\n", c.READ_BL_LEN) + fmt.Printf("READ_BL_PARTIAL: %X\r\n", c.READ_BL_PARTIAL) + fmt.Printf("WRITE_BLK_MISALIGN: %X\r\n", c.WRITE_BLK_MISALIGN) + fmt.Printf("READ_BLK_MISALIGN: %X\r\n", c.READ_BLK_MISALIGN) + fmt.Printf("DSR_IMP: %X\r\n", c.DSR_IMP) + fmt.Printf("C_SIZE: %X\r\n", c.C_SIZE) + fmt.Printf("ERASE_BLK_EN: %X\r\n", c.ERASE_BLK_EN) + fmt.Printf("SECTOR_SIZE: %X\r\n", c.SECTOR_SIZE) + fmt.Printf("WP_GRP_SIZE: %X\r\n", c.WP_GRP_SIZE) + fmt.Printf("WP_GRP_ENABLE: %X\r\n", c.WP_GRP_ENABLE) + fmt.Printf("R2W_FACTOR: %X\r\n", c.R2W_FACTOR) + fmt.Printf("WRITE_BL_LEN: %X\r\n", c.WRITE_BL_LEN) + fmt.Printf("WRITE_BL_PARTIAL: %X\r\n", c.WRITE_BL_PARTIAL) + fmt.Printf("FILE_FORMAT_GRP: %X\r\n", c.FILE_FORMAT_GRP) + fmt.Printf("COPY: %X\r\n", c.COPY) + fmt.Printf("PERM_WRITE_PROTECT: %X\r\n", c.PERM_WRITE_PROTECT) + fmt.Printf("TMP_WRITE_PROTECT: %X\r\n", c.TMP_WRITE_PROTECT) + fmt.Printf("FILE_FORMAT: %X\r\n", c.FILE_FORMAT) + fmt.Printf("CRC: %X\r\n", c.CRC) +} + +func (c *CSD) Sectors() (int64, error) { + sectors := int64(0) + if c.CSD_STRUCTURE == 0x01 { + // CSD version 2.0 + sectors = (int64(c.C_SIZE) + 1) * 1024 + } else if c.CSD_STRUCTURE == 0x00 { + // CSD version 1.0 (old, <=2GB) + return 0, fmt.Errorf("CSD format version 1.0 is not supported") + } else { + return 0, fmt.Errorf("unknown CSD format") + } + return sectors, nil +} + +func (c *CSD) Size() uint64 { + return uint64(c.C_SIZE) * 512 * 1024 +} diff --git a/sdcard/sdcard.go b/sdcard/sdcard.go new file mode 100644 index 0000000..902c63e --- /dev/null +++ b/sdcard/sdcard.go @@ -0,0 +1,606 @@ +package sdcard + +import ( + "fmt" + "machine" + "time" +) + +const ( + _CMD_TIMEOUT = 100 + + _R1_IDLE_STATE = 1 << 0 + _R1_ERASE_RESET = 1 << 1 + _R1_ILLEGAL_COMMAND = 1 << 2 + _R1_COM_CRC_ERROR = 1 << 3 + _R1_ERASE_SEQUENCE_ERROR = 1 << 4 + _R1_ADDRESS_ERROR = 1 << 5 + _R1_PARAMETER_ERROR = 1 << 6 + + // card types + SD_CARD_TYPE_SD1 = 1 // Standard capacity V1 SD card + SD_CARD_TYPE_SD2 = 2 // Standard capacity V2 SD card + SD_CARD_TYPE_SDHC = 3 // High Capacity SD card +) + +type Device struct { + bus machine.SPI + cs machine.Pin + cmdbuf []byte + dummybuf []byte + tokenbuf []byte + sdCardType byte + CID *CID + CSD *CSD +} + +func New(b machine.SPI, cs machine.Pin) Device { + return Device{ + bus: b, + cs: cs, + cmdbuf: make([]byte, 6), + dummybuf: make([]byte, 512), + tokenbuf: make([]byte, 1), + sdCardType: 0, + } +} + +func (d *Device) Configure() error { + return d.initCard() +} + +func (d *Device) initCard() error { + // 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)) + } + + d.cs.Low() + + // CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts) + ok := false + for i := 0; i < 2000; i++ { + // 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") + } + + // CMD8: determine card version + r := d.cmd(CMD8_SEND_IF_COND, 0x01AA, 0x87) + if (r & _R1_ILLEGAL_COMMAND) == _R1_ILLEGAL_COMMAND { + d.sdCardType = SD_CARD_TYPE_SD1 + return fmt.Errorf("init_card_v1 not impl\r\n") + } else { + // r7 response + status := byte(0) + for i := 0; i < 3; i++ { + var err error + status, err = d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + } + if (status & 0x0F) != 0x01 { + return fmt.Errorf("SD_CARD_ERROR_CMD8 %02X", status) + } + + for i := 3; i < 4; i++ { + var err error + status, err = d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + } + if status != 0xAA { + return fmt.Errorf("SD_CARD_ERROR_CMD8 %02X", status) + } + d.sdCardType = SD_CARD_TYPE_SD2 + } + + // initialize card and send host supports SDHC if SD2 + arg := uint32(0) + if d.sdCardType == SD_CARD_TYPE_SD2 { + arg = 0x40000000 + } + + // check for timeout + ok = false + for i := 0; i < 2000; i++ { + 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 { + return fmt.Errorf("SD_CARD_ERROR_CMD58") + } + + status, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + if (status & 0xC0) == 0xC0 { + d.sdCardType = SD_CARD_TYPE_SDHC + } + // discard rest of ocr - contains allowed voltage range + for i := 1; i < 4; i++ { + d.bus.Transfer(byte(0xFF)) + } + } + + var buf [16]byte + // read CID + err := d.ReadCID(buf[:]) + if err != nil { + return err + } + d.CID = NewCID(buf[:]) + + // read CSD + err = d.ReadCSD(buf[:]) + if err != nil { + return err + } + d.CSD = NewCSD(buf[:]) + + d.cs.High() + + return nil +} + +func (d Device) acmd(cmd byte, arg uint32) byte { + d.cmd(CMD55_APP_CMD, 0, 0) + return d.cmd(cmd, arg, 0) +} + +func (d Device) cmd(cmd byte, arg uint32, crc byte) byte { + d.cs.Low() + + if cmd != 12 { + d.waitNotBusy(300) + } + + // create and send the command + buf := d.cmdbuf + buf[0] = 0x40 | cmd + buf[1] = byte(arg >> 24) + buf[2] = byte(arg >> 16) + buf[3] = byte(arg >> 8) + buf[4] = byte(arg) + buf[5] = crc + d.bus.Tx(buf, nil) + + if cmd == 12 { + // skip 1 byte + d.bus.Transfer(byte(0xFF)) + } + + // wait for the response (response[7] == 0) + for i := 0; i < 0xFFFF; i++ { + d.bus.Tx([]byte{0xFF}, d.tokenbuf) + response := d.tokenbuf[0] + if (response & 0x80) == 0 { + return response + } + } + + // TODO + //// timeout + d.cs.High() + d.bus.Transfer(byte(0xFF)) + + return 0xFF // -1 +} + +func (d Device) waitNotBusy(timeoutMs int) error { + for i := 0; i < timeoutMs; i++ { + r, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + if r == 0xFF { + return nil + } + + time.Sleep(1 * time.Millisecond) + } + return nil +} + +func (d Device) waitStartBlock() error { + status := byte(0xFF) + + for i := 0; i < 3000; i++ { + var err error + status, err = d.bus.Transfer(byte(0xFF)) + if err != nil { + d.cs.High() + return err + } + if status != 0xFF { + break + } + time.Sleep(100 * time.Microsecond) + } + + if status != 254 { + d.cs.High() + return fmt.Errorf("SD_CARD_START_BLOCK") + } + + return nil +} + +// ReadCSD reads the CSD using CMD9. +func (d Device) ReadCSD(csd []byte) error { + return d.readRegister(CMD9_SEND_CSD, csd) +} + +// ReadCID reads the CID using CMD10 +func (d Device) ReadCID(csd []byte) error { + return d.readRegister(CMD10_SEND_CID, csd) +} + +func (d Device) readRegister(cmd uint8, dst []byte) error { + if d.cmd(cmd, 0, 0) != 0 { + return fmt.Errorf("SD_CARD_ERROR_READ_REG") + } + if err := d.waitStartBlock(); err != nil { + return err + } + // transfer data + for i := uint16(0); i < 16; i++ { + r, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + dst[i] = r + } + d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(byte(0xFF)) + d.cs.High() + + return nil +} + +// ReadData reads 512 bytes from sdcard into dst. +func (d Device) ReadData(block uint32, dst []byte) error { + if len(dst) < 512 { + return fmt.Errorf("len(dst) must be greater than or equal to 512") + } + + // use address if not SDHC card + if d.sdCardType != SD_CARD_TYPE_SDHC { + block <<= 9 + } + if d.cmd(CMD17_READ_SINGLE_BLOCK, block, 0) != 0 { + return fmt.Errorf("CMD17 error") + } + if err := d.waitStartBlock(); err != nil { + return fmt.Errorf("waitStartBlock()") + } + + err := d.bus.Tx(nil, dst) + if err != nil { + return err + } + + // skip CRC (2byte) + d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(byte(0xFF)) + + // TODO: probably not necessary + d.cs.High() + + return nil +} + +// WriteMultiStart starts the continuous write mode using CMD25. +func (d Device) WriteMultiStart(block uint32) error { + // use address if not SDHC card + if d.sdCardType != SD_CARD_TYPE_SDHC { + block <<= 9 + } + if d.cmd(CMD25_WRITE_MULTIPLE_BLOCK, block, 0) != 0 { + return fmt.Errorf("CMD25 error") + } + + // skip 1 byte + d.bus.Transfer(byte(0xFF)) + + return nil +} + +// WriteMulti performs continuous writing. It is necessary to call +// WriteMultiStart() in prior. +func (d Device) WriteMulti(buf []byte) error { + // send Data Token for CMD25 + d.bus.Transfer(byte(0xFC)) + + for i := 0; i < 512; i++ { + _, err := d.bus.Transfer(buf[i]) + if err != nil { + return err + } + } + + // send dummy CRC (2 byte) + d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(byte(0xFF)) + + // Data Resp. + r, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + if (r & 0x1F) != 0x05 { + return fmt.Errorf("SD_CARD_ERROR_WRITE") + } + + // wait no busy + err = d.waitNotBusy(600) + if err != nil { + return fmt.Errorf("SD_CARD_ERROR_WRITE_TIMEOUT") + } + + return nil +} + +// WriteMultiStop exits the continuous write mode. +func (d Device) WriteMultiStop() error { + defer d.cs.High() + + // Stop Tran token for CMD25 + d.bus.Transfer(0xFD) + + // skip 1 byte + d.bus.Transfer(byte(0xFF)) + + err := d.waitNotBusy(600) + if err != nil { + return nil + } + + return nil +} + +// WriteData writes 512 bytes from dst to sdcard. +func (d Device) WriteData(block uint32, src []byte) error { + if len(src) < 512 { + return fmt.Errorf("len(src) must be greater than or equal to 512") + } + + // use address if not SDHC card + if d.sdCardType != SD_CARD_TYPE_SDHC { + block <<= 9 + } + if d.cmd(CMD24_WRITE_BLOCK, block, 0) != 0 { + return fmt.Errorf("CMD24 error") + } + + // wait 1 byte? + token := byte(0xFE) + d.bus.Transfer(token) + + err := d.bus.Tx(src[:512], nil) + if err != nil { + return err + } + + // send dummy CRC (2 byte) + d.bus.Transfer(byte(0xFF)) + d.bus.Transfer(byte(0xFF)) + + // Data Resp. + r, err := d.bus.Transfer(byte(0xFF)) + if err != nil { + return err + } + if (r & 0x1F) != 0x05 { + return fmt.Errorf("SD_CARD_ERROR_WRITE") + } + + // wait no busy + err = d.waitNotBusy(600) + if err != nil { + return fmt.Errorf("SD_CARD_ERROR_WRITE_TIMEOUT") + } + + // TODO: probably not necessary + d.cs.High() + return nil +} + +// ReadAt reads the given number of bytes from the sdcard. +func (dev *Device) ReadAt(buf []byte, addr int64) (int, error) { + block := uint64(addr) + // use address if not SDHC card + if dev.sdCardType == SD_CARD_TYPE_SDHC { + block >>= 9 + } + + idx := uint32(0) + + start := uint32(addr % 512) + end := uint32(0) + remain := uint32(len(buf)) + + // If data starts in the middle + if 0 < start { + if start+remain <= 512 { + end = start + remain + } else { + end = 512 + } + + err := dev.ReadData(uint32(block), dev.dummybuf) + if err != nil { + return 0, err + } + copy(buf[idx:], dev.dummybuf[start:end]) + + remain -= end - start + idx += end - start + block++ + } + + // If more than 512 bytes left + for 512 <= remain { + start = 0 + end = 512 + + err := dev.ReadData(uint32(block), dev.dummybuf) + if err != nil { + return 0, err + } + copy(buf[idx:], dev.dummybuf[start:end]) + + remain -= end - start + idx += end - start + block++ + } + + // Read to the end + if 0 < remain { + start = 0 + end = remain + + err := dev.ReadData(uint32(block), dev.dummybuf) + if err != nil { + return 0, err + } + copy(buf[idx:], dev.dummybuf[start:end]) + + remain -= end - start + idx += end - start + block++ + } + + return int(idx), nil +} + +// WriteAt writes the given number of bytes to sdcard. +func (dev *Device) WriteAt(buf []byte, addr int64) (n int, err error) { + block := uint64(addr) + // use address if not SDHC card + if dev.sdCardType == SD_CARD_TYPE_SDHC { + block >>= 9 + } + + idx := uint32(0) + + start := uint32(addr % 512) + end := uint32(0) + remain := uint32(len(buf)) + + // If data starts in the middle + if 0 < start { + if start+remain <= 512 { + end = start + remain + } else { + end = 512 + } + + err := dev.ReadData(uint32(block), dev.dummybuf) + if err != nil { + return 0, err + } + copy(dev.dummybuf[start:end], buf[idx:]) + + err = dev.WriteData(uint32(block), dev.dummybuf) + if err != nil { + return 0, err + } + + remain -= end - start + idx += end - start + block++ + } + + // If more than 512 bytes left + for 512 <= remain { + start = 0 + end = 512 + + err := dev.WriteData(uint32(block), buf[idx:idx+512]) + if err != nil { + return 0, err + } + + remain -= end - start + idx += end - start + block++ + } + + // Write to the end + if 0 < remain { + start = 0 + end = remain + + err := dev.ReadData(uint32(block), dev.dummybuf) + if err != nil { + return 0, err + } + copy(dev.dummybuf[start:end], buf[idx:]) + + err = dev.WriteData(uint32(block), dev.dummybuf) + if err != nil { + return 0, err + } + + remain -= end - start + idx += end - start + block++ + } + + return int(idx), nil +} + +// Size returns the number of bytes in this sdcard. +func (dev *Device) Size() int64 { + return int64(dev.CSD.Size()) +} + +// WriteBlockSize returns the block size in which data can be written to +// memory. +func (dev *Device) WriteBlockSize() int64 { + return 512 +} + +// EraseBlockSize returns the smallest erasable area on this sdcard in bytes. +func (dev *Device) EraseBlockSize() int64 { + return 512 +} + +// EraseBlocks erases the given number of blocks. +func (dev *Device) EraseBlocks(start, len int64) error { + dev.WriteMultiStart(uint32(start)) + + for i := range dev.dummybuf { + dev.dummybuf[i] = 0 + } + + for i := 0; i < int(len); i++ { + dev.WriteMulti(dev.dummybuf) + } + + dev.WriteMultiStop() + return nil +}