这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 浅浅的理解一下SPI接口驱动TF卡

共2条 1/1 1 跳转至

浅浅的理解一下SPI接口驱动TF卡

院士
2026-02-19 18:24:39     打赏

STM32通过SPI接口驱动TF卡是一种常见且高效的嵌入式数据存储解决方案,广泛应用于数据记录、文件系统构建和固件升级等场景。该方案利用STM32的SPI外设与TF卡进行通信,工作在SPI模式下,接口简单(仅需SCK、MOSI、MISO和CS四根线),兼容性强,适合资源受限的系统。

硬件连接与配置

STM32与TF卡的硬件连接需确保电平匹配和信号完整性:

SCK‌(时钟)接STM32的SPI_SCK引脚

MOSI‌(主出从入)接SPI_MOSI

MISO‌(主入从出)接SPI_MISO

CS‌(片选)接任意GPIO或SPI_NSS引脚

为提高稳定性,建议在MOSI、SCK等信号线上加10kΩ上拉电阻,并保证走线短而直,远离干扰源。

软件实现流程

SPI初始化‌ 配置SPI为主模式,时钟极性(CPOL)为低,相位(CPHA)为第一边沿采样(即模式0),波特率分频适中(如64分频),确保与TF卡兼容。

TF卡初始化‌

上电后发送至少74个时钟脉冲,然后发送CMD0(复位命令),使TF卡进入SPI模式;接着发送CMD8、ACMD41等命令完成初始化,直到卡返回就绪状态。

读写操作‌

使用CMD17/CMD18进行单/多扇区读取 使用CMD24/CMD25进行单/多扇区写入 每个扇区大小通常为512字节,必须对齐操作

错误处理与优化‌ 增加超时重试机制,轮询响应状态(如0x00表示成功),并在写入后等待非忙状态。可结合DMA提升传输效率,减少CPU占用。

文件系统支持

若需支持文件管理,可集成FATFS等轻量级文件系统库,实现对FAT16/FAT32/exFAT格式TF卡的读写支持。

#include "spi_tfcard.h"

static SPI_HandleTypeDef *tf_spi_handle;
static uint8_t tf_card_type;

/**
 * @brief  向TF卡发送一个字节
 * @param  data: 要发送的数据
 * @retval 接收到的数据
 */
static uint8_t TF_SPI_ReadWrite_Byte(uint8_t data)
{
    uint8_t rx_data;
    HAL_SPI_TransmitReceive(tf_spi_handle, &data, &rx_data, 1, 100);
    return rx_data;
}

/**
 * @brief  向TF卡发送多个字节
 * @param  data: 要发送的数据指针
 * @param  size: 数据大小
 * @retval 无
 */
static void TF_SPI_ReadWrite_Buffer(uint8_t *tx_data, uint8_t *rx_data, uint16_t size)
{
    HAL_SPI_TransmitReceive(tf_spi_handle, tx_data, rx_data, size, 1000);
}

/**
 * @brief  TF卡片选使能
 * @retval 无
 */
static void TF_CS_Enable(void)
{
    TF_CS_LOW();
    // 延时等待
    TF_SPI_ReadWrite_Byte(0xFF);
}

/**
 * @brief  TF卡片选失能
 * @retval 无
 */
static void TF_CS_Disable(void)
{
    TF_CS_HIGH();
    // 延时等待
    TF_SPI_ReadWrite_Byte(0xFF);
}

/**
 * @brief  向TF卡发送命令
 * @param  cmd: 命令
 * @param  arg: 参数
 * @param  crc: CRC校验值
 * @retval 响应值
 */
static uint8_t TF_Send_Cmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
    uint8_t response;
    uint8_t cmd_buffer[6];
    uint8_t retry = 0;

    // 构造命令帧
    cmd_buffer[0] = (cmd | 0x40);
    cmd_buffer[1] = (uint8_t)(arg >> 24);
    cmd_buffer[2] = (uint8_t)(arg >> 16);
    cmd_buffer[3] = (uint8_t)(arg >> 8);
    cmd_buffer[4] = (uint8_t)(arg);
    cmd_buffer[5] = crc;

    // 发送命令
    TF_CS_Enable();
    TF_SPI_ReadWrite_Buffer(cmd_buffer, &response, 6);

    // 等待响应
    while ((response = TF_SPI_ReadWrite_Byte(0xFF)) == 0xFF) {
        retry++;
        if (retry > 100) break;
    }

    return response;
}

/**
 * @brief  等待TF卡准备就绪
 * @retval 状态值
 */
uint8_t TF_Wait_Ready(void)
{
    uint8_t response;
    uint16_t retry = 0;

    do {
        response = TF_SPI_ReadWrite_Byte(0xFF);
        retry++;
    } while ((response != 0xFF) && (retry < 0xFFF));

    return response;
}

/**
 * @brief  接收数据包
 * @param  buffer: 数据缓冲区
 * @param  length: 数据长度
 * @retval 无
 */
static void TF_Receive_Data_Packet(uint8_t *buffer, uint16_t length)
{
    uint8_t token;
    uint16_t i;

    // 等待数据起始令牌
    while ((token = TF_SPI_ReadWrite_Byte(0xFF)) == 0xFF);

    if (token == 0xFE) {
        // 接收数据
        for (i = 0; i < length; i++) {
            buffer[i] = TF_SPI_ReadWrite_Byte(0xFF);
        }
        // 读取CRC(忽略)
        TF_SPI_ReadWrite_Byte(0xFF);
        TF_SPI_ReadWrite_Byte(0xFF);
    }
}

/**
 * @brief  发送数据包
 * @param  buffer: 数据缓冲区
 * @param  length: 数据长度
 * @param  token: 起始令牌
 * @retval 响应值
 */
static uint8_t TF_Send_Data_Packet(uint8_t *buffer, uint16_t length, uint8_t token)
{
    uint8_t response;
    uint16_t i;

    // 发送起始令牌
    TF_SPI_ReadWrite_Byte(token);

    if (token != 0xFD) {
        // 发送数据
        for (i = 0; i < length; i++) {
            TF_SPI_ReadWrite_Byte(buffer[i]);
        }
        // 发送CRC(忽略)
        TF_SPI_ReadWrite_Byte(0xFF);
        TF_SPI_ReadWrite_Byte(0xFF);

        // 读取数据响应
        response = TF_SPI_ReadWrite_Byte(0xFF);
        response &= 0x1F;

        if (response != 0x05) {
            return TF_CARD_ERROR;
        }
    }

    return TF_CARD_READY;
}

/**
 * @brief  初始化TF卡
 * @param  hspi: SPI句柄
 * @retval 卡类型
 */
uint8_t TF_Card_Init(SPI_HandleTypeDef *hspi)
{
    uint8_t response;
    uint8_t retry;
    uint16_t i;

    tf_spi_handle = hspi;

    // 片选初始化为高电平
    TF_CS_Disable();

    // 发送至少74个时钟脉冲
    for (i = 0; i < 10; i++) {
        TF_SPI_ReadWrite_Byte(0xFF);
    }

    // 发送CMD0进入SPI模式
    retry = 0;
    do {
        response = TF_Send_Cmd(CMD0, 0, 0x95);
        retry++;
    } while ((response != 0x01) && (retry < 100));

    if (response != 0x01) {
        return TF_CARD_ERROR;
    }

    // 检查是否支持V2.0
    response = TF_Send_Cmd(CMD8, 0x1AA, 0x87);
    if (response == 0x01) {
        // 支持V2.0
        // 读取剩余4字节响应
        TF_SPI_ReadWrite_Byte(0xFF);
        TF_SPI_ReadWrite_Byte(0xFF);
        TF_SPI_ReadWrite_Byte(0xFF);
        TF_SPI_ReadWrite_Byte(0xFF);

        // 发送ACMD41激活卡
        retry = 0;
        do {
            TF_Send_Cmd(CMD55, 0, 0);
            response = TF_Send_Cmd(ACMD41, 0x40000000, 0);
            retry++;
        } while ((response != 0x00) && (retry < 2000));

        if (retry >= 2000) {
            return TF_CARD_ERROR;
        }

        // 检查是否为SDHC卡
        response = TF_Send_Cmd(CMD58, 0, 0);
        if (response == 0x00) {
            // 读取OCR寄存器
            if ((TF_SPI_ReadWrite_Byte(0xFF) & 0x40) == 0x40) {
                tf_card_type = TF_CARD_TYPE_SDHC;
            } else {
                tf_card_type = TF_CARD_TYPE_SD_V2;
            }
            // 读取剩余3字节
            TF_SPI_ReadWrite_Byte(0xFF);
            TF_SPI_ReadWrite_Byte(0xFF);
            TF_SPI_ReadWrite_Byte(0xFF);
        }
    } else {
        // V1.0卡或MMC卡
        TF_Send_Cmd(CMD55, 0, 0);
        response = TF_Send_Cmd(ACMD41, 0, 0);
        if (response <= 1) {
            tf_card_type = TF_CARD_TYPE_SD_V1;
            retry = 0;
            do {
                TF_Send_Cmd(CMD55, 0, 0);
                response = TF_Send_Cmd(ACMD41, 0, 0);
                retry++;
            } while ((response != 0x00) && (retry < 2000));
        } else {
            tf_card_type = TF_CARD_TYPE_MMC;
            retry = 0;
            do {
                response = TF_Send_Cmd(CMD1, 0, 0);
                retry++;
            } while ((response != 0x00) && (retry < 2000));
        }

        if (retry >= 2000) {
            return TF_CARD_ERROR;
        }

        // 设置扇区大小为512字节
        if (TF_Send_Cmd(CMD16, 512, 0) != 0x00) {
            return TF_CARD_ERROR;
        }
    }

    TF_CS_Disable();
    TF_SPI_ReadWrite_Byte(0xFF);

    return tf_card_type;
}

/**
 * @brief  获取TF卡类型
 * @retval 卡类型
 */
uint8_t TF_Get_Card_Type(void)
{
    return tf_card_type;
}

/**
 * @brief  读取扇区数据
 * @param  sector: 扇区号
 * @param  buffer: 数据缓冲区
 * @retval 状态值
 */
uint8_t TF_Read_Sector(uint32_t sector, uint8_t *buffer)
{
    uint8_t response;

    // SDHC卡使用扇区地址,其他卡使用字节地址
    if (tf_card_type != TF_CARD_TYPE_SDHC) {
        sector *= 512;
    }

    response = TF_Send_Cmd(CMD17, sector, 0);
    if (response != 0x00) {
        TF_CS_Disable();
        return TF_CARD_ERROR;
    }

    TF_Receive_Data_Packet(buffer, 512);
    TF_CS_Disable();
    TF_SPI_ReadWrite_Byte(0xFF);

    return TF_CARD_READY;
}

/**
 * @brief  写入扇区数据
 * @param  sector: 扇区号
 * @param  buffer: 数据缓冲区
 * @retval 状态值
 */
uint8_t TF_Write_Sector(uint32_t sector, uint8_t *buffer)
{
    uint8_t response;

    // SDHC卡使用扇区地址,其他卡使用字节地址
    if (tf_card_type != TF_CARD_TYPE_SDHC) {
        sector *= 512;
    }

    response = TF_Send_Cmd(CMD24, sector, 0);
    if (response != 0x00) {
        TF_CS_Disable();
        return TF_CARD_ERROR;
    }

    response = TF_Send_Data_Packet(buffer, 512, 0xFE);
    if (response != TF_CARD_READY) {
        TF_CS_Disable();
        return TF_CARD_ERROR;
    }

    // 等待写入完成
    TF_Wait_Ready();
    TF_CS_Disable();
    TF_SPI_ReadWrite_Byte(0xFF);

    return TF_CARD_READY;
}






关键词: SPI     TF    

助工
2026-02-20 09:29:12     打赏
2楼

对于普通的MCU来说,使用SPI读写TF卡的方案实在太香了。

成本低,实现又简单。


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]