MCU:STM32F429ZIT6
开发环境:STM32CubeMX+MDK5

外购了一个SPI接口的SD Card模块,想要实现SD卡存储数据的功能。

首先需要打开STM32CubeMX工具。输入开发板MCU对应型号,找到开发板对应封装的MCU型号,双击打开(图中第三)。

此时,双击完后会关闭此界面,然后打开一个新界面。

然后,我们开始基本配置。

现在我们选择一个LED作为系统LED,该步骤可以忽略,只是本人喜欢这样子。以硬件原理图的LD3为例子。

基本配置除了时钟树外,基本上已经配置好了。
现在配置时钟树

基本配置已经配置完,现在开始配置实验使用的内容。
配置USART1,重定向printf函数作为串口输出。

然后配置SPI1,作为驱动SD Card读写的接口。

然后配置文件系统,可以让文件的使用更方便。

现在配置按键,触发中断处理一些事情。

配置完成,完善工程,生成工程。

到此,STM32CubeMX工具的使用结束!可以发现在桌面已经生成了SDCard_rw工程。

使用MDK5打开SDCard_rw工程打开。点击魔法棒,勾选微库。选择对应的下载器,勾选下载完复位允许。USB线一端接开发板USB_Device,一端接PC。

现在可以开始实验了

在usart.c中重定向printf函数,并在usart.h中声明。

1 //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
2 int fputc(int ch, FILE *f)
3 {
4     HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);    
5     return (ch);
6 }

在sdcard_write工程下创建UserCode文件夹,编写drive_spisd.c和drive_spisd.h。

然后在MDK5这里的SDCard_rw工程添加一个新文件夹UserCode,装入drive_spisd.c。并在魔法棒这里加入头文件路径。

drive_spisd.c如下

/* Includes ------------------------------------------------------------------*/
#include "drive_spisd.h"
/* Private includes ----------------------------------------------------------*/
#include "spi.h"
#include "ff.h"
#include "usart.h"
/* Private typedef -----------------------------------------------------------*/

/* Private define ------------------------------------------------------------*/

/* Private macro -------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
uint8_t test;
uint8_t SD_TYPE = 0x00;
MSD_CARDINFO SD0_CardInfo;
char SD_FileName[] = "hello.txt";
/* Private function prototypes -----------------------------------------------*/
static int         SD_SendCMD(uint8_t cmd, uint32_t arg, uint8_t crc);
static uint8_t     SD_ReceiveData(uint8_t *data, uint16_t len);
static uint8_t     SD_SendBlock(uint8_t*buf, uint8_t cmd);
/* Private user code ---------------------------------------------------------*/

/**
  * @brief  SPI_CS片选
  * @note   None
  * @retval None
  */
void SPISD_CS(uint8_t p)
{
    HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, (p==0)?GPIO_PIN_SET:GPIO_PIN_RESET);
}

/**
  * @brief  发送命令(CMD0~CMD63),发完释放
  * @note   命令格式:0+传输标志(1-命令、0-响应)+CONTENT(6+32)+7CRC+1
  * @retval None
  */
static int SD_SendCMD(uint8_t cmd, uint32_t arg, uint8_t crc)
{
    uint8_t r1,retry;

    SPISD_CS(0);    //取消片选
    HAL_Delay(20);
    SPISD_CS(1);    //选通

    //SD卡的SPI通信协议规定,每个命令操作之前都需要发送至少8个时钟周期
    do
    {
        retry = SPI_ReadWrite(0xFF);
    }while(retry != 0xFF);

    SPI_ReadWrite(cmd | 0x40);
    SPI_ReadWrite(arg >> 24);
    SPI_ReadWrite(arg >> 16);
    SPI_ReadWrite(arg >> 8);
    SPI_ReadWrite(arg);
    SPI_ReadWrite(crc);

    if(cmd == CMD12)
        SPI_ReadWrite(0xFF);
    do
    {
        r1 = SPI_ReadWrite(0xFF);
    }while(r1 & 0x80);

    return r1;
}

//SD卡初始化
uint8_t SD_Init(void)
{
    uint8_t     r1,i;
    uint8_t     buff[6] = {0};
    uint16_t     retry;

    SPI_SetSpeed(SPI_BAUDRATEPRESCALER_256);
    SPISD_CS(0);
    for(retry=0;retry<10;retry++)
        SPI_ReadWrite(0xFF);

    //SD卡进入IDLE状态
    do
    {
        r1 = SD_SendCMD(CMD0 ,0, 0x95);
    }while(r1 != 0x01);

    //查看SD卡的类型
    SD_TYPE = 0;
    r1 = SD_SendCMD(CMD8, 0x1AA, 0x87);
    if(r1 == 0x01)
    {
        for(i=0;i<4;i++)
            buff[i] = SPI_ReadWrite(0xFF);                    //Get trailing return value of R7 resp
        if( buff[2]==0X01 && buff[3]==0XAA )                //卡是否支持2.7~3.6V
        {
            retry = 0XFFFE;
            do
            {
                SD_SendCMD(CMD55, 0, 0X01);                    //发送CMD55
                r1 = SD_SendCMD(CMD41, 0x40000000, 0X01);    //发送CMD41
            }while(r1&&retry--);

            if(retry && SD_SendCMD(CMD58, 0, 0X01) == 0)    //鉴别SD2.0卡版本开始
            {
                for(i=0;i<4;i++)
                    buff[i] = SPI_ReadWrite(0XFF);            //得到OCR值
                SD_TYPE = (buff[0]&0x40) ? V2HC:V2;
            }
        }else
        {
            SD_SendCMD(CMD55, 0, 0X01);                        //发送CMD55
            r1 = SD_SendCMD(CMD41, 0, 0X01);                //发送CMD41
            if(r1<=1)
            {
                SD_TYPE = V1;
                retry = 0XFFFE;
                do                                             //等待退出IDLE模式
                {
                    SD_SendCMD(CMD55, 0, 0X01);                //发送CMD55
                    r1 = SD_SendCMD(CMD41, 0, 0X01);        //发送CMD41
                }while(r1&&retry--);
            }else                                            //MMC卡不支持CMD55+CMD41识别
            {
                SD_TYPE = MMC;                                //MMC V3
                retry = 0XFFFE;
                do                                             //等待退出IDLE模式
                {
                    r1 = SD_SendCMD(CMD1, 0, 0X01);            //发送CMD1
                }while(r1&&retry--);
            }
            if( retry==0 || SD_SendCMD(CMD16, 512, 0X01)!=0 )
                SD_TYPE = ERR;                                //错误的卡
        }
    }
    SPISD_CS(0);
    SPI_SetSpeed(SPI_BAUDRATEPRESCALER_4);

    return SD_TYPE?0:1;
}

void FileSystem_Init(void)
{
    FATFS         *fs;
    DWORD         fre_clust, AvailableSize, UserSize;
    uint8_t     res;
    uint8_t     *work;
    uint16_t     TotalSpace;

    res = SD_Init();
    if(res == 1)
        printf("SD卡初始化失败! \r\n");

    res = f_mount(&USERFatFS, USERPath, 1);                        //挂载
    if(res == FR_NO_FILESYSTEM)                                    //没有文件系统,格式化
    {
        printf("没有文件系统! \r\n");

        work = malloc(_MIN_SS);
        res = f_mkfs(USERPath, FM_FAT, 0, work, _MIN_SS);        //格式化sd卡
        free(work);

        if(res == FR_OK)
        {
            res = f_mount(NULL, USERPath, 1);                     //格式化后先取消挂载
            res = f_mount(&USERFatFS, USERPath, 1);                //重新挂载
            if(res == FR_OK)
            {
                printf("SD卡已经成功挂载,可以进进行文件写入测试! \r\n");
            }
        }
        else
        {
            printf("格式化失败! \r\n");
        }
    }else if(res == FR_OK)
    {
        printf("挂载成功! \r\n");
    }else
    {
        printf("挂载失败! (%d)\r\n", res);
    }

    res = f_getfree(USERPath, &fre_clust, &fs);  /* 根目录 */
    if ( res == FR_OK )
    {
        TotalSpace = (uint16_t)(((fs->n_fatent - 2) * fs->csize ) / 2 /1024);
        AvailableSize = (uint16_t)((fre_clust * fs->csize) / 2 /1024);
        UserSize = TotalSpace - AvailableSize;
        /* Print free space in unit of MB (assuming 512 bytes/sector) */
        printf("\r\n%d MB total drive space.\r\n%ld MB available.\r\n%ld MB used.\r\n",TotalSpace, AvailableSize, UserSize);
    }
    else
    {
        printf("Get SDCard Capacity Failed (%d)\r\n", res);
    }
//    f_mount(NULL, USERPath, 1);             //取消挂载
}

/**
  * @brief  读取指定长度数据
  * @note   None
  * @retval None
  */
static uint8_t SD_ReceiveData(uint8_t *data, uint16_t len)
{
    uint8_t r1;

    SPISD_CS(1);
    do
    {
        r1 = SPI_ReadWrite(0xFF);
        HAL_Delay(100);
    }while(r1 != 0xFE);

    while(len--)
    {
        *data = SPI_ReadWrite(0xFF);
        data++;
    }
    SPI_ReadWrite(0xFF);
    SPI_ReadWrite(0xFF);

    return 0;
}

/**
  * @brief  向SD卡写入一个数据包(512字节)的内容
  * @note   None
  * @retval None
  */
static uint8_t SD_SendBlock(uint8_t*buf, uint8_t cmd)
{
    uint8_t     r1;
    uint16_t     t;

    do{
        r1 = SPI_ReadWrite(0xFF);
    }while(r1!=0xFF);

    SPI_ReadWrite(cmd);
    if(cmd != 0XFD)                    //不是结束指令
    {
        for(t=0; t<512; t++)
            SPI_ReadWrite(buf[t]);    //提高速度,减少函数传参时间
        SPI_ReadWrite(0xFF);        //忽略crc
        SPI_ReadWrite(0xFF);
        t = SPI_ReadWrite(0xFF);    //接收响应
        if( (t&0x1F) != 0x05 )
            return 2;                //响应错误
    }

    return 0;                        //写入成功
}

/**
  * @brief  CSD,卡的操作条件信息,128bit
  * @note   None
  * @retval None
  */
uint8_t SD_GetCSD(uint8_t *csd_data)
{
    uint8_t r1;

    r1 = SD_SendCMD(CMD9, 0, 0x01);            //读取CSD寄存器
    if(r1 == 0x00)
        r1 = SD_ReceiveData(csd_data, 16);    //接收16个字节的数据
    SPISD_CS(0);                            //取消片选

    return r1?1:0;
}

/**
  * @brief  CID,卡识别号,128bit
  * @note   None
  * @retval None
  */
uint8_t SD_GetCID(uint8_t *cid_data)
{
    uint8_t    r1;

    r1 = SD_SendCMD(CMD10, 0, 0x01);         //读取CID寄存器
    if(r1==0x00)
        r1 = SD_ReceiveData(cid_data, 16);    //接收16个字节的数据
    SPISD_CS(0);                            //取消片选

    return r1?1:0;
}

//获取SD卡的总扇区数
uint32_t SD_GetSectorCount(void)
{
    uint8_t     n;
    uint8_t     csd[16];
    uint16_t     csize;
    uint32_t     Capacity;

    if(SD_GetCSD(csd) != 0)         //取CSD信息,如果期间出错,返回0
        return 0;

    if( (csd[0]&0xC0) == 0x40 )         //如果为SDHC卡,按照下面方式计算。V2.00的卡
    {
        csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
        Capacity = (uint32_t)csize << 10;                //得到扇区数
    }else                            //V1.xx的卡
    {
        n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
        csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
        Capacity = (uint32_t)csize << (n - 9);            //得到扇区数
    }

    return Capacity;
}

int MSD0_GetCardInfo(PMSD_CARDINFO SD0_CardInfo)
{
    uint8_t r1;
    uint8_t CSD_Tab[16], CID_Tab[16];

    /* Send CMD9, Read CSD */
    r1 = SD_SendCMD(CMD9, 0, 0xFF);
    if(r1 != 0x00)
        return r1;
    if(SD_ReceiveData(CSD_Tab, 16))
        return 1;

    /* Send CMD10, Read CID */
    r1 = SD_SendCMD(CMD10, 0, 0xFF);
    if(r1 != 0x00)
        return r1;
    if(SD_ReceiveData(CID_Tab, 16))
        return 2;

    /* Byte 0 */
    SD0_CardInfo->CSD.CSDStruct = (CSD_Tab[0] & 0xC0) >> 6;
    SD0_CardInfo->CSD.SysSpecVersion = (CSD_Tab[0] & 0x3C) >> 2;
    SD0_CardInfo->CSD.Reserved1 = CSD_Tab[0] & 0x03;
    /* Byte 1 */
    SD0_CardInfo->CSD.TAAC = CSD_Tab[1] ;
    /* Byte 2 */
    SD0_CardInfo->CSD.NSAC = CSD_Tab[2];
    /* Byte 3 */
    SD0_CardInfo->CSD.MaxBusClkFrec = CSD_Tab[3];
    /* Byte 4 */
    SD0_CardInfo->CSD.CardComdClasses = CSD_Tab[4] << 4;
    /* Byte 5 */
    SD0_CardInfo->CSD.CardComdClasses |= (CSD_Tab[5] & 0xF0) >> 4;
    SD0_CardInfo->CSD.RdBlockLen = CSD_Tab[5] & 0x0F;
    /* Byte 6 */
    SD0_CardInfo->CSD.PartBlockRead = (CSD_Tab[6] & 0x80) >> 7;
    SD0_CardInfo->CSD.WrBlockMisalign = (CSD_Tab[6] & 0x40) >> 6;
    SD0_CardInfo->CSD.RdBlockMisalign = (CSD_Tab[6] & 0x20) >> 5;
    SD0_CardInfo->CSD.DSRImpl = (CSD_Tab[6] & 0x10) >> 4;
    SD0_CardInfo->CSD.Reserved2 = 0; /* Reserved */
    SD0_CardInfo->CSD.DeviceSize = (CSD_Tab[6] & 0x03) << 10;
    /* Byte 7 */
    SD0_CardInfo->CSD.DeviceSize |= (CSD_Tab[7]) << 2;
    /* Byte 8 */
    SD0_CardInfo->CSD.DeviceSize |= (CSD_Tab[8] & 0xC0) >> 6;
    SD0_CardInfo->CSD.MaxRdCurrentVDDMin = (CSD_Tab[8] & 0x38) >> 3;
    SD0_CardInfo->CSD.MaxRdCurrentVDDMax = (CSD_Tab[8] & 0x07);
    /* Byte 9 */
    SD0_CardInfo->CSD.MaxWrCurrentVDDMin = (CSD_Tab[9] & 0xE0) >> 5;
    SD0_CardInfo->CSD.MaxWrCurrentVDDMax = (CSD_Tab[9] & 0x1C) >> 2;
    SD0_CardInfo->CSD.DeviceSizeMul = (CSD_Tab[9] & 0x03) << 1;
    /* Byte 10 */
    SD0_CardInfo->CSD.DeviceSizeMul |= (CSD_Tab[10] & 0x80) >> 7;
    SD0_CardInfo->CSD.EraseGrSize = (CSD_Tab[10] & 0x7C) >> 2;
    SD0_CardInfo->CSD.EraseGrMul = (CSD_Tab[10] & 0x03) << 3;
    /* Byte 11 */
    SD0_CardInfo->CSD.EraseGrMul |= (CSD_Tab[11] & 0xE0) >> 5;
    SD0_CardInfo->CSD.WrProtectGrSize = (CSD_Tab[11] & 0x1F);
    /* Byte 12 */
    SD0_CardInfo->CSD.WrProtectGrEnable = (CSD_Tab[12] & 0x80) >> 7;
    SD0_CardInfo->CSD.ManDeflECC = (CSD_Tab[12] & 0x60) >> 5;
    SD0_CardInfo->CSD.WrSpeedFact = (CSD_Tab[12] & 0x1C) >> 2;
    SD0_CardInfo->CSD.MaxWrBlockLen = (CSD_Tab[12] & 0x03) << 2;
    /* Byte 13 */
    SD0_CardInfo->CSD.MaxWrBlockLen |= (CSD_Tab[13] & 0xc0) >> 6;
    SD0_CardInfo->CSD.WriteBlockPaPartial = (CSD_Tab[13] & 0x20) >> 5;
    SD0_CardInfo->CSD.Reserved3 = 0;
    SD0_CardInfo->CSD.ContentProtectAppli = (CSD_Tab[13] & 0x01);
    /* Byte 14 */
    SD0_CardInfo->CSD.FileFormatGrouop = (CSD_Tab[14] & 0x80) >> 7;
    SD0_CardInfo->CSD.CopyFlag = (CSD_Tab[14] & 0x40) >> 6;
    SD0_CardInfo->CSD.PermWrProtect = (CSD_Tab[14] & 0x20) >> 5;
    SD0_CardInfo->CSD.TempWrProtect = (CSD_Tab[14] & 0x10) >> 4;
    SD0_CardInfo->CSD.FileFormat = (CSD_Tab[14] & 0x0C) >> 2;
    SD0_CardInfo->CSD.ECC = (CSD_Tab[14] & 0x03);
    /* Byte 15 */
    SD0_CardInfo->CSD.CSD_CRC = (CSD_Tab[15] & 0xFE) >> 1;
    SD0_CardInfo->CSD.Reserved4 = 1;

    if(SD0_CardInfo->CardType == V2HC)
    {
        /* Byte 7 */
        SD0_CardInfo->CSD.DeviceSize = (uint16_t)(CSD_Tab[8]) *256;
        /* Byte 8 */
        SD0_CardInfo->CSD.DeviceSize += CSD_Tab[9] ;
    }

    SD0_CardInfo->Capacity = SD0_CardInfo->CSD.DeviceSize * MSD_BLOCKSIZE * 1024;
    SD0_CardInfo->BlockSize = MSD_BLOCKSIZE;

    /* Byte 0 */
    SD0_CardInfo->CID.ManufacturerID = CID_Tab[0];
    /* Byte 1 */
    SD0_CardInfo->CID.OEM_AppliID = CID_Tab[1] << 8;
    /* Byte 2 */
    SD0_CardInfo->CID.OEM_AppliID |= CID_Tab[2];
    /* Byte 3 */
    SD0_CardInfo->CID.ProdName1 = CID_Tab[3] << 24;
    /* Byte 4 */
    SD0_CardInfo->CID.ProdName1 |= CID_Tab[4] << 16;
    /* Byte 5 */
    SD0_CardInfo->CID.ProdName1 |= CID_Tab[5] << 8;
    /* Byte 6 */
    SD0_CardInfo->CID.ProdName1 |= CID_Tab[6];
    /* Byte 7 */
    SD0_CardInfo->CID.ProdName2 = CID_Tab[7];
    /* Byte 8 */
    SD0_CardInfo->CID.ProdRev = CID_Tab[8];
    /* Byte 9 */
    SD0_CardInfo->CID.ProdSN = CID_Tab[9] << 24;
    /* Byte 10 */
    SD0_CardInfo->CID.ProdSN |= CID_Tab[10] << 16;
    /* Byte 11 */
    SD0_CardInfo->CID.ProdSN |= CID_Tab[11] << 8;
    /* Byte 12 */
    SD0_CardInfo->CID.ProdSN |= CID_Tab[12];
    /* Byte 13 */
    SD0_CardInfo->CID.Reserved1 |= (CID_Tab[13] & 0xF0) >> 4;
    /* Byte 14 */
    SD0_CardInfo->CID.ManufactDate = (CID_Tab[13] & 0x0F) << 8;
    /* Byte 15 */
    SD0_CardInfo->CID.ManufactDate |= CID_Tab[14];
    /* Byte 16 */
    SD0_CardInfo->CID.CID_CRC = (CID_Tab[15] & 0xFE) >> 1;
    SD0_CardInfo->CID.Reserved2 = 1;

    return 0;
}


//写SD卡
//buf:数据缓存区
//sector:起始扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
uint8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint8_t cnt)
{
    uint8_t r1;

    if(SD_TYPE != V2HC)
        sector *= 512;                            //转换为字节地址
    if(cnt == 1)
    {
        r1 = SD_SendCMD(CMD24, sector, 0x01);    //读命令
        if(r1 == 0)                                //指令发送成功
            r1=SD_SendBlock(buf, 0xFE);            //写512个字节
    }else
    {
        if(SD_TYPE != MMC)
        {
            SD_SendCMD(CMD55, 0, 0x01);
            SD_SendCMD(CMD23, cnt, 0x01);        //发送指令
        }
         r1 = SD_SendCMD(CMD25, sector, 0x01);    //连续读命令
        if(r1 == 0)
        {
            do
            {
                r1 = SD_SendBlock(buf,0xFC);    //接收512个字节
                buf += 512;
            }while(--cnt && r1==0);
            r1 = SD_SendBlock(0,0xFD);            //接收512个字节
        }
    }
    SPISD_CS(0);                                //取消片选

    return r1;
}

//读SD卡
//buf:数据缓存区
//sector:扇区
//cnt:扇区数
//返回值:0,ok;其他,失败.
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt)
{
    uint8_t r1;

    if(SD_TYPE != V2HC)
        sector <<= 9;                            //转换为字节地址
    if(cnt == 1)
    {
        r1 = SD_SendCMD(CMD17, sector, 0x01);    //读命令
        if(r1 == 0)
            r1 = SD_ReceiveData(buf, 512);        //接收512个字节
    }else
    {
        r1 = SD_SendCMD(CMD18, sector, 0x01);    //连续读命令
        do
        {
            r1 = SD_ReceiveData(buf, 512);        //接收512个字节
            buf += 512;
        }while(--cnt && r1==0);
        SD_SendCMD(CMD12, 0, 0x01);                //发送停止命令
    }
    SPISD_CS(0);                                //取消片选

    return r1;
}

