Add rp2 PIO and DMA fast SD card driver

The builtin micropython SD card driver using the default micropython SPI
implementation suffered from very low read speeds. Add an optimized SD
card driver using rp2 PIO and DMA. Currently read only, write support to
be added later.

Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
2025-02-18 21:36:44 +01:00
parent 48c5caa078
commit 460a67cf9f
11 changed files with 840 additions and 1 deletions

View File

@@ -15,3 +15,5 @@ set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
set(GEN_PINS_BOARD_CSV "${CMAKE_CURRENT_LIST_DIR}/pins.csv")
set(GEN_PINS_CSV_ARG --board-csv "${GEN_PINS_BOARD_CSV}")
add_link_options("-Wl,--print-memory-usage")

View File

@@ -8,7 +8,7 @@ set -eu
make -C mpy-cross -j "$(nproc)"
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR="$TOPDIR"/boards/RPI_PICO_W clean
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR="$TOPDIR"/boards/RPI_PICO_W \
USER_C_MODULES="$TOPDIR"/src/audiocore/micropython.cmake -j "$(nproc)"
USER_C_MODULES="$TOPDIR"/src/micropython.cmake -j "$(nproc)"
)
echo "Output in lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/firmware.uf2"

View File

@@ -0,0 +1,2 @@
include(${CMAKE_CURRENT_LIST_DIR}/audiocore/micropython.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rp2_sd/micropython.cmake)

View File

@@ -0,0 +1,19 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
add_library(usermod_rp2_sd INTERFACE)
pico_generate_pio_header(usermod_rp2_sd ${CMAKE_CURRENT_LIST_DIR}/sd_spi_pio.pio)
target_sources(usermod_rp2_sd INTERFACE
${CMAKE_CURRENT_LIST_DIR}/module.c
${CMAKE_CURRENT_LIST_DIR}/sd.c
${CMAKE_CURRENT_LIST_DIR}/sd_spi.c
${CMAKE_CURRENT_BINARY_DIR}/sd_spi_pio.pio.h
)
target_include_directories(usermod_rp2_sd INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(usermod INTERFACE usermod_rp2_sd)

View File

@@ -0,0 +1,117 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
#include "py/obj.h"
#include "sd.h"
// Include MicroPython API.
#include "py/mperrno.h"
#include "py/runtime.h"
// This module is RP2 specific
#include "mphalport.h"
#include <string.h>
const mp_obj_type_t sdcard_type;
struct sdcard_obj {
mp_obj_base_t base;
struct sd_context sd_context;
};
static void sdcard_init(struct sdcard_obj *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
{
enum { ARG_mosi, ARG_miso, ARG_sck, ARG_ss, ARG_baudrate };
static const mp_arg_t allowed_args[] = {
{MP_QSTR_mosi, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_miso, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_sck, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_ss, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
{MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 15000000}},
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
const mp_hal_pin_obj_t pin_mosi = mp_hal_get_pin_obj(args[ARG_mosi].u_obj);
const mp_hal_pin_obj_t pin_miso = mp_hal_get_pin_obj(args[ARG_miso].u_obj);
const mp_hal_pin_obj_t pin_sck = mp_hal_get_pin_obj(args[ARG_sck].u_obj);
const mp_hal_pin_obj_t pin_ss = mp_hal_get_pin_obj(args[ARG_ss].u_obj);
const unsigned baudrate = args[ARG_baudrate].u_int;
if (!sd_init(&obj->sd_context, pin_mosi, pin_miso, pin_sck, pin_ss, baudrate)) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("sd_init() failed"));
}
}
static mp_obj_t sdcard_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args)
{
struct sdcard_obj *sdcard = mp_obj_malloc(struct sdcard_obj, &sdcard_type);
mp_map_t kw_args;
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
sdcard_init(sdcard, n_args, args, &kw_args);
return MP_OBJ_FROM_PTR(sdcard);
}
static mp_obj_t sdcard_deinit(mp_obj_t self_obj)
{
struct sdcard_obj *self = MP_OBJ_TO_PTR(self_obj);
if (!sd_deinit(&self->sd_context))
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("sd_deinit() failed"));
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(sdcard_deinit_obj, sdcard_deinit);
static mp_obj_t sdcard_readblocks(mp_obj_t self_obj, mp_obj_t block_obj, mp_obj_t buf_obj)
{
struct sdcard_obj *self = MP_OBJ_TO_PTR(self_obj);
const int start_block = mp_obj_get_int(block_obj);
mp_buffer_info_t bufinfo;
if (!mp_get_buffer(buf_obj, &bufinfo, MP_BUFFER_WRITE))
mp_raise_ValueError("Not a write buffer");
if (bufinfo.len % 512 != 0)
mp_raise_ValueError("Buffer length is invalid");
const int nblocks = bufinfo.len / 512;
for (int block = 0; block < nblocks; block++) {
// TODO: Implement CMD18 read multiple blocks
if (!sd_readblock(&self->sd_context, start_block + block, bufinfo.buf + block * 512))
mp_raise_OSError(MP_EIO);
}
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_3(sdcard_readblocks_obj, sdcard_readblocks);
static mp_obj_t sdcard_ioctl(mp_obj_t self_obj, mp_obj_t op_obj, mp_obj_t arg_obj)
{
struct sdcard_obj *self = MP_OBJ_TO_PTR(self_obj);
int op = mp_obj_get_int(op_obj);
switch (op) {
case 4:
return mp_obj_new_int(self->sd_context.blocks);
case 5:
return mp_obj_new_int(512);
default:
return mp_const_none;
}
};
static MP_DEFINE_CONST_FUN_OBJ_3(sdcard_ioctl_obj, sdcard_ioctl);
static const mp_rom_map_elem_t sdcard_locals_dict_table[] = {
{MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&sdcard_deinit_obj)},
{MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&sdcard_ioctl_obj)},
{MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&sdcard_readblocks_obj)},
};
static MP_DEFINE_CONST_DICT(sdcard_locals_dict, sdcard_locals_dict_table);
MP_DEFINE_CONST_OBJ_TYPE(sdcard_type, MP_QSTR_SDCard, MP_TYPE_FLAG_NONE, locals_dict, &sdcard_locals_dict, make_new,
&sdcard_make_new);
static const mp_rom_map_elem_t rp2_sd_module_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rp2_sd)},
{MP_ROM_QSTR(MP_QSTR_SDCard), MP_ROM_PTR(&sdcard_type)},
};
static MP_DEFINE_CONST_DICT(rp2_sd_module_globals, rp2_sd_module_globals_table);
const mp_obj_module_t rp2_sd_cmodule = {
.base = {&mp_type_module},
.globals = (mp_obj_dict_t *)&rp2_sd_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_rp2_sd, rp2_sd_cmodule);

267
software/src/rp2_sd/sd.c Normal file
View File

@@ -0,0 +1,267 @@
#include "sd.h"
#include "sd_spi.h"
#include "sd_util.h"
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
// #define SD_DEBUG
#define SD_R1_ILLEGAL_COMMAND (1 << 2)
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) {
#ifdef SD_DEBUG
printf("sd_init: card does not understand ACMD41, try CMD1...\n");
#endif
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 [[maybe_unused]] (void)
{
uint8_t buf[16];
if (sd_cmd_read(10, 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;
}
}
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: %08" PRIx32 ", mdt_year: %d, mdt_month: %d\n",
mid, oid, pnm, prv, psn, mdt_year, mdt_month);
}
}
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 = 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(struct sd_context *sd_context, int mosi, int miso, int sck, int ss, int rate)
{
if (!sd_spi_init(mosi, miso, sck, ss)) {
return false;
}
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(rate);
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(sd_context)) {
return false;
}
#ifdef SD_DEBUG
sd_dump_cid();
#endif
sd_context->initialized = true;
return true;
}
bool sd_deinit(struct sd_context *sd_context)
{
if (!sd_spi_deinit())
return false;
sd_context->initialized = false;
return true;
}
bool sd_readblock(struct sd_context *sd_context, 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);
}
bool sd_readblock_start(struct sd_context *sd_context, size_t sector_num, uint8_t buffer[static 512])
{
if (!sd_context->initialized || sector_num >= sd_context->blocks)
return false;
return sd_cmd_read_start(17, sector_num, 512, buffer);
}
bool sd_readblock_complete(struct sd_context *sd_context)
{
if (!sd_context->initialized)
return false;
sd_cmd_read_complete();
return true;
}
bool sd_readblock_is_complete(struct sd_context *sd_context) { return sd_cmd_read_is_complete(); }

