From d04161a16e61b1634d2ddd3bffc36249568bb495 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Wed, 25 Feb 2026 13:58:48 -0800 Subject: [PATCH 1/2] sdcard: Compute CRC7 for all SPI commands. Some SD cards (e.g. SDXC) have CRC checking permanently enabled in SPI mode. Sending commands with CRC=0x00 causes them to return ILLEGAL_COMMAND, making initialisation fail. Fix this by computing a valid CRC7 for every command. Signed-off-by: Kwabena W. Agyeman --- micropython/drivers/storage/sdcard/sdcard.py | 43 ++++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/micropython/drivers/storage/sdcard/sdcard.py b/micropython/drivers/storage/sdcard/sdcard.py index 3df4788a2..642bf859e 100644 --- a/micropython/drivers/storage/sdcard/sdcard.py +++ b/micropython/drivers/storage/sdcard/sdcard.py @@ -38,6 +38,15 @@ _TOKEN_DATA = const(0xFE) +def _crc7(buf, n): + crc = 0 + for i in range(n): + crc ^= buf[i] + for j in range(8): + crc = ((crc << 1) ^ (0x12 * (crc >> 7))) & 0xFF + return crc | 1 + + class SDCard: def __init__(self, spi, cs, baudrate=1320000): self.spi = spi @@ -76,13 +85,13 @@ def init_card(self, baudrate): # CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts) for _ in range(5): - if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE: + if self.cmd(0, 0) == _R1_IDLE_STATE: break else: raise OSError("no SD card") # CMD8: determine card version - r = self.cmd(8, 0x01AA, 0x87, 4) + r = self.cmd(8, 0x01AA, 4) if r == _R1_IDLE_STATE: self.init_card_v2() elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND): @@ -92,7 +101,7 @@ def init_card(self, baudrate): # get the number of sectors # CMD9: response R2 (R1 byte + 16-byte block read) - if self.cmd(9, 0, 0, 0, False) != 0: + if self.cmd(9, 0, 0, False) != 0: raise OSError("no response from SD card") csd = bytearray(16) self.readinto(csd) @@ -109,7 +118,7 @@ def init_card(self, baudrate): # print('sectors', self.sectors) # CMD16: set block length to 512 bytes - if self.cmd(16, 512, 0) != 0: + if self.cmd(16, 512) != 0: raise OSError("can't set 512 block size") # set to high data rate now that it's initialised @@ -118,8 +127,8 @@ def init_card(self, baudrate): def init_card_v1(self): for i in range(_CMD_TIMEOUT): time.sleep_ms(50) - self.cmd(55, 0, 0) - if self.cmd(41, 0, 0) == 0: + self.cmd(55, 0) + if self.cmd(41, 0) == 0: # SDSC card, uses byte addressing in read/write/erase commands self.cdv = 512 # print("[SDCard] v1 card") @@ -129,10 +138,10 @@ def init_card_v1(self): def init_card_v2(self): for i in range(_CMD_TIMEOUT): time.sleep_ms(50) - self.cmd(58, 0, 0, 4) - self.cmd(55, 0, 0) - if self.cmd(41, 0x40000000, 0) == 0: - self.cmd(58, 0, 0, -4) # 4-byte response, negative means keep the first byte + self.cmd(58, 0, 4) + self.cmd(55, 0) + if self.cmd(41, 0x40000000) == 0: + self.cmd(58, 0, -4) # 4-byte response, negative means keep the first byte ocr = self.tokenbuf[0] # get first byte of response, which is OCR if not ocr & 0x40: # SDSC card, uses byte addressing in read/write/erase commands @@ -144,7 +153,7 @@ def init_card_v2(self): return raise OSError("timeout waiting for v2 card") - def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False): + def cmd(self, cmd, arg, final=0, release=True, skip1=False): self.cs(0) # create and send the command @@ -154,7 +163,7 @@ def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False): buf[2] = arg >> 16 buf[3] = arg >> 8 buf[4] = arg - buf[5] = crc + buf[5] = _crc7(buf, 5) self.spi.write(buf) if skip1: @@ -250,7 +259,7 @@ def readblocks(self, block_num, buf): assert nblocks and not len(buf) % 512, "Buffer length is invalid" if nblocks == 1: # CMD17: set read address for single block - if self.cmd(17, block_num * self.cdv, 0, release=False) != 0: + if self.cmd(17, block_num * self.cdv, release=False) != 0: # release the card self.cs(1) raise OSError(5) # EIO @@ -258,7 +267,7 @@ def readblocks(self, block_num, buf): self.readinto(buf) else: # CMD18: set read address for multiple blocks - if self.cmd(18, block_num * self.cdv, 0, release=False) != 0: + if self.cmd(18, block_num * self.cdv, release=False) != 0: # release the card self.cs(1) raise OSError(5) # EIO @@ -269,7 +278,7 @@ def readblocks(self, block_num, buf): self.readinto(mv[offset : offset + 512]) offset += 512 nblocks -= 1 - if self.cmd(12, 0, 0xFF, skip1=True): + if self.cmd(12, 0, skip1=True): raise OSError(5) # EIO def writeblocks(self, block_num, buf): @@ -281,14 +290,14 @@ def writeblocks(self, block_num, buf): assert nblocks and not err, "Buffer length is invalid" if nblocks == 1: # CMD24: set write address for single block - if self.cmd(24, block_num * self.cdv, 0) != 0: + if self.cmd(24, block_num * self.cdv) != 0: raise OSError(5) # EIO # send the data self.write(_TOKEN_DATA, buf) else: # CMD25: set write address for first block - if self.cmd(25, block_num * self.cdv, 0) != 0: + if self.cmd(25, block_num * self.cdv) != 0: raise OSError(5) # EIO # send the data offset = 0 From 95f72a74edecc310a1357b2fa708c2f01a7f50ce Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Wed, 25 Feb 2026 14:08:08 -0800 Subject: [PATCH 2/2] sdcard: Add read/write speed test to sdtest. Writes and reads 1 MB through the FAT filesystem and prints throughput in KB/s. Signed-off-by: Kwabena W. Agyeman --- micropython/drivers/storage/sdcard/sdtest.py | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/micropython/drivers/storage/sdcard/sdtest.py b/micropython/drivers/storage/sdcard/sdtest.py index ce700e2a8..074e8ae6d 100644 --- a/micropython/drivers/storage/sdcard/sdtest.py +++ b/micropython/drivers/storage/sdcard/sdtest.py @@ -2,6 +2,7 @@ # Peter hinch 30th Jan 2016 import machine import os +import time import sdcard @@ -44,6 +45,26 @@ def sdtest(): result2 = f.read() print(len(result2), "bytes read") + fn = "/fc/speed.bin" + buf = bytearray(32768) # 32 KB buffer + + print() + print("Write speed test") + t = time.ticks_ms() + with open(fn, "wb") as f: + for _ in range(32): # 1 MB total + f.write(buf) + elapsed = time.ticks_diff(time.ticks_ms(), t) + print("{} KB/s".format(32768 * 32 // elapsed)) + + print("Read speed test") + t = time.ticks_ms() + with open(fn, "rb") as f: + while f.readinto(buf): + pass + elapsed = time.ticks_diff(time.ticks_ms(), t) + print("{} KB/s".format(32768 * 32 // elapsed)) + os.umount("/fc") print()