Skip to content

Instantly share code, notes, and snippets.

@Vbitz
Created December 15, 2023 02:39
Show Gist options
  • Save Vbitz/c5590ea2343dc88bbfc08c7d81530f51 to your computer and use it in GitHub Desktop.
Save Vbitz/c5590ea2343dc88bbfc08c7d81530f51 to your computer and use it in GitHub Desktop.
Glasgow script capable of reading and writing to SD Cards.
#!/bin/bash
# SS_EEPROM (Chip Select) Pin 2
# 3.3v -
# MISO (CIPO) Pin 0
# SCK (SCK) Pin 3
# GND -
# MOSI (COPI) Pin 1
set -x
glasgow script sdcard.py spi-controller \
--port B \
-V 3.3 \
--pin-sck 3 \
--pin-cs 2 \
--pin-copi 1 \
--pin-cipo 0 \
-f 100
glasgow safe
# Based on: https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py
# My first time talking to SD cards so expect lots of problems with the code.
# Tested with MKDV1GIL-AS
_CMD_TIMEOUT = 100
_R1_IDLE_STATE = 1 << 0
# R1_ERASE_RESET = const(1 << 1)
_R1_ILLEGAL_COMMAND = 1 << 2
# R1_COM_CRC_ERROR = const(1 << 3)
# R1_ERASE_SEQUENCE_ERROR = const(1 << 4)
# R1_ADDRESS_ERROR = const(1 << 5)
# R1_PARAMETER_ERROR = const(1 << 6)
_TOKEN_CMD25 = 0xFC
_TOKEN_STOP_TRAN = 0xFD
_TOKEN_DATA = 0xFE
# cmdbuf = bytearray(6)
# dummybuf = bytearray(512)
# tokenbuf = bytearray(1)
spi = iface
async def readinto(size: int, byte: int) -> bytearray:
buf = await spi.transfer(bytearray([byte] * size))
return buf
async def full_readinto(size: int) -> bytearray:
# read until start byte (0xff)
for i in range(_CMD_TIMEOUT):
result = await readinto(1, 0xFF)
if result[0] == _TOKEN_DATA:
break
await spi.delay_ms(1)
else:
raise OSError("timeout waiting for response")
# read data
result = await readinto(size, 0xFF)
# read checksum
await spi.write(b"\xff")
await spi.write(b"\xff")
await spi.write(b"\xff")
return result
async def write(token, buf):
# send: start of block, data, checksum
await readinto(1, token)
await spi.write(buf)
await spi.write(b"\xff")
await spi.write(b"\xff")
# check the response
if ((await readinto(1, 0xFF))[0] & 0x1F) != 0x05:
await spi.write(b"\xff")
return
# wait for write to finish
while (await readinto(1, 0xFF))[0] == 0:
pass
await spi.write(b"\xff")
async def cmd(cmd, arg, crc, final=0, release=True, skip1=False):
# create and send the command
buf = bytearray(6)
buf[0] = 0x40 | cmd
buf[1] = (arg >> 24) % 256
buf[2] = (arg >> 16) % 256
buf[3] = (arg >> 8) % 256
buf[4] = arg % 256
buf[5] = crc
await spi.write(buf)
if skip1:
tokenbuf = await readinto(1, 0xFF)
# wait for the response (response[7] == 0)
for i in range(_CMD_TIMEOUT):
tokenbuf = await readinto(1, 0xFF)
response = tokenbuf[0]
if not (response & 0x80):
# this could be a big-endian integer that we are getting here
# if final<0 then store the first byte to tokenbuf and discard the rest
if final < 0:
tokenbuf = await readinto(1, 0xFF)
final = -1 - final
for j in range(final):
await spi.write(b"\xff")
if release:
await spi.write(b"\xff")
return response
# timeout
await spi.write(b"\xff")
return -1
async def init_card_v2():
for i in range(_CMD_TIMEOUT):
await spi.delay_ms(50)
await cmd(58, 0, 0, 4)
await cmd(55, 0, 0)
if await cmd(41, 0x40000000, 0) == 0:
ocr = await cmd(
58, 0, 0, -4
) # 4-byte response, negative means keep the first byte
cdv = 0
if not ocr & 0x40:
# SDSC card, uses byte addressing in read/write/erase commands
cdv = 512
else:
# SDHC/SDXC card, uses block addressing in read/write/erase commands
cdv = 1
# print("[SDCard] v2 card")
return cdv
raise OSError("timeout waiting for v2 card")
async def read_blocks(block_num, num_blocks, block_size):
if num_blocks == 1:
if await cmd(17, block_num * block_size, 0, release=False) != 0:
raise OSError(5) # EIO
return await full_readinto(block_size)
else:
# Just read block after block.
ret = bytes([])
for i in range(num_blocks):
ret += await read_blocks(block_num + i, 1, block_size)
return ret
async def write_blocks(block_num, buf, block_size):
num_blocks = len(buf) // 512
if num_blocks == 1:
# CMD24: set write address for single block
if await cmd(24, block_num * block_size, 0) != 0:
raise OSError(5) # EIO
# send the data
await write(_TOKEN_DATA, buf)
else:
raise NotImplementedError()
async def main():
# clock card at least 100 cycles with cs high
for i in range(16):
await spi.write(b"\xff")
for _ in range(5):
result = await cmd(0, 0, 0x95)
if result == _R1_IDLE_STATE:
break
else:
raise OSError("no SD card")
print("got SD card!")
r = await cmd(8, 0x01AA, 0x87, 4)
if r != _R1_IDLE_STATE:
raise OSError("couldn't determine SD card version")
block_size = await init_card_v2()
print("block_size = ", block_size)
if await cmd(9, 0, 0, 0, False) != 0:
raise OSError("no response from SD card")
csd = await full_readinto(16)
sectors = 0
if csd[0] & 0xC0 == 0x40: # CSD version 2.0
print("csd v2", csd)
elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)
c_size = (csd[6] & 0b11) << 10 | csd[7] << 2 | csd[8] >> 6
c_size_mult = (csd[9] & 0b11) << 1 | csd[10] >> 7
read_bl_len = csd[5] & 0b1111
capacity = (c_size + 1) * (2 ** (c_size_mult + 2)) * (2**read_bl_len)
sectors = capacity // 512
else:
raise OSError("SD card CSD format not supported")
print("sectors", sectors)
if await cmd(16, 512, 0) != 0:
raise OSError("can't set 512 block size")
# data = open("sdcard.py", "rb").read()[:512]
# block_0 = await read_blocks(0, 1, block_size)
# await write_blocks(0, data, block_size)
# print(block_0)
await main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment