定义:15个
有调用:14个
未调用:CMD10
常见SDHC/SDXC卡:
初始化涉及6个
读取涉及3个
写入涉及3个
共12种命令
额外兼容:
CMD1、CMD16
/**
* @file sd_spi.c
* @brief SD 卡 SPI 模式驱动 (SPI4), 适配 JW-MCU-TM30-2
*
* 管脚 (按用户确认):
* 39脚 PB2 = SD_PWR 电源使能(低有效: 拉低上电/供电, 拉高关断)
* 3脚 PE2 = SPI4_SCK CLK
* 48脚 PB12 = SPI4_NSS CS (CD/DATA3)
* 66脚 PE13 = SPI4_MISO DATA0
* 67脚 PE14 = SPI4_MOSI CMD
* 93脚 PB4 = SD_DET 插入检测
*
* SPI4 挂在 APB2, 系统 APB2 = 48MHz (SYSCLK 288M / APB2 div 6)。
* 初始化用低速分频 256 -> 187.5kHz (< 400kHz), 协议完成提速到 12MHz。
*/
#include "mh245x_gpio.h"
#include "mh245x_spi.h"
#include "peripheral.h"
#include "system_mh2457.h"
#include "sd_spi.h"
#include <stdio.h>
/* Keep the proven reference driver intact while routing its diagnostics to
* this project's console service. */
#define BSP_LOGI(...) do { printf("[SD] "); printf(__VA_ARGS__); printf("\n"); } while (0)
#define BSP_LOGW(...) do { printf("[SD][WARN] "); printf(__VA_ARGS__); printf("\n"); } while (0)
#define BSP_LOGE(...) do { printf("[SD][ERR] "); printf(__VA_ARGS__); printf("\n"); } while (0)
/* ===================== SD 命令集 ===================== */
#define CMD0 0 /* GO_IDLE_STATE */
#define CMD1 1 /* SEND_OP_COND (MMC) */
#define CMD8 8 /* SEND_IF_COND */
#define CMD9 9 /* SEND_CSD */
#define CMD10 10 /* SEND_CID */
#define CMD12 12 /* STOP_TRANSMISSION */
#define CMD16 16 /* SET_BLOCKLEN */
#define CMD17 17 /* READ_SINGLE_BLOCK */
#define CMD18 18 /* READ_MULTIPLE_BLOCK */
#define CMD23 23 /* SET_BLOCK_COUNT (ACMD) */
#define CMD24 24 /* WRITE_BLOCK */
#define CMD25 25 /* WRITE_MULTIPLE_BLOCK */
#define CMD55 55 /* APP_CMD */
#define CMD58 58 /* READ_OCR */
#define ACMD41 41 /* SEND_OP_COND (SD) */
/* ===================== 私有宏 ===================== */
#define SD_CS_HIGH() GPIO_SetBits(SD_SPI_CS_PORT, SD_SPI_CS_PIN)
#define SD_CS_LOW() GPIO_ResetBits(SD_SPI_CS_PORT, SD_SPI_CS_PIN)
#define SD_SPI_SLOW_PRESCALER SPI_BaudRatePrescaler_256 /* 187.5kHz */
#define SD_SPI_FAST_PRESCALER SPI_BaudRatePrescaler_4 /* 12MHz */
/* 下面三个计数只在 DWT 周期计数器不可用时作为兜底(见 sd_timeout_cycles),
* 正常路径的超时按真实时间算。
*
* 原来它们是唯一的超时机制,而同一个数字代表的真实时间随 SPI 时钟变化:
* 初始化 187.5 kHz → 20000 次 ≈ 854 ms
* 运行中 12 MHz → 20000 次 ≈ 14~20 ms
* 偏偏"等卡写完一个块的 busy"只发生在 12 MHz 那个阶段,也就是预算最小的
* 阶段。而 SD 规范允许卡写完块后把 DO 拉低到 250 ms,所以这个预算偏小,
* 表现为"数据能写、紧接其后的元数据单扇区写失败"。 */
#define SD_INIT_TIMEOUT 1000
#define SD_READ_TIMEOUT 20000
#define SD_WRITE_TIMEOUT 20000
/* 等卡 busy / 等响应 token 的时间上限。规范上限 250 ms,这里留一倍余量。 */
#define SD_BUSY_TIMEOUT_MS 500U
/* SD_SendCmdEx 回传的命令阶段子状态。0xFF 作为 R1 有两种来源,混成一个值
* 会让日志完全无法解释:一个是卡 busy 选不中,一个是命令被吞。 */
#define SD_CMD_ST_OK 0
#define SD_CMD_ST_SELECT 1 /* SD_Select 失败:卡一直 busy,选不中 */
#define SD_CMD_ST_NO_R1 2 /* 命令已上总线,但卡一个字节都没回 */
/* SD_XmitData 的失败阶段。原来只返回 0/1,看不出是卡 busy 超时还是卡明确
* 拒绝了数据块,而这两种原因的修法完全不同。 */
#define SD_XMIT_OK 0
#define SD_XMIT_BUSY 1 /* 进入时卡一直 busy,等到超时 */
#define SD_XMIT_NO_TOKEN 2 /* 数据块已发完,但等不到数据响应 token */
#define SD_XMIT_REJECTED 3 /* 收到响应但不是"接受"(0x05),如 CRC 错/写错 */
#define SD_CMD_REJECTED 4 /* 命令阶段失败(R1 非 0,或根本没收到 R1) */
/* ===================== 私有变量 ===================== */
static uint8_t s_sdType = 0; /* 卡类型 SD_CT_* */
static uint8_t s_inited = 0; /* 协议初始化完成 */
static DSTATUS s_status = STA_NOINIT;
static DWORD s_sectorCount = 0; /* 卡总扇区数 (CSD 解析) */
/* 连续写失败计数:FatFs 上层会把同一个扇区反复重发,逐次打印会淹没串口。 */
static uint32_t s_write_fail_streak;
/* 本次事务的超时上限,单位 CPU 周期。返回 0 表示 DWT 周期计数器没在跑,
* 调用方必须退回固定的迭代计数——否则 CYCCNT 恒为 0、比较永远不成立,
* 等待循环会退化成死循环。DWT 由 BootTask 在任何 SD 事务之前使能
* (app/tasks/task_boot.c)。 */
static uint32_t sd_timeout_cycles(void)
{
uint32_t cycles_per_ms = SystemCoreClock / 1000U;
if ((DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) == 0U || cycles_per_ms == 0U) {
return 0U;
}
return cycles_per_ms * SD_BUSY_TIMEOUT_MS;
}
/* ===================== SD 协议层 ===================== */
/* 等待卡就绪: 返回 0=就绪 1=超时
* 超时优先按真实时间算,DWT 不可用时才退回 fallback 次迭代。 */
static int SD_WaitReady(uint32_t fallback)
{
uint8_t d;
uint32_t start = DWT->CYCCNT;
uint32_t limit = sd_timeout_cycles();
for (;;) {
d = SPI4_RW(0xFF);
if (d == 0xFF) return 0;
if (limit != 0U) {
if ((uint32_t)(DWT->CYCCNT - start) >= limit) return 1;
} else if (--fallback == 0U) {
return 1;
}
}
}
static void SD_Deselect(void)
{
SD_CS_HIGH();
SPI4_RW(0xFF); /* 额外 8 时钟释放 DO */
}
/* 选卡并等待就绪, 返回 1=成功 0=失败
* 关键: 先拉高 CS 再拉低, 保证每个命令事务都有 CS 下降沿。
* SD SPI 规范要求每个命令以 CS 下降沿开始; 若 CS 持续为低
* (背靠背命令), 部分 v2 卡的命令状态机不重置, CMD8/ACMD41 无响应。 */
static int SD_Select(void)
{
SD_CS_HIGH();
SPI4_RW(0xFF); /* CS 高期间 8 时钟, 让卡释放 MISO */
SD_CS_LOW();
SPI4_RW(0xFF); /* CS 低后 8 时钟, 卡开始驱动 MISO */
if (SD_WaitReady(SD_READ_TIMEOUT)) {
SD_Deselect();
return 0;
}
return 1;
}
/* 发送命令, 返回 R1。
*
* 返回值 0xFF 有两种完全不同的来源,混在一起就无法解释日志:
* - SD_Select() 失败:卡一直把 DO 拉低(busy),根本选不中
* - R1 轮询超时:命令已经发上总线,卡一个字节都没回
* 两者都指向"卡不应答",但一个是忙死、一个是命令被吞。cmd_status 回传
* 具体是哪种;cmd_status 非 0 时 R1 无意义。 */
static uint8_t SD_SendCmdEx(uint8_t cmd, uint32_t arg, int *cmd_status)
{
uint8_t buf[6];
uint8_t r1;
uint32_t n;
if (cmd_status != NULL) *cmd_status = SD_CMD_ST_OK;
if (SD_Select() == 0) {
if (cmd_status != NULL) *cmd_status = SD_CMD_ST_SELECT;
return 0xFF;
}
buf[0] = (uint8_t)(0x40 | cmd);
buf[1] = (uint8_t)(arg >> 24);
buf[2] = (uint8_t)(arg >> 16);
buf[3] = (uint8_t)(arg >> 8);
buf[4] = (uint8_t)(arg);
buf[5] = 0x01;
if (cmd == CMD0) buf[5] = 0x95; /* CMD0 CRC */
else if (cmd == CMD8) buf[5] = 0x87; /* CMD8 CRC */
SPI4_TxMulti(buf, 6);
if (cmd == CMD12) SPI4_RW(0xFF); /* CMD12 后补 1 字节 */
n = SD_INIT_TIMEOUT;
do {
r1 = SPI4_RW(0xFF);
} while ((r1 & 0x80) && --n);
if ((r1 & 0x80) != 0U && cmd_status != NULL) {
*cmd_status = SD_CMD_ST_NO_R1;
}
return r1;
}
static uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg)
{
return SD_SendCmdEx(cmd, arg, NULL);
}
/* 发送应用命令。CMD55 在卡仍处于 idle 时合法返回 0x01;旧代码把它当成
* 失败并被 || 短路,结果 ACMD41 从未真正上总线,卡永远停在 idle。
* 两个命令各自结束 CS 事务,兼容要求命令间存在 CS 高电平的 SD 卡。 */
static uint8_t SD_SendAppCmd(uint8_t acmd, uint32_t arg)
{
uint8_t r1 = SD_SendCmd(CMD55, 0);
SD_Deselect();
if (r1 > SD_R1_IDLE) {
return r1;
}
r1 = SD_SendCmd(acmd, arg);
SD_Deselect();
return r1;
}
/* 接收数据块 (含 0xFE 起始令牌 + 2 CRC), 返回 0=OK 1=ERR
* 等 token 同样按真实时间计时:读也可能紧跟在一次写的 busy 之后。 */
static int SD_RecvData(uint8_t *buff, uint16_t btr)
{
uint8_t token;
uint32_t start = DWT->CYCCNT;
uint32_t limit = sd_timeout_cycles();
uint32_t n = SD_READ_TIMEOUT;
for (;;) {
token = SPI4_RW(0xFF);
if (token == 0xFE) break;
if (token != 0xFF) return 1; /* 非起始、非空闲 → 错误 token */
if (limit != 0U) {
if ((uint32_t)(DWT->CYCCNT - start) >= limit) return 1;
} else if (--n == 0U) {
return 1;
}
}
SPI4_RxMulti(buff, btr);
SPI4_RW(0xFF); /* CRC16 高 */
SPI4_RW(0xFF); /* CRC16 低 */
return 0;
}
/* 发送数据块, token=0xFE(单块)/0xFC(多块)。
* 返回 SD_XMIT_* 阶段码,并把卡的数据响应写进 resp_out(0xFF = 没收到)。 */
static int SD_XmitData(const uint8_t *buff, uint8_t token, uint8_t *resp_out)
{
uint8_t resp;
uint32_t n = SD_INIT_TIMEOUT;
if (resp_out != NULL) *resp_out = 0xFFU;
if (SD_WaitReady(SD_WRITE_TIMEOUT) != 0) return SD_XMIT_BUSY;
SPI4_RW(token);
SPI4_TxMulti(buff, 512);
SPI4_RW(0xFF); /* 伪 CRC */
SPI4_RW(0xFF);
/* 数据响应 token 在 CRC 之后才出现,卡可以再拖几个字节时钟。原来只读
* 一次就下结论:读到 0xFF(空闲线)直接算失败,卡稍慢一点就被误判。 */
do {
resp = SPI4_RW(0xFF);
} while (resp == 0xFFU && --n != 0U);
if (resp_out != NULL) *resp_out = resp;
if (resp == 0xFFU) return SD_XMIT_NO_TOKEN;
/* 0x05 = 接受;0x0B = CRC 错;0x0D = 写错 */
return ((resp & 0x1FU) == 0x05U) ? SD_XMIT_OK : SD_XMIT_REJECTED;
}
/* 读取 CSD 并解析总扇区数 (512B 单位)。返回 0=OK */
static int SD_ReadCapacity(DWORD *pSectors)
{
uint8_t csd[16];
uint32_t c_size, c_size_mult, read_bl_len;
uint64_t blocks;
if (SD_SendCmd(CMD9, 0) != 0 || SD_RecvData(csd, 16)) {
SD_Deselect();
return 1;
}
SD_Deselect();
if ((csd[0] & 0xC0) == 0x40) { /* CSD v2.0 (SDHC/SDXC) */
c_size = ((uint32_t)csd[7] << 16) | ((uint32_t)csd[8] << 8) | csd[9];
blocks = ((uint64_t)c_size + 1) << 10; /* (c_size+1)*1024 KB / 512B */
} else { /* CSD v1.0 (SDSC) */
read_bl_len = csd[5] & 0x0F;
c_size = ((uint32_t)(csd[6] & 0x03) << 10) |
((uint32_t)csd[7] << 2) | ((csd[8] & 0xC0) >> 6);
c_size_mult = ((uint32_t)(csd[9] & 0x03) << 1) | ((csd[10] & 0x80) >> 7);
blocks = ((uint64_t)(c_size + 1) << (c_size_mult + 2 + read_bl_len))
>> 9; /* 转 512B 扇区 */
}
*pSectors = (blocks > 0xFFFFFFFFUL) ? 0xFFFFFFFFUL : (DWORD)blocks;
return 0;
}
/* ===================== 公有接口 ===================== */
DSTATUS SD_SPI_Initialize(void)
{
uint8_t n, r1, ocr[4];
uint8_t ty = 0;
uint32_t ntry;
/* 幂等: 协议已成功且卡仍在位, 直接返回就绪。
* f_mount 会再次调用 disk_initialize, 避免重复跑 CMD0~CMD9 握手。 */
if (s_inited && SD_SPI_IsInserted()) {
s_status = 0;
return s_status;
}
BSP_LOGI("[SD] init start: inserted=%d", (int)SD_SPI_IsInserted());
if (!SD_SPI_IsInserted()) {
s_status = STA_NOINIT | STA_NODISK;
s_inited = 0;
s_sdType = 0;
s_sectorCount = 0;
BSP_LOGW("[SD] init FAIL: card NOT inserted (DET pin high)");
return s_status;
}
SD_SPI_PowerOn();
SystemDelay(50); /* 上电稳定:10ms 对部分卡(尤其热重启)不足,曾致 CMD0 失败 */
SPI4_SetPrescaler(SD_SPI_SLOW_PRESCALER);
SD_CS_HIGH();
for (n = 0; n < 10; n++) SPI4_RW(0xFF); /* >=74 时钟 */
SD_Deselect();
/* 进入 Idle: CMD0 */
ntry = SD_INIT_TIMEOUT;
while ((r1 = SD_SendCmd(CMD0, 0)) != SD_R1_IDLE) {
SD_Deselect();
if (!--ntry) {
BSP_LOGE("[SD] init FAIL: CMD0 timeout, last R1=0x%02X", r1);
s_status = STA_NOINIT; return s_status;
}
}
BSP_LOGI("[SD] CMD0 OK (idle), R1=0x%02X", r1);
/* CMD8 检查电压 2.7-3.6V */
r1 = SD_SendCmd(CMD8, 0x1AA);
if (r1 == SD_R1_IDLE) {
/* v2.x */
for (n = 0; n < 4; n++) ocr[n] = SPI4_RW(0xFF);
SD_Deselect();
BSP_LOGI("[SD] CMD8 OK (v2), OCR=%02X %02X %02X %02X",
ocr[0], ocr[1], ocr[2], ocr[3]);
if (ocr[3] == 0xAA) {
ntry = SD_INIT_TIMEOUT;
while ((r1 = SD_SendAppCmd(ACMD41, 0x40000000U)) != 0) {
if (!--ntry) {
BSP_LOGE("[SD] init FAIL: ACMD41 timeout, last R1=0x%02X", r1);
s_status = STA_NOINIT; return s_status;
}
}
BSP_LOGI("[SD] ACMD41 OK, R1=0x%02X (tries left=%lu)", r1, (unsigned long)ntry);
r1 = SD_SendCmd(CMD58, 0);
if (r1 == 0) {
for (n = 0; n < 4; n++) ocr[n] = SPI4_RW(0xFF);
SD_Deselect();
/* OCR bit30(CCS) 单独表示块寻址;bit31 只是上电完成状态。 */
ty = (ocr[0] & 0x40U) ? (SD_CT_SD2 | SD_CT_BLOCK) : SD_CT_SD2;
BSP_LOGI("[SD] CMD58 OCR=%02X %02X %02X %02X, type=%s",
ocr[0], ocr[1], ocr[2], ocr[3],
(ty & SD_CT_BLOCK) ? "SDHC/SDXC(block)" : "SD2(byte)");
} else {
SD_Deselect();
BSP_LOGW("[SD] CMD58 failed, R1=0x%02X", r1);
}
} else {
BSP_LOGE("[SD] init FAIL: CMD8 echo mismatch (got 0x%02X, expect 0xAA)", ocr[3]);
s_status = STA_NOINIT; return s_status;
}
} else {
/* v1.x 或 MMC */
SD_Deselect();
BSP_LOGI("[SD] CMD8 R1=0x%02X -> try v1/MMC path", r1);
r1 = SD_SendAppCmd(ACMD41, 0);
ty = (r1 <= SD_R1_IDLE) ? SD_CT_SD1 : SD_CT_MMC;
ntry = SD_INIT_TIMEOUT;
if (ty == SD_CT_SD1) {
while ((r1 = SD_SendAppCmd(ACMD41, 0)) != 0) {
if (!--ntry) { ty = 0; break; }
}
} else if (ty == SD_CT_MMC) {
while (SD_SendCmd(CMD1, 0) != 0) {
SD_Deselect();
if (!--ntry) { ty = 0; break; }
}
}
}
SD_Deselect();
if (ty == 0) {
BSP_LOGE("[SD] init FAIL: card type detection failed (v1/MMC path)");
s_status = STA_NOINIT; return s_status;
}
/* SDHC/SDXC 固定为 512 字节块且不接受 CMD16;仅字节寻址卡设置块长。 */
if ((ty & SD_CT_BLOCK) == 0U) {
if (SD_SendCmd(CMD16, 512) != 0) {
BSP_LOGE("[SD] init FAIL: CMD16 (SET_BLOCKLEN) failed");
SD_Deselect(); s_status = STA_NOINIT; return s_status;
}
SD_Deselect();
BSP_LOGI("[SD] CMD16 OK (blocklen=512)");
} else {
BSP_LOGI("[SD] CMD16 skipped (SDHC/SDXC fixed 512-byte blocks)");
}
/* 提速 */
SPI4_SetPrescaler(SD_SPI_FAST_PRESCALER);
/* 解析 CSD 取得总扇区数 */
if (SD_ReadCapacity(&s_sectorCount) != 0) {
BSP_LOGE("[SD] init FAIL: CMD9 (READ_CSD) failed - no data token 0xFE");
s_status = STA_NOINIT; return s_status;
}
BSP_LOGI("[SD] CMD9 CSD OK, sectors=%lu", (unsigned long)s_sectorCount);
s_sdType = ty;
s_inited = 1;
s_status = 0;
BSP_LOGI("[SD] init SUCCESS: type=0x%02X, sectors=%lu", (unsigned)ty, (unsigned long)s_sectorCount);
return s_status;
}
DWORD SD_SPI_GetSectorCount(void)
{
return s_sectorCount;
}
DSTATUS SD_SPI_Status(void)
{
if (!SD_SPI_IsInserted()) {
/* 拔卡会丢失整个协议会话;再次插入必须重新 CMD0..CMD9,不能只
* 清 STA_NODISK 后把尚未初始化的新卡误报为 ready。 */
s_status = STA_NOINIT | STA_NODISK;
s_inited = 0;
s_sdType = 0;
s_sectorCount = 0;
return s_status;
}
if (!s_inited) {
s_status = STA_NOINIT;
} else {
s_status = 0;
}
return s_status;
}
DRESULT SD_SPI_ReadSectors(BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_OK;
DWORD sec = (s_sdType & SD_CT_BLOCK) ? sector : (sector << 9);
if (s_status & STA_NOINIT) return RES_NOTRDY;
if (count == 1) {
if (SD_SendCmd(CMD17, sec) != 0 || SD_RecvData(buff, 512)) res = RES_ERROR;
SD_Deselect();
} else {
if (SD_SendCmd(CMD18, sec) == 0) {
do {
if (SD_RecvData(buff, 512)) { res = RES_ERROR; break; }
buff += 512;
} while (--count);
} else {
res = RES_ERROR;
}
SD_SendCmd(CMD12, 0);
SD_Deselect();
}
return res;
}
/* 单次写入尝试。sec 已经按卡类型换算过(块寻址 / 字节寻址)。
* 返回失败阶段(SD_XMIT_OK = 成功),并回传全部诊断信息。 */
static int sd_write_once(const BYTE *buff, DWORD sec, UINT count,
uint8_t *resp, int *cmd_status, uint8_t *r1,
UINT *done)
{
int stage;
*resp = 0xFFU;
*r1 = 0xFFU;
*done = 0U;
*cmd_status = SD_CMD_ST_OK;
if (count == 1) {
*r1 = SD_SendCmdEx(CMD24, sec, cmd_status);
stage = (*r1 != 0U) ? SD_CMD_REJECTED : SD_XmitData(buff, 0xFE, resp);
if (stage == SD_XMIT_OK) *done = 1U;
SD_Deselect();
} else {
/* ACMD23 只是可选的预擦除提示;不支持时仍应继续尝试 CMD25。 */
(void)SD_SendAppCmd(CMD23, count);
*r1 = SD_SendCmdEx(CMD25, sec, cmd_status);
if (*r1 == 0U) {
for (;;) {
stage = SD_XmitData(buff, 0xFC, resp);
if (stage != SD_XMIT_OK) break;
buff += 512;
++*done;
if (--count == 0U) break;
}
if (stage == SD_XMIT_OK) {
/* 停止令牌必须等卡结束编程(DO 拉高)之后再发。
*
* SD_XmitData 是一读到数据响应 token 就返回的,而卡在发出响应
* 之后立刻拉低 DO 开始编程;卡在整个 busy 期间会忽略 MOSI 上的
* 一切 —— 紧跟着发出去的 0xFD 会被直接丢掉,多块写收不了尾。
* 中间那些块之所以没出事,是因为下一次 SD_XmitData 开头的
* SD_WaitReady 把 busy 兜住了;只有最后这个令牌后面没有防护,
* 所以必然被吞,卡停在多块写状态。
*
* 规范时序:数据块 → 数据响应 → busy(DO 低) → DO 高 → 停止令牌。 */
if (SD_WaitReady(SD_WRITE_TIMEOUT) != 0) {
stage = SD_XMIT_BUSY;
} else {
SPI4_RW(0xFD);
}
}
/* 中途失败时不做额外动作:后面紧跟的 SD_Deselect()(CS 拉高)
* 就是 SPI 模式下的中止手段,不需要再加 CMD12。 */
} else {
stage = SD_CMD_REJECTED;
}
SD_Deselect();
}
return stage;
}
DRESULT SD_SPI_WriteSectors(const BYTE *buff, DWORD sector, UINT count)
{
DWORD sec = (s_sdType & SD_CT_BLOCK) ? sector : (sector << 9);
UINT done = 0U;
uint8_t resp = 0xFFU;
uint8_t r1 = 0xFFU;
int cmd_status = SD_CMD_ST_OK;
int stage;
if (s_status & STA_NOINIT) return RES_NOTRDY;
stage = sd_write_once(buff, sec, count, &resp, &cmd_status, &r1, &done);
if (stage == SD_XMIT_OK) {
s_write_fail_streak = 0U;
return RES_OK;
}
++s_write_fail_streak;
if (s_write_fail_streak <= 1U || (s_write_fail_streak % 16U) == 0U) {
/* 每个字段都用于区分原因:
* stage = 1 卡 busy 超时 / 2 数据块发完没等到响应 token /
* 3 卡回了响应但不是"接受"(resp 0x0B=CRC错 0x0D=写错)/
* 4 命令阶段失败,看 cmd:1=选卡时卡 busy、2=没收到 R1
* r1 = 卡对 CMD24/CMD25 的真实应答(0x20 写保护、0x40 参数错、
* 0x04 非法命令)
* 没有这几个数,上层只能看到 FatFs 的 result=1,无从定位。 */
BSP_LOGE("write failed: stage=%d cmd=%d r1=0x%02X resp=0x%02X "
"sector=%lu count=%u done=%u streak=%lu",
stage, cmd_status, (unsigned)r1, (unsigned)resp,
(unsigned long)sec, (unsigned)count, (unsigned)done,
(unsigned long)s_write_fail_streak);
}
return RES_ERROR;
}
DRESULT SD_SPI_ReadSectors(BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_OK;
DWORD sec = (s_sdType & SD_CT_BLOCK) ? sector : (sector << 9);
if (s_status & STA_NOINIT) return RES_NOTRDY;
if (count == 1) {
if (SD_SendCmd(CMD17, sec) != 0 || SD_RecvData(buff, 512)) res = RES_ERROR;
SD_Deselect();
} else {
if (SD_SendCmd(CMD18, sec) == 0) {
do {
if (SD_RecvData(buff, 512)) { res = RES_ERROR; break; }
buff += 512;
} while (--count);
} else {
res = RES_ERROR;
}
SD_SendCmd(CMD12, 0);
SD_Deselect();
}
return res;
}
DRESULT SD_SPI_WriteSectors(const BYTE *buff, DWORD sector, UINT count)
{
DRESULT res = RES_OK;
DWORD sec = (s_sdType & SD_CT_BLOCK) ? sector : (sector << 9);
if (s_status & STA_NOINIT) return RES_NOTRDY;
if (count == 1) {
if (SD_SendCmd(CMD24, sec) != 0 || SD_XmitData(buff, 0xFE)) res = RES_ERROR;
SD_Deselect();
} else {
/* ACMD23 只是可选的预擦除提示;不支持时仍应继续尝试 CMD25。 */
(void)SD_SendAppCmd(CMD23, count);
if (SD_SendCmd(CMD25, sec) == 0) {
do {
if (SD_XmitData(buff, 0xFC)) { res = RES_ERROR; break; }
buff += 512;
} while (--count);
SPI4_RW(0xFD); /* 停止令牌 */
} else {
res = RES_ERROR;
}
SD_Deselect();
}
return res;
}