STM32F407 如何成功执行 ACMD41 完成 SDIO 初始化?
这两个标志不一定表示 ACMD41 失败
执行 ACMD41 后,只要收到的是本次命令的有效 R3 响应,就应按以下规则判断:
STA.CCRCFAIL置位可按 R3 的特殊情况处理。R3 不包含有效的 CRC7,不能套用 R1、R7 的 CRC 检查规则。RESP1的 bit 31 为1,表示卡已完成上电初始化,可以继续发送 CMD2。该位虽然称为 OCR 的 busy 位,但0表示仍在初始化,1表示初始化完成。
因此,不应仅因 CCRCFAIL 置位就反复发送 ACMD41,也不能把 OCR bit 31 为 1 理解为“卡仍忙”。
原代码中的命令寄存器值 0x00000469 设置了命令号 41、短响应和命令状态机使能,这部分没有明显错误。需要修正的是响应完成的判定、参数中的保留位、电压窗口和后续初始化流程。
为什么 ACMD41 会出现 CRCFAIL
ACMD41 返回 R3,响应主体是 OCR。R3 没有有效的 CRC7 字段,STM32F407 的 SDIO 硬件仍可能报告 CCRCFAIL。
发送 ACMD41 后,应等待以下事件之一:
STA.CMDREND
STA.CCRCFAIL
STA.CTIMEOUT
各状态的处理方式如下:
| 状态 | ACMD41 的处理方式 |
|---|---|
CTIMEOUT = 1 | 本次命令未正常收到响应,应报告超时 |
CCRCFAIL = 1 | 对 R3 允许继续读取 OCR |
CMDREND = 1 | 响应接收结束,可以读取 OCR |
| 软件等待超时 | 退出等待,检查或复位 SDIO 外设 |
发送下一条命令前,还应确认 CMDACT 已清零。
这一例外只适用于 R3 等明确不带有效 CRC 的响应。CMD8 的 R7、CMD55 的 R1 都需要正常检查 CRC,不能全局忽略 CCRCFAIL。
R3 也不携带通常意义上的响应命令索引,因此不应要求 ACMD41 的 RESPCMD 必须等于 41。
OCR bit 31 应如何判断
成功接收 ACMD41 响应后,按以下逻辑判断:
if ((ocr & (1U << 31)) != 0U) {
/* Card power-up initialization is complete. */
} else {
/* Send CMD55 + ACMD41 again within the initialization deadline. */
}
ocr 必须来自刚刚完成的 ACMD41。原代码发送命令后只固定等待 1 ms,既未检查本次命令是否超时,也未确认响应是否接收结束,可能读到旧的 RESP1 内容。
以下三个状态含义不同,不能互相代替:
STA.CMDACT:STM32 命令状态机是否正在工作。- R3 OCR bit 31:卡的上电初始化是否完成。
- R1b 或写入期间的 DAT0 busy:卡是否通过数据线表示忙。
修正 ACMD41 参数和初始化时钟
清除参数中的 bit 31
当前参数为:
SDIO->ARG = 0xC0100000;
它设置了 bit 31、bit 30 和 bit 20。这里的 ACMD41 参数中,bit 31 是保留位,应写 0。响应 OCR 的 bit 31 由卡返回,主机不需要在参数中预先置位。
主要字段的设置原则如下:
| 参数字段 | 设置原则 |
|---|---|
bit 30,HCS | CMD8 成功验证后,可置 1,声明主机支持 SDHC/SDXC |
bit 24,S18R | 当前方案不执行 1.8 V 切换,应保持 0 |
| bits 23,电压窗口 | 按主机实际提供的卡电压选择 |
| 保留位 | 写 0 |
HCS 表示主机能力。卡最终是否采用块寻址,需要在初始化完成后检查响应 OCR 的 CCS,即 bit 30。
电压窗口必须与供电一致
0x00100000 设置的是 bit 20,对应 3.2~3.3 V 电压窗口。题目给出的卡供电是 3.0 V,与该窗口不一致。
OCR 电压窗口应根据卡端实际电压及其容差选择。“模块只支持 3V”不等于稳压器必须精确调到 3.000 V,也不应无条件使用覆盖所有电压窗口的掩码。具体供电范围需要结合模块说明和卡的要求确认。
当前初始化分频基本正确
假定 HSE 确实为 8 MHz,当前 PLL 配置得到:
PLL VCO = 8 MHz / 8 × 320 = 320 MHz
SYSCLK = 320 MHz / 2 = 160 MHz
SDIOCLK = 320 MHz / 8 = 40 MHz
当 BYPASS = 0 时:
SDIO_CK = SDIOCLK / (CLKDIV + 2)
CLKCR = 0x00000170 对应:
CLKEN = 1;CLKDIV = 112;WIDBUS = 00,即 1-bit;BYPASS = 0。
因此:
SDIO_CK = 40 MHz / (112 + 2) ≈ 350.9 kHz
该频率符合初始化阶段不超过 400 kHz 的要求。APB2 的 PCLK2 用于寄存器接口,不能代入上述 SDIO 总线时钟公式。
用状态轮询替代固定延时和无限重发
每条命令都应发送一次,等待完成后检查结果。卡尚未就绪时,需要重复发送整个 CMD55 + ACMD41 命令对。
下面的示例适用于题目中的 SDHC 卡路径,使用前需满足以下条件:
- CMD0 已完成;
- CMD8 已通过 CRC 和响应内容检查,
RESP1低 12 位为0x1AA; - 总线仍为 1-bit、约 350 kHz;
msTicks正常递增;host_vdd_window已根据实际供电选择。
示例仅实现短响应命令和 ACMD41 轮询,不包含完整读写驱动,也未经过目标硬件验证。
#include <stdint.h>
#include <stdbool.h>
#define SD_STA_CCRCFAIL (1U << 0)
#define SD_STA_CTIMEOUT (1U << 2)
#define SD_STA_CMDREND (1U << 6)
#define SD_STA_CMDSENT (1U << 7)
#define SD_STA_CMDACT (1U << 11)
#define SD_CMD_SHORT_RESPONSE (1U << 6)
#define SD_CMD_CPSMEN (1U << 10)
#define SD_CMD_CLEAR_FLAGS \
(SD_STA_CCRCFAIL | SD_STA_CTIMEOUT | \
SD_STA_CMDREND | SD_STA_CMDSENT)
#define SD_OCR_READY (1U << 31)
#define SD_OCR_HCS (1U << 30)
#define SD_OCR_VDD_MASK 0x00FF8000U
#define SD_R1_APP_CMD (1U << 5)
#define SD_R1_ERROR_MASK 0xFDFFE008U
typedef enum {
SD_OK = 0,
SD_TIMEOUT,
SD_CRC_ERROR,
SD_BAD_RESPONSE,
SD_CARD_ERROR,
SD_BAD_ARGUMENT,
SD_INIT_TIMEOUT
} SD_Result;
/* Short responses only: for example R1, R7 and R3. */
static SD_Result sd_send_short(uint32_t cmd, uint32_t arg,
bool response_is_r3,
uint32_t *response)
{
uint32_t start = msTicks;
while ((SDIO->STA & SD_STA_CMDACT) != 0U) {
if ((uint32_t)(msTicks - start) >= 50U) {
return SD_TIMEOUT;
}
}
SDIO->ICR = SD_CMD_CLEAR_FLAGS;
SDIO->ARG = arg;
SDIO->CMD = (cmd & 0x3FU) |
SD_CMD_SHORT_RESPONSE |
SD_CMD_CPSMEN;
start = msTicks;
uint32_t sta;
for (;;) {
sta = SDIO->STA;
if (((sta & (SD_STA_CMDREND |
SD_STA_CCRCFAIL |
SD_STA_CTIMEOUT)) != 0U) &&
((sta & SD_STA_CMDACT) == 0U)) {
break;
}
if ((uint32_t)(msTicks - start) >= 50U) {
return SD_TIMEOUT;
}
}
uint32_t resp = SDIO->RESP1;
uint32_t resp_cmd = SDIO->RESPCMD & 0x3FU;
SDIO->ICR = SD_CMD_CLEAR_FLAGS;
if ((sta & SD_STA_CTIMEOUT) != 0U) {
return SD_TIMEOUT;
}
if (!response_is_r3) {
if ((sta & SD_STA_CCRCFAIL) != 0U) {
return SD_CRC_ERROR;
}
if (resp_cmd != (cmd & 0x3FU)) {
return SD_BAD_RESPONSE;
}
}
*response = resp;
return SD_OK;
}
static SD_Result sd_poll_acmd41(uint32_t host_vdd_window,
uint32_t *ocr)
{
if ((ocr == 0) ||
(host_vdd_window == 0U) ||
((host_vdd_window & ~SD_OCR_VDD_MASK) != 0U)) {
return SD_BAD_ARGUMENT;
}
uint32_t start = msTicks;
for (;;) {
uint32_t r1;
uint32_t r3;
SD_Result result;
/* Before RCA assignment, CMD55 uses argument 0. */
result = sd_send_short(55U, 0U, false, &r1);
if (result != SD_OK) {
return result;
}
if (((r1 & SD_R1_ERROR_MASK) != 0U) ||
((r1 & SD_R1_APP_CMD) == 0U)) {
return SD_CARD_ERROR;
}
/* HCS = 1; S18R and reserved bits remain zero. */
result = sd_send_short(41U,
host_vdd_window | SD_OCR_HCS,
true, &r3);
if (result != SD_OK) {
return result;
}
if ((r3 & host_vdd_window) == 0U) {
return SD_BAD_RESPONSE;
}
if ((r3 & SD_OCR_READY) != 0U) {
*ocr = r3;
return SD_OK;
}
if ((uint32_t)(msTicks - start) >= 1000U) {
return SD_INIT_TIMEOUT;
}
delay_ms(1U);
}
}
50 ms 是单次命令的软件等待上限,1000 ms 是初始化轮询期限。这两个限制用于避免程序永久卡死,不能代替硬件状态检查。
如果软件超时时 CMDACT 仍未清零,应退出当前流程,处理外设恢复,不能继续覆盖 ARG、CMD 强行发送下一条命令。
清除状态标志时,应向 ICR 中对应的清除位写入。STA 是状态寄存器。也应避免使用 ICR = 0xFFFFFFFF,以免向保留位写入 1。
ACMD41 完成后,还需要继续初始化
OCR bit 31 为 1 表示上电初始化完成。进入正常的数据传输状态前,还需依次执行:
- CMD2:读取 CID,使用 R2 长响应。
- CMD3:取得 RCA,检查 R6。
- CMD9:使用
RCA << 16读取 CSD,使用 R2 长响应。 - CMD7:使用
RCA << 16选中卡,正确处理 R1b 和 busy。 - CMD55 + ACMD51:读取 SCR,确认卡支持的总线宽度。
- CMD55 + ACMD6:ACMD6 参数为
2,请求卡切换为 4-bit。 - 卡确认成功后,将主机
CLKCR.WIDBUS配置为01。
分配 RCA 后,发给该卡的 CMD55 参数应使用 RCA << 16。
对于已确认采用块寻址的 SDHC 卡,读写命令的地址参数是 512 字节块的编号,不是字节偏移。写入 raw data 不需要 FATFS,但仍需完成卡选择、数据通道配置,以及写入结束和 busy 检查。
数据传输前必须修正 SDIO 寄存器结构体
原结构体在 MASK 后直接声明了:
volatile uint32_t FIFOCNT;
volatile uint32_t FIFO;
C 编译器不会根据注释自动插入保留地址空间,实际生成的偏移是:
FIFOCNT: 0x40
FIFO: 0x44
正确偏移应为:
FIFOCNT: 0x48
FIFO: 0x80
应将结构体尾部改为:
volatile uint32_t MASK; /* 0x3C */
uint32_t RESERVED0[2]; /* 0x40..0x44 */
volatile uint32_t FIFOCNT; /* 0x48 */
uint32_t RESERVED1[13]; /* 0x4C..0x7C */
volatile uint32_t FIFO; /* 0x80 */
使用 C11 时,可以加入编译期检查:
_Static_assert(offsetof(SDIO_Register, FIFOCNT) == 0x48U,
"Incorrect FIFOCNT offset");
_Static_assert(offsetof(SDIO_Register, FIFO) == 0x80U,
"Incorrect FIFO offset");
这个错误不影响前面的 STA、ICR、RESP1 偏移,无法解释 ACMD41 的 CRC 现象,但会直接影响后续 SCR、CMD6 状态数据和块数据的传输。
40 MHz 和硬件调试注意事项
完成初始化和速度协商后,才能启用 40 MHz。SD 卡默认速度模式的时钟上限为 25 MHz;目标频率为 40 MHz 时,需要通过普通 CMD6 查询并切换到 High Speed,确认返回的切换结果。
ACMD6 和 CMD6 的用途不同:
- ACMD6:设置数据总线宽度。
- CMD6:查询或切换访问模式等功能。
当前 SDIOCLK = 40 MHz,普通分频模式下,CLKDIV = 0 得到的时钟是 20 MHz。要输出 40 MHz,需要使用 BYPASS,并确认卡、STM32F407 时序和板级连接均满足要求。卡上的 UHS-I、U3、V30、A1 标记不能代替主机侧的模式协商。
硬件调试时,应在卡端检查以下项目:
- 确认 MCU 与独立供电的 microSD 模块共地。
- 卡供电稳定后,在 CMD0 前提供至少 74 个初始化时钟,并让 CMD、DAT 线保持要求的空闲电平。
- 核实 CMD 和 DAT0~DAT3 的上拉电阻。GPIO 内部弱上拉不能直接替代板级上拉设计。
- PC12 的 CLK 通常配置为无上下拉,不必与 CMD、DAT 统一设置为上拉。
- 用示波器观察卡座处的供电和 CLK、CMD。即使万用表测得稳定的 3.0 V、0.05 A,也无法排除瞬时压降、纹波或信号完整性问题。
针对当前现象,应优先记录每次命令结束时的 STA 和 RESP1。如果 ACMD41 没有超时,响应 OCR 的电压窗口合理且 bit 31 已置位,就应继续发送 CMD2,无需等待 CCRCFAIL 消失。
备注:内容仅供参考。
