你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【福利三:逢7发帖赢大礼】7、基于STM32G070板子TF卡的FATFS移植

[复制链接]
STMWoodData 发布时间:2026-7-24 16:17

本次分享一下如何驱动SPI接口TF卡,如何实现TF卡读写移植FATFS文件系统。

板上TF卡接口:

image.png

电路部分如下,和SPI flash共用SPI接口,片选不同。

image.png

下面实现spi接口驱动TF。首先实现几个接口函数:

/* 片选控制 */
void SD_SPI_CS_Low(void);      /* 拉低CS,选中TF卡 */
void SD_SPI_CS_High(void);     /* 拉高CS,释放TF卡 */

/* SPI读写一个字节(同时收发) */
uint8_t SD_SPI_ReadWriteByte(uint8_t tx_data);

/* 时钟频率切换(初始化时必须≤400kHz,正常读写可提高) */
void SD_SPI_SetSpeedLow(void);   /* ≤400kHz,用于初始化 */
void SD_SPI_SetSpeedHigh(void);  /* 正常读写速度,建议≤25MHz */

/* 毫秒级延时(用于超时等待) */
void SD_SPI_DelayMs(uint32_t ms);
#include "main.h"
#include "sd_spi_interface.h"
#include "spi.h"      /* HAL库生成的SPI句柄 */
#include "gpio.h"     /* HAL库生成的GPIO定义 */

/* 用户根据实际工程修改以下宏 */
#define SD_SPI_HANDLE  hspi1
#define SD_CS_PIN      TF_CS_Pin
#define SD_CS_PORT     TF_CS_GPIO_Port

void SD_SPI_CS_Low(void)
{
    HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_RESET);
}

void SD_SPI_CS_High(void)
{
    HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_SET);
}

uint8_t SD_SPI_ReadWriteByte(uint8_t tx_data)
{
    uint8_t rx_data;
    HAL_SPI_TransmitReceive(&SD_SPI_HANDLE, &tx_data, &rx_data, 1, 100);
    return rx_data;
}

void SD_SPI_SetSpeedLow(void)
{
    /* 设置为400kHz以下 */
    SD_SPI_HANDLE.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
    HAL_SPI_Init(&SD_SPI_HANDLE);
}

void SD_SPI_SetSpeedHigh(void)
{
    /* 设置为较高速度,如18MHz(根据MCU调整) */
    SD_SPI_HANDLE.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
    HAL_SPI_Init(&SD_SPI_HANDLE);
}

void SD_SPI_DelayMs(uint32_t ms)
{
    HAL_Delay(ms);
}

TF卡驱动部分代码如下:

/* 命令类型 SD/MMC command list - SPI mode */
#define CMD0        0       /* Reset */
#define CMD1        1       /* Send Operator Condition - SEND_OP_COND */
#define CMD8        8       /* Send Interface Condition - SEND_IF_COND  */
#define CMD9        9       /* Read CSD */
#define CMD10       10      /* Read CID */
#define CMD12       12      /* Stop data transmit */
#define CMD16       16      /* Set block size, should return 0x00 */ 
#define CMD17       17      /* Read single block */
#define CMD18       18      /* Read multi block */
#define ACMD23      23      /* Prepare erase N-blokcs before multi block write (需先发CMD55)*/
#define CMD24       24      /* Write single block */
#define CMD25       25      /* Write multi block */
#define ACMD41      41      /* should return 0x00 (需先发CMD55)*/
#define CMD55       55      /* should return 0x01 */
#define CMD58       58      /* Read OCR */
#define CMD59       59      /* CRC disable/enbale, should return 0x00 */


/* 响应类型 */
#define R1_READY       0x00   /* 就绪 */
#define R1_IDLE        0x01   /* 卡处于空闲状态 */
#define R1_ILLEGAL_CMD 0x04   /* 非法命令 */