uint8_t SPI_ReadWrite(uint8_t Txdata)
{
    uint8_t Rxdata;
    HAL_SPI_TransmitReceive(&hspi1, &Txdata, &Rxdata, 1, 100);
    return Rxdata;
}

//SPI1波特率设置
void SPI_SetSpeed(uint8_t speed)
{
    hspi1.Init.BaudRatePrescaler = speed;
    if (HAL_SPI_Init(&hspi1) != HAL_OK)
    {
        Error_Handler();
    }
}
///////////////////////////END//////////////////////////////////////

然后在文件系统文件夹里修改内容。

user_diskio.c如下

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * @file    user_diskio.c
  * @brief   This file includes a diskio driver skeleton to be completed by the user.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
 /* USER CODE END Header */

#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/*
 * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
 * To be suppressed in the future.
 * Kept to ensure backward compatibility with previous CubeMx versions when
 * migrating projects.
 * User code previously added there should be copied in the new user sections before
 * the section contents can be deleted.
 */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif

/* USER CODE BEGIN DECL */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"

/* Private typedef -----------------------------------------------------------*/
#include "drive_spisd.h"
/* Private define ------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  USER_Driver =
{
  USER_initialize,
  USER_status,
  USER_read,
#if  _USE_WRITE
  USER_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1
  USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_initialize (
    BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
    uint8_t res;
    res = SD_Init();
    if(res)    //STM32 SPI的bug,在sd卡操作失败的时候如果不执行下面的语句,可能导致SPI读写异常
    {
        SPI_SetSpeed(SPI_BAUDRATEPRESCALER_256);
        SPI_ReadWrite(0xff);    //提供额外的8个时钟
        SPI_SetSpeed(SPI_BAUDRATEPRESCALER_4);
    }
    if(res)
        return STA_NOINIT;
    else
        return RES_OK;
  /* USER CODE END INIT */
}

