PAYATHON 2026

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,HCSCMD8 成功验证后,可置 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 表示上电初始化完成。进入正常的数据传输状态前,还需依次执行:

  1. CMD2:读取 CID,使用 R2 长响应。
  2. CMD3:取得 RCA,检查 R6。
  3. CMD9:使用 RCA << 16 读取 CSD,使用 R2 长响应。
  4. CMD7:使用 RCA << 16 选中卡,正确处理 R1b 和 busy。
  5. CMD55 + ACMD51:读取 SCR,确认卡支持的总线宽度。
  6. CMD55 + ACMD6:ACMD6 参数为 2,请求卡切换为 4-bit。
  7. 卡确认成功后,将主机 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 消失。

备注:内容仅供参考。