/* 卡类型 */
#define SD_TYPE_UNKNOWN  0
#define SD_TYPE_V1       1    /* SD卡 v1.x */
#define SD_TYPE_V2       2    /* SD卡 v2.0 */
#define SD_TYPE_HC       3    /* SDHC/SDXC */
#define SD_TYPE_MMC      4    /* MMC       */

/* 函数声明 */
uint8_t SD_Init(void);
uint8_t SD_ReadBlock(uint32_t sector, uint8_t *buffer);
uint8_t SD_WriteBlock(uint32_t sector, const uint8_t *buffer);
uint64_t SD_GetSectorCount(void);
uint8_t SD_GetCardType(void);

#include "main.h"
#include "sd_driver.h"
#include "sd_spi_interface.h"

/* ---------- 私有宏 ---------- */
#define SD_DUMMY_BYTE  0xFF
#define SD_CMD_TIMEOUT 1000    /* 命令超时(ms) */

/* ---------- 私有变量 ---------- */
static uint8_t sd_card_type = SD_TYPE_UNKNOWN;
static uint64_t sd_sector_count = 0;

/* ---------- 私有函数 ---------- */
/* 向SD卡发送6字节命令,返回R1响应 */
static uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
    uint8_t cmd_frame[6];
    uint8_t resp;
    uint32_t timeout;

    /* 构造命令帧:CMD + 4字节参数 + CRC */
    cmd_frame[0] = cmd | 0x40;          /* 起始位0 + 命令索引 */
    cmd_frame[1] = (uint8_t)(arg >> 24);
    cmd_frame[2] = (uint8_t)(arg >> 16);
    cmd_frame[3] = (uint8_t)(arg >> 8);
    cmd_frame[4] = (uint8_t)(arg >> 0);
    cmd_frame[5] = crc;

    /* 发送命令前先发若干dummy时钟,保证上一操作完成 */
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);

    /* 片选有效,开始发送命令 */
    SD_SPI_CS_Low();

    /* 发送6字节命令 */
    for (uint8_t i = 0; i < 6; i++) 
    {
        SD_SPI_ReadWriteByte(cmd_frame[i]);
    }

    /* 等待R1响应(0x00~0xFF,最高位为0表示有效响应) */
    timeout = SD_CMD_TIMEOUT;
    do {
        resp = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
        timeout--;
    } while ((resp & 0x80) && timeout);
//    printf("resp %d = %d \r\n",cmd, resp);
    return resp;
}

/* 发送带APP前缀的命令(ACMD) */
static uint8_t SD_SendAppCmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
    uint8_t resp;

    /* 先发送CMD55,告诉SD卡下一条是应用命令 */
    resp = SD_SendCmd(CMD55, 0, 0x65);  /* CMD55的CRC固定为0x65 */
    if (resp != R1_READY) {
        return resp;
    }

    /* 发送实际的ACMD命令 */
    return SD_SendCmd(cmd, arg, crc);
}

/* 等待SD卡退出忙状态(数据线变高) */
static uint8_t SD_WaitReady(void)
{
    uint32_t timeout = 5000;  /* 5ms * 5000 = 25秒超时 */
    uint8_t resp;

    do {
        resp = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
        timeout--;
        if (timeout == 0) {
            return 0;  /* 超时失败 */
        }
    } while (resp != SD_DUMMY_BYTE);

    return 1;  /* 成功 */
}

/**
 * @brief 从CSD寄存器解析扇区总数
 * @param csd 指向16字节CSD数据的指针
 * @return 扇区总数(每扇区512字节)
 */
