From 7727cf0b80cae9eba05e3463ed8f8a02c7e0db8a Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Tue, 29 Oct 2024 20:56:13 +0100 Subject: [PATCH] Start work on SD card driver Working: - card initialization - card size & type detection - read block TODO: - write block - Use interrupts and DMA for read/write block --- software/src/standalone-mp3/CMakeLists.txt | 21 ++ software/src/standalone-mp3/main.c | 27 +++ software/src/standalone-mp3/sd.c | 237 +++++++++++++++++++++ software/src/standalone-mp3/sd.h | 9 + software/src/standalone-mp3/sd_spi.c | 101 +++++++++ software/src/standalone-mp3/sd_spi.h | 20 ++ software/src/standalone-mp3/sd_util.h | 30 +++ 7 files changed, 445 insertions(+) create mode 100644 software/src/standalone-mp3/CMakeLists.txt create mode 100644 software/src/standalone-mp3/main.c create mode 100644 software/src/standalone-mp3/sd.c create mode 100644 software/src/standalone-mp3/sd.h create mode 100644 software/src/standalone-mp3/sd_spi.c create mode 100644 software/src/standalone-mp3/sd_spi.h create mode 100644 software/src/standalone-mp3/sd_util.h diff --git a/software/src/standalone-mp3/CMakeLists.txt b/software/src/standalone-mp3/CMakeLists.txt new file mode 100644 index 0000000..3ff369d --- /dev/null +++ b/software/src/standalone-mp3/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.13) + +# initialize pico-sdk from submodule +# note: this must happen before project() +include(../../lib/micropython/lib/pico-sdk/pico_sdk_init.cmake) + +project(standalone_mp3) + +# initialize the Raspberry Pi Pico SDK +pico_sdk_init() + +add_executable(standalone_mp3 + main.c + sd.c + sd_spi.c +) + +target_link_libraries(standalone_mp3 PRIVATE pico_stdlib hardware_spi) + +pico_add_extra_outputs(standalone_mp3) +pico_enable_stdio_uart(standalone_mp3 1) diff --git a/software/src/standalone-mp3/main.c b/software/src/standalone-mp3/main.c new file mode 100644 index 0000000..e232178 --- /dev/null +++ b/software/src/standalone-mp3/main.c @@ -0,0 +1,27 @@ +#include "sd.h" + +#include + +#include +#include +#include + +int main() +{ + stdio_init_all(); + + if (!sd_init()) { + return 1; + } + + uint8_t buf[512]; + if (sd_readblock(0, buf)) { + for(int row = 0;row < 512/16;++row) { + printf("%03x: ", row*16); + for (int col = 0; col < 16;++col) + printf("%02hhx%c", buf[row*16+col], (col==15)?'\n':' '); + } + } + + printf("Done.\n"); +} diff --git a/software/src/standalone-mp3/sd.c b/software/src/standalone-mp3/sd.c new file mode 100644 index 0000000..2c924ee --- /dev/null +++ b/software/src/standalone-mp3/sd.c @@ -0,0 +1,237 @@ +#include "sd.h" +#include "sd_util.h" +#include "sd_spi.h" + +#include +#include + +#define SD_DEBUG + +#define SD_R1_ILLEGAL_COMMAND (1 << 2) + +struct sd_context { + size_t blocks; + bool initialized; + bool old_card; + bool sdhc_sdxc; +}; + +static struct sd_context sd_context; + +static bool sd_acmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t res[static resplen]) +{ + if (!sd_cmd(55, 0, 1, res)) + return false; + if ((res[0] & 0x7e) != 0x00) + return false; + + return sd_cmd(cmd, arg, resplen, res); +} + +static bool sd_early_init(void) +{ + uint8_t buf; + for (int i = 0; i < 5; ++i) { + if (sd_cmd(0, 0, 1, &buf)) { +#ifdef SD_DEBUG + printf("CMD0 resp %02hhx\n", buf); +#endif + if (buf == 0x01) { + return true; + } + } +#ifdef SD_DEBUG + printf("CMD0 timeout, try again...\n"); +#endif + } + return false; +} + +static bool sd_check_interface_condition(void) +{ + uint8_t buf[5]; + if (sd_cmd(8, 0x000001AA, 5, buf)) { + if ((buf[3] & 0xf) != 0x1 || buf[4] != 0xAA) { + printf("sd_init: check interface condition failed\n"); + return false; + } + } else { + if (buf[0] & SD_R1_ILLEGAL_COMMAND) { + printf("sd_init: check interface condition returned illegal command - old card?\n"); + } else { + printf("sd_init: check interface condition failed\n"); + return false; + } + } + + return true; +} + +static bool sd_send_op_cond(void) +{ + uint8_t buf[1]; + bool use_acmd = true; + for (int timeout = 0; timeout < 500; ++timeout) { + bool result = false; + if (use_acmd) + result = sd_acmd(41, 0x40000000, 1, buf); + else + result = sd_cmd(1, 0x40000000, 1, buf); + if (!result) { + if (use_acmd && buf[0] & SD_R1_ILLEGAL_COMMAND) { + printf("sd_init: card does not understand ACMD41, try CMD1...\n"); + continue; + } else if (buf[0] != 0x01) { + printf("sd_init: send_op_cond failed\n"); + return false; + } else { + continue; + } + } + if (buf[0] == 0x00) { + return true; + } + } + printf("sd_init: send_op_cond: timeout waiting for !idle\n"); + return false; +} + +static bool sd_read_ocr(uint32_t *const ocr) +{ + uint8_t buf[5]; + if (!sd_cmd(58, 0, 5, buf)) + return false; + *ocr = buf[1] << 24 | buf[2] << 16 | buf[3] << 8 | buf[4]; + return true; +} + +static void sd_dump_cid(void) +{ + uint8_t buf[16]; + if (sd_cmd_read(10, 0, 16, buf)) { + const uint8_t crc = sd_crc7(15, buf); + if (buf[15] >> 1 != crc) { + printf("CRC mismatch: Got %02hhx, expected %02hhx\n", buf[15] >> 1, crc); + return; + } + + uint8_t mid = buf[0]; + char oid[2], pnm[5]; + memcpy(oid, buf + 1, 2); + memcpy(pnm, buf + 3, 5); + uint8_t prv = buf[8]; + uint32_t psn = buf[9] << 24 | buf[10] << 16 | buf[11] << 8 | buf[12]; + int mdt_year = 2000 + ((buf[13] & 0xf) << 4 | (buf[14] & 0xf0) >> 4); + int mdt_month = buf[14] & 0x0f; + printf("CID: mid: %02hhx, oid: %.2s, pnm: %.5s, prv: %02hhx, psn: %08x, mdt_year: %d, mdt_month: %d\n", mid, + oid, pnm, prv, psn, mdt_year, mdt_month); + } +} + +static bool sd_read_csd(void) +{ + uint8_t buf[16]; + if (sd_cmd_read(9, 0, 16, buf)) { + const uint8_t crc = sd_crc7(15, buf); + if (buf[15] >> 1 != crc) { + printf("CRC mismatch: Got %02hhx, expected %02hhx\n", buf[15] >> 1, crc); + return false; + } + const unsigned csd_ver = buf[0] >> 6; + unsigned blocksize = 0; + unsigned blocks = 0; + unsigned version = 0; + switch (csd_ver) { + case 0: { + if (sd_context.sdhc_sdxc) { + printf("sd_init: Got CSD v1.0 but card is SDHC/SDXC?\n"); + return false; + } + const unsigned read_bl_len = buf[5] & 0xf; + if (read_bl_len < 9 || read_bl_len > 11) { + printf("Invalid read_bl_len in CSD 1.0\n"); + return false; + } + blocksize = 1 << (buf[5] & 0xf); + const unsigned c_size_mult = (buf[9] & 0x1) << 2 | (buf[10] & 0xc0) >> 6; + const unsigned c_size = (buf[6] & 0x3) << 10 | (buf[7] << 2) | (buf[8] & 0xc0) >> 6; + blocks = (c_size + 1) * (1 << (c_size_mult + 2)); + version = 1; + break; + } + case 1: { + blocksize = 512; + const unsigned c_size = (buf[7] & 0x3f) << 16 | buf[8] << 8 | buf[9]; + blocks = (c_size + 1) * 1024; + version = 2; + break; + } + case 2: { + printf("sd_init: Got CSD v3.0, but SDUC does not support SPI.\n"); + return false; + } + } + sd_context.blocks = blocks; +#ifdef SD_DEBUG + printf("CSD version %u.0, blocksize %u, blocks %u, capacity %llu MiB\n", version, blocksize, blocks, + ((uint64_t)blocksize * blocks) / (1024 * 1024)); +#endif + } + return true; +} + +bool sd_init(void) +{ + sd_spi_init(); + if (!sd_early_init()) { + return false; + } + + if (!sd_check_interface_condition()) { + return false; + } + + uint32_t ocr; + if (!sd_read_ocr(&ocr)) { + printf("sd_init: read OCR failed\n"); + return false; + } + if ((ocr & 0x00380000) != 0x00380000) { + printf("sd_init: unsupported card voltage range\n"); + return false; + } + + if (!sd_send_op_cond()) + return false; + + sd_spi_set_bitrate(SD_BITRATE); + + if (!sd_read_ocr(&ocr)) { + printf("sd_init: read OCR failed\n"); + return false; + } + if (!(ocr & (1 << 31))) { + printf("sd_init: card not powered up but !idle?\n"); + return false; + } + sd_context.sdhc_sdxc = (ocr & (1 << 30)); + + if (!sd_read_csd()) { + return false; + } + +#ifdef SD_DEBUG + sd_dump_cid(); +#endif + + sd_context.initialized = true; + return true; +} + +bool sd_readblock(size_t sector_num, uint8_t buffer[static 512]) +{ + if (!sd_context.initialized || sector_num >= sd_context.blocks) + return false; + + return sd_cmd_read(17, sector_num, 512, buffer); +} diff --git a/software/src/standalone-mp3/sd.h b/software/src/standalone-mp3/sd.h new file mode 100644 index 0000000..27fe451 --- /dev/null +++ b/software/src/standalone-mp3/sd.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +bool sd_init(void); + +bool sd_readblock(size_t sector_num, uint8_t buffer[static 512]); diff --git a/software/src/standalone-mp3/sd_spi.c b/software/src/standalone-mp3/sd_spi.c new file mode 100644 index 0000000..fe7a895 --- /dev/null +++ b/software/src/standalone-mp3/sd_spi.c @@ -0,0 +1,101 @@ +#include "sd_spi.h" +#include "sd_util.h" + +#include +#include +#include +#include + +static void sd_spi_cmd_send(const uint8_t cmd, const uint32_t arg) +{ + uint8_t buf[6] = {0x40 | cmd, arg >> 24, arg >> 16, arg >> 8, arg, 0}; + buf[5] = sd_crc7(5, buf) << 1 | 1; + gpio_put(SD_CS, false); + // Write command, argument and CRC + spi_write_blocking(SD_SPI, buf, 6); +} + +bool sd_cmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t resp[static resplen]) +{ + sd_spi_cmd_send(cmd, arg); + resp[0] = 0; + // Read up to 8 garbage bytes (0xff), followed by R1 (MSB is zero) + bool got_r1 = false; + for (int timeout = 0; timeout < 8; ++timeout) { + spi_read_blocking(SD_SPI, 0xff, resp, 1); + if (!(resp[0] & 0x80)) { + got_r1 = true; + break; + } + } + if (got_r1 && (resp[0] & 0x7e) == 0) { + // read rest of response if R1 does not indicate an error + spi_read_blocking(SD_SPI, 0xff, resp + 1, resplen - 1); + } + gpio_put(SD_CS, true); + const uint8_t buf = 0xff; + // Ensure 8 SPI clock cycles after CS deasserted + spi_write_blocking(SD_SPI, &buf, 1); + + return got_r1 && (resp[0] & 0x7e) == 0; +} + +bool sd_cmd_read(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen]) +{ + uint8_t buf[2]; + sd_spi_cmd_send(cmd, arg); + // Read up to 8 garbage bytes (0xff), followed by R1 (MSB is zero) + bool got_r1 = false; + for (int timeout = 0; timeout < 8; ++timeout) { + spi_read_blocking(SD_SPI, 0xff, buf, 1); + if (!(buf[0] & 0x80)) { + got_r1 = true; + break; + } + } + if (!got_r1 || buf[0] != 0x00) + goto abort; + while (true) { // TODO: what is a sensible timeout here? + spi_read_blocking(SD_SPI, 0xff, buf, 1); + if (buf[0] != 0xff) + break; + } + if (buf[0] != 0xfe) // unexpected read token + goto abort; + spi_read_blocking(SD_SPI, 0xff, data, datalen); + spi_read_blocking(SD_SPI, 0xff, buf, 2); + // we ignore the CRC16 for performance reasons + gpio_put(SD_CS, true); + spi_read_blocking(SD_SPI, 0xff, buf, 1); + return true; + +abort: + gpio_put(SD_CS, true); + spi_read_blocking(SD_SPI, 0xff, buf, 1); + return false; +} + +void sd_spi_init(void) +{ + gpio_set_function(SD_MISO, GPIO_FUNC_SPI); + gpio_set_function(SD_SCK, GPIO_FUNC_SPI); + gpio_set_function(SD_MOSI, GPIO_FUNC_SPI); + gpio_init(SD_CS); + gpio_set_dir(SD_CS, true); + + sd_spi_set_bitrate(SD_INIT_BITRATE); + spi_set_format(SD_SPI, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + + gpio_put(SD_CS, true); + uint8_t buf[16]; + memset(buf, 0xff, 16); + // Ensure at least 74 SPI clock cycles without CS asserted + spi_write_blocking(SD_SPI, buf, 10); + +} + +void sd_spi_set_bitrate(const int rate) +{ + const unsigned actual_rate = spi_init(SD_SPI, rate); + printf("SPI init: Requested bitrate %u, got %u\n", rate, actual_rate); +} diff --git a/software/src/standalone-mp3/sd_spi.h b/software/src/standalone-mp3/sd_spi.h new file mode 100644 index 0000000..4d1f790 --- /dev/null +++ b/software/src/standalone-mp3/sd_spi.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#define SD_MISO 4 +#define SD_SCK 2 +#define SD_MOSI 3 +#define SD_CS 5 + +#define SD_SPI spi0 + +#define SD_INIT_BITRATE 400000 +#define SD_BITRATE 25000000 + +bool sd_cmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t resp[static resplen]); +bool sd_cmd_read(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen]); + +void sd_spi_init(void); +void sd_spi_set_bitrate(const int rate); diff --git a/software/src/standalone-mp3/sd_util.h b/software/src/standalone-mp3/sd_util.h new file mode 100644 index 0000000..0d22058 --- /dev/null +++ b/software/src/standalone-mp3/sd_util.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +inline static uint8_t sd_crc7(size_t len, const uint8_t data[const static len]) +{ + const uint8_t poly = 0b1001; + uint8_t crc = 0; + for (size_t pos = 0; pos < len; ++pos) { + crc ^= data[pos]; + for (int bit = 0; bit < 8; ++bit) { + crc = (crc << 1) ^ ((crc & 0x80) ? (poly << 1) : 0); + } + } + return crc >> 1; +} + +/* inline static uint16_t sd_crc16(size_t len, const uint8_t data[const static len]) */ +/* { */ +/* const uint16_t poly = 0b1000000100001; */ +/* uint16_t crc = 0; */ +/* for (size_t pos = 0; pos < len; ++pos) { */ +/* crc ^= data[pos] << 8; */ +/* for (int bit = 0; bit < 8; ++bit) { */ +/* crc = (crc << 1) ^ ((crc & 0x8000) ? poly : 0); */ +/* } */ +/* } */ +/* return crc; */ +/* } */