File syn_sd.c¶
File List > drivers > syn_sd.c
Go to the documentation of this file
#if __has_include("syn_config.h")
#include "syn_config.h"
#endif
#if !defined(SYN_USE_SD) || SYN_USE_SD
#include "../util/syn_assert.h"
#include "syn_sd.h"
#include <string.h>
#define SD_CMD0 0u
#define SD_CMD8 8u
#define SD_CMD9 9u
#define SD_CMD13 13u
#define SD_CMD16 16u
#define SD_CMD17 17u
#define SD_CMD24 24u
#define SD_CMD55 55u
#define SD_CMD58 58u
#define SD_ACMD41 41u
#define SD_R1_IDLE 0x01u
#define SD_R1_ILLCMD 0x04u
#define SD_R1_READY 0x00u
#define SD_R1_ERR_MSK 0xFEu
#define SD_R1_TIMEOUT 0xFFu
#define SD_TOKEN_START 0xFEu
#define SD_TOKEN_ACCEPTED 0x05u
#define SD_R1_POLL_RETRIES 8u
#define SD_ACMD41_RETRIES 1000u
#define SD_TOKEN_RETRIES 2000u
#define SD_BUSY_RETRIES 2000u
static uint8_t sd_xfer(const SYN_SD *sd, uint8_t out)
{
uint8_t in = 0xFFu;
syn_port_spi_transfer(sd->spi_bus, &out, &in, 1u);
return in;
}
static uint8_t sd_cmd(const SYN_SD *sd, uint8_t cmd, uint32_t arg, uint8_t crc)
{
uint8_t buf[6];
buf[0] = (uint8_t)(0x40u | cmd);
buf[1] = (uint8_t)((arg >> 24) & 0xFFu);
buf[2] = (uint8_t)((arg >> 16) & 0xFFu);
buf[3] = (uint8_t)((arg >> 8) & 0xFFu);
buf[4] = (uint8_t)(arg & 0xFFu);
buf[5] = crc;
/* rx_buf=NULL: port drives MOSI=0xFF during any simultaneous card output */
syn_port_spi_transfer(sd->spi_bus, buf, NULL, sizeof(buf));
/* Poll up to 8 bytes for the first non-0xFF response */
uint8_t r1 = SD_R1_TIMEOUT;
uint8_t i;
for (i = 0u; i < SD_R1_POLL_RETRIES && r1 == SD_R1_TIMEOUT; i++) {
r1 = sd_xfer(sd, 0xFFu);
}
return r1;
}
static bool sd_wait_ready(const SYN_SD *sd)
{
uint16_t i;
for (i = 0u; i < SD_BUSY_RETRIES; i++) {
if (sd_xfer(sd, 0xFFu) == 0xFFu) {
return true;
}
}
return false;
}
static SYN_Status sd_read_csd(SYN_SD *sd)
{
uint8_t r1 = sd_cmd(sd, SD_CMD9, 0u, 0xFFu);
if (r1 != SD_R1_READY) {
return SYN_ERROR;
}
/* Wait for start data token */
uint8_t token = SD_R1_TIMEOUT;
uint16_t i;
for (i = 0u; i < SD_TOKEN_RETRIES && token == SD_R1_TIMEOUT; i++) {
token = sd_xfer(sd, 0xFFu);
}
if (token != SD_TOKEN_START) {
return SYN_ERROR;
}
/* Read 16 CSD bytes */
uint8_t csd[16];
uint8_t j;
for (j = 0u; j < 16u; j++) {
csd[j] = sd_xfer(sd, 0xFFu);
}
/* Discard 2-byte CRC (CRC mode off) */
sd_xfer(sd, 0xFFu);
sd_xfer(sd, 0xFFu);
/* Parse by CSD structure version */
uint8_t csd_ver = (csd[0] >> 6) & 0x03u;
if (csd_ver == 0u) {
/* CSD v1 (SDSC): decode C_SIZE, C_SIZE_MULT, READ_BL_LEN */
uint8_t read_bl_len = csd[5] & 0x0Fu;
uint32_t c_size = (uint32_t)((csd[6] & 0x03u) << 10) | ((uint32_t)csd[7] << 2) |
(uint32_t)((csd[8] >> 6) & 0x03u);
uint8_t c_size_mult = (uint8_t)(((csd[9] & 0x03u) << 1) | ((csd[10] >> 7) & 0x01u));
uint32_t mult = (uint32_t)1u << ((uint32_t)c_size_mult + 2u);
/* block_len / 512 avoids intermediate overflow for large SDSC cards */
uint32_t bl_shift = (read_bl_len >= 9u) ? (uint32_t)(read_bl_len - 9u) : 0u;
uint32_t block_factor = (uint32_t)1u << bl_shift;
sd->sector_count = (c_size + 1u) * mult * block_factor;
} else {
/* CSD v2 (SDHC/SDXC): C_SIZE directly encodes 512KB units */
uint32_t c_size =
((uint32_t)(csd[7] & 0x3Fu) << 16) | ((uint32_t)csd[8] << 8) | (uint32_t)csd[9];
sd->sector_count = (c_size + 1u) * 1024u;
}
return SYN_OK;
}
SYN_Status syn_sd_init(SYN_SD *sd, uint8_t spi_bus, SYN_GPIO_Pin cs)
{
SYN_ASSERT(sd != NULL);
sd->spi_bus = spi_bus;
sd->cs_pin = cs;
sd->type = SYN_SD_UNKNOWN;
sd->sector_count = 0u;
sd->initialized = false;
/* Configure SPI at 400 kHz (SD spec: <= 400 kHz during init) */
SYN_SPI_Config cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.bus = spi_bus;
cfg.clock_hz = 400000u;
cfg.mode = SYN_SPI_MODE_0;
cfg.bit_order = 0u; /* MSB first */
if (syn_port_spi_init(&cfg) != SYN_OK) {
return SYN_ERROR;
}
/* Send >= 74 clock cycles with CS deasserted (SD power-up requirement) */
syn_port_spi_cs_deassert(spi_bus, cs);
{
uint8_t clk_buf[10];
memset(clk_buf, 0xFFu, sizeof(clk_buf));
syn_port_spi_transfer(spi_bus, clk_buf, NULL, sizeof(clk_buf));
}
/* CMD0: reset card to SPI idle state. CRC7 = 0x95 (hardcoded). */
syn_port_spi_cs_assert(spi_bus, cs);
uint8_t r1 = sd_cmd(sd, SD_CMD0, 0u, 0x95u);
if (r1 != SD_R1_IDLE) {
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
/* CMD8: check interface condition — distinguishes V2/SDHC cards.
* Arg = 0x000001AA: VHS=1 (2.7-3.6 V), check=0xAA. CRC7 = 0x87. */
bool is_v2 = false;
r1 = sd_cmd(sd, SD_CMD8, 0x000001AAu, 0x87u);
if (r1 == SD_R1_IDLE) {
/* V2 card: read 4-byte R7 payload and verify voltage/echo fields */
uint8_t r7[4];
r7[0] = sd_xfer(sd, 0xFFu);
r7[1] = sd_xfer(sd, 0xFFu);
r7[2] = sd_xfer(sd, 0xFFu);
r7[3] = sd_xfer(sd, 0xFFu);
if (r7[3] == 0xAAu && (r7[2] & 0x0Fu) == 0x01u) {
is_v2 = true;
} else {
/* Voltage range mismatch — card unsupported */
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
} else if ((r1 & SD_R1_ILLCMD) != 0u) {
/* V1 / SDSC card: CMD8 not supported, proceed with byte-address path */
is_v2 = false;
} else {
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
/* ACMD41: activate card initialization. HCS=1 for V2, 0 for V1. */
uint32_t acmd41_arg = is_v2 ? 0x40000000u : 0x00000000u;
uint16_t retries = SD_ACMD41_RETRIES;
do {
r1 = sd_cmd(sd, SD_CMD55, 0u, 0xFFu);
if ((r1 & SD_R1_ERR_MSK) != 0u) {
/* Any error bit besides IDLE is fatal */
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
r1 = sd_cmd(sd, SD_ACMD41, acmd41_arg, 0xFFu);
retries--;
} while (r1 == SD_R1_IDLE && retries > 0u);
if (r1 != SD_R1_READY) {
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
if (is_v2) {
/* CMD58: read OCR, check CCS bit (bit 30) to distinguish SDHC */
r1 = sd_cmd(sd, SD_CMD58, 0u, 0xFFu);
if (r1 != SD_R1_READY) {
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
uint8_t ocr[4];
ocr[0] = sd_xfer(sd, 0xFFu);
ocr[1] = sd_xfer(sd, 0xFFu);
ocr[2] = sd_xfer(sd, 0xFFu);
ocr[3] = sd_xfer(sd, 0xFFu);
sd->type = ((ocr[0] & 0x40u) != 0u) ? SYN_SD_SDHC : SYN_SD_SDSC;
} else {
sd->type = SYN_SD_SDSC;
/* CMD16: force block length to 512 bytes for V1 SDSC cards */
r1 = sd_cmd(sd, SD_CMD16, SYN_SD_SECTOR_SIZE, 0xFFu);
if (r1 != SD_R1_READY) {
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
}
/* CMD9: read CSD register for capacity */
if (sd_read_csd(sd) != SYN_OK) {
syn_port_spi_cs_deassert(spi_bus, cs);
return SYN_ERROR;
}
syn_port_spi_cs_deassert(spi_bus, cs);
sd->initialized = true;
return SYN_OK;
}
SYN_Status syn_sd_read(const SYN_SD *sd, uint32_t sector, uint8_t *buf)
{
SYN_ASSERT(sd != NULL);
SYN_ASSERT(buf != NULL);
SYN_ASSERT(sd->initialized);
/* SDSC: byte address (sector * 512). SDHC: sector address. */
uint32_t addr = (sd->type == SYN_SD_SDSC) ? (sector * (uint32_t)SYN_SD_SECTOR_SIZE) : sector;
syn_port_spi_cs_assert(sd->spi_bus, sd->cs_pin);
uint8_t r1 = sd_cmd(sd, SD_CMD17, addr, 0xFFu);
if (r1 != SD_R1_READY) {
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return SYN_ERROR;
}
/* Wait for start data token */
uint8_t token = SD_R1_TIMEOUT;
uint16_t i;
for (i = 0u; i < SD_TOKEN_RETRIES && token == SD_R1_TIMEOUT; i++) {
token = sd_xfer(sd, 0xFFu);
}
if (token != SD_TOKEN_START) {
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return SYN_ERROR;
}
/* Read 512 data bytes */
uint16_t j;
for (j = 0u; j < (uint16_t)SYN_SD_SECTOR_SIZE; j++) {
buf[j] = sd_xfer(sd, 0xFFu);
}
/* Discard 2-byte CRC */
sd_xfer(sd, 0xFFu);
sd_xfer(sd, 0xFFu);
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return SYN_OK;
}
SYN_Status syn_sd_write(const SYN_SD *sd, uint32_t sector, const uint8_t *buf)
{
SYN_ASSERT(sd != NULL);
SYN_ASSERT(buf != NULL);
SYN_ASSERT(sd->initialized);
uint32_t addr = (sd->type == SYN_SD_SDSC) ? (sector * (uint32_t)SYN_SD_SECTOR_SIZE) : sector;
syn_port_spi_cs_assert(sd->spi_bus, sd->cs_pin);
uint8_t r1 = sd_cmd(sd, SD_CMD24, addr, 0xFFu);
if (r1 != SD_R1_READY) {
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return SYN_ERROR;
}
/* Send start token */
sd_xfer(sd, SD_TOKEN_START);
/* Send 512 data bytes */
syn_port_spi_transfer(sd->spi_bus, buf, NULL, (size_t)SYN_SD_SECTOR_SIZE);
/* Send dummy CRC (CRC mode disabled) */
sd_xfer(sd, 0xFFu);
sd_xfer(sd, 0xFFu);
/* Read data response token: (xxx0_0101) = accepted */
uint8_t resp = sd_xfer(sd, 0xFFu);
if ((resp & 0x1Fu) != SD_TOKEN_ACCEPTED) {
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return SYN_ERROR;
}
/* Wait until write completes */
if (!sd_wait_ready(sd)) {
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return SYN_ERROR;
}
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return SYN_OK;
}
SYN_Status syn_sd_sync(const SYN_SD *sd)
{
SYN_ASSERT(sd != NULL);
SYN_ASSERT(sd->initialized);
syn_port_spi_cs_assert(sd->spi_bus, sd->cs_pin);
/* CMD13: SEND_STATUS returns R2 (two bytes) */
uint8_t r1 = sd_cmd(sd, SD_CMD13, 0u, 0xFFu);
uint8_t r2 = sd_xfer(sd, 0xFFu);
syn_port_spi_cs_deassert(sd->spi_bus, sd->cs_pin);
return (r1 == SD_R1_READY && r2 == 0x00u) ? SYN_OK : SYN_ERROR;
}
/* ── Accessors ──────────────────────────────────────────────────────────── */
uint32_t syn_sd_sectors(const SYN_SD *sd)
{
SYN_ASSERT(sd != NULL);
return sd->sector_count;
}
SYN_SD_Type syn_sd_type(const SYN_SD *sd)
{
SYN_ASSERT(sd != NULL);
return sd->type;
}
#endif /* SYN_USE_SD */