static uint64_t SD_ParseCSD(uint8_t *csd)
{
    uint64_t sector_cnt = 0;

    /* 检查CSD版本(位于第1字节的高2位) */
    uint8_t csd_version = (csd[0] & 0xC0) >> 6;

    if (csd_version == 0x01)  /* CSD V2.0 —— SDHC / SDXC */
    {
        /* C_SIZE 为 22位,位于 Byte7[5:0]、Byte8[7:0]、Byte9[7:0] */
        uint32_t c_size = ((uint32_t)(csd[7] & 0x3F) << 16) |
                          ((uint32_t)csd[8] << 8) |
                          ((uint32_t)csd[9]);

        /* V2.0卡:扇区数 = (C_SIZE + 1) * 1024 */
        sector_cnt = (uint64_t)(c_size + 1) * 1024;
    }
    else if (csd_version == 0x00)  /* CSD V1.0 —— 标准容量卡 (≤2GB) */
    {
        /* READ_BL_LEN: 4位,位于 Byte5[3:0] */
        uint8_t read_bl_len = csd[5] & 0x0F;

        /* C_SIZE: 12位,位于 Byte6[1:0]、Byte7[7:0]、Byte8[7:6] */
        uint16_t c_size = ((uint16_t)(csd[6] & 0x03) << 10) |
                          ((uint16_t)csd[7] << 2) |
                          ((uint16_t)(csd[8] & 0xC0) >> 6);

        /* C_SIZE_MULT: 3位,位于 Byte9[1:0]、Byte10[7] */
        uint8_t c_size_mult = ((csd[9] & 0x03) << 1) |
                              ((csd[10] & 0x80) >> 7);

        /* 总字节数 = (C_SIZE+1) * 2^(C_SIZE_MULT+2) * 2^(READ_BL_LEN) */
        /* 扇区数(512B) = 总字节数 / 512 */
        uint32_t exp = c_size_mult + 2 + read_bl_len - 9;
        sector_cnt = (uint64_t)(c_size + 1) << exp;
    }
    else
    {
        /* 不支持的CSD版本 */
        sector_cnt = 0;
    }

    return sector_cnt;
}
/* ---------- 公共函数 ---------- */

/**
 * @brief 初始化SD卡,使其进入SPI模式
 * @return 0=成功, 1=失败
 */