/**
  * @brief  Gets Disk Status
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_status (
    BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */
    switch (pdrv)
    {
        case 0 :
            return RES_OK;
        case 1 :
            return RES_OK;
        case 2 :
            return RES_OK;
        default:
            return STA_NOINIT;
    }
  /* USER CODE END STATUS */
}

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @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 USER_read (
    BYTE pdrv,      /* Physical drive nmuber to identify the drive */
    BYTE *buff,     /* Data buffer to store read data */
    DWORD sector,   /* Sector address in LBA */
    UINT count      /* Number of sectors to read */
)
{
  /* USER CODE BEGIN READ */
    uint8_t res;
    if( !count )
    {
        return RES_PARERR;  /* count不能等于0,否则返回参数错误 */
    }
    switch (pdrv)
    {
        case 0:
            res = SD_ReadDisk(buff,sector,count);
            if(res == 0)
                return RES_OK;
            else
                return RES_ERROR;
        default:
            return RES_ERROR;
    }
  /* USER CODE END READ */
}

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @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 USER_write (
    BYTE pdrv,          /* Physical drive nmuber to identify the drive */
    const BYTE *buff,   /* Data to be written */
    DWORD sector,       /* Sector address in LBA */
    UINT count          /* Number of sectors to write */
)
{
  /* USER CODE BEGIN WRITE */
  /* USER CODE HERE */
    uint8_t  res;
    if( !count )
        return RES_PARERR;  /* count不能等于0,否则返回参数错误 */
    switch (pdrv)
    {
        case 0:
            res=SD_WriteDisk((uint8_t *)buff,sector,count);
                if(res == 0)
                    return RES_OK;
                else
                    return RES_ERROR;
        default:
            return RES_ERROR;
    }
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE cmd,       /* Control code */
    void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
    DRESULT res;
    switch(cmd)
    {
        case CTRL_SYNC:
            SPISD_CS(1);
            do{
                HAL_Delay(20);
            }while(SPI_ReadWrite(0xFF)!=0xFF);
            res=RES_OK;
            SPISD_CS(0);
            break;
        case GET_SECTOR_SIZE:
            *(WORD*)buff = 512;
            res = RES_OK;
            break;
        case GET_BLOCK_SIZE:
            *(WORD*)buff = 8;
            res = RES_OK;
            break;
        case GET_SECTOR_COUNT:
            *(DWORD*)buff = SD_GetSectorCount();
            res = RES_OK;
            break;
        default:
            res = RES_PARERR;
            break;
    }
    return res;
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "drive_spisd.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint8_t     status = 0;
uint8_t        writeBuf[] = "demo program forever no bug!!!\r\n";
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
    UINT     Bw;
    FIL     file;
    uint16_t cb_task = 0;
    uint8_t res1 = 0, res2 = 0;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
    FileSystem_Init();    //初始化文件系统

    status = 1;
    res1 = f_open(&file, "sdRW1.txt", FA_OPEN_ALWAYS | FA_WRITE);
    if((res1 & FR_DENIED) == FR_DENIED)
        printf("卡存储已满,写入失败! \r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    if(status == 1)
    {
        if(res1 == FR_OK)
        {
            f_lseek(&file, f_size(&file));                                //确保写入不会覆盖之前的数据
            res2 = f_write(&file, writeBuf, sizeof(writeBuf), &Bw);        //写数据到SD卡
            if(res2 != FR_OK)
            {
                printf("文件写入失败! \r\n");
                HAL_GPIO_WritePin(User_led_GPIO_Port, User_led_Pin, GPIO_PIN_RESET);
            }else
            {
                HAL_GPIO_WritePin(User_led_GPIO_Port, User_led_Pin, GPIO_PIN_SET);
            }
        }
        else
        {
            printf("打开文件失败! %d\r\n",res1);
        }
        if(++cb_task%4096==0)
            f_sync(&file);
    }else if(status == 2)
    {
        f_close(&file);
        f_mount(NULL, USERPath, 1);             //取消挂载
        HAL_GPIO_WritePin(User_led_GPIO_Port, User_led_Pin, GPIO_PIN_RESET);
        status = 0;
    }
    HAL_Delay(10);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if(GPIO_Pin == User_Key_Pin)
    {
        status = 2;
    }
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

实测是可以的,但是写速度并没有网上说的1M/s那么快,也有可能是我引线的问题。

时代越来越好,开发效率越来越高,希望能帮助到你!!!

还有就是,开源万岁。