mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-20 14:39:00 +00:00
add documentation, smoke test and fix a couple bugs
This commit is contained in:
+17
-4
@@ -15,6 +15,9 @@ var _ Card = (*SPICard)(nil)
|
|||||||
var _ io.ReaderAt = (*BlockDevice)(nil)
|
var _ io.ReaderAt = (*BlockDevice)(nil)
|
||||||
var _ io.WriterAt = (*BlockDevice)(nil)
|
var _ io.WriterAt = (*BlockDevice)(nil)
|
||||||
|
|
||||||
|
// Card is the interface implemented by SD card drivers such as [SPICard].
|
||||||
|
// It provides block-aligned I/O over the card's contents. Use [NewBlockDevice]
|
||||||
|
// to wrap a Card with byte-addressed [io.ReaderAt] and [io.WriterAt] interfaces.
|
||||||
type Card interface {
|
type Card interface {
|
||||||
// WriteBlocks writes the given data to the card, starting at the given block index.
|
// WriteBlocks writes the given data to the card, starting at the given block index.
|
||||||
// The data must be a multiple of the block size.
|
// The data must be a multiple of the block size.
|
||||||
@@ -26,7 +29,9 @@ type Card interface {
|
|||||||
EraseBlocks(startBlock, numBlocks int64) error
|
EraseBlocks(startBlock, numBlocks int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlockDevice creates a new BlockDevice from a Card.
|
// NewBlockDevice creates a new [BlockDevice] from a Card. blockSize must be a
|
||||||
|
// power of 2. For an initialized [SPICard], blockSize is typically the CSD's
|
||||||
|
// [CSD.ReadBlockLen] and numBlocks is [SPICard.NumberOfBlocks].
|
||||||
func NewBlockDevice(card Card, blockSize int, numBlocks int64) (*BlockDevice, error) {
|
func NewBlockDevice(card Card, blockSize int, numBlocks int64) (*BlockDevice, error) {
|
||||||
if card == nil || blockSize <= 0 || numBlocks <= 0 {
|
if card == nil || blockSize <= 0 || numBlocks <= 0 {
|
||||||
return nil, errors.New("invalid argument(s)")
|
return nil, errors.New("invalid argument(s)")
|
||||||
@@ -44,7 +49,10 @@ func NewBlockDevice(card Card, blockSize int, numBlocks int64) (*BlockDevice, er
|
|||||||
return bd, nil
|
return bd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockDevice implements tinyfs.BlockDevice interface for an [sd.Card] type.
|
// BlockDevice implements the tinyfs.BlockDevice interface for a [Card],
|
||||||
|
// providing byte-addressed reads and writes at arbitrary offsets by buffering
|
||||||
|
// non-block-aligned accesses through an internal single-block buffer.
|
||||||
|
// BlockDevice is not safe for concurrent use.
|
||||||
type BlockDevice struct {
|
type BlockDevice struct {
|
||||||
card Card
|
card Card
|
||||||
blockbuf []byte
|
blockbuf []byte
|
||||||
@@ -52,7 +60,8 @@ type BlockDevice struct {
|
|||||||
numblocks int64
|
numblocks int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAt implements [io.ReadAt] interface for an SD card.
|
// ReadAt implements the [io.ReaderAt] interface for an SD card.
|
||||||
|
// Reads need not be aligned to block boundaries.
|
||||||
func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
|
func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
|
||||||
if off < 0 {
|
if off < 0 {
|
||||||
return 0, errNegativeOffset
|
return 0, errNegativeOffset
|
||||||
@@ -93,7 +102,9 @@ func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteAt implements [io.WriterAt] interface for an SD card.
|
// WriteAt implements the [io.WriterAt] interface for an SD card. Writes need
|
||||||
|
// not be aligned to block boundaries: partial blocks are read, modified and
|
||||||
|
// written back.
|
||||||
func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
|
func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
|
||||||
if off < 0 {
|
if off < 0 {
|
||||||
return 0, errNegativeOffset
|
return 0, errNegativeOffset
|
||||||
@@ -174,6 +185,8 @@ type blkIdxer struct {
|
|||||||
blockmask int64
|
blockmask int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makeBlockIndexer returns a blkIdxer for the given block size,
|
||||||
|
// which must be a power of 2.
|
||||||
func makeBlockIndexer(blockSize int) (blkIdxer, error) {
|
func makeBlockIndexer(blockSize int) (blkIdxer, error) {
|
||||||
if blockSize <= 0 {
|
if blockSize <= 0 {
|
||||||
return blkIdxer{}, errNoblocks
|
return blkIdxer{}, errNoblocks
|
||||||
|
|||||||
@@ -85,6 +85,89 @@ func TestCRC7(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCSDv2Capacity checks CSDv2 C_SIZE decoding and the capacity formula.
|
||||||
|
//
|
||||||
|
// Field layout and formula are from the SD Physical Layer Simplified
|
||||||
|
// Specification Version 9.10, section 5.3.3 "CSD Register (CSD Version 2.0)":
|
||||||
|
// C_SIZE occupies CSD bits [69:48] and user memory capacity is
|
||||||
|
//
|
||||||
|
// memory capacity = (C_SIZE+1) * 512KByte (512KByte = 524288 bytes)
|
||||||
|
//
|
||||||
|
// Specification download: https://www.sdcard.org/downloads/pls/
|
||||||
|
//
|
||||||
|
// Regression test for two past bugs in CSDv2:
|
||||||
|
// - csize() read byte 7 as data[7]>>2 instead of data[7]&0x3F, dropping
|
||||||
|
// C_SIZE bits [17:16] (misdecoded cards > 32GiB).
|
||||||
|
// - DeviceCapacity() computed csize*512000 instead of (csize+1)*524288.
|
||||||
|
func TestCSDv2Capacity(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
csd []byte
|
||||||
|
wantCSize uint32
|
||||||
|
wantCap int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Same 8GB-card register as in TestCRC7 above, CRC byte appended
|
||||||
|
// (CRC7=0b1110010 per that test, stored as crc<<1|always1).
|
||||||
|
// C_SIZE = 0x003C01 = 15361 -> 15362 * 524288 = 8054112256 bytes.
|
||||||
|
name: "8GB card (in-repo vector)",
|
||||||
|
csd: []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0, 0b1110010<<1 | 1},
|
||||||
|
wantCSize: 0x003C01,
|
||||||
|
wantCap: 8054112256,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: "8GB card",
|
||||||
|
// csd: csdv2Bytes(0x003C01),
|
||||||
|
// wantCSize: 0x003FFF,
|
||||||
|
// wantCap: 2 << 40,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
// Synthetic: C_SIZE = 0x01FFFF -> 131072 * 524288 = 64GiB.
|
||||||
|
// Exercises C_SIZE bits [17:16], stored in CSD byte 7 (CSD bits [65:64]).
|
||||||
|
name: "64GiB synthetic",
|
||||||
|
csd: csdv2Bytes(0x01FFFF),
|
||||||
|
wantCSize: 0x01FFFF,
|
||||||
|
wantCap: 64 << 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Synthetic: maximum v2 C_SIZE = 0x3FFFFF -> 4194304 * 524288 = 2TiB,
|
||||||
|
// the SDXC upper bound per section 5.3.3.
|
||||||
|
name: "2TiB synthetic (max C_SIZE)",
|
||||||
|
csd: csdv2Bytes(0x3FFFFF),
|
||||||
|
wantCSize: 0x3FFFFF,
|
||||||
|
wantCap: 2 << 40,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
csd, err := DecodeCSD(tt.csd)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: DecodeCSD: %v", tt.name, err)
|
||||||
|
}
|
||||||
|
v2 := csd.MustV2()
|
||||||
|
if got := v2.csize(); got != tt.wantCSize {
|
||||||
|
t.Errorf("%s: csize() = %#x, want %#x", tt.name, got, tt.wantCSize)
|
||||||
|
}
|
||||||
|
if got := csd.DeviceCapacity(); got != tt.wantCap {
|
||||||
|
t.Errorf("%s: DeviceCapacity() = %d, want %d", tt.name, got, tt.wantCap)
|
||||||
|
}
|
||||||
|
wantBlocks := tt.wantCap / int64(csd.ReadBlockLen())
|
||||||
|
if got := csd.NumberOfBlocks(); got != wantBlocks {
|
||||||
|
t.Errorf("%s: NumberOfBlocks() = %d, want %d", tt.name, got, wantBlocks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// csdv2Bytes returns a 16-byte CSD v2 register with csize spliced into
|
||||||
|
// CSD bits [69:48] and a freshly computed CRC7+always1 last byte.
|
||||||
|
// Non-capacity fields are copied from the 8GB-card vector above.
|
||||||
|
func csdv2Bytes(csize uint32) []byte {
|
||||||
|
b := []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0}
|
||||||
|
b[7] = byte(csize >> 16 & 0x3F)
|
||||||
|
b[8] = byte(csize >> 8)
|
||||||
|
b[9] = byte(csize)
|
||||||
|
return append(b, crc7noshift(b)|1)
|
||||||
|
}
|
||||||
|
|
||||||
func putCmd(dst []byte, cmd command, arg uint32) {
|
func putCmd(dst []byte, cmd command, arg uint32) {
|
||||||
dst[0] = byte(cmd) | (1 << 6)
|
dst[0] = byte(cmd) | (1 << 6)
|
||||||
dst[1] = byte(arg >> 24)
|
dst[1] = byte(arg >> 24)
|
||||||
|
|||||||
+58
-19
@@ -11,8 +11,11 @@ import (
|
|||||||
// For reference of CID/CSD structs see:
|
// For reference of CID/CSD structs see:
|
||||||
// See https://github.com/arduino-libraries/SD/blob/1c56f58252553c7537f7baf62798cacc625aa543/src/utility/SdInfo.h#L110
|
// See https://github.com/arduino-libraries/SD/blob/1c56f58252553c7537f7baf62798cacc625aa543/src/utility/SdInfo.h#L110
|
||||||
|
|
||||||
|
// CardKind classifies an SD card by its capacity class and specification
|
||||||
|
// version, as discovered during card initialization.
|
||||||
type CardKind uint8
|
type CardKind uint8
|
||||||
|
|
||||||
|
// isTimeout reports whether err is one of the package's timeout errors.
|
||||||
func isTimeout(err error) bool {
|
func isTimeout(err error) bool {
|
||||||
return err == errReadTimeout || err == errWriteTimeout || err == errBusyTimeout
|
return err == errReadTimeout || err == errWriteTimeout || err == errBusyTimeout
|
||||||
}
|
}
|
||||||
@@ -24,10 +27,16 @@ const (
|
|||||||
TypeSDHC CardKind = 3 // High Capacity SD card
|
TypeSDHC CardKind = 3 // High Capacity SD card
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CID is the Card Identification register, a 128-bit (16-byte) read-only
|
||||||
|
// register holding the card's identification information: manufacturer,
|
||||||
|
// product name, serial number and manufacturing date, among other data.
|
||||||
|
// It is programmed during card manufacture and cannot be changed.
|
||||||
type CID struct {
|
type CID struct {
|
||||||
data [16]byte
|
data [16]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeCID decodes a CID from the first 16 bytes of b. It returns an error
|
||||||
|
// if b is too short or if the CRC7/always-1 fields are invalid.
|
||||||
func DecodeCID(b []byte) (cid CID, _ error) {
|
func DecodeCID(b []byte) (cid CID, _ error) {
|
||||||
if len(b) < 16 {
|
if len(b) < 16 {
|
||||||
return CID{}, io.ErrShortBuffer
|
return CID{}, io.ErrShortBuffer
|
||||||
@@ -52,7 +61,7 @@ func (c *CID) OEMApplicationID() uint16 {
|
|||||||
return binary.BigEndian.Uint16(c.data[1:3])
|
return binary.BigEndian.Uint16(c.data[1:3])
|
||||||
}
|
}
|
||||||
|
|
||||||
// The product name is a string, 5-character ASCII string.
|
// ProductName returns the product name, an ASCII string of up to 5 characters.
|
||||||
func (c *CID) ProductName() string {
|
func (c *CID) ProductName() string {
|
||||||
return string(upToNull(c.data[3:8]))
|
return string(upToNull(c.data[3:8]))
|
||||||
}
|
}
|
||||||
@@ -65,12 +74,13 @@ func (c *CID) ProductRevision() (n, m uint8) {
|
|||||||
return rev >> 4, rev & 0x0F
|
return rev >> 4, rev & 0x0F
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Serial Number is 32 bits of binary number.
|
// ProductSerialNumber returns the product serial number, a 32-bit binary number.
|
||||||
func (c *CID) ProductSerialNumber() uint32 {
|
func (c *CID) ProductSerialNumber() uint32 {
|
||||||
return binary.BigEndian.Uint32(c.data[9:13])
|
return binary.BigEndian.Uint32(c.data[9:13])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManufacturingDate returns the manufacturing date of the card.
|
// ManufacturingDate returns the manufacturing date of the card,
|
||||||
|
// e.g. year=2023, month=4 for April 2023.
|
||||||
func (c *CID) ManufacturingDate() (year uint16, month uint8) {
|
func (c *CID) ManufacturingDate() (year uint16, month uint8) {
|
||||||
date := binary.BigEndian.Uint16(c.data[13:15])
|
date := binary.BigEndian.Uint16(c.data[13:15])
|
||||||
return (date >> 4) + 2000, uint8(date & 0x0F)
|
return (date >> 4) + 2000, uint8(date & 0x0F)
|
||||||
@@ -129,6 +139,7 @@ func (c CSD) MustV1() CSDv1 {
|
|||||||
return CSDv1{CSD: c}
|
return CSDv1{CSD: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustV2 returns the CSD as a CSDv2. Panics if the CSD is not version 2.0.
|
||||||
func (c CSD) MustV2() CSDv2 {
|
func (c CSD) MustV2() CSDv2 {
|
||||||
if c.csdStructure() != 1 {
|
if c.csdStructure() != 1 {
|
||||||
panic("CSD is not version 2.0")
|
panic("CSD is not version 2.0")
|
||||||
@@ -136,6 +147,7 @@ func (c CSD) MustV2() CSDv2 {
|
|||||||
return CSDv2{CSD: c}
|
return CSDv2{CSD: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawCopy returns a copy of the raw CSD data.
|
||||||
func (c *CSD) RawCopy() [16]byte { return c.data }
|
func (c *CSD) RawCopy() [16]byte { return c.data }
|
||||||
|
|
||||||
// TAAC returns the Time Access Attribute Class (data read access-time-1).
|
// TAAC returns the Time Access Attribute Class (data read access-time-1).
|
||||||
@@ -147,17 +159,20 @@ func (c *CSD) NSAC() NSAC { return NSAC(c.data[2]) }
|
|||||||
// TransferSpeed returns the Max Data Transfer Rate. Either 0x32 or 0x5A.
|
// TransferSpeed returns the Max Data Transfer Rate. Either 0x32 or 0x5A.
|
||||||
func (c *CSD) TransferSpeed() TransferSpeed { return TransferSpeed(c.data[3]) }
|
func (c *CSD) TransferSpeed() TransferSpeed { return TransferSpeed(c.data[3]) }
|
||||||
|
|
||||||
// CommandClasses returns the supported Card Command Classes.
|
// CommandClasses returns the supported Card Command Classes as a bitfield;
|
||||||
// This is a bitfield, each bit position indicates whether the
|
// bit position i set means command class i is supported by the card.
|
||||||
func (c *CSD) CommandClasses() CommandClasses {
|
func (c *CSD) CommandClasses() CommandClasses {
|
||||||
return CommandClasses(uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4)
|
return CommandClasses(uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBlockLen returns the Max Read Data Block Length in bytes.
|
// ReadBlockLen returns the Max Read Data Block Length in bytes.
|
||||||
func (c *CSD) ReadBlockLen() int { return 1 << c.ReadBlockLenShift() }
|
func (c *CSD) ReadBlockLen() int { return 1 << c.ReadBlockLenShift() }
|
||||||
|
|
||||||
|
// ReadBlockLenShift returns the base-2 logarithm of [CSD.ReadBlockLen] (READ_BL_LEN field).
|
||||||
func (c *CSD) ReadBlockLenShift() uint8 { return c.data[5] & 0x0F }
|
func (c *CSD) ReadBlockLenShift() uint8 { return c.data[5] & 0x0F }
|
||||||
|
|
||||||
// AllowsReadBlockPartial should always return true. Indicates that
|
// AllowsReadBlockPartial indicates that partial block reads (down to a
|
||||||
|
// single byte) are allowed. Always true for SD cards.
|
||||||
func (c *CSD) AllowsReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 }
|
func (c *CSD) AllowsReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 }
|
||||||
|
|
||||||
// AllowsWriteBlockMisalignment defines if the data block to be written by one command
|
// AllowsWriteBlockMisalignment defines if the data block to be written by one command
|
||||||
@@ -183,15 +198,18 @@ func (c *CSD) IsValid() bool {
|
|||||||
// ImplementsDSR defines if the configurable driver stage is integrated on the card.
|
// 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 *CSD) ImplementsDSR() bool { return c.data[6]&(1<<4) != 0 }
|
||||||
|
|
||||||
// EraseSectorSizeInBlocks represents how much memory is erased in an erase
|
// EraseSectorSizeInBytes returns how much memory is erased by a single
|
||||||
// command in multiple of block size.
|
// erase command, in bytes (SectorSize multiplied by the write block length).
|
||||||
func (c *CSDv1) EraseSectorSizeInBytes() int64 {
|
func (c *CSDv1) EraseSectorSizeInBytes() int64 {
|
||||||
blklen := c.WriteBlockLen()
|
blklen := c.WriteBlockLen()
|
||||||
numblocks := c.SectorSize()
|
numblocks := c.SectorSize()
|
||||||
return int64(numblocks) * blklen
|
return int64(numblocks) * blklen
|
||||||
}
|
}
|
||||||
|
|
||||||
// SectorSize varies in meaning depending on the version.
|
// SectorSize returns the size of an erasable sector in units of write blocks
|
||||||
|
// (SECTOR_SIZE field, range 1..128). Its meaning varies with the CSD version:
|
||||||
|
// for V1 it is the erase unit when [CSD.EraseBlockEnabled] is false; for V2
|
||||||
|
// it is fixed to 64KiB and does not reflect the real erase unit.
|
||||||
func (c *CSD) SectorSize() uint8 {
|
func (c *CSD) SectorSize() uint8 {
|
||||||
return 1 + ((c.data[10]&0b11_1111)<<1 | (c.data[11] >> 7))
|
return 1 + ((c.data[10]&0b11_1111)<<1 | (c.data[11] >> 7))
|
||||||
}
|
}
|
||||||
@@ -200,6 +218,8 @@ func (c *CSD) SectorSize() uint8 {
|
|||||||
// If enabled the erase operation can erase either one or multiple units of 512 bytes.
|
// 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) EraseBlockEnabled() bool { return (c.data[10]>>6)&1 != 0 }
|
||||||
|
|
||||||
|
// ReadToWriteFactor returns the typical write time as a power-of-2 multiple of
|
||||||
|
// the read access time (R2W_FACTOR field), i.e. writeTime = readTime << factor.
|
||||||
func (c *CSD) ReadToWriteFactor() uint8 { return (c.data[12] >> 2) & 0b111 }
|
func (c *CSD) ReadToWriteFactor() uint8 { return (c.data[12] >> 2) & 0b111 }
|
||||||
|
|
||||||
// WriteProtectGroupSizeInSectors indicates the size of a write protected
|
// WriteProtectGroupSizeInSectors indicates the size of a write protected
|
||||||
@@ -231,8 +251,12 @@ func (c *CSD) PermWriteProtected() bool { return c.data[14]&(1<<5) != 0 }
|
|||||||
// IsCopy whether contents are original or have been copied.
|
// IsCopy whether contents are original or have been copied.
|
||||||
func (c *CSD) IsCopy() bool { return c.data[14]&(1<<6) != 0 }
|
func (c *CSD) IsCopy() bool { return c.data[14]&(1<<6) != 0 }
|
||||||
|
|
||||||
|
// FileFormatGroup returns the file format group bit, which selects between
|
||||||
|
// the two [FileFormat] tables. Interpret together with [CSD.FileFormat].
|
||||||
func (c *CSD) FileFormatGroup() bool { return c.data[14]&(1<<7) != 0 }
|
func (c *CSD) FileFormatGroup() bool { return c.data[14]&(1<<7) != 0 }
|
||||||
|
|
||||||
|
// DeviceCapacity returns the total device capacity in bytes, dispatching on
|
||||||
|
// the CSD version. Returns 0 for unknown CSD versions.
|
||||||
func (c *CSD) DeviceCapacity() (size int64) {
|
func (c *CSD) DeviceCapacity() (size int64) {
|
||||||
switch c.csdStructure() {
|
switch c.csdStructure() {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -256,14 +280,16 @@ func (c *CSD) NumberOfBlocks() (numBlocks int64) {
|
|||||||
|
|
||||||
// After byte 5 CSDv1 and CSDv2 differ in structure at some fields.
|
// After byte 5 CSDv1 and CSDv2 differ in structure at some fields.
|
||||||
|
|
||||||
// DeviceCapacity returns the device capacity in bytes.
|
// DeviceCapacity returns the device capacity in bytes:
|
||||||
|
// (C_SIZE+1) * 512KiB, as per section 5.3.3 of the SD Simplified Specification.
|
||||||
func (c *CSDv2) DeviceCapacity() int64 {
|
func (c *CSDv2) DeviceCapacity() int64 {
|
||||||
csize := c.csize()
|
csize := c.csize()
|
||||||
return int64(csize) * 512_000
|
return (int64(csize) + 1) * (512 * 1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// csize returns the 22-bit C_SIZE field (CSD bits 69:48).
|
||||||
func (c *CSDv2) csize() uint32 {
|
func (c *CSDv2) csize() uint32 {
|
||||||
return uint32(c.data[7]>>2)<<16 | uint32(c.data[8])<<8 | uint32(c.data[9])
|
return uint32(c.data[7]&0x3F)<<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.
|
// DeviceCapacity returns the total memory capacity of the SDCard in bytes. Max is 2GB for V1.
|
||||||
@@ -301,6 +327,7 @@ func (c *CSDv1) VddWriteCurrent() (min, max uint8) {
|
|||||||
return c.data[9] >> 5, (c.data[9] >> 3) & 0b111
|
return c.data[9] >> 5, (c.data[9] >> 3) & 0b111
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns a human-readable multi-line summary of the CSD fields.
|
||||||
func (c *CSD) String() string {
|
func (c *CSD) String() string {
|
||||||
version := c.csdStructure() + 1
|
version := c.csdStructure() + 1
|
||||||
if version > 2 {
|
if version > 2 {
|
||||||
@@ -424,13 +451,23 @@ const (
|
|||||||
acmdCHANGE_SECURE_AREA appcommand = 49
|
acmdCHANGE_SECURE_AREA appcommand = 49
|
||||||
)
|
)
|
||||||
|
|
||||||
// CSD enum types.
|
// CSD field types.
|
||||||
type (
|
type (
|
||||||
TransferSpeed uint8
|
// TransferSpeed is the TRAN_SPEED CSD field: the maximum data transfer
|
||||||
TAAC uint8
|
// rate encoded as a rate unit (lower 3 bits) and time value multiplier.
|
||||||
FileFormat uint8
|
TransferSpeed uint8
|
||||||
|
// TAAC is the data read access time CSD field, encoded as a time unit
|
||||||
|
// (lower 3 bits) and time value multiplier.
|
||||||
|
TAAC uint8
|
||||||
|
// FileFormat is the format of the data stored on the card. See the
|
||||||
|
// FileFmt* constants for possible values.
|
||||||
|
FileFormat uint8
|
||||||
|
// CommandClasses is the CCC CSD field, a bitfield where bit position i
|
||||||
|
// set means command class i is supported.
|
||||||
CommandClasses uint16
|
CommandClasses uint16
|
||||||
NSAC uint8
|
// NSAC is the data read access time 2 CSD field, given in units of
|
||||||
|
// 100 clock cycles.
|
||||||
|
NSAC uint8
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -440,6 +477,7 @@ const (
|
|||||||
FileFmtUnknown
|
FileFmtUnknown
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a human-readable name for the file format.
|
||||||
func (ff FileFormat) String() (s string) {
|
func (ff FileFormat) String() (s string) {
|
||||||
switch ff {
|
switch ff {
|
||||||
case FileFmtPartition:
|
case FileFmtPartition:
|
||||||
@@ -466,11 +504,12 @@ var log10table = [...]int64{
|
|||||||
1000000,
|
1000000,
|
||||||
}
|
}
|
||||||
|
|
||||||
// RateMegabits returns the transfer rate in kilobits per second.
|
// RateKilobits returns the transfer rate in kilobits per second.
|
||||||
func (t TransferSpeed) RateKilobits() int64 {
|
func (t TransferSpeed) RateKilobits() int64 {
|
||||||
return 100 * log10table[t&0b111]
|
return 100 * log10table[t&0b111]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccessTime returns the asynchronous part of the data access time.
|
||||||
func (t TAAC) AccessTime() (d time.Duration) {
|
func (t TAAC) AccessTime() (d time.Duration) {
|
||||||
return time.Duration(log10table[t&0b111]) * time.Nanosecond
|
return time.Duration(log10table[t&0b111]) * time.Nanosecond
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Package sd implements SD card drivers and the SD card specification:
|
||||||
|
// CID and CSD register decoding, command definitions and CRC7/CRC16 checksums.
|
||||||
|
//
|
||||||
|
// The [SPICard] type drives an SD card over a SPI bus and implements the
|
||||||
|
// [Card] interface, which exposes block-aligned I/O. [BlockDevice] wraps any
|
||||||
|
// [Card] with byte-addressed [io.ReaderAt]/[io.WriterAt] implementations
|
||||||
|
// suitable for filesystem libraries such as tinyfs.
|
||||||
|
package sd
|
||||||
+11
-5
@@ -2,7 +2,6 @@ package sd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -20,6 +19,8 @@ const (
|
|||||||
_DATA_RES_ACCEPTED = 0x05
|
_DATA_RES_ACCEPTED = 0x05
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// response1 is the R1 response token returned by the card in SPI mode
|
||||||
|
// after every command; a bitfield of error and idle-state flags.
|
||||||
type response1 uint8
|
type response1 uint8
|
||||||
|
|
||||||
func (r response1) IsIdle() bool { return r&_R1_IDLE_STATE != 0 }
|
func (r response1) IsIdle() bool { return r&_R1_IDLE_STATE != 0 }
|
||||||
@@ -30,17 +31,17 @@ func (r response1) EraseSeqError() bool { return r&_R1_ERASE_SEQUENCE_ERROR !=
|
|||||||
func (r response1) AddressError() bool { return r&_R1_ADDRESS_ERROR != 0 }
|
func (r response1) AddressError() bool { return r&_R1_ADDRESS_ERROR != 0 }
|
||||||
func (r response1) ParamError() bool { return r&_R1_PARAMETER_ERROR != 0 }
|
func (r response1) ParamError() bool { return r&_R1_PARAMETER_ERROR != 0 }
|
||||||
|
|
||||||
|
// response1Err wraps a non-zero response1 status as an error.
|
||||||
type response1Err struct {
|
type response1Err struct {
|
||||||
context string
|
context string
|
||||||
status response1
|
status response1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e response1Err) Error() string {
|
func (e response1Err) Error() string {
|
||||||
return e.status.Response()
|
|
||||||
if e.context != "" {
|
if e.context != "" {
|
||||||
return "sd:" + e.context + " " + strconv.Itoa(int(e.status))
|
return "sd:" + e.context + " " + e.status.Response()
|
||||||
}
|
}
|
||||||
return "sd:status " + strconv.Itoa(int(e.status))
|
return e.status.Response()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e response1) Response() string {
|
func (e response1) Response() string {
|
||||||
@@ -96,6 +97,8 @@ const (
|
|||||||
tokWRITE_MULT = 0xfc
|
tokWRITE_MULT = 0xfc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// state is the card state machine state as encoded in the
|
||||||
|
// CURRENT_STATE bits of the Card Status register (section 4.10.1).
|
||||||
type state uint8
|
type state uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -153,7 +156,8 @@ const (
|
|||||||
statusAddrOutOfRange // address out of range
|
statusAddrOutOfRange // address out of range
|
||||||
)
|
)
|
||||||
|
|
||||||
// r1 is the normal response to a command.
|
// r1 is the normal 48-bit response to a command in SD-bus mode,
|
||||||
|
// as per section 4.9.1. It carries the 32-bit Card Status register.
|
||||||
type r1 struct {
|
type r1 struct {
|
||||||
data [48 / 8]byte // 48 bits of response.
|
data [48 / 8]byte // 48 bits of response.
|
||||||
}
|
}
|
||||||
@@ -178,6 +182,8 @@ func (r *r1) IsValid() bool {
|
|||||||
return r.endbit() && CRC7(r.data[:5]) == r.CRC7()
|
return r.endbit() && CRC7(r.data[:5]) == r.CRC7()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// r6 is the 48-bit Published RCA response, as per section 4.9.5. It carries
|
||||||
|
// the card's new Relative Card Address and a subset of the Card Status bits.
|
||||||
type r6 struct {
|
type r6 struct {
|
||||||
data [48 / 8]byte
|
data [48 / 8]byte
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-4
@@ -10,8 +10,6 @@ import (
|
|||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// See rustref.go for the new implementation.
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errBadCSDCID = errors.New("sd:bad CSD/CID in CRC or always1")
|
errBadCSDCID = errors.New("sd:bad CSD/CID in CRC or always1")
|
||||||
errNoSDCard = errors.New("sd:no card")
|
errNoSDCard = errors.New("sd:no card")
|
||||||
@@ -26,8 +24,12 @@ var (
|
|||||||
errNoblocks = errors.New("sd:no readable blocks")
|
errNoblocks = errors.New("sd:no readable blocks")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// digitalPinout sets the logic level of an output pin; true for high, false for low.
|
||||||
type digitalPinout = func(b bool)
|
type digitalPinout = func(b bool)
|
||||||
|
|
||||||
|
// SPICard is a SPI-mode SD card driver. It implements the [Card] interface
|
||||||
|
// and is initialized with [NewSPICard] followed by a call to [SPICard.Init].
|
||||||
|
// SPICard is not safe for concurrent use.
|
||||||
type SPICard struct {
|
type SPICard struct {
|
||||||
bus drivers.SPI
|
bus drivers.SPI
|
||||||
cs digitalPinout
|
cs digitalPinout
|
||||||
@@ -46,6 +48,9 @@ type SPICard struct {
|
|||||||
lastCRC uint16
|
lastCRC uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSPICard returns a new [SPICard] that communicates over spi using cs as
|
||||||
|
// the chip select pin. The returned card must be initialized with [SPICard.Init]
|
||||||
|
// before use.
|
||||||
func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard {
|
func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard {
|
||||||
const defaultTimeout = 300 * time.Millisecond
|
const defaultTimeout = 300 * time.Millisecond
|
||||||
s := &SPICard{
|
s := &SPICard{
|
||||||
@@ -74,6 +79,8 @@ func (d *SPICard) Init() error {
|
|||||||
return d.initRs()
|
return d.initRs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumberOfBlocks returns the number of readable and writable blocks on the card,
|
||||||
|
// as calculated from the CSD read during [SPICard.Init].
|
||||||
func (d *SPICard) NumberOfBlocks() int64 {
|
func (d *SPICard) NumberOfBlocks() int64 {
|
||||||
return d.csd.NumberOfBlocks()
|
return d.csd.NumberOfBlocks()
|
||||||
}
|
}
|
||||||
@@ -233,7 +240,9 @@ func (d *SPICard) updateCSDCID() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBlock reads to a buffer multiple of 512 bytes from sdcard into dst starting at block `startBlockIdx`.
|
// ReadBlocks reads card data into dst beginning at the block index startBlockIdx.
|
||||||
|
// len(dst) must be a multiple of the card's block size (see [CSD.ReadBlockLen]).
|
||||||
|
// It returns the number of bytes read into dst and any error encountered.
|
||||||
func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) {
|
func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) {
|
||||||
numblocks, err := d.checkBounds(startBlockIdx, len(dst))
|
numblocks, err := d.checkBounds(startBlockIdx, len(dst))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -267,11 +276,15 @@ func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) {
|
|||||||
panic("unreachable numblocks<=0")
|
panic("unreachable numblocks<=0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EraseBlocks erases numberOfBlocks blocks beginning at startBlock.
|
||||||
|
// It always returns an error since erase is not yet implemented for SPICard.
|
||||||
func (d *SPICard) EraseBlocks(startBlock, numberOfBlocks int64) error {
|
func (d *SPICard) EraseBlocks(startBlock, numberOfBlocks int64) error {
|
||||||
return errors.New("sd:erase not implemented")
|
return errors.New("sd:erase not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBlocks writes to sdcard from a buffer multiple of 512 bytes from src starting at block `startBlockIdx`.
|
// WriteBlocks writes data to the card beginning at the block index startBlockIdx.
|
||||||
|
// len(data) must be a multiple of the card's block size (see [CSD.WriteBlockLen]).
|
||||||
|
// It returns the number of bytes written and any error encountered.
|
||||||
func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) (int, error) {
|
func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) (int, error) {
|
||||||
numblocks, err := d.checkBounds(startBlockIdx, len(data))
|
numblocks, err := d.checkBounds(startBlockIdx, len(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ndir/ma
|
|||||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ndir/main_ndir.go
|
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ndir/main_ndir.go
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/main.go
|
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/sh1106/macropad_spi
|
tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/sh1106/macropad_spi
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sd
|
||||||
# network examples (espat)
|
# network examples (espat)
|
||||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||||
# network examples (wifinina)
|
# network examples (wifinina)
|
||||||
|
|||||||
Reference in New Issue
Block a user