21
software/src/rp2_sd/sd.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct sd_context {
size_t blocks;
bool initialized;
bool old_card;
bool sdhc_sdxc;
};
bool sd_init(struct sd_context *context, int mosi, int miso, int sck, int ss, int rate);
bool sd_deinit(struct sd_context *sd_context);
bool sd_readblock(struct sd_context *context, size_t sector_num, uint8_t buffer[static 512]);
bool sd_readblock_start(struct sd_context *context, size_t sector_num, uint8_t buffer[static 512]);
bool sd_readblock_complete(struct sd_context *context);
bool sd_readblock_is_complete(struct sd_context *context);

View File

@@ -0,0 +1,293 @@
#include "hardware/pio.h"
#ifdef NDEBUG
#undef NDEBUG
#endif
#include "sd_spi.h"
#include "sd_util.h"
#include "sd_spi_pio.pio.h"
#include "hardware/dma.h"
#include <hardware/gpio.h>
#include <hardware/spi.h>
#include <hardware/sync.h>
#include <pico/time.h>
#include <string.h>
struct sd_dma_context {
uint8_t *read_buf;
size_t len;
uint8_t crc_buf[2];
uint8_t read_token_buf;
uint8_t wrdata;
_Atomic enum { DMA_READ_TOKEN, DMA_READ, DMA_IDLE } state;
};
struct sd_spi_context {
struct sd_dma_context sd_dma_context;
int spi_sm;
unsigned spi_offset;
int spi_dma_rd, spi_dma_wr, spi_dma_rd_crc;
dma_channel_config spi_dma_rd_cfg, spi_dma_wr_cfg, spi_dma_rd_crc_cfg;
int mosi, miso, sck, ss;
bool initialized;
};
/* We only realistically need one context, so reduce overhead by statically allocating it here */
static struct sd_spi_context sd_spi_context = {};
static void __time_critical_func(sd_spi_write_blocking)(const uint8_t *data, size_t len)
{
if (len == 0)
return;
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, data[0] << 24);
for (size_t i = 1; i < len; ++i) {
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, data[i] << 24);
pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
}
pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
assert(pio_sm_is_tx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
assert(pio_sm_is_rx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
}
static void __time_critical_func(sd_spi_read_blocking)(uint8_t wrdata, uint8_t *data, size_t len)
{
if (len == 0)
return;
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, wrdata << 24);
for (size_t i = 0; i < len - 1; ++i) {
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, wrdata << 24);
data[i] = pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
}
data[len - 1] = pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
assert(pio_sm_is_tx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
assert(pio_sm_is_rx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
}
static void sd_spi_dma_isr(void)
{
if (dma_channel_get_irq0_status(sd_spi_context.spi_dma_rd)) {
dma_channel_acknowledge_irq0(sd_spi_context.spi_dma_rd);
if (sd_spi_context.sd_dma_context.state == DMA_READ_TOKEN) {
if (sd_spi_context.sd_dma_context.read_token_buf != 0xff) {
if (sd_spi_context.sd_dma_context.read_token_buf == 0xfe) {
channel_config_set_chain_to(&sd_spi_context.spi_dma_rd_cfg, sd_spi_context.spi_dma_rd_crc);
channel_config_set_irq_quiet(&sd_spi_context.spi_dma_rd_cfg, true);
dma_channel_configure(sd_spi_context.spi_dma_rd, &sd_spi_context.spi_dma_rd_cfg,
sd_spi_context.sd_dma_context.read_buf, &SD_PIO->rxf[sd_spi_context.spi_sm],
sd_spi_context.sd_dma_context.len, false);
dma_channel_configure(sd_spi_context.spi_dma_wr, &sd_spi_context.spi_dma_wr_cfg,
&SD_PIO->txf[sd_spi_context.spi_sm], &sd_spi_context.sd_dma_context.wrdata,
sd_spi_context.sd_dma_context.len + 2, false);
dma_channel_configure(sd_spi_context.spi_dma_rd_crc, &sd_spi_context.spi_dma_rd_crc_cfg,
sd_spi_context.sd_dma_context.crc_buf, &SD_PIO->rxf[sd_spi_context.spi_sm], 2,
false);
dma_start_channel_mask((1 << sd_spi_context.spi_dma_rd) | (1 << sd_spi_context.spi_dma_wr));
sd_spi_context.sd_dma_context.state = DMA_READ;
} else {
// Bad read token, abort transfer
sd_spi_context.sd_dma_context.state = DMA_IDLE;
}
} else {
// try again
dma_channel_configure(sd_spi_context.spi_dma_rd, &sd_spi_context.spi_dma_rd_cfg,
&sd_spi_context.sd_dma_context.read_token_buf,
&SD_PIO->rxf[sd_spi_context.spi_sm], 1, false);
dma_channel_configure(sd_spi_context.spi_dma_wr, &sd_spi_context.spi_dma_wr_cfg,
&SD_PIO->txf[sd_spi_context.spi_sm], &sd_spi_context.sd_dma_context.wrdata, 1,
false);
dma_start_channel_mask((1 << sd_spi_context.spi_dma_rd) | (1 << sd_spi_context.spi_dma_wr));
}
}
}
if (dma_channel_get_irq0_status(sd_spi_context.spi_dma_rd_crc)) {
dma_channel_acknowledge_irq0(sd_spi_context.spi_dma_rd_crc);
assert(sd_spi_context.sd_dma_context.state == DMA_READ);
sd_spi_context.sd_dma_context.state = DMA_IDLE;
}
}
void sd_spi_wait_complete(void)
{
while (sd_spi_context.sd_dma_context.state != DMA_IDLE)
__wfi();
}
bool sd_cmd_read_is_complete(void) { return sd_spi_context.sd_dma_context.state == DMA_IDLE; }
static void sd_spi_read_dma(uint8_t wrdata, uint8_t *data, size_t len)
{
assert(sd_spi_context.sd_dma_context.state == DMA_IDLE);
channel_config_set_chain_to(&sd_spi_context.spi_dma_rd_cfg, sd_spi_context.spi_dma_rd);
channel_config_set_irq_quiet(&sd_spi_context.spi_dma_rd_cfg, false);
dma_channel_configure(sd_spi_context.spi_dma_rd, &sd_spi_context.spi_dma_rd_cfg,
&sd_spi_context.sd_dma_context.read_token_buf, &SD_PIO->rxf[sd_spi_context.spi_sm], 1, false);
dma_channel_configure(sd_spi_context.spi_dma_wr, &sd_spi_context.spi_dma_wr_cfg,
&SD_PIO->txf[sd_spi_context.spi_sm], &sd_spi_context.sd_dma_context.wrdata, 1, false);
sd_spi_context.sd_dma_context.state = DMA_READ_TOKEN;
sd_spi_context.sd_dma_context.len = len;
sd_spi_context.sd_dma_context.read_buf = data;
sd_spi_context.sd_dma_context.wrdata = wrdata;
dma_start_channel_mask((1 << sd_spi_context.spi_dma_rd) | (1 << sd_spi_context.spi_dma_wr));
}
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_spi_context.ss, false);
// Write command, argument and CRC
sd_spi_write_blocking(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) {
sd_spi_read_blocking(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
sd_spi_read_blocking(0xff, resp + 1, resplen - 1);
}
gpio_put(sd_spi_context.ss, true);
const uint8_t buf = 0xff;
// Ensure 8 SPI clock cycles after CS deasserted
sd_spi_write_blocking(&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])
{
if (!sd_cmd_read_start(cmd, arg, datalen, data))
return false;
return sd_cmd_read_complete();
}
bool sd_cmd_read_start(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) {
sd_spi_read_blocking(0xff, buf, 1);
if (!(buf[0] & 0x80)) {
got_r1 = true;
break;
}
}
if (!got_r1 || buf[0] != 0x00)
goto abort;
sd_spi_read_dma(0xff, data, datalen);
return true;
abort:
gpio_put(sd_spi_context.ss, true);
sd_spi_read_blocking(0xff, buf, 1);
return false;
}
bool sd_cmd_read_complete(void)
{
uint8_t buf[1];
sd_spi_wait_complete();
gpio_put(sd_spi_context.ss, true);
sd_spi_read_blocking(0xff, buf, 1);
return (sd_spi_context.sd_dma_context.read_token_buf == 0xfe);
}
bool sd_spi_init(int mosi, int miso, int sck, int ss)
{
if (sd_spi_context.initialized)
return false;
if (!pio_can_add_program(SD_PIO, &sd_spi_pio_program))
return false;
sd_spi_context.spi_sm = pio_claim_unused_sm(SD_PIO, false);
if (sd_spi_context.spi_sm == -1)
return false;
sd_spi_context.spi_offset = pio_add_program(SD_PIO, &sd_spi_pio_program);
sd_spi_context.mosi = mosi;
sd_spi_context.miso = miso;
sd_spi_context.sck = sck;
sd_spi_context.ss = ss;
sd_spi_pio_program_init(SD_PIO, sd_spi_context.spi_sm, sd_spi_context.spi_offset, sd_spi_context.mosi,
sd_spi_context.miso, sd_spi_context.sck, SD_INIT_BITRATE);
pio_sm_set_enabled(SD_PIO, sd_spi_context.spi_sm, true);
gpio_init(sd_spi_context.ss);
gpio_set_dir(sd_spi_context.ss, true);
sd_spi_context.spi_dma_rd = dma_claim_unused_channel(false);
sd_spi_context.spi_dma_rd_crc = dma_claim_unused_channel(false);
sd_spi_context.spi_dma_wr = dma_claim_unused_channel(false);
if (sd_spi_context.spi_dma_rd == -1 || sd_spi_context.spi_dma_rd_crc == -1 || sd_spi_context.spi_dma_wr == -1)
return false;
sd_spi_context.spi_dma_rd_cfg = dma_channel_get_default_config(sd_spi_context.spi_dma_rd);
channel_config_set_read_increment(&sd_spi_context.spi_dma_rd_cfg, false);
channel_config_set_write_increment(&sd_spi_context.spi_dma_rd_cfg, true);
channel_config_set_dreq(&sd_spi_context.spi_dma_rd_cfg, pio_get_dreq(SD_PIO, sd_spi_context.spi_sm, false));
channel_config_set_transfer_data_size(&sd_spi_context.spi_dma_rd_cfg, DMA_SIZE_8);
sd_spi_context.spi_dma_rd_crc_cfg = dma_channel_get_default_config(sd_spi_context.spi_dma_rd_crc);
channel_config_set_read_increment(&sd_spi_context.spi_dma_rd_crc_cfg, false);
channel_config_set_dreq(&sd_spi_context.spi_dma_rd_crc_cfg, pio_get_dreq(SD_PIO, sd_spi_context.spi_sm, false));
channel_config_set_transfer_data_size(&sd_spi_context.spi_dma_rd_crc_cfg, DMA_SIZE_8);
sd_spi_context.spi_dma_wr_cfg = dma_channel_get_default_config(sd_spi_context.spi_dma_wr);
channel_config_set_read_increment(&sd_spi_context.spi_dma_wr_cfg, false);
channel_config_set_dreq(&sd_spi_context.spi_dma_wr_cfg, pio_get_dreq(SD_PIO, sd_spi_context.spi_sm, true));
channel_config_set_transfer_data_size(&sd_spi_context.spi_dma_wr_cfg, DMA_SIZE_8);
sd_spi_context.sd_dma_context.state = DMA_IDLE;
irq_add_shared_handler(DMA_IRQ_0, &sd_spi_dma_isr, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd, true);
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd_crc, true);
irq_set_enabled(DMA_IRQ_0, true);
gpio_put(sd_spi_context.ss, true);
uint8_t buf[16];
memset(buf, 0xff, 16);
// Ensure at least 74 SPI clock cycles without CS asserted
sd_spi_write_blocking(buf, 10);
sd_spi_context.initialized = true;
return true;
}
bool sd_spi_deinit(void)
{
if (!sd_spi_context.initialized)
return false;
if (sd_spi_context.sd_dma_context.state != DMA_IDLE)
return false;
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd, false);
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd_crc, false);
irq_remove_handler(DMA_IRQ_0, &sd_spi_dma_isr);
dma_channel_unclaim(sd_spi_context.spi_dma_rd);
dma_channel_unclaim(sd_spi_context.spi_dma_rd_crc);
dma_channel_unclaim(sd_spi_context.spi_dma_wr);
pio_remove_program(SD_PIO, &sd_spi_pio_program, sd_spi_context.spi_offset);
pio_sm_unclaim(SD_PIO, sd_spi_context.spi_sm);
sd_spi_context.initialized = false;
return true;
}
void sd_spi_set_bitrate(const int rate)
{
pio_sm_set_enabled(SD_PIO, sd_spi_context.spi_sm, false);
sd_spi_pio_program_init(SD_PIO, sd_spi_context.spi_sm, sd_spi_context.spi_offset, sd_spi_context.mosi,
sd_spi_context.miso, sd_spi_context.sck, rate);
pio_sm_set_enabled(SD_PIO, sd_spi_context.spi_sm, true);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#define SD_MISO 4
#define SD_SCK 2
#define SD_MOSI 3
#define SD_CS 5
#define SD_PIO pio0
#define SD_INIT_BITRATE 400000
#define SD_BITRATE 15000000
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]);
bool sd_spi_init(int mosi, int miso, int sck, int ss);
bool sd_spi_deinit(void);
void sd_spi_set_bitrate(const int rate);
void sd_spi_dbg_clk(const int div, const int frac);
bool sd_cmd_read_start(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen]);
bool sd_cmd_read_complete(void);
bool sd_cmd_read_is_complete(void);

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
.program sd_spi_pio
.side_set 1
// data - MISO MOSI
// sideset - SCK
// normal SPI
normal:
.wrap_target
out pins, 1 side 0 [1]
in pins, 1 side 1 [1]
.wrap
// Special "wait for token" repeated read SPI
wait_loop:
pull block side 0 [0]
mov osr, x side 0 [0]
set y, 8 side 0 [0]
read_loop:
out pins, 1 side 0 [1]
in pins, 1 side 1 [0]
jmp y--, read_loop side 1 [0]
mov isr, y side 0 [0]
jmp x != y, wait_done side 0 [0]
set y, 0 side 0 [0]
mov y, isr side 0 [0]
jmp wait_loop side 0 [0]
wait_done:
push block side 0 [0]
jmp normal side 0 [0]
% c-sdk {
#include "hardware/clocks.h"
#include <stdio.h>
#include <math.h>
static inline void sd_spi_pio_program_init(PIO pio, uint sm, uint offset, uint mosi, uint miso, uint sck, uint bitrate) {
pio_gpio_init(pio, mosi);
pio_gpio_init(pio, miso);
pio_gpio_init(pio, sck);
pio_sm_set_consecutive_pindirs(pio, sm, mosi, 1, true);
pio_sm_set_consecutive_pindirs(pio, sm, miso, 1, false);
pio_sm_set_consecutive_pindirs(pio, sm, sck, 1, true);
pio_sm_config c = sd_spi_pio_program_get_default_config(offset);
sm_config_set_out_pins(&c, mosi, 1);
sm_config_set_in_pins(&c, miso);
sm_config_set_sideset_pins(&c, sck);
sm_config_set_out_shift(&c, false, true, 8);
sm_config_set_in_shift(&c, false, true, 8);
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);
pio_sm_init(pio, sm, offset, &c);
}
%}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
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; */
/* } */