T 卡(SD 卡)

本文说明 Solution 工程中 T 卡的接入方式、设备注册逻辑、挂载关系以及插拔检测流程。这里的 T 卡既包括 SPI 模式下的 TF 卡,也包括 SDIO/SDHCI 模式下接入的 SD 卡。对应用层来说,最关键的是先确认“卡是否已注册为块设备”,再确认“文件系统是否已经挂载到 /external_sd”。

1. 参考代码

文件

作用

solution/components/tf/tf_init.c

T 卡服务入口,负责插拔检测、状态同步和业务目录创建

solution/components/tf/tf_init.h

T 卡路径、状态枚举和对外接口定义

solution/components/tf/tf_ota.c

TF 卡 OTA 包扫描、校验和安装流程

sdk/rtos/rtthread/components/drivers/spi/spi_msd.c

SPI 模式 TF/SD 卡驱动,将卡注册为 sd0

sdk/rtos/rtthread/components/drivers/sdio/mmcsd_core.c

SDIO/SDHCI 模式下的卡识别流程

sdk/rtos/rtthread/components/drivers/sdio/block_dev.c

SDIO/SDHCI 模式下块设备注册、移除和卸载流程

sdk/customer/peripherals/sd/Kconfig

T 卡检测脚、供电脚和设备名配置

sdk/rtos/rtthread/components/drivers/Kconfig

SPI MSD 驱动配置项

2. 简介

T 卡在工程中最终会体现为两层结构:

  1. 块设备:SPI 方案通常注册为 sd0,SDIO 方案则使用 host->name 对应的设备名;

  2. 文件系统挂载点:业务上通常使用 /external_sd 作为根目录,musicvideoebookphotoota 等目录都围绕这个挂载点展开。

因此,排查 T 卡问题时,建议按以下顺序进行:

  • 设备有没有注册成功;

  • 卡插拔有没有被检测到;

  • 文件系统有没有挂载成功;

  • 业务目录有没有创建出来。

对于应用开发者来说,真正需要关注的是:设备是否存在、挂载是否正常、读写是否可用,而不是只看底层驱动层面的实现细节。

4. 核心接入方式

4.1 SPI TF 卡

SPI 方案适合引脚资源紧张、仅需要基础读写能力的场景。当前代码中的初始化链路大致为:

  1. 查找名为 sdcard 的 SPI 设备;

  2. 若不存在,则将 sdcard 绑定到 spi1

  3. 打开 SPI 设备;

  4. 调用 msd_init("sd0", "sdcard") 注册块设备;

  5. 后续通过 sd0 访问卡内容。

关键代码路径在 rt_spi_msd_init()msd_init(),对应的是 SPI MSD 的标准初始化流程。

4.2 SDIO/SDHCI SD 卡

SDIO/SDHCI 方案使用芯片自带的 SDMMC 控制器,吞吐一般更高,也更适合标准 SD 卡场景。它不是由 spi_msd.c 直接注册设备,而是走 MMC/SD 框架:

  1. 检测到插卡事件后触发 mmcsd_detect()

  2. 框架按检测顺序识别 SDCARD / EMMC / SDIO;

  3. 卡识别成功后,在 block_dev.c 中注册块设备;

  4. 设备名通常来自 host->name

这类方案的特点是:卡的存在性判断依赖 MMC/SD 框架本身,而不是 T 卡服务层人为推断。

4.2.1 SD 卡切换 SDIO/SDHCI

58x / 57x / 55x / 56x 等平台通常支持两个 SDIO/SDHCI 设备,可用于 SD 卡接入。下面以 57x 平台中 SD 卡从 SDIO1 切换到 SDIO2 为例说明:

  • 使能 SDIO2 总线,并将总线用途配置为 SDCARD

  • 配置 TF 对应的 SDIO 总线为 sd1

  • 修改 flashmap 文件,将 TF 分区名称调整为 SD1

5. 初始化流程

5.1 SPI TF 卡

SPI TF 卡的关键初始化步骤如下:

rt_hw_spi_device_attach("spi1", "sdcard");
rt_device_t spi_dev = rt_device_find("sdcard");
rt_device_open(spi_dev, RT_DEVICE_FLAG_DMA_RX | RT_DEVICE_FLAG_DMA_TX | RT_DEVICE_FLAG_RDWR);
msd_init("sd0", "sdcard");

初始化完成后,系统中会出现一个名为 sd0 的块设备。驱动进行读写前还会进行状态更新;如果检测到卡已拔出,会直接返回失败,避免继续访问无效介质。

5.2 SDIO/SDHCI SD 卡

SDIO/SDHCI 路径下,卡识别交给 MMC/SD 框架。识别流程大致如下:

  • 先通过 mmcsd_detect_orders 决定优先检测类型;

  • 进入 MMCSD_HOST_DETECT_SDCARD 分支后执行 SD 卡识别;

  • 识别成功后,block_dev.c 会把卡注册为块设备;

  • 设备名通常就是 host->name

也就是说,SDIO 场景下“卡是否存在”主要取决于 MMC/SD 的识别结果,而不是 TF 服务组件单独判断。

6. TF 卡状态管理

6.1 TF 卡状态管理框图

SPI TF 卡通常仅有三种状态:MSD_CARD_STATUS_INVALIDMSD_CARD_STATUS_INACTIVEMSD_CARD_STATUS_ACTIVE。其状态迁移逻辑可概括为:

  • 开机时默认处于 MSD_CARD_STATUS_INVALID

  • 上电初始化完成后切换到 MSD_CARD_STATUS_ACTIVE

  • 待机时,掉电后切换到 MSD_CARD_STATUS_INACTIVE

  • 唤醒后重新上电并恢复到 MSD_CARD_STATUS_ACTIVE

  • TF 卡拔除后切换为 MSD_CARD_STATUS_INVALID

  • 重新插入并上电初始化完成后再切回 MSD_CARD_STATUS_ACTIVE

../../_images/spi_tf_status_machine.svg

6.2 SPI TF 设备状态管理

SPI TF 卡状态机变量通常位于 msd_device->status,状态枚举如下:

typedef enum
{
    MSD_CARD_STATUS_UNKNOWN = 0, /**< unknown */
    MSD_CARD_STATUS_INVALID,     /**< TF card is invalid */
    MSD_CARD_STATUS_INACTIVE,    /**< TF card is inactive */
    MSD_CARD_STATUS_ACTIVE,      /**< TF card is active */
} msd_card_status;

状态管理接口示例:

void set_spi_msd_state(msd_card_status new_state);
msd_card_status get_spi_msd_state(void);

6.3 SDIO TF 卡设备状态管理

SDIO 卡状态机变量通常位于 sdio->state,状态枚举如下:

typedef enum
{
    SDIO_STATE_UNKNOWN = 0,
    SDIO_STATE_INVALID,      /**< SDIO is invalid */
    SDIO_STATE_INACTIVE,     /**< SDIO is inactive */
    SDIO_STATE_ACTIVE,       /**< SDIO is active */
} sdio_state_t;

状态管理接口示例:

static void sifli_sdio_set_state(struct rthw_sdio *sdio, sdio_state_t new_state);
static sdio_state_t get_sifli_sdio_state(struct rthw_sdio *sdio);

7. 插拔检测

tf_init.c 中有一套统一的插拔处理逻辑:

  1. GPIO 中断触发;

  2. 中断回调只负责释放信号量;

  3. tf_task 线程中做去抖和状态判断;

  4. 根据当前状态调用插入或拔出处理。

代码中对检测脚的采样是两次比较,两个采样一致才会认为检测结果有效。当前实现中,检测脚低电平表示插入、高电平表示拔出,但最终仍要结合硬件电路和板级定义来判断。

7.1 插入时

  • SPI 方案:查找 sd0,如果尚未注册则初始化并注册;如果已有设备但状态无效,则尝试重新初始化。

  • SDIO 方案:调用 mmcsd_change(host) 触发重新识别,并等待设备就绪。

7.2 拔出时

  • SPI 方案:将 sd0 状态标记为无效,阻止后续读写;

  • SDIO 方案:注销对应块设备,并触发文件系统卸载流程。

8. 挂载和业务路径

tf_init.h 中定义了常见业务路径:

路径

用途

TF_METE_PATH

/external_sd

T 卡根路径

TF_METE_MUSIC_PATH

/external_sd/music

音乐

TF_METE_VIDEO_PATH

/external_sd/video

视频

TF_METE_EBOOK_PATH

/external_sd/ebook

电子书

TF_METE_ALBUM_PATH

/external_sd/photo

图片

TF_OTA_BIN_PATH

/external_sd/ota/ota.bin

TF OTA 升级包

T/SD 卡介质按 exFAT 格式使用,挂载点为 /external_sd。代码中仍通过 DFS 使用 ElmFat 挂载,工程需要开启 RT_DFS_ELM_USE_EXFAT

这里需要注意:设备注册并不等于文件系统已经挂载成功。如果工程没有做自动挂载,则应在合适时机显式挂载,例如:

mkdir("/external_sd", 0);
dfs_mount("sd0", "/external_sd", "elm", 0, 0);

卸载时,通常需要先停止正在访问卡的业务,再执行卸载操作。

9. TF 卡的低功耗处理

9.1 SPI TF 卡低功耗

SPI TF 卡支持在读写后强制进入 idle,以降低空闲时功耗。系统待机时,如果不需要继续监测插拔,可以对 SPI TF 进行掉电处理。

实现方法如下:

  1. 睡眠时,在 BSP_SPI_MSD_Power_Down() 中增加掉电逻辑,并将 TF 状态切换为 MSD_CARD_STATUS_INACTIVE

void BSP_SPI_MSD_Power_Down(void)
{
    /* power off */
    BSP_GPIO_Set(PIN_LCD_TF_POWER, 0, 1);
    /* set state to inactive */
    set_spi_msd_state(MSD_CARD_STATUS_INACTIVE);
}
  1. 唤醒后,在 BSP_SPI_MSD_Power_Up() 中处理上电恢复,并在恢复过程中执行 TF 卡初始化。

void BSP_SPI_MSD_Power_Up(void)
{
    /* power on */
    if (!BSP_GPIO_Get(PIN_LCD_TF_POWER, 1))
    {
        BSP_GPIO_Set(PIN_LCD_TF_POWER, 1, 1);
        HAL_Delay_us(50);
    }
}

SPI TF 卡初始化分两类情况:

  • GUI 界面恢复完成后执行初始化;

  • 读写接口在状态判断时,如果为 MSD_CARD_STATUS_INVALID,直接返回;如果为 MSD_CARD_STATUS_INACTIVE,则执行初始化。

参考文件:app_pm.capp_gui_pm_process(sleep_event_t event) 增加:

#ifdef MSD_INIT_ON_GUI_WAKEUP
    extern void msd_request_reinit(void);
    msd_request_reinit();
#endif

读写接口示例:

static rt_size_t rt_msd_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    /* if card removed, will return */
    if (get_spi_msd_state() == MSD_CARD_STATUS_INVALID)
    {
        MSD_DEBUG("[err] SPI MSD is invalid!\r\n");
        return 0;
    }

    if (get_spi_msd_state() == MSD_CARD_STATUS_INACTIVE)
    {
#ifdef MSD_INIT_ON_GUI_WAKEUP
        msd_request_reinit();
#endif
    }

    return 0;
}
static rt_size_t rt_msd_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    if (get_spi_msd_state() == MSD_CARD_STATUS_INVALID)
    {
        MSD_DEBUG("[err] SD card is invalid!\r\n");
        return 0;
    }

    if (get_spi_msd_state() == MSD_CARD_STATUS_INACTIVE)
    {
#ifdef MSD_INIT_ON_GUI_WAKEUP
        msd_request_reinit();
#endif
    }

    return 0;
}

10. 快速上手

T 卡的验证顺序建议按下面的步骤进行:

  1. 在串口中查看设备列表,确认是否存在 sd0 或对应 SDIO 设备;

  2. 检查 /external_sd 是否能正常访问,目录是否可列出;

  3. 确认业务目录如 musicvideophotoota 是否存在;

  4. 若需升级或读取文件,先确认文件系统和挂载状态正常,然后再操作。

10.1 常见日志

日志

含义

[BUS]SPI1 probe sdcard...

SPI 总线和 sdcard 设备已经绑定

[MSD] no card insert, skip msd_init

上电时检测到卡未插入,SPI MSD 初始化被跳过

[SD]msd init ok

SPI 模式卡初始化成功

find sd0 ok !

sd0 已经注册成功

detect SD card BEGIN / DONE

SDIO 框架正在识别或已经识别成功

10.3 常见判定

  • 设备列表无 sd0 或主机名:通常是 SPI 绑定、Pinmux 或 SDIO 设备树配置有问题;

  • 卡已识别但无法访问挂载点:通常检查 FAT/ELM 文件系统是否成功挂载;

  • 只识别不读写:需检查挂载参数、文件句柄是否释放、是否有读写权限问题;

  • 热拔出后异常:需确认应用在拔卡前已停止读写,并按流程执行卸载。

11. 常见问题

11.1 找不到 sdcard

检查 SPI 总线名、Pinmux 配置和 rt_hw_spi_device_attach() 是否与实际硬件一致。

11.2 sd0 注册失败

检查卡座、供电、检测脚和 SPI 工作模式是否正确。若是 SDIO 路径,则需确认 SDMMC 控制器是否启用,以及卡类型是否正确识别。

11.3 只能识别不能读写

通常是文件系统格式不正确、挂载失败,或者有尚未关闭的文件句柄。建议先验证挂载点可否正常列目录,再继续读写。

11.4 热拔出后异常

拔卡前应先停止所有访问,再触发卸载流程,避免文件系统处于脏状态。

12. 说明

  1. SPI TF 卡和 SDIO SD 卡的底层接法不同,但上层业务最终都应统一到“先确认卡存在,再访问挂载点”的思路。

  2. 当前代码里,SPI 方案默认使用 spi1sdcardsd0;SDIO 方案则更依赖 host->name

  3. /external_sdmusicvideoebookphotoota 等目录是工程里已约定好的业务路径,实际挂载点和文件结构应与板级配置保持一致。

  4. 对于 T 卡相关开发,最稳妥的排查方法是:先看“块设备是否存在”,再看“挂载点是否正常”,最后看“业务目录和文件操作是否正常”。

13. 结论

TF 卡的状态管理本质上是一个典型的三态生命周期:

  • MSD_CARD_STATUS_INVALID:未插入或已拔出,设备不可用;

  • MSD_CARD_STATUS_INACTIVE:已掉电或休眠状态,需在唤醒/重入时恢复;

  • MSD_CARD_STATUS_ACTIVE:卡已正常初始化并可供业务访问。

在实际开发中,关键不是单纯监测卡是否存在,而是统一管理“状态机 + 挂载点 + 插拔检测”三者之间的关系,避免在休眠、唤醒或热插拔场景中出现误判或访问无效块设备。