diff --git a/examples/sd/main.go b/examples/sd/main.go index 440180c..555da63 100644 --- a/examples/sd/main.go +++ b/examples/sd/main.go @@ -38,9 +38,21 @@ func main() { if err != nil { panic(err.Error()) } + cid := sdcard.CID() + pname := cid.ProductName() csd := sdcard.CSD() - fmt.Printf("cid=%+v\ncsd=%+v\ncsdfmt=\n%s\ndone!", sdcard.CID(), csd, csd.String()) + valid := csd.IsValid() + if !valid { + data := csd.RawCopy() + crc := sd.CRC7(data[:15]) + always1 := data[15]&(1<<7) != 0 + println("CSD not valid got", crc, "want", data[15]&^(1<<7), "always1:", always1) + } else { + println("CSD valid!") + } + fmt.Printf("name=%s\ncsd=\n%s\n", pname, csd.String()) + return var buf [512]byte for i := 1; i < 11; i += 1 { time.Sleep(time.Millisecond) diff --git a/sd/card.go b/sd/card.go index 190fb83..51f32db 100644 --- a/sd/card.go +++ b/sd/card.go @@ -35,6 +35,7 @@ type Card struct { cid CID csd CSD lastCRC uint16 + timers [2]timer } func NewCard(spi drivers.SPI, cs digitalPinout) *Card { @@ -62,7 +63,7 @@ func (d *Card) Init() error { // CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts) ok := false - tm := setTimeout(0, 2*time.Second) + tm := d.timers[0].setTimeout(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) @@ -121,7 +122,7 @@ func (d *Card) Init() error { // check for timeout ok = false - tm = setTimeout(0, 2*time.Second) + tm = tm.setTimeout(2 * time.Second) for !tm.expired() { r1, err = d.appCmd(ACMD41_SD_APP_OP_COND, arg) if err != nil { @@ -325,8 +326,10 @@ func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { buf[0] = 0x40 | cmd binary.BigEndian.PutUint32(buf[1:5], arg) buf[5] = crc - d.bus.Tx(buf, nil) - + err := d.bus.Tx(buf, nil) + if err != nil { + return 0, err + } if cmd == 12 { // skip 1 byte d.bus.Transfer(byte(0xFF)) @@ -352,7 +355,7 @@ func (d *Card) cmd(cmd byte, arg uint32, crc byte) (response1, error) { } func (d *Card) waitNotBusy(timeout time.Duration) error { - tm := setTimeout(1, timeout) + tm := d.timers[1].setTimeout(timeout) for !tm.expired() { r, err := d.bus.Transfer(byte(0xFF)) if err != nil { @@ -368,8 +371,7 @@ func (d *Card) waitNotBusy(timeout time.Duration) error { func (d *Card) waitStartBlock() error { status := byte(0xFF) - - tm := setTimeout(0, 300*time.Millisecond) + tm := d.timers[0].setTimeout(300 * time.Millisecond) for !tm.expired() { var err error status, err = d.bus.Transfer(byte(0xFF)) @@ -380,6 +382,7 @@ func (d *Card) waitStartBlock() error { if status != 0xFF { break } + runtime.Gosched() } if status != 254 { @@ -396,14 +399,65 @@ type response1Err struct { } func (e response1Err) Error() string { + return e.status.Response() if e.context != "" { return "sd:" + e.context + " " + strconv.Itoa(int(e.status)) } return "sd:status " + strconv.Itoa(int(e.status)) } +func (e response1) Response() string { + b := make([]byte, 0, 8) + return string(e.appendf(b)) +} + +func (r response1) appendf(b []byte) []byte { + b = append(b, '[') + if r.IsIdle() { + b = append(b, "idle,"...) + } + if r.EraseReset() { + b = append(b, "erase-rst,"...) + } + if r.EraseSeqError() { + b = append(b, "erase-seq,"...) + } + if r.CRCError() { + b = append(b, "crc-err,"...) + } + if r.AddressError() { + b = append(b, "addr-err,"...) + } + if r.ParamError() { + b = append(b, "param-err,"...) + } + if r.IllegalCmdError() { + b = append(b, "illegal-cmd,"...) + } + if len(b) > 1 { + b = b[:len(b)-1] + } + b = append(b, ']') + return b +} + func makeResponseError(status response1) error { return response1Err{ status: status, } } + +var timeoutTimer [2]timer + +type timer struct { + deadline time.Time +} + +func (t *timer) setTimeout(timeout time.Duration) *timer { + t.deadline = time.Now().Add(timeout) + return t +} + +func (t timer) expired() bool { + return time.Since(t.deadline) >= 0 +} diff --git a/sd/card_test.go b/sd/card_test.go index 91fa129..419723a 100644 --- a/sd/card_test.go +++ b/sd/card_test.go @@ -27,19 +27,3 @@ func TestCRC(t *testing.T) { } } } - -func CRC16(buf []byte) (sum uint16) { - const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1 - var crc uint16 = 0x0000 // Initial value - for _, b := range buf { - crc ^= (uint16(b) << 8) // Shift byte into MSB of crc - for i := 0; i < 8; i++ { // Process each bit - if crc&0x8000 != 0 { - crc = (crc << 1) ^ poly - } else { - crc <<= 1 - } - } - } - return crc -} diff --git a/sd/definitions.go b/sd/definitions.go index 433d8bf..0cb26ec 100644 --- a/sd/definitions.go +++ b/sd/definitions.go @@ -48,54 +48,17 @@ func DecodeCID(b []byte) (CID, error) { return cid, nil } -func (c *CID) ProductName() []byte { - return upToNull(c.prodName[:]) +func (c *CID) ProductName() string { + return string(upToNull(c.prodName[:])) } func (c *CID) ProductRevision() (n, m uint8) { return c.productRev >> 4, c.productRev & 0x0F } -/* -CSD Register Fields: -Name: Field: Width: Value: CSD-Slice: -CSD Structure CSD_STRUCTURE 2 00b R[127:128] -TAAC TAAC 8 00h R[119:113] -NSAC NSAC 8 00h R[111:105] -TRAN_SPEED TRAN_SPEED 8 32h or 5Ah R[103:97] -CCC CCC 12 01x110110101b R[95:85] -READ_BL_LEN READ_BL_LEN 4 xh R[83:81] -READ_BL_PARTIAL READ_BL_PARTIAL 1 1b R[79:80] -WRITE_BLK_MISALIGN WRITE_BLK_MISALIGN 1 xb R[78:79] -READ_BLK_MISALIGN READ_BLK_MISALIGN 1 xb R[77:78] -DSR_IMP DSR_IMP 1 xb R[76:77] -C_SIZE C_SIZE 12 xxxh R[73:63] -VDD_R_CURR_MIN VDD_R_CURR_MIN 3 xxxb R[61:60] -VDD_R_CURR_MAX VDD_R_CURR_MAX 3 xxxb R[58:57] -VDD_W_CURR_MIN VDD_W_CURR_MIN 3 xxxb R[55:54] -VDD_W_CURR_MAX VDD_W_CURR_MAX 3 xxxb R[52:51] -C_SIZE_MULT C_SIZE_MULT 3 xxxb R[49:48] -ERASE_BLK_EN ERASE_BLK_EN 1 xb R[46:47] -SECTOR_SIZE SECTOR_SIZE 7 xxxxxxxb R[45:40] -WP_GRP_SIZE WP_GRP_SIZE 7 xxxxxxxb R[38:33] -WP_GRP_ENABLE WP_GRP_ENABLE 1 xb R[31:32] -R2W_FACTOR R2W_FACTOR 2 xxxb R[28:27] -WRITE_BL_LEN WRITE_BL_LEN 4 xxxxb R[25:23] -WRITE_BL_PARTIAL WRITE_BL_PARTIAL 1 xb R[21:22] -FILE_FORMAT_GRP FILE_FORMAT_GRP 1 xb R[15:16] -COPY COPY 1 xb R[14:15] -PERM_WRITE_PROTECT PERM_WRITE_PROTECT 1 xb R[13:14] -TMP_WRITE_PROTECT TMP_WRITE_PROTECT 1 xb R[12:13] -FILE_FORMAT FILE_FORMAT 2 xxb R[11:11] -CRC CRC 7 xxxxxxxb R[7:2] -Not Used - 1 1b R[0:1] - -Note: 'R' indicates read-only fields, 'R/W' indicates read/write fields. -The values in the 'CSD-Slice' column indicate the bit positions in the CSD register. -*/ - // CSD is the Card Specific Data register, a 128-bit (16-byte) register that defines how -// the SD card standard communicates with the memory field or register. +// the SD card standard communicates with the memory field or register. This type is +// shared among V1 and V2 type devices. type CSD struct { data [16]byte } @@ -104,6 +67,10 @@ type CSDv1 struct { CSD } +type CSDv2 struct { + CSD +} + func DecodeCSD(b []byte) (CSD, error) { if len(b) < 16 { return CSD{}, io.ErrShortBuffer @@ -117,57 +84,203 @@ func DecodeCSD(b []byte) (CSD, error) { func (c *CSD) CSDStructure() uint8 { return c.data[0] >> 6 } func (c CSD) MustV1() CSDv1 { - if c.CSDStructure() != 1 { - panic("CSD is not version 1") + if c.CSDStructure() != 0 { + panic("CSD is not version 1.0") } return CSDv1{CSD: c} } +func (c CSD) MustV2() CSDv2 { + if c.CSDStructure() != 1 { + panic("CSD is not version 2.0") + } + return CSDv2{CSD: c} +} + +func (c *CSD) RawCopy() [16]byte { return c.data } + // TAAC returns the Time Access Attribute Class (data read access-time-1). func (c *CSD) TAAC() TAAC { return TAAC(c.data[1]) } // NSAC returns the Data Read Access-time 2 in CLK cycles (NSAC*100). -func (c *CSD) NSAC() uint8 { return c.data[2] } +func (c *CSD) NSAC() NSAC { return NSAC(c.data[2]) } // TransferSpeed returns the Max Data Transfer Rate. Either 0x32 or 0x5A. func (c *CSD) TransferSpeed() TransferSpeed { return TransferSpeed(c.data[3]) } -// CCC returns the Card Command Classes. -func (c *CSD) CCC() uint16 { - return uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4 +// CommandClasses returns the supported Card Command Classes. +// This is a bitfield, each bit position indicates whether the +func (c *CSD) CommandClasses() CommandClasses { + return CommandClasses(uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4) } // ReadBlockLen returns the Max Read Data Block Length in bytes. func (c *CSD) ReadBlockLen() uint16 { return 1 << (c.data[5] & 0x0F) } -func (c *CSD) ReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 } -func (c *CSD) WriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 } -func (c *CSD) ReadBlockMisalignment() bool { return c.data[6]&(1<<5) != 0 } +// AllowsReadBlockPartial should always return true. Indicates that +func (c *CSD) AllowsReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 } -// ImplementsDSR returns whether the card implements the DSR register. +// AllowsWriteBlockMisalignment defines if the data block to be written by one command +// can be spread over more than one physical block of the memory device. +func (c *CSD) AllowsWriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 } + +// AllowsReadBlockMisalignment defines if the data block to be read by one command +// can be spread over more than one physical block of the memory device. +func (c *CSD) AllowsReadBlockMisalignment() bool { return c.data[6]&(1<<5) != 0 } + +// CRC7 returns the CRC read for this CSD. May be invalid. Use [IsValid] to check validity of CRC7+Always1 fields. +func (c *CSD) CRC7() uint8 { return c.data[15] & 0b111_1111 } + +// IsValid checks if the CRC and always1 fields are expected values. +func (c *CSD) IsValid() bool { + // Compare last byte with CRC and also the always1 bit. + got := CRC7(c.data[:15]) + return got|(1<<7) == c.data[15] +} + +// ImplementsDSR defines if the configurable driver stage is integrated on the card. func (c *CSD) ImplementsDSR() bool { return c.data[6]&(1<<4) != 0 } -func (c *CSDv1) CSize() uint16 { +// EraseSectorSizeInBlocks represents how much memory is erased in an erase +// command in multiple of block size. +func (c *CSD) EraseSectorSizeInBlocks() uint8 { + return 1 + ((c.data[10]&0b11_1111)<<1 | (c.data[11] >> 7)) +} + +// EraseBlockEnabled defines granularity of unit size of data to be erased. +// If enabled the erase operation can erase either one or multiple units of 512 bytes. +func (c *CSD) EraseBlockEnabled() bool { return (c.data[10]>>6)&1 != 0 } + +func (c *CSD) ReadToWriteFactor() uint8 { return (c.data[12] >> 2) & 0b111 } + +// WriteProtectGroupSizeInSectors indicates the size of a write protected +// group in multiple of erasable sectors. +func (c *CSD) WriteProtectGroupSizeInSectors() uint8 { + return 1 + (c.data[11] & 0b111_1111) +} + +// WriteBlockLength represents maximum write data block length in bytes. +func (c *CSD) WriteBlockLength() uint16 { + return 1 << ((c.data[12]&0b11)<<2 | (c.data[13] >> 6)) +} + +// WriteGroupEnabled indicates if write group protection is available. +func (c *CSD) WriteGroupEnabled() bool { return c.data[12]&(1<<7) != 0 } + +// AllowsWritePartial Defines whether partial block sizes can be used in write block sizes. +func (c *CSD) AllowsWritePartial() bool { return c.data[13]&(1<<5) != 0 } + +// FileFormat returns the file format on the card. This field is read-only for ROM. +func (c *CSD) FileFormat() FileFormat { return FileFormat(c.data[14]>>2) & 0b11 } + +// TmpWriteProtected indicates temporary protection over the entire card content from being overwritten or erased. +func (c *CSD) TmpWriteProtected() bool { return c.data[14]&(1<<4) != 0 } + +// PermWriteProtected indicates permanent protecttion of entire card content against overwriting or erasing (write+erase permanently disabled). +func (c *CSD) PermWriteProtected() bool { return c.data[14]&(1<<5) != 0 } + +// IsCopy whether contents are original or have been copied. +func (c *CSD) IsCopy() bool { return c.data[14]&(1<<6) != 0 } + +func (c *CSD) FileFormatGroup() bool { return c.data[14]&(1<<7) != 0 } + +func (c *CSD) DeviceCapacity() (size uint64) { + switch c.CSDStructure() { + case 0: + v1 := c.MustV1() + size = uint64(v1.DeviceCapacity()) + case 1: + v2 := c.MustV2() + size = v2.DeviceCapacity() + } + return size +} + +// After byte 5 CSDv1 and CSDv2 differ in structure at some fields. + +// DeviceCapacity returns the device capacity in bytes. +func (c *CSDv2) DeviceCapacity() uint64 { + csize := c.csize() + return uint64(csize) * 512_000 +} + +func (c *CSDv2) csize() uint32 { + return uint32(c.data[7]>>2)<<16 | uint32(c.data[8])<<8 | uint32(c.data[9]) +} + +// DeviceCapacity returns the total memory capacity of the SDCard in bytes. Max is 2GB for V1. +func (c *CSDv1) DeviceCapacity() uint32 { + mult := c.mult() + csize := c.csize() + blklen := c.ReadBlockLen() + blockNR := uint32(csize+1) * uint32(mult) + return blockNR * uint32(blklen) +} + +func (c *CSDv1) csize() uint16 { // Jesus, why did SD make this so complicated? return uint16(c.data[8]>>6) | uint16(c.data[7])<<2 | uint16(c.data[6]&0b11)<<10 } -func (c *CSD) String() string { - buf := make([]byte, 0, 64) - return string(c.appendf(buf, '\n')) +// mult is a factor for computing total device size with csize and csizemult. +func (c *CSDv1) mult() uint16 { return 1 << (2 + c.csizemult()) } + +func (c *CSDv1) csizemult() uint8 { + return (c.data[9]&0b11)<<1 | (c.data[10] >> 7) } +// VddReadCurrent indicates min and max values for read power supply currents. +// - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA +// - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA +func (c *CSDv1) VddReadCurrent() (min, max uint8) { + return (c.data[8] >> 3) & 0b111, c.data[8] & 0b111 +} + +// VddWriteCurrent indicates min and max values for write power supply currents. +// - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA +// - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA +func (c *CSDv1) VddWriteCurrent() (min, max uint8) { + return c.data[9] >> 5, (c.data[9] >> 3) & 0b111 +} + +func (c *CSD) String() string { + version := c.CSDStructure() + 1 + if version > 2 { + return "" + } + const delim = '\n' + buf := make([]byte, 0, 64) + buf = c.appendf(buf, delim) + return string(buf) +} + +func (c *CSDv1) String() string { return c.CSD.String() } + +func (c *CSDv2) String() string { return c.CSD.String() } + func (c *CSD) appendf(b []byte, delim byte) []byte { - b = appendnum(b, "CSDStructure", uint64(c.CSDStructure()), delim) + b = appendnum(b, "Version", uint64(c.CSDStructure()+1), delim) + b = appendnum(b, "Capacity(bytes)", c.DeviceCapacity(), delim) b = appendnum(b, "TimeAccess_ns", uint64(c.TAAC().AccessTime()), delim) b = appendnum(b, "NSAC", uint64(c.NSAC()), delim) b = appendnum(b, "Tx_kb/s", uint64(c.TransferSpeed().RateKilobits()), delim) - b = appendnum(b, "CCC", uint64(c.CCC()), delim) + b = appendnum(b, "CCC", uint64(c.CommandClasses()), delim) b = appendnum(b, "ReadBlockLen", uint64(c.ReadBlockLen()), delim) - b = appendbit(b, "ReadBlockPartial", c.ReadBlockPartial(), delim) - b = appendbit(b, "WriteBlockMisalignment", c.WriteBlockMisalignment(), delim) - b = appendbit(b, "ReadBlockMisalignment", c.ReadBlockMisalignment(), delim) + b = appendbit(b, "ReadBlockPartial", c.AllowsReadBlockPartial(), delim) + b = appendbit(b, "AllowWriteBlockMisalignment", c.AllowsWriteBlockMisalignment(), delim) + b = appendbit(b, "AllowReadBlockMisalignment", c.AllowsReadBlockMisalignment(), delim) b = appendbit(b, "ImplementsDSR", c.ImplementsDSR(), delim) + b = appendnum(b, "WProtectNumSectors", uint64(c.WriteProtectGroupSizeInSectors()), delim) + b = appendnum(b, "WriteBlockLen", uint64(c.WriteBlockLength()), delim) + b = appendbit(b, "WGrpEnable", c.WriteGroupEnabled(), delim) + b = appendbit(b, "WPartialAllow", c.AllowsWritePartial(), delim) + b = append(b, "FileFmt:"...) + b = append(b, c.FileFormat().String()...) + b = append(b, delim) + b = appendbit(b, "TmpWriteProtect", c.TmpWriteProtected(), delim) + b = appendbit(b, "PermWriteProtect", c.PermWriteProtected(), delim) + b = appendbit(b, "IsCopy", c.IsCopy(), delim) + b = appendbit(b, "FileFormatGrp", c.FileFormatGroup(), delim) return b } @@ -246,11 +359,38 @@ const ( ACMD49_CHANGE_SECURE_AREA = 49 ) +// CSD enum types. type ( - TransferSpeed uint8 - TAAC uint8 + TransferSpeed uint8 + TAAC uint8 + FileFormat uint8 + CommandClasses uint16 + NSAC uint8 ) +const ( + FileFmtPartition FileFormat = iota // Hard disk like file system with partition table. + FileFmtDOSFAT // DOS FAT (floppy like) + FileFmtUFF // Universal File Format + FileFmtUnknown +) + +func (ff FileFormat) String() (s string) { + switch ff { + case FileFmtPartition: + s = "partition" + case FileFmtDOSFAT: + s = "DOS/FAT" + case FileFmtUFF: + s = "UFF" + case FileFmtUnknown: + s = "unknown" + default: + s = "" + } + return s +} + var log10table = [...]int64{ 1, 10, @@ -298,3 +438,38 @@ func b2u8(b bool) uint8 { } return 0 } + +// CRC16 computes the CRC16 checksum for a given payload using the CRC-16-CCITT polynomial. +func CRC16(buf []byte) (sum uint16) { + const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1 + var crc uint16 = 0x0000 // Initial value + for _, b := range buf { + crc ^= (uint16(b) << 8) // Shift byte into MSB of crc + for i := 0; i < 8; i++ { // Process each bit + if crc&0x8000 != 0 { + crc = (crc << 1) ^ poly + } else { + crc <<= 1 + } + } + } + return crc +} + +// CRC7 computes the CRC7 checksum for a given payload using the polynomial x^7 + x^3 + 1. +func CRC7(data []byte) uint8 { + const poly uint8 = 0x09 // Generator polynomial G(x) = x^7 + x^3 + 1 + var crc uint8 = 0x00 // Initial value + + for _, b := range data { + crc ^= b // Initial XOR + for i := 0; i < 8; i++ { // Process each bit + if crc&0x80 != 0 { + crc = (crc << 1) ^ poly + } else { + crc <<= 1 + } + } + } + return crc >> 1 +} diff --git a/sd/timer.go b/sd/timer.go deleted file mode 100644 index 53c2754..0000000 --- a/sd/timer.go +++ /dev/null @@ -1,22 +0,0 @@ -package sd - -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) -}