mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-06 16:03:41 +00:00
410 lines
7.9 KiB
Go
410 lines
7.9 KiB
Go
package sd
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"runtime"
|
|
"strconv"
|
|
"time"
|
|
|
|
"tinygo.org/x/drivers"
|
|
)
|
|
|
|
var (
|
|
errNoSDCard = errors.New("sd:no card")
|
|
errCardNotSupported = errors.New("sd:card not supported")
|
|
errCmd8 = errors.New("sd:cmd8")
|
|
errCmdOCR = errors.New("sd:cmd_ocr")
|
|
errCmdBlkLen = errors.New("sd:cmd_blklen")
|
|
errAcmdAppCond = errors.New("sd:acmd_appOrCond")
|
|
errWaitStartBlock = errors.New("sd:wait start block")
|
|
errNeed512 = errors.New("sd:need 512 bytes for I/O")
|
|
errWrite = errors.New("sd:write")
|
|
errWriteTimeout = errors.New("sd:write timeout")
|
|
)
|
|
|
|
type digitalPinout func(b bool)
|
|
|
|
type Card struct {
|
|
bus drivers.SPI
|
|
cs digitalPinout
|
|
bufcmd [6]byte
|
|
buf [512]byte
|
|
bufTok [1]byte
|
|
kind CardKind
|
|
cid CID
|
|
csd CSD
|
|
lastCRC uint16
|
|
}
|
|
|
|
func NewCard(spi drivers.SPI, cs digitalPinout) *Card {
|
|
return &Card{bus: spi, cs: cs}
|
|
}
|
|
|
|
func (c *Card) csEnable(b bool) { c.cs(!b) }
|
|
|
|
// LastReadCRC returns the CRC for the last ReadBlock operation.
|
|
func (c *Card) LastReadCRC() uint16 { return c.lastCRC }
|
|
|
|
func (d *Card) Init() error {
|
|
dummy := d.buf[:]
|
|
for i := range dummy {
|
|
dummy[i] = 0xFF
|
|
}
|
|
defer d.csEnable(false)
|
|
|
|
d.csEnable(true)
|
|
// clock card at least 100 cycles with cs high
|
|
d.bus.Tx(dummy[:10], nil)
|
|
d.csEnable(false)
|
|
|
|
d.bus.Tx(dummy[:], nil)
|
|
|
|
// CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts)
|
|
ok := false
|
|
tm := setTimeout(0, 2*time.Second)
|
|
for !tm.expired() {
|
|
// Wait up to 2 seconds to be the same as the Arduino
|
|
result, err := d.cmd(CMD0_GO_IDLE_STATE, 0, 0x95)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if result == _R1_IDLE_STATE {
|
|
ok = true
|
|
break
|
|
}
|
|
}
|
|
if !ok {
|
|
return errNoSDCard
|
|
}
|
|
|
|
// CMD8: determine card version
|
|
r1, err := d.cmd(CMD8_SEND_IF_COND, 0x01AA, 0x87)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if r1.IllegalCmdError() {
|
|
d.kind = TypeSD1
|
|
return errCardNotSupported
|
|
} 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 makeResponseError(response1(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 makeResponseError(response1(status))
|
|
}
|
|
d.kind = TypeSD2
|
|
}
|
|
|
|
// initialize card and send host supports SDHC if SD2
|
|
arg := uint32(0)
|
|
if d.kind == TypeSD2 {
|
|
arg = 0x40000000
|
|
}
|
|
|
|
// check for timeout
|
|
ok = false
|
|
tm = setTimeout(0, 2*time.Second)
|
|
for !tm.expired() {
|
|
r1, err = d.appCmd(ACMD41_SD_APP_OP_COND, arg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if r1 == 0 {
|
|
break
|
|
}
|
|
}
|
|
if r1 != 0 {
|
|
return makeResponseError(r1)
|
|
}
|
|
|
|
// if SD2 read OCR register to check for SDHC card
|
|
if d.kind == TypeSD2 {
|
|
err := d.cmdEnsure0Status(CMD58_READ_OCR, 0, 0xFF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
statusb, err := d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (statusb & 0xC0) == 0xC0 {
|
|
d.kind = TypeSDHC
|
|
}
|
|
// discard rest of ocr - contains allowed voltage range
|
|
for i := 1; i < 4; i++ {
|
|
d.bus.Transfer(byte(0xFF))
|
|
}
|
|
}
|
|
err = d.cmdEnsure0Status(CMD16_SET_BLOCKLEN, 0x0200, 0xff)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// read CID
|
|
d.cid, err = d.readCID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.csd, err = d.readCSD()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadData reads 512 bytes from sdcard into dst.
|
|
func (d *Card) ReadBlock(block uint32, dst []byte) error {
|
|
if len(dst) != 512 {
|
|
return errNeed512
|
|
}
|
|
|
|
// use address if not SDHC card
|
|
if d.kind != TypeSDHC {
|
|
block <<= 9
|
|
}
|
|
err := d.cmdEnsure0Status(CMD17_READ_SINGLE_BLOCK, block, 0xFF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer d.csEnable(false)
|
|
|
|
if err := d.waitStartBlock(); err != nil {
|
|
return err
|
|
}
|
|
buf := d.buf[:]
|
|
err = d.bus.Tx(buf, dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// skip CRC (2byte)
|
|
hi, _ := d.bus.Transfer(byte(0xFF))
|
|
lo, _ := d.bus.Transfer(byte(0xFF))
|
|
d.lastCRC = uint16(hi)<<8 | uint16(lo)
|
|
return nil
|
|
}
|
|
|
|
// WriteBlock writes 512 bytes from dst to sdcard.
|
|
func (d *Card) WriteBlock(block uint32, src []byte) error {
|
|
if len(src) != 512 {
|
|
return errNeed512
|
|
}
|
|
|
|
// use address if not SDHC card
|
|
if d.kind != TypeSDHC {
|
|
block <<= 9
|
|
}
|
|
err := d.cmdEnsure0Status(CMD24_WRITE_BLOCK, block, 0xFF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer d.csEnable(false)
|
|
// 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 errWrite
|
|
}
|
|
|
|
// wait no busy
|
|
err = d.waitNotBusy(600 * time.Millisecond)
|
|
if err != nil {
|
|
return errWriteTimeout
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CID returns a copy of the Card Identification Register value last read.
|
|
func (d *Card) CID() CID { return d.cid }
|
|
|
|
// CSD returns a copy of the Card Specific Data Register value last read.
|
|
func (d *Card) CSD() CSD { return d.csd }
|
|
|
|
func (d *Card) readCID() (CID, error) {
|
|
buf := d.buf[len(d.buf)-16:]
|
|
if err := d.readRegister(CMD10_SEND_CID, buf); err != nil {
|
|
return CID{}, err
|
|
}
|
|
return DecodeCID(buf)
|
|
}
|
|
|
|
func (d *Card) readCSD() (CSD, error) {
|
|
buf := d.buf[len(d.buf)-16:]
|
|
if err := d.readRegister(CMD9_SEND_CSD, buf); err != nil {
|
|
return CSD{}, err
|
|
}
|
|
return DecodeCSD(buf)
|
|
}
|
|
|
|
func (d *Card) readRegister(cmd uint8, dst []byte) error {
|
|
err := d.cmdEnsure0Status(cmd, 0, 0xFF)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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
|
|
}
|
|
// skip CRC.
|
|
d.bus.Transfer(byte(0xFF))
|
|
d.bus.Transfer(byte(0xFF))
|
|
d.csEnable(false)
|
|
return nil
|
|
}
|
|
|
|
func (d *Card) appCmd(cmd byte, arg uint32) (response1, error) {
|
|
status, err := d.cmd(CMD55_APP_CMD, 0, 0xFF)
|
|
if err != nil {
|
|
return status, err
|
|
}
|
|
return d.cmd(cmd, arg, 0xFF)
|
|
}
|
|
|
|
func (d *Card) cmdEnsure0Status(cmd byte, arg uint32, crc byte) error {
|
|
status, err := d.cmd(cmd, arg, crc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if status != 0 {
|
|
return makeResponseError(status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) {
|
|
d.csEnable(true)
|
|
|
|
if cmd != 12 {
|
|
d.waitNotBusy(300 * time.Millisecond)
|
|
}
|
|
|
|
// create and send the command
|
|
buf := d.bufcmd[:]
|
|
buf[0] = 0x40 | cmd
|
|
binary.BigEndian.PutUint32(buf[1:5], 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)
|
|
d.buf[0] = 0xFF
|
|
dummy := d.buf[:1]
|
|
for i := 0; i < 0xFFFF; i++ {
|
|
d.bus.Tx(dummy, d.bufTok[:])
|
|
response := response1(d.bufTok[0])
|
|
if (response & 0x80) == 0 {
|
|
return response, nil
|
|
}
|
|
}
|
|
|
|
// TODO
|
|
//// timeout
|
|
d.csEnable(false)
|
|
d.bus.Transfer(byte(0xFF))
|
|
|
|
return 0xFF, nil // -1
|
|
}
|
|
|
|
func (d *Card) waitNotBusy(timeout time.Duration) error {
|
|
tm := setTimeout(1, timeout)
|
|
for !tm.expired() {
|
|
r, err := d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if r == 0xFF {
|
|
return nil
|
|
}
|
|
runtime.Gosched()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Card) waitStartBlock() error {
|
|
status := byte(0xFF)
|
|
|
|
tm := setTimeout(0, 300*time.Millisecond)
|
|
for !tm.expired() {
|
|
var err error
|
|
status, err = d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
d.csEnable(false)
|
|
return err
|
|
}
|
|
if status != 0xFF {
|
|
break
|
|
}
|
|
}
|
|
|
|
if status != 254 {
|
|
d.csEnable(false)
|
|
return errWaitStartBlock
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type response1Err struct {
|
|
context string
|
|
status response1
|
|
}
|
|
|
|
func (e response1Err) Error() string {
|
|
if e.context != "" {
|
|
return "sd:" + e.context + " " + strconv.Itoa(int(e.status))
|
|
}
|
|
return "sd:status " + strconv.Itoa(int(e.status))
|
|
}
|
|
|
|
func makeResponseError(status response1) error {
|
|
return response1Err{
|
|
status: status,
|
|
}
|
|
}
|