From 502805e2e82761be5f12f9a17f718a8b66d91716 Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Sun, 12 Oct 2025 12:03:30 +0200 Subject: [PATCH 1/4] hwconfig: Fix pad config for SD SPI, move clockrate to hwconfig The code snippet in hwconfig to adjust the drive strength was incorrect: It was adjusting the wrong pins. This was probably not updated during some pin shuffling in the breadboard phase. Fix this by adjusting the correct pins. Experimentation shows that both setting the slew rate to fast or setting the drive strength to 8 mA (default: slow and 4 mA) is sufficient to make SD SPI work at 25 MHz on the Rev1 PCB. For now, the combination 8 mA and slow is chosen (slow slew rate should result in less high frequency EMI). Also make the SD clock rate adjustable in hwconfig, and set it to 25 MHz for Rev1 and 15 MHz for the breadboard setup. Signed-off-by: Matthias Blankertz --- software/src/hwconfig_Rev1.py | 10 ++++++---- software/src/hwconfig_breadboard.py | 7 ++----- software/src/main.py | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/software/src/hwconfig_Rev1.py b/software/src/hwconfig_Rev1.py index 642cd2f..668d38f 100644 --- a/software/src/hwconfig_Rev1.py +++ b/software/src/hwconfig_Rev1.py @@ -9,6 +9,7 @@ SD_DI = Pin.board.GP3 SD_DO = Pin.board.GP4 SD_SCK = Pin.board.GP2 SD_CS = Pin.board.GP5 +SD_CLOCKRATE = 25000000 # MAX98357 I2S_LRCLK = Pin.board.GP6 @@ -46,10 +47,11 @@ def board_init(): POWER_EN.init(mode=Pin.OUT) POWER_EN.value(1) - # Set 8 mA drive strength and fast slew rate for SD SPI - machine.mem32[0x4001c004 + 6*4] = 0x67 - machine.mem32[0x4001c004 + 7*4] = 0x67 - machine.mem32[0x4001c004 + 8*4] = 0x67 + # SD_DO / MISO input doesn't need any special configuration + # Set 8 mA drive strength for SCK and MOSI + machine.mem32[0x4001c004 + 2*4] = 0x60 # SCK + machine.mem32[0x4001c004 + 3*4] = 0x60 # MOSI + # SD_CS doesn't need any special configuration # Permanently enable amplifier # TODO: Implement amplifier power management diff --git a/software/src/hwconfig_breadboard.py b/software/src/hwconfig_breadboard.py index 1e2b8ad..c291b3e 100644 --- a/software/src/hwconfig_breadboard.py +++ b/software/src/hwconfig_breadboard.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2025 Matthias Blankertz -import machine from machine import Pin # SD Card SPI @@ -9,6 +8,7 @@ SD_DI = Pin.board.GP3 SD_DO = Pin.board.GP4 SD_SCK = Pin.board.GP2 SD_CS = Pin.board.GP5 +SD_CLOCKRATE = 15000000 # MAX98357 I2S_LRCLK = Pin.board.GP7 @@ -41,10 +41,7 @@ VBAT_ADC = Pin.board.GP26 def board_init(): - # Set 8 mA drive strength and fast slew rate for SD SPI - machine.mem32[0x4001c004 + 6*4] = 0x67 - machine.mem32[0x4001c004 + 7*4] = 0x67 - machine.mem32[0x4001c004 + 8*4] = 0x67 + pass def get_battery_voltage(): diff --git a/software/src/main.py b/software/src/main.py index 67bed0e..9c22721 100644 --- a/software/src/main.py +++ b/software/src/main.py @@ -62,7 +62,7 @@ def run(): # Setup MP3 player with SDContext(mosi=hwconfig.SD_DI, miso=hwconfig.SD_DO, sck=hwconfig.SD_SCK, ss=hwconfig.SD_CS, - baudrate=15000000), \ + baudrate=hwconfig.SD_CLOCKRATE), \ AudioContext(hwconfig.I2S_DIN, hwconfig.I2S_DCLK, hwconfig.I2S_LRCLK) as audioctx: # Setup NFC From 9357b4d24355b1ab4dc43c74842ce6dc3af9ca52 Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Sun, 19 Oct 2025 16:49:46 +0200 Subject: [PATCH 2/4] rp2_sd: Disable input synchronizer for MISO pin The PIO has an internal synchronizer on each GPIO input which adds two cycles of delay. This prevents metastabilities in the PIO logic (see RP2040 datasheet p. 374f). For high speed synchronous interfaces such as SPI this needs to be disabled to reduce input delay. Signed-off-by: Matthias Blankertz --- software/modules/rp2_sd/sd_spi_pio.pio | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/software/modules/rp2_sd/sd_spi_pio.pio b/software/modules/rp2_sd/sd_spi_pio.pio index 5ce7cfe..e3fad15 100644 --- a/software/modules/rp2_sd/sd_spi_pio.pio +++ b/software/modules/rp2_sd/sd_spi_pio.pio @@ -52,10 +52,12 @@ static inline void sd_spi_pio_program_init(PIO pio, uint sm, uint offset, uint m sm_config_set_out_shift(&c, false, true, 8); sm_config_set_in_shift(&c, false, true, 8); + // high speed SPI needs to bypass the input synchronizers on the MISO pin + hw_set_bits(&pio->input_sync_bypass, 1u << miso); + const unsigned pio_freq = bitrate*4; const float div = clock_get_hz(clk_sys) / (float)pio_freq; - // for some reason, small clkdiv values (even integer ones) cause issues - sm_config_set_clkdiv(&c, div < 2.5f ? 2.5f : div); + sm_config_set_clkdiv(&c, div); pio_sm_init(pio, sm, offset, &c); } %} From 8e4f2fde21d2e6a18c70361fe356c27e1cdfe353 Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Sun, 19 Oct 2025 16:53:57 +0200 Subject: [PATCH 3/4] rp2_sd: Improve error handling * In sd_cmd_read_complete, check read token and report appropriate error _before_ performing CRC check. * In sd_read_csd, correctly handle the sd_cmd_read() failing. * In sd_init, deinit the lower level sd_spi driver correctly on failure. Signed-off-by: Matthias Blankertz --- software/modules/rp2_sd/sd.c | 125 +++++++++++++++++-------------- software/modules/rp2_sd/sd_spi.c | 8 +- 2 files changed, 74 insertions(+), 59 deletions(-) diff --git a/software/modules/rp2_sd/sd.c b/software/modules/rp2_sd/sd.c index 54dd452..d459719 100644 --- a/software/modules/rp2_sd/sd.c +++ b/software/modules/rp2_sd/sd.c @@ -130,57 +130,62 @@ static void sd_dump_cid [[maybe_unused]] (void) static bool sd_read_csd(struct sd_context *sd_context) { uint8_t buf[16]; - if (sd_cmd_read(9, 0, 16, buf)) { - const uint8_t crc = sd_crc7(15, buf); - const uint8_t card_crc = buf[15] >> 1; - if (card_crc != crc) { - printf("CRC mismatch: Got %02hhx, expected %02hhx\n", card_crc, crc); - // Some cheap SD cards always report CRC=0, don't fail in that case - if (card_crc != 0) { - return false; - } - } - const unsigned csd_ver = buf[0] >> 6; - unsigned blocksize [[maybe_unused]] = 0; - unsigned blocks = 0; - unsigned version [[maybe_unused]] = 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 = SD_SECTOR_SIZE; - 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"); + if (!sd_cmd_read(9, 0, 16, buf)) { + printf("Failed to read CSD\n"); + return false; + } + const uint8_t crc = sd_crc7(15, buf); + const uint8_t card_crc = buf[15] >> 1; + if (card_crc != crc) { + printf("CRC mismatch: Got %02hhx, expected %02hhx\n", card_crc, crc); + // Some cheap SD cards always report CRC=0, don't fail in that case + if (card_crc != 0) { return false; } - } - sd_context->blocks = blocks; - sd_context->blocksize = blocksize; -#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 } + const unsigned csd_ver = buf[0] >> 6; + unsigned blocksize [[maybe_unused]] = 0; + unsigned blocks = 0; + unsigned version [[maybe_unused]] = 0; + unsigned max_speed [[maybe_unused]] = 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; + max_speed = buf[3]; + break; + } + case 1: { + blocksize = SD_SECTOR_SIZE; + const unsigned c_size = (buf[7] & 0x3f) << 16 | buf[8] << 8 | buf[9]; + blocks = (c_size + 1) * 1024; + version = 2; + max_speed = buf[3]; + break; + } + case 2: { + printf("sd_init: Got CSD v3.0, but SDUC does not support SPI.\n"); + return false; + } + } + sd_context->blocks = blocks; + sd_context->blocksize = blocksize; +#ifdef SD_DEBUG + printf("CSD version %u.0, blocksize %u, blocks %u, capacity %llu MiB, max speed %u\n", version, blocksize, blocks, + ((uint64_t)blocksize * blocks) / (1024 * 1024), max_speed); +#endif return true; } @@ -191,52 +196,52 @@ bool sd_init(struct sd_context *sd_context, int mosi, int miso, int sck, int ss, } if (!sd_early_init()) { - return false; + goto out_spi; } if (!sd_check_interface_condition()) { - return false; + goto out_spi; } uint32_t ocr; if (!sd_read_ocr(&ocr)) { printf("sd_init: read OCR failed\n"); - return false; + goto out_spi; } if ((ocr & 0x00380000) != 0x00380000) { printf("sd_init: unsupported card voltage range\n"); - return false; + goto out_spi; } if (!sd_send_op_cond()) - return false; + goto out_spi; sd_spi_set_bitrate(rate); if (!sd_read_ocr(&ocr)) { printf("sd_init: read OCR failed\n"); - return false; + goto out_spi; } if (!(ocr & (1 << 31))) { printf("sd_init: card not powered up but !idle?\n"); - return false; + goto out_spi; } sd_context->sdhc_sdxc = (ocr & (1 << 30)); if (!sd_read_csd(sd_context)) { - return false; + goto out_spi; } if (sd_context->blocksize != SD_SECTOR_SIZE) { if (sd_context->blocksize != 1024 && sd_context->blocksize != 2048) { printf("sd_init: Unsupported block size %u\n", sd_context->blocksize); - return false; + goto out_spi; } // Attempt SET_BLOCKLEN command uint8_t resp[1]; if (!sd_cmd(16, SD_SECTOR_SIZE, 1, resp)) { printf("sd_init: SET_BLOCKLEN failed\n"); - return false; + goto out_spi; } // Successfully set blocksize to SD_SECTOR_SIZE, adjust context sd_context->blocks *= sd_context->blocksize / SD_SECTOR_SIZE; @@ -253,6 +258,10 @@ bool sd_init(struct sd_context *sd_context, int mosi, int miso, int sck, int ss, sd_context->initialized = true; return true; + +out_spi: + sd_spi_deinit(); + return false; } bool sd_deinit(struct sd_context *sd_context) diff --git a/software/modules/rp2_sd/sd_spi.c b/software/modules/rp2_sd/sd_spi.c index 79c8f39..09d5715 100644 --- a/software/modules/rp2_sd/sd_spi.c +++ b/software/modules/rp2_sd/sd_spi.c @@ -208,6 +208,12 @@ bool sd_cmd_read_complete(void) sd_spi_wait_complete(); gpio_put(sd_spi_context.ss, true); sd_spi_read_blocking(0xff, &buf, 1); + if (sd_spi_context.sd_dma_context.read_token_buf != 0xfe) { +#ifdef SD_DEBUG + printf("read failed: invalid read token %02hhx\n", sd_spi_context.sd_dma_context.read_token_buf); +#endif + return false; + } #ifdef SD_READ_CRC_CHECK const uint16_t expect_crc = sd_crc16(sd_spi_context.sd_dma_context.len, sd_spi_context.sd_dma_context.read_buf); const uint16_t act_crc = sd_spi_context.sd_dma_context.crc_buf[0] << 8 | sd_spi_context.sd_dma_context.crc_buf[1]; @@ -218,7 +224,7 @@ bool sd_cmd_read_complete(void) return false; } #endif - return (sd_spi_context.sd_dma_context.read_token_buf == 0xfe); + return true; } bool sd_cmd_write(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[const static datalen]) From 2809d3f6e76385bb492782ec9a98d0a4008677a8 Mon Sep 17 00:00:00 2001 From: Matthias Blankertz Date: Sun, 19 Oct 2025 16:57:19 +0200 Subject: [PATCH 4/4] tools: standalone_mp3: Add read test, increase speed to 25 MHz Add a read test to the SD tests in standalone_mp3, and also apply the drive strength setup and higher clockrate from board_Rev1.py. Signed-off-by: Matthias Blankertz --- software/tools/standalone-mp3/CMakeLists.txt | 5 ++++ software/tools/standalone-mp3/main.c | 31 ++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/software/tools/standalone-mp3/CMakeLists.txt b/software/tools/standalone-mp3/CMakeLists.txt index 54d700b..148d438 100644 --- a/software/tools/standalone-mp3/CMakeLists.txt +++ b/software/tools/standalone-mp3/CMakeLists.txt @@ -10,6 +10,7 @@ include(../../lib/micropython/lib/pico-sdk/pico_sdk_init.cmake) project(standalone_mp3) option(ENABLE_WRITE_TEST "Enable write test" OFF) +option(ENABLE_READ_TEST "Enable read test" ON) option(ENABLE_PLAY_TEST "Enable mp3 playback test" OFF) option(ENABLE_SD_READ_CRC "Enable crc check when reading from sd card" OFF) option(ENABLE_SD_DEBUG "Enable debug output for sd card driver" OFF) @@ -37,6 +38,10 @@ if(ENABLE_WRITE_TEST) target_compile_definitions(standalone_mp3 PRIVATE WRITE_TEST) endif() +if(ENABLE_READ_TEST) + target_compile_definitions(standalone_mp3 PRIVATE READ_TEST) +endif() + if(ENABLE_PLAY_TEST) target_compile_definitions(standalone_mp3 PRIVATE PLAY_TEST) endif() diff --git a/software/tools/standalone-mp3/main.c b/software/tools/standalone-mp3/main.c index f4d4ae3..b03ec70 100644 --- a/software/tools/standalone-mp3/main.c +++ b/software/tools/standalone-mp3/main.c @@ -157,7 +157,7 @@ static void write_test(struct sd_context *sd_context) data_buffer[i] ^= 0xff; } - if(!sd_writeblock(sd_context, 0, data_buffer)) { + if (!sd_writeblock(sd_context, 0, data_buffer)) { printf("sd_writeblock failed\n"); return; } @@ -165,6 +165,20 @@ static void write_test(struct sd_context *sd_context) } while (data_buffer[SD_SECTOR_SIZE - 1] != 0xAA); } +static void read_test(struct sd_context *sd_context) +{ + uint8_t data_buffer[512]; + const uint64_t before = time_us_64(); + for (int block = 0; block < 245760; ++block) { + if (!sd_readblock(sd_context, block, data_buffer)) { + printf("sd_readblock(%d) failed\n", block); + return; + } + } + const uint64_t elapsed = time_us_64() - before; + printf("%llu ms elapsed, %f kB/s\n", elapsed / 1000LLU, 128 * 1024.f / (elapsed / 1000000.f)); +} + int main() { stdio_init_all(); @@ -172,10 +186,23 @@ int main() struct sd_context sd_context; - if (!sd_init(&sd_context, 3, 4, 2, 5, 15000000)) { +#define DRIVE_STRENGTH GPIO_DRIVE_STRENGTH_8MA +#define SLEW_RATE GPIO_SLEW_RATE_SLOW + + gpio_set_drive_strength(2, DRIVE_STRENGTH); + gpio_set_slew_rate(2, SLEW_RATE); + gpio_set_drive_strength(3, DRIVE_STRENGTH); + gpio_set_slew_rate(3, SLEW_RATE); + + if (!sd_init(&sd_context, 3, 4, 2, 5, 25000000)) { + printf("sd_init failed\n"); return 1; } +#ifdef READ_TEST + read_test(&sd_context); +#endif + #ifdef WRITE_TEST write_test(&sd_context); #endif