uint8_t SD_Init(void)
{
    uint8_t ocr[4];
    uint8_t resp;
    uint32_t i;

    printf("\r\nsd init start ...\r\n");

    SD_SPI_SetSpeedLow();
    SD_SPI_CS_High();
    for (i = 0; i < 100; i++) SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);

    /* CMD0 */
    i = 0;
    do {
        resp = SD_SendCmd(CMD0, 0, 0x95);
        i++;
        if(100 == i)
        {
            SD_SPI_CS_High();
            printf("Reset card into IDLE state failed!\r\n");
            return 1;
        }
    } while (resp != R1_IDLE);

    /* CMD8 检测V2 */
    resp = SD_SendCmd(CMD8, 0x000001AA, 0x87);
    if (resp == R1_IDLE) 
    {
        for (i = 0; i < 4; i++) 
        {
            ocr[i] = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
        }
        SD_SPI_CS_High();

        printf("CMD8_C :");
        for(i=0;i<4;i++)
        {
          printf("%02X ",ocr[i]);
        }
        printf("\r\n");
        sd_card_type = SD_TYPE_V2;
    } else 
    {
        sd_card_type = SD_TYPE_V1;
        SD_SPI_CS_High();
    }

    /* ACMD41 初始化 */
    i = 0;
    do {
        resp = SD_SendAppCmd(ACMD41, 0x40000000, 0x77);
        i++;
        if (i >= 10)
        {
            SD_SPI_CS_High();
            printf("ACMD41 failed!\r\n");

            /* CMD1 MMC card initialize start*/
            i = 0;
            do {
                resp = SD_SendCmd(CMD1, 0x40000000, 0x77);
                i++;
                if (i >= 10)
                {
                    SD_SPI_CS_High();
                    printf("CMD1 failed!\r\n");
                    return 1;
                }
            } while (resp != R1_READY);
            sd_card_type = SD_TYPE_MMC;
            break;
        }
    } while (resp != R1_READY);

    /* 判断HC卡 */
    if (sd_card_type == SD_TYPE_V2) 
    {
        resp = SD_SendCmd(CMD58, 0, 0xFD);
        if (resp == R1_READY) 
        {
            for (i = 0; i < 4; i++)
            {
                ocr[i] = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            }
            SD_SPI_CS_High();

            printf("OCR_V2:");
            for(i=0;i<4;i++)
            {
              printf("%02X ",ocr[i]);
            }
            printf("\r\n");

            if (ocr[0] & 0x40) sd_card_type = SD_TYPE_HC;
        }
        SD_SPI_CS_High();
    }
    printf("sd_card_type = %d.\r\n",sd_card_type);

    /* ====== 读取CSD获取容量 ====== */
    uint8_t csd[16];
    resp = SD_SendCmd(CMD9, 0, 0xFF);
    if (resp == R1_READY) 
    {
        i = 1000;
        do {
            resp = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            i--;
        } while ((resp != 0xFE) && i);
        if (resp == 0xFE)
        {
            for (uint8_t idx = 0; idx < 16; idx++) 
            {
                csd[idx] = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            }
            SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            SD_SPI_CS_High();

            printf("CSD_Tab:");
            for(i=0;i<16;i++)
            {
              printf("%02X ",csd[i]);
            }
            printf("\r\n");
            sd_sector_count = SD_ParseCSD(csd);
        }
    }
    SD_SPI_CS_High();
    printf("sd_sector_count = %u .\r\n",(uint32_t)sd_sector_count);

    /* ====== 读取CID ====== */
    resp = SD_SendCmd(CMD10, 0, 0xFF);
    if (resp == R1_READY) 
    {
        i = 1000;
        do {
            resp = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            i--;
        } while ((resp != 0xFE) && i);
        if (resp == 0xFE)
        {
            for (uint8_t idx = 0; idx < 16; idx++) 
            {
                csd[idx] = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            }
            SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
            SD_SPI_CS_High();

            printf("CID_Tab:");
            for(i=0;i<16;i++)
            {
              printf("%02X ",csd[i]);
            }
            printf("\r\n");
        }
    }
    SD_SPI_CS_High();

    /* 设置块大小 */
    resp = SD_SendCmd(CMD16, 512, 0xFF);
    SD_SPI_CS_High();
    if (resp != R1_READY) return 1;

    printf("sd init ok.\r\n");
    SD_SPI_SetSpeedHigh();
    return 0;
}

/**
 * @brief 读取一个扇区(512字节)
 * @param sector 扇区地址(对于SDHC卡为LBA地址,SD v1为字节地址/512)
 * @param buffer 数据缓冲区(至少512字节)
 * @return 0=成功, 1=失败
 */
uint8_t SD_ReadBlock(uint32_t sector, uint8_t *buffer)
{
    uint8_t resp;
    uint32_t i;

    /* 如果是V1卡,扇区地址需转换为字节地址 */
    uint32_t addr = sector;
    if (sd_card_type != SD_TYPE_HC) 
    {
        addr = sector << 9;  /* 乘以512 */
    }

    /* 发送CMD17读单块命令 */
    resp = SD_SendCmd(CMD17, addr, 0x00);
    if (resp != R1_READY) {
        SD_SPI_CS_High();
        return 1;
    }

    /* 等待数据令牌(0xFE) */
    i = 10000;
    do {
        resp = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
        i--;
        if (i == 0) 
        {
            SD_SPI_CS_High();
            return 1;
        }
    } while (resp != 0xFE);

    /* 读取512字节数据 */
    for (i = 0; i < 512; i++) 
    {
        buffer[i] = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    }
    /* 读取2字节CRC(忽略) */
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    /* 释放片选 */
    SD_SPI_CS_High();

    /* 发送CMD12结束命令 */
    resp = SD_SendCmd(CMD12, 0, 0);
    /* 释放片选 */
    SD_SPI_CS_High();

    return 0;
}

/**
 * @brief 写入一个扇区(512字节)
 * @param sector 扇区地址
 * @param buffer 数据缓冲区(512字节)
 * @return 0=成功, 1=失败
 */
uint8_t SD_WriteBlock(uint32_t sector, const uint8_t *buffer)
{
    uint8_t resp;
    uint16_t i;

    /* 地址转换(同读操作) */
    uint32_t addr = sector;
    if (sd_card_type != SD_TYPE_HC) 
    {
        addr = sector << 9;
    }

    /* 发送CMD24写单块命令 */
    resp = SD_SendCmd(CMD24, addr, 0x00);
    if (resp != R1_READY) {
        SD_SPI_CS_High();
        return 1;
    }

    /* Card enable, Prepare to write */
    SD_SPI_CS_Low();
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    /* 发送数据起始令牌 0xFE */
    SD_SPI_ReadWriteByte(0xFE);

    /* 发送512字节数据 */
    for (i = 0; i < 512; i++) {
        SD_SPI_ReadWriteByte(buffer[i]);
    }
    /* 发送2字节CRC(SPI模式下通常忽略,填0xFF) */
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);

    /* 读取数据响应(0x05=数据接受) */
    resp = SD_SPI_ReadWriteByte(SD_DUMMY_BYTE);
    if ((resp & 0x1F) != 0x05) {
        SD_SPI_CS_High();
        return 1;
    }

    /* 等待写完成(卡忙时输出0x00) */
    if (!SD_WaitReady()) {
        SD_SPI_CS_High();
        return 1;
    }
    SD_SPI_CS_High();

    return 0;
}

/**
 * @brief 获取卡类型
 */
uint8_t SD_GetCardType(void)
{
    return sd_card_type;
}

/**
 * @brief 获取扇区总数(需通过CSD解析,此处为占位)
 */
uint64_t  SD_GetSectorCount(void)
{
    return sd_sector_count;
}

TF卡驱动部分注意:

初始化时钟 SD卡初始化时SPI时钟必须 ≤400kHz ,否则可能无法识别
片选控制 建议用独立GPIO 手动控制CS,不要使用硬件NSS自动模式
上电延时 上电后需发送至少74个时钟脉冲 让SD卡完成自检
命令CRC CMD0和CMD8等特定命令有固定CRC值,不可随意更改
速度切换 初始化完成后可提高SPI时钟,但建议不超过25MHz

可以通过下面shell命令读TF内容:

//LSY_SHELL
#include "lsy_shell_core.h"

extern uint32_t str2hex(char *str);
int cmd_tf_read(int argc, char **argv)
{
    uint32_t i,addr,size;

    addr= 0; size = 512;
    if(argc >= 2)    addr = str2hex(argv[1]);

    printf("tf_read [addr=0x%0X] [len:512].\r\n",addr);
    /* read test */
    if (SD_ReadBlock(addr >>9 , work_buffer) == 0) 
    {
        printf("Read the tf card data success. Start from 0x%08X, size is %u. The data is:\r\n", addr, size);
        printf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
        for (i = 0; i < size; i++) {
            if (i % 16 == 0) {
                printf("[%08X] ", addr + i);
            }
            printf("%02X ", work_buffer[i]);
            if (((i + 1) % 16 == 0) || i == size - 1) {
                printf("\r\n");
            }
        }
        printf("\r\n");
    } else {
        printf("Read the tf card data failed.\r\n");
    }
    return SHELL_OK;
}
LSY_SHELL_CMD_EXPORT(tf_read, cmd_tf_read, "TF card read data.");

完成TF卡驱动初始化和读写之后就可以移植到FATFS文件系统上了。

首先就是实现 diskio.c 的核心接口函数。

#include "ff_gen_drv.h"

#define     DEV_SD_CARD     0    // 为TF卡分配一个物理编号
/* 定义扇区大小,SD卡通常为512字节 */
#define SD_SECTOR_SIZE 512


/* Private function prototypes -----------------------------------------------*/
DSTATUS SD_initialize (BYTE);
DSTATUS SD_status (BYTE);
DRESULT SD_read (BYTE, BYTE*, DWORD, UINT);
#if _USE_WRITE == 1
  DRESULT SD_write (BYTE, const BYTE*, DWORD, UINT);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT SD_ioctl (BYTE, BYTE, void*);
#endif  /* _USE_IOCTL == 1 */

const Diskio_drvTypeDef  SD_Driver =
{
  SD_initialize,
  SD_status,
  SD_read,
#if  _USE_WRITE == 1
  SD_write,
#endif /* _USE_WRITE == 1 */

#if  _USE_IOCTL == 1
  SD_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/**
  * @brief  Initializes a Drive
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_initialize(
    BYTE pdrv       /* 物理驱动编号 */
)
{
    DSTATUS status = STA_NOINIT;

    switch (pdrv) {
    case DEV_SD_CARD:
        /* 调用之前实现的SD卡初始化函数 */
        /* 假设初始化成功返回0,失败返回1 */
        if (SD_Init() == 0) {
            status &= ~STA_NOINIT; // 清除“未初始化”标志
        } else {
            status = STA_NOINIT;
        }
        break;
    default:
        status = STA_NOINIT;
    }

    return status;
}
/**
  * @brief  Gets Disk Status
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_status(
    BYTE pdrv       /* 物理驱动编号 */
)
{
    DSTATUS status = STA_NOINIT;

    switch (pdrv) {
    case DEV_SD_CARD:
        /* 简单起见,我们认为只要驱动能工作,设备就是正常的 */
        /* 更严谨的做法是调用驱动中的检测函数,比如 SD_IsDetected() */
        status &= ~STA_NOINIT; 
        break;
    default:
        status = STA_NOINIT;
    }

    return status;
}

/**
  * @brief  Reads Sector(s)
  * @param  lun : not used
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT SD_read(
    BYTE pdrv,      /* 物理驱动编号 */
    BYTE *buff,     /* 存放读取数据的缓冲区指针 */
    DWORD sector,   /* 起始扇区地址 (LBA) */
    UINT count      /* 要读取的扇区数量 */
)
{
    DRESULT res = RES_OK;

    switch (pdrv) {
    case DEV_SD_CARD:
        /* 循环读取每个扇区 */
        for (UINT i = 0; i < count; i++) {
            /* 调用驱动中的读扇区函数 */
            /* 假设成功返回0,失败返回非0 */
            if (SD_ReadBlock(sector + i, buff + (i * SD_SECTOR_SIZE)) != 0) {
                res = RES_ERROR;
                break;
            }
        }
        break;
    default:
        res = RES_PARERR; // 参数错误
    }

    return res;
}

/**
  * @brief  Writes Sector(s)
  * @param  lun : not used
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT SD_write(
    BYTE pdrv,          /* 物理驱动编号 */
    const BYTE *buff,   /* 要写入数据的缓冲区指针 */
    DWORD sector,       /* 起始扇区地址 (LBA) */
    UINT count          /* 要写入的扇区数量 */
)
{
    DRESULT res = RES_OK;

    switch (pdrv) {
    case DEV_SD_CARD:
        for (UINT i = 0; i < count; i++) {
            /* 调用驱动中的写扇区函数 */
            if (SD_WriteBlock(sector + i, buff + (i * SD_SECTOR_SIZE)) != 0) {
                res = RES_ERROR;
                break;
            }
        }
        break;
    default:
        res = RES_PARERR;
    }

    return res;
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation
  * @param  lun : not used
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT SD_ioctl(
    BYTE pdrv,      /* 物理驱动编号 */
    BYTE cmd,       /* 控制命令 */
    void *buff      /* 命令相关的数据缓冲区 */
)
{
    DRESULT res = RES_OK;

    switch (pdrv) {
    case DEV_SD_CARD:
        switch (cmd) {
        case CTRL_SYNC:         /* 确保所有写操作已完成 */
            /* SPI模式无需额外操作,SD总线模式可调用等待函数 */
            res = RES_OK;
            break;

        case GET_SECTOR_SIZE:   /* 获取扇区大小 */
            *(DWORD*)buff = SD_SECTOR_SIZE;
            break;

        case GET_BLOCK_SIZE:    /* 获取擦除块大小(扇区为单位),通常与GET_SECTOR_SIZE相同 */
            *(DWORD*)buff = SD_SECTOR_SIZE;
            break;

        case GET_SECTOR_COUNT:  /* 获取总扇区数 */
            *(DWORD*)buff = (DWORD)SD_GetSectorCount();
            break;

        default:
            res = RES_PARERR;
            break;
        }
        break;
    default:
        res = RES_PARERR;
    }

    return res;
}
#endif /* _USE_IOCTL == 1 */

再就是配置 ffconf.h

ffconf.h 是FatFS的配置文件,你需要根据需求进行配置。

  • _USE_WRITE :设置为 1 以启用写操作功能。
  • _VOLUMES :设置逻辑驱动器的数量,这里设置为2,支持SPI FLASH和TF卡2个。
  • _MAX_SS :设置扇区缓冲区大小。对于SD卡,通常设为 512
  • _FS_MINIMUM :如果你只需要最基本的文件读写功能,可以设置为 12 以减小代码体积。

最后就是链接到文件系统,挂载驱动器。

  FATFS_LinkDriver(&USER_Driver, USERPath);
  FATFS_LinkDriver(&SD_Driver  , USERPath);

image.png

void fatfs_test_demo(void)
{
    FRESULT     res;
    FIL         fsrc;         /* file objects */
    UINT        br;
    const uint8_t textFileBuffer[] = "Thank you for using Development Board ^_^ \r\n";

    res = f_mount(&fs,"0:",1);
    printf("f_mount disk0 res=%d!\r\n",res);
    if(res == FR_NO_FILESYSTEM)
    {
        sfud_chip_erase((sfud_flash *)sfud_get_device_table());
        res = f_mkfs("0:",FM_FAT,4096,work_buffer,4096);
        printf("f_mkfs disk0 res=%d!\r\n",res);
        res = f_mount(&fs,"0:",1);
        printf("f_mount disk0 res2=%d!\r\n",res);
    }
    if(res == 0)
    {
        res = f_open( &fsrc , "0:/demo.txt" , FA_OPEN_APPEND | FA_WRITE | FA_READ);
        if ( res == FR_OK )
        {
            /* Write buffer to file */
            res = f_write(&fsrc, textFileBuffer, sizeof(textFileBuffer), &br);
            printf("Demo.TXT successfully created        \r\n");
        }else if ( res == FR_EXIST )
        {
            /* Write buffer to file */
            res = f_write(&fsrc, textFileBuffer, sizeof(textFileBuffer), &br);
            printf("Demo.TXT created in the disk1      \r\n");
        }else
        {
            printf("Demo.TXT created in the disk1 error %d.\r\n",res);
        }
        /*close file */
        f_close(&fsrc);

        cur_path[0]='0';
        cur_path[1]=':';
        cur_path[2]='/';
        cur_path[3]= 0;
        disk_total_size(cur_path);
    }

    res = f_mount(&fs,"1:",1);
    printf("f_mount disk1 res=%d!\r\n",res);
    if(res == FR_NO_FILESYSTEM)
    {
        printf("f_mkfs disk1 res=%d!\r\n",res);
        res = f_mount(&fs,"1:",1);
        printf("f_mount disk0 res2=%d!\r\n",res);
    }
    if(res == 0)
    {
        cur_path[0]='1';
        cur_path[1]=':';
        cur_path[2]='/';
        cur_path[3]= 0;
        disk_total_size(cur_path);
    }
}

最终编译下载之后就可以正常挂载2个存储器了。

image.png

image.png

收藏 评论1 发布时间:2026-7-24 16:17

举报

1个回答
STMWoodData 回答时间:昨天 16:41
test()

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版