【STM32】基于SPI协议读写SD,详解!

0 前言

  因为项目需要,使用stm32读写sd卡,这一块网上的资料很多,但是比较杂乱。有些是不能跑,有些是代码可以跑,但是相关的注释或者配置方法、流程不够清晰明确,于是花了几天时间,研究了几个成功案例之后,总结出一个相对明确的流程。【基于STM32F103C8T6

网上有各种流传的例程,经过测试确实可以用,但是魔改得有点多,个人觉得不是很便于理解,所以想着能不能从最开始的FATFS包来自己手动移植一个,最好是这个流程完全可复制,操作也非常简单,就像一个插件一样,基本实现模块化。

1 SD卡的种类和简介

  既然要读写SD卡,那首先要对SD卡的底层有一定的了解,这样才能够真正理解后面的代码。

1.1 SD卡的种类

  首先需要明确的是,SD卡指的是那种大的卡,一般用在相机里面,如下图所示:

在这里插入图片描述在这里插入图片描述

而这种卡:

在这里插入图片描述在这里插入图片描述
一般叫microSD卡TF卡,二者其实相差不大,只是引脚略微不同,其实读写都是一样的,也可以考虑买一个TF卡转SD卡的卡套,来适应两种接口。

  相比于这个SD卡的名字,另一个SD卡的标准显得更加重要。所谓的标准,差别主要体现在容量上面,这个需要在使用前明确。目前仍然有很多老年手机不支持大容量的TF卡,其本质就是因为不支持更高的标准。常见的SD卡标准如下图所示。
在这里插入图片描述
这个标准SD卡和TF卡是一样的,只是名字不同。

参考链接

  另外,根据这个链接, 实际上SD的通信协议也有多个版本,最早支持的版本是1.x,在SDHC之后,基本都是使用2.0版本,来兼容FAT32格式(原来都是FAT和FAT16),这两个协议的区别在驱动方面主要体现在指令上(2.0版本的指令更多,且兼容1.x版本的指令),这个后面有相关介绍,先埋个伏笔。

1.2 SD卡的整体结构

  理解了SD卡的种类,再来看看结构,主要是以下这张图
在这里插入图片描述
简单来说,就是除了存储单元外,还有好几个寄存器用于存放卡相关的信息,这些信息可以通过一些特定的指令读写。

1.3 SD卡运行机制——指令和响应

  SD卡的核心就是存储,那外部的主机如何对这个进行读写呢?就是通过指令。主机发送一条指令,然后SD卡会发送响应,让主机知道指令执行情况。
  每一条指令都是6个字节(48bit),其结构如下所示:
在这里插入图片描述

其中,Command占6位,所以一共有64个指令,从0-63,依次叫CMD0,CMD1,。。。CMD63,但是因为一次性是发送一个字节,也就是8位,所以会加上前面的两位,即0x40+CMDx才是指令。
  紧接着的是32位指令执行的参数,一般是存储地址或者寄存器值等,不是所有指令都有参数,对于没有参数的指令,直接传0即可。
  最后是校验值,这里采用的是循环校验,计算有点复杂,这个其实在后续的代码中,都是把部分常用的指令对应的校验计算出来给他传过去,并没有现场计算。

  指令发出之后,主机要等待SD卡的响应,其响应有很多类型,长度也各自不一样。短的响应只有一个字节,长的响应可以有多个字节。大部分的指令都是R1类型,即只有一个字节,R2表示响应有两个字节,还有一种类型是R1b,即在R1的基础上,后面紧跟着busy信号,可能有多个字节,一般不怎么使用。R1响应的结构各个位都有单独的含义,如下图所示。
在这里插入图片描述
可以看到,第6-1位都是错误,为1表示错误(“有效”),为0表示没有错误;第0位表示卡是否处于空闲状态,一般是发送进入IDLE指令(CMD0)之后会响应,也就是0x01。

  以上就是SD卡使用的基本讨论,即写入一个6字节的指令,然后读取响应的1-2个字节,并判断指令执行状态。时序图如下所示。
在这里插入图片描述
  接下来就是重点:SD卡数据的读写。和上面一样,读写数据之前,需要先发送一个指令,然后再读入或写入数据。对应的指令主要是这几个:
在这里插入图片描述
分别有读单块、读多块、写单块、写多块四个指令。其中,读写多块貌似需要使用到ACMD指令,所以用得比较少,可以通过多次调用读写单块的函数达到读写多块的目的。【一般SD卡一块(block)是512 Byte】
  根据官方的手册,读数据的流程大概是这样:
在这里插入图片描述
即先发送读的指令,然后等待sd卡响应指令后(根据上图,读单块和多块的响应都是R1类型),再读取数据块。
类似地,写指令的操作流程时序如下所示。
在这里插入图片描述
和上面不一样的是,在数据写入完毕后,还会有一个响应(Data Response),表示数据写入的情况,由SD卡传输给主机,是一个字节,其格式如下所示

在这里插入图片描述

  但是,这个时序图中并没有对“Data Block”部分进行展开叙述,但其实其内部结构同样重要,这里根据官方的描述和可行代码自行绘制了这张图:
在这里插入图片描述
其中,First Byte类似于一个启动符号,告知后面有数据来了,然后是一个block的数据,一般是512字节,最后是两位校验码。
  对于读数据,首先要读第一个字节,判断是不是0xFE,如果是,表示后面是数据,要把后面的数据给收了,收完512字节之后,最后的两位校验码可以忽略;对于写数据,是在发完写指令之后,手动写入0xFE,作为写数据的第一个字节,然后再写入512字节数据,最后两位校验码一般直接传0xFF即可。

2 SD卡的通信总线

  上面介绍的是SD卡的运行机制,从上面的结构图可以看出,这个运行机制到MCU控制端还需要一个通信协议,来约定这些数据该如何传输。常见的SD卡通信协议主要有两种:SPI模式SD模式(SDIO),其中两种通信协议下的引脚定义如下图所示。
在这里插入图片描述

在SPI协议中,SD卡扮演的角色是Slave,即从机,故其中MOSI和MISO中“M”指的控制数据读写的芯片,如MCU等;“S”从机是指SD卡。

参考链接

关于引脚的理解:以SPI为例,MCU对SD卡的控制指令都是通过CMD引脚串行传输的,所以CMD引脚是MOSI;而SD卡返回的数据是通过D0传输,所以D0是MISO。而SDIO数据传输可以选定多个引脚,常见的有只使用D0,和使用D0~D3四个引脚,并行传输。

2.1 SDIO

  在STM32F10x系列型号中,只有大容量的芯片才支持这个协议,没有实践过,这里只放一个网上的教程:

值得一提的是,不同协议其实只是传输方式不一样,底层的那些逻辑是差不多的,当然有些指令SPI协议不支持,只支持SDIO协议。

2.2 SPI

  • 概述
      SPI是四线协议:SCK(同步时钟),MOSI(主机到从机的数据),MISO(从机到主机的数据),CS(片选)。和IIC类似,也是一个串行协议,因为有时钟信号,所以是一个同步传输的协议(UART是异步协议)。但是,值得一提的是,因为收发数据是两根线,所以SPI是全双工协议,而IIC因为只有SCK和SDA,所以是半双工协议。

  • 运行模式
      SPI比较特殊的地方在于,它的电平和采样边沿可以额外设置,也就是设置不同的传输模式,这个设置由两个变量来确定:CPOL(Clock Polarity)、CPHA(Clock Phase),这两个变量分别可以设置0或1,因此组合起来有四种模式:

    • 0 0 CLK空闲时为低电平,CLK上升沿(第一个边沿)采样数据。
    • 0 1 CLK空闲时为低电平,CLK下降沿(第二个边沿)采样数据。
    • 1 0 CLK空闲时为高电平,CLK下降沿(第一个边沿)采样数据。
    • 1 1 CLK空闲时为高电平,CLK上升沿(第二个边沿)采样数据。
  • 数据同步
      由于SPI是全双工协议,且时钟只能是主设备发出,所以在主设备看来,不管是发送还是接收数据,都必须提供时钟,加上数据发送和接收是分开的两根线,所以数据在发送时也需要接收,或者说,接收时因为需要时钟,所以其实接收缓冲区也会新增数据,只是用不用的问题。
      那问题来了,如果我要收一个数据,必须发一个数据,那对方因为该数据误操作了怎么办?所以在接收数据时,要发送一个对从机设备来说无效的数据,也就是所谓的dummy data,这样就不会误响应了。

  • 代码配置
      网上有很多流传的软件SPI,即在理解SPI协议的基础上,使用IO口实现这个时序,但是这样一方面是代码比较麻烦,另外就是时钟配置难以掌握,所以这种只适用于硬件SPI没有或者被用完的情况,在有硬件SPI外设的前提下,还是用硬件比较方便。
      这里以一个标准库下的SPI外设初始化为例理解一下SPI配置的方法:

    void SD_SPI_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
    	
    	//使能时钟——宏定义实现
        ENABLE_SD_SPI_GPIO_CLK();
        ENABLE_SD_SPI_CLK();
    	
    	//GPIO初始化
        GPIO_InitStructure.GPIO_Pin = SD_SPI_MOSI_PIN | SD_SPI_SCK_PIN;   //MOSI & SCK: AFIO,Output
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(SD_SPI_GPIO_PORT, &GPIO_InitStructure);
    
        GPIO_InitStructure.GPIO_Pin = SD_SPI_MISO_PIN;                    //MISO: Input
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //输入
        GPIO_Init(SD_SPI_GPIO_PORT, &GPIO_InitStructure);
    
    	//SPI外设初始化
        SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
        SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
        SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
        SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//选择了串行时钟的稳态:时钟悬空高
        SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//数据捕获于第二个时钟沿
        SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
        SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//定义波特率预分频的值:波特率预分频值为256
        SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
        SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
        SPI_Init(SD_SPI, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
    
        SPI_Cmd(SD_SPI, ENABLE); //使能SPI外设
    
        SD_SPI_ReadWriteByte(0xff);//启动传输
    }
    

    重点是GPIO口输出和输入要分别配置。

3 硬件连接

  SD卡电路设计如下图所示,在画电路板时,记得在几个sd卡的引脚上加上上拉电阻:

在这里插入图片描述

CD引脚全称是Card Detect,用于检测卡是否插入,在一些开发板的原理图中有类似的做法,但是软件其实也可以判断出来,所以必要性不强

4 代码实践【重点】

  在使用SD卡时,建议在充分理解上述展示的SD卡运行原理后先实现存储的访问,比如先写入一段,然后再去读取,串口输出读取的内容,对比一下是否一致。然后再考虑加上FATFS,实现基本的读写文件功能。
  很显然,我其实并没有按照这个流程学习,而是先找了网上的一个可运行的代码(已经带了FATFS),然后在此基础上不断尝试新的操作,在这个尝试的过程中对SD卡运行原理有了比较深刻的认识。

  言归正传,如果以实用为主,建议直接使用HAL库,如果愿意折腾,可以自己尝试在标准库实现,建议在HAL库的基础上再去移植标准库。由于这两个步骤我都实践了一遍,后文都有介绍。

参考链接:

4.1 HAL库移植

  这部分内容基本参考自上面的教程,只做了一些小的修改,让这个部分集成度更高。

  • 首先先设置一些系统参数,不设置其实问题也不大,但是设置全面,不留风险是编程开发的一个好习惯:
    在这里插入图片描述

    在这里插入图片描述

  • 然后使能SPI外设

    在这里插入图片描述
    这里简单介绍一下NSS,所谓硬件NSS类似于串口的硬件流控一样,即通过实际的引脚来实现片选,这样就可以直接调用SPI的函数来进行控制,而所谓软件(即下面 NSS Signal Type: Software)即是额外再初始化一个引脚来控制。

    这里其实个人觉得两者是差不多的,只是硬件是芯片指定的引脚,而软件则可以随便指定,相对自由一些。代码上其实差别不大,只是一个调SPI库的函数,一个调GPIO库的函数。但是网上相关的代码基本都是使用软件形式,所以这里也跟风一下。

  • 然后再添加FATFS,这里只改动两个设置:

    • USE_LFN:Enable with static working buffer on the BSS
    • MAX_SS:4096

    如下图所示
    在这里插入图片描述

  • 项目配置那块,需要把堆栈加大

    在这里插入图片描述
    分文件显示,模块化更容易理解:
    在这里插入图片描述

    私以为将不同外设分为不同文件是一个很好的习惯

最后,生成代码即可,代码方面主要修改3个文件:

fat_sd_card.c【额外添加的一个文件】

#define TRUE  1
#define FALSE 0
#define bool BYTE

#include "fatfs_sd_card.h"

static volatile DSTATUS Stat = STA_NOINIT;  /* Disk Status */
static uint8_t CardType;                    /* Type 0:MMC, 1:SDC, 2:Block addressing */
static uint8_t PowerFlag = 0;               /* Power flag */

/***************************************
 * SPI functions
 **************************************/

/* slave select */
static void SELECT(void)
{
    HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_RESET);
    HAL_Delay(1);
}

/* slave deselect */
static void DESELECT(void)
{
    HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_SET);
    HAL_Delay(1);
}

/* SPI transmit a byte */
static void SPI_TxByte(uint8_t data)
{
    while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
    HAL_SPI_Transmit(HSPI_SDCARD, &data, 1, SPI_TIMEOUT);
}

/* SPI transmit buffer */
static void SPI_TxBuffer(uint8_t* buffer, uint16_t len)
{
    while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
    HAL_SPI_Transmit(HSPI_SDCARD, buffer, len, SPI_TIMEOUT);
}

/* SPI receive a byte */
static uint8_t SPI_RxByte(void)
{
    uint8_t dummy, data;
    dummy = 0xFF;

    while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
    HAL_SPI_TransmitReceive(HSPI_SDCARD, &dummy, &data, 1, SPI_TIMEOUT);

    return data;
}

/* SPI receive a byte via pointer */
static void SPI_RxBytePtr(uint8_t* buff)
{
    *buff = SPI_RxByte();
}

/***************************************
 * SD functions
 **************************************/

/* wait SD ready */
static uint8_t SD_ReadyWait(void)
{
    uint8_t res;

    /* timeout 500ms */
    int32_t Timer2 = 0xffffff;

    /* if SD goes ready, receives 0xFF */
    do
    {
        res = SPI_RxByte();
        Timer2--;
    }
    while((res != 0xFF) && Timer2 > 0);

    return res;
}

/* power on */
static void SD_PowerOn(void)
{
    uint8_t args[6];
    uint32_t cnt = 0x1FFF;

    /* transmit bytes to wake up */
    DESELECT();
    for(int i = 0; i < 10; i++)
    {
        SPI_TxByte(0xFF);
    }

    /* slave select */
    SELECT();

    /* make idle state */
    args[0] = CMD0;   /* CMD0:GO_IDLE_STATE */
    args[1] = 0;
    args[2] = 0;
    args[3] = 0;
    args[4] = 0;
    args[5] = 0x95;   /* CRC */

    SPI_TxBuffer(args, sizeof(args));

    /* wait response */
    while((SPI_RxByte() != 0x01) && cnt)
    {
        cnt--;
    }

    DESELECT();
    SPI_TxByte(0XFF);

    PowerFlag = 1;
}

/* power off */
static void SD_PowerOff(void)
{
    PowerFlag = 0;
}

/* check power flag */
static uint8_t SD_CheckPower(void)
{
    return PowerFlag;
}

/* receive data block */
static bool SD_RxDataBlock(BYTE* buff, UINT len)
{
    uint8_t token;

    /* timeout 200ms */
    int32_t Timer1 = 0xffffff;

    /* loop until receive a response or timeout */
    do
    {
        token = SPI_RxByte();
        Timer1--;
    }
    while((token == 0xFF) && Timer1 > 0);

    /* invalid response */
    if(token != 0xFE) return FALSE;

    /* receive data */
    do
    {
        SPI_RxBytePtr(buff++);
    }
    while(len--);

    /* discard CRC */
    SPI_RxByte();
    SPI_RxByte();

    return TRUE;
}

/* transmit data block */
#if _USE_WRITE == 1
static bool SD_TxDataBlock(const uint8_t* buff, BYTE token)
{
    uint8_t resp;
    uint8_t i = 0;

    /* wait SD ready */
    if(SD_ReadyWait() != 0xFF) return FALSE;

    /* transmit token */
    SPI_TxByte(token);

    /* if it's not STOP token, transmit data */
    if(token != 0xFD)
    {
        SPI_TxBuffer((uint8_t*)buff, 512);

        /* discard CRC */
        SPI_RxByte();
        SPI_RxByte();

        /* receive response */
        while(i <= 64)
        {
            resp = SPI_RxByte();

            /* transmit 0x05 accepted */
            if((resp & 0x1F) == 0x05) break;
            i++;
        }

        /* recv buffer clear */
        while(SPI_RxByte() == 0);
    }

    /* transmit 0x05 accepted */
    if((resp & 0x1F) == 0x05) return TRUE;

    return FALSE;
}
#endif /* _USE_WRITE */

/* transmit command */
static BYTE SD_SendCmd(BYTE cmd, uint32_t arg)
{
    uint8_t crc, res;

    /* wait SD ready */
    if(SD_ReadyWait() != 0xFF) return 0xFF;

    /* transmit command */
    SPI_TxByte(cmd);          /* Command */
    SPI_TxByte((uint8_t)(arg >> 24));   /* Argument[31..24] */
    SPI_TxByte((uint8_t)(arg >> 16));   /* Argument[23..16] */
    SPI_TxByte((uint8_t)(arg >> 8));  /* Argument[15..8] */
    SPI_TxByte((uint8_t)arg);       /* Argument[7..0] */

    /* prepare CRC */
    if(cmd == CMD0) crc = 0x95; /* CRC for CMD0(0) */
    else if(cmd == CMD8) crc = 0x87;  /* CRC for CMD8(0x1AA) */
    else crc = 1;

    /* transmit CRC */
    SPI_TxByte(crc);

    /* Skip a stuff byte when STOP_TRANSMISSION */
    if(cmd == CMD12) SPI_RxByte();

    /* receive response */
    uint8_t n = 10;
    do
    {
        res = SPI_RxByte();
    }
    while((res & 0x80) && --n);

    return res;
}

/***************************************
 * user_diskio.c functions
 **************************************/

/* initialize SD */
DSTATUS SD_disk_initialize(BYTE drv)
{
    uint8_t n, type, ocr[4];

    /* single drive, drv should be 0 */
    if(drv) return STA_NOINIT;

    /* no disk */
    if(Stat & STA_NODISK) return Stat;

    /* power on */
    SD_PowerOn();

    /* slave select */
    SELECT();

    /* check disk type */
    type = 0;

    /* send GO_IDLE_STATE command */
    if(SD_SendCmd(CMD0, 0) == 1)
    {
        /* timeout 1 sec */
        int32_t Timer1 = 0xfffff;

        /* SDC V2+ accept CMD8 command, http://elm-chan.org/docs/mmc/mmc_e.html */
        if(SD_SendCmd(CMD8, 0x1AA) == 1)
        {
            /* operation condition register */
            for(n = 0; n < 4; n++)
            {
                ocr[n] = SPI_RxByte();
            }

            /* voltage range 2.7-3.6V */
            if(ocr[2] == 0x01 && ocr[3] == 0xAA)
            {
                /* ACMD41 with HCS bit */
                do
                {
                    if(SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 1UL << 30) == 0) break;
                    Timer1--;
                }
                while(Timer1 > 0);

                /* READ_OCR */
                if(Timer1 > 0 && SD_SendCmd(CMD58, 0) == 0)
                {
                    /* Check CCS bit */
                    for(n = 0; n < 4; n++)
                    {
                        ocr[n] = SPI_RxByte();
                    }

                    /* SDv2 (HC or SC) */
                    type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
                }
            }
        }
        else
        {
            /* SDC V1 or MMC */
            type = (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) <= 1) ? CT_SD1 : CT_MMC;
            do
            {
                if(type == CT_SD1)
                {
                    if(SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) == 0) break;  /* ACMD41 */
                }
                else
                {
                    if(SD_SendCmd(CMD1, 0) == 0) break;  /* CMD1 */
                }
                Timer1--;
            }
            while(Timer1 > 0);

            /* SET_BLOCKLEN */
            if(Timer1 <= 0 || SD_SendCmd(CMD16, 512) != 0) type = 0;
        }
    }

    CardType = type;

    /* Idle */
    DESELECT();
    SPI_RxByte();

    /* Clear STA_NOINIT */
    if(type)
    {
        Stat &= ~STA_NOINIT;
    }
    else
    {
        /* Initialization failed */
        SD_PowerOff();
    }

    return Stat;
}

/* return disk status */
DSTATUS SD_disk_status(BYTE drv)
{
    if(drv) return STA_NOINIT;
    return Stat;
}

/* read sector */
DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
    /* pdrv should be 0 */
    if(pdrv || !count) return RES_PARERR;

    /* no disk */
    if(Stat & STA_NOINIT) return RES_NOTRDY;

    /* convert to byte address */
    if(!(CardType & CT_SD2)) sector *= 512;

    SELECT();

    if(count == 1)
    {
        /* READ_SINGLE_BLOCK */
        if((SD_SendCmd(CMD17, sector) == 0) && SD_RxDataBlock(buff, 512)) count = 0;
    }
    else
    {
        /* READ_MULTIPLE_BLOCK */
        if(SD_SendCmd(CMD18, sector) == 0)
        {
            do
            {
                if(!SD_RxDataBlock(buff, 512)) break;
                buff += 512;
            }
            while(--count);

            /* STOP_TRANSMISSION */
            SD_SendCmd(CMD12, 0);
        }
    }

    /* Idle */
    DESELECT();
    SPI_RxByte();

    return count ? RES_ERROR : RES_OK;
}

/* write sector */
#if _USE_WRITE == 1
DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
    /* pdrv should be 0 */
    if(pdrv || !count) return RES_PARERR;

    /* no disk */
    if(Stat & STA_NOINIT) return RES_NOTRDY;

    /* write protection */
    if(Stat & STA_PROTECT) return RES_WRPRT;

    /* convert to byte address */
    if(!(CardType & CT_SD2)) sector *= 512;

    SELECT();

    if(count == 1)
    {
        /* WRITE_BLOCK */
        if((SD_SendCmd(CMD24, sector) == 0) && SD_TxDataBlock(buff, 0xFE))
            count = 0;
    }
    else
    {
        /* WRITE_MULTIPLE_BLOCK */
        if(CardType & CT_SD1)
        {
            SD_SendCmd(CMD55, 0);
            SD_SendCmd(CMD23, count); /* ACMD23 */
        }

        if(SD_SendCmd(CMD25, sector) == 0)
        {
            do
            {
                if(!SD_TxDataBlock(buff, 0xFC)) break;
                buff += 512;
            }
            while(--count);

            /* STOP_TRAN token */
            if(!SD_TxDataBlock(0, 0xFD))
            {
                count = 1;
            }
        }
    }

    /* Idle */
    DESELECT();
    SPI_RxByte();

    return count ? RES_ERROR : RES_OK;
}
#endif /* _USE_WRITE */

/* ioctl */
DRESULT SD_disk_ioctl(BYTE drv, BYTE ctrl, void* buff)
{
    DRESULT res;
    uint8_t n, csd[16], *ptr = buff;
    WORD csize;

    /* pdrv should be 0 */
    if(drv) return RES_PARERR;
    res = RES_ERROR;

    if(ctrl == CTRL_POWER)
    {
        switch(*ptr)
        {
        case 0:
            SD_PowerOff();    /* Power Off */
            res = RES_OK;
            break;
        case 1:
            SD_PowerOn();   /* Power On */
            res = RES_OK;
            break;
        case 2:
            *(ptr + 1) = SD_CheckPower();
            res = RES_OK;   /* Power Check */
            break;
        default:
            res = RES_PARERR;
        }
    }
    else
    {
        /* no disk */
        if(Stat & STA_NOINIT) return RES_NOTRDY;

        SELECT();

        switch(ctrl)
        {
        case GET_SECTOR_COUNT:
            /* SEND_CSD */
            if((SD_SendCmd(CMD9, 0) == 0) && SD_RxDataBlock(csd, 16))
            {
                if((csd[0] >> 6) == 1)
                {
                    /* SDC V2 */
                    csize = csd[9] + ((WORD) csd[8] << 8) + 1;
                    *(DWORD*) buff = (DWORD) csize << 10;
                }
                else
                {
                    /* MMC or SDC V1 */
                    n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
                    csize = (csd[8] >> 6) + ((WORD) csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
                    *(DWORD*) buff = (DWORD) csize << (n - 9);
                }
                res = RES_OK;
            }
            break;
        case GET_SECTOR_SIZE:
            *(WORD*) buff = 512;
            res = RES_OK;
            break;
        case CTRL_SYNC:
            if(SD_ReadyWait() == 0xFF) res = RES_OK;
            break;
        case MMC_GET_CSD:
            /* SEND_CSD */
            if(SD_SendCmd(CMD9, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
            break;
        case MMC_GET_CID:
            /* SEND_CID */
            if(SD_SendCmd(CMD10, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
            break;
        case MMC_GET_OCR:
            /* READ_OCR */
            if(SD_SendCmd(CMD58, 0) == 0)
            {
                for(n = 0; n < 4; n++)
                {
                    *ptr++ = SPI_RxByte();
                }
                res = RES_OK;
            }
        default:
            res = RES_PARERR;
        }

        DESELECT();
        SPI_RxByte();
    }

    return res;
}

fat_sd_card.h 【额外添加】

#ifndef __FATFS_SD_H
#define __FATFS_SD_H

#include "diskio.h"
#include "stm32f1xx_hal.h"

/* Definitions for MMC/SDC command */
#define CMD0     (0x40+0)       /* GO_IDLE_STATE */
#define CMD1     (0x40+1)       /* SEND_OP_COND */
#define CMD8     (0x40+8)       /* SEND_IF_COND */
#define CMD9     (0x40+9)       /* SEND_CSD */
#define CMD10    (0x40+10)      /* SEND_CID */
#define CMD12    (0x40+12)      /* STOP_TRANSMISSION */
#define CMD16    (0x40+16)      /* SET_BLOCKLEN */
#define CMD17    (0x40+17)      /* READ_SINGLE_BLOCK */
#define CMD18    (0x40+18)      /* READ_MULTIPLE_BLOCK */
#define CMD23    (0x40+23)      /* SET_BLOCK_COUNT */
#define CMD24    (0x40+24)      /* WRITE_BLOCK */
#define CMD25    (0x40+25)      /* WRITE_MULTIPLE_BLOCK */
#define CMD41    (0x40+41)      /* SEND_OP_COND (ACMD) */
#define CMD55    (0x40+55)      /* APP_CMD */
#define CMD58    (0x40+58)      /* READ_OCR */

/* MMC card type flags (MMC_GET_TYPE) */
#define CT_MMC    0x01    /* MMC ver 3 */
#define CT_SD1    0x02    /* SD ver 1 */
#define CT_SD2    0x04    /* SD ver 2 */
#define CT_SDC    0x06    /* SD */
#define CT_BLOCK  0x08    /* Block addressing */

/* Functions */
DSTATUS SD_disk_initialize(BYTE pdrv);
DSTATUS SD_disk_status(BYTE pdrv);
DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT SD_disk_ioctl(BYTE pdrv, BYTE cmd, void* buff);

#define SPI_TIMEOUT 100

extern SPI_HandleTypeDef  hspi2;

#define HSPI_SDCARD     &hspi2
#define SD_CS_PORT      GPIOB
#define SD_CS_PIN       GPIO_PIN_12

#endif

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) 2024 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"
#include "fatfs_sd_card.h"
/* Private typedef -----------------------------------------------------------*/
/* 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 */
	return SD_disk_initialize(pdrv);
  /* 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 */
	return SD_disk_status(pdrv);
  /* 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 */
	return SD_disk_read(pdrv, buff, sector, count);
  /* 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 */
	return SD_disk_write(pdrv, buff, sector, count);
  /* 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 */
	return SD_disk_ioctl(pdrv, cmd, buff);
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

可以看出,起始这个代码基本就只是调用了一下上述两个文件已经实现的函数,结构非常明确,也很有利于移植。

  最后,再在main文件中添加一个测试函数,前提是自己实现printf函数

/**
  * @brief SD Card Operation Test
  * @param none
  * @retval none
  */
void SD_Card_ReadWrite_Test(void)
{
    FATFS    FatFs;
    FIL      fil;
    FRESULT  fres;
    char     buf[100];

    do
    {
        //Mount the SD Card
        fres = f_mount(&FatFs, "0:", 1);    //1=mount now
        if(fres != FR_OK)
        {
            printf("No SD Card found : (%i)\r\n", fres);
            break;
        }
        printf("SD Card Mounted Successfully!!!\r\n");

        //Read the SD Card Total size and Free Size
        FATFS*   pfs;
        DWORD    fre_clust;
        uint32_t totalSpace, freeSpace;

        f_getfree("", &fre_clust, &pfs);
        totalSpace = (uint32_t)((pfs->n_fatent - 2) * pfs->csize * 0.5);
        freeSpace = (uint32_t)(fre_clust * pfs->csize * 0.5);

        printf("TotalSpace : %u bytes, FreeSpace = %u bytes\n", totalSpace, freeSpace);

        //Open the file
        fres = f_open(&fil, "Test.txt", FA_WRITE | FA_READ | FA_CREATE_ALWAYS);
        if(fres != FR_OK)
        {
            printf("File creation/open Error : (%i)\r\n", fres);
            break;
        }

        printf("Writing data...\r\n");
        //write the data
        int n = f_puts("Hello World!\n", &fil);
        printf("Wrote %i Bytes\n", n);
        //close your file
        f_close(&fil);

        //Open the file
        fres = f_open(&fil, "Test.txt", FA_READ);
        if(fres != FR_OK)
        {
            printf("File opening Error : (%i)\r\n", fres);
            break;
        }

        //read the data
        f_gets(buf, sizeof(buf), &fil);

        printf("Read Data : %s\n", buf);

        //close your file
        f_close(&fil);
        printf("Closing File!!!\r\n");
#if 0
        //Delete the file.
        fres = f_unlink(EmbeTronicX.txt);
        if(fres != FR_OK)
        {
            printf("Cannot able to delete the file\n");
        }
#endif
    }
    while(0);

    //We're done, so de-mount the drive
    f_mount(NULL, "", 0);
    printf("SD Card Unmounted Successfully!!!\r\n");
}

然后直接运行代码,接上串口,如果正常的话可以看到最后的串口输出。

4.2 标准库移植

  标准库移植方面,一开始在网上找到别人做的基于其他型号芯片移植的版本,确实可以用,但是感觉结构有点乱,模块化做得不是很好,所以打算基于上述这个代码进行移植。

  既然要折腾,那就从头开始。先从fatfs官网下载最新版的源码(R15)。得到一个ff15.zip的压缩包,里面结构如下所示。
在这里插入图片描述
我们需要的就是source文件夹下面的这个代码文件。把这几个文件复制到一个准库模板工程下面,为便于管理,在根目录下创建一个文件夹名为FATFS,存放这些文件,然后再添加一些基本的文件,主要是串口和printf函数重定向的代码。

当然,创建文件夹的同时也要在Keil中创建对应的Group,并把这个文件夹添加到包含路径当中,都是基本操作,不要忘了。

  然后再把上述HAL库的代码转换成标准库下的格式,得到如下代码(将SPI相关的函数也放到一起了,减少了代码的耦合性)

fatfs_sd_card.c

#define TRUE  1
#define FALSE 0
#define bool BYTE

#include "fatfs_sd_card.h"

static volatile DSTATUS Stat = STA_NOINIT;  /* Disk Status */
static uint8_t CardType;                    /* Type 0:MMC, 1:SDC, 2:Block addressing */
static uint8_t PowerFlag = 0;       /* Power flag */

/***************************************
 * SPI functions
 **************************************/

SPI_InitTypeDef SPI_InitStructure;

/**
  * @brief  初始化SD卡对应的SPI外设
  * @param  None
  * @retval None
  */
void SD_SPI_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	
	//使能时钟——宏定义实现
    ENABLE_SD_SPI_GPIO_CLK();
    ENABLE_SD_SPI_CLK();
	
	//GPIO初始化
    GPIO_InitStructure.GPIO_Pin = SD_SPI_MOSI_PIN | SD_SPI_SCK_PIN;   //MOSI & SCK: AFIO,Output
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(SD_SPI_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = SD_SPI_MISO_PIN;                    //MISO: Input
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //输入
    GPIO_Init(SD_SPI_GPIO_PORT, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = SD_SPI_CS_PIN;                    //CS: Output
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       //输出
    GPIO_Init(SD_SPI_GPIO_PORT, &GPIO_InitStructure);

	//SPI外设初始化
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//选择了串行时钟的稳态:时钟悬空高
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//数据捕获于第二个时钟沿
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//定义波特率预分频的值:波特率预分频值为256
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
    SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
    SPI_Init(SD_SPI, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

    SPI_Cmd(SD_SPI, ENABLE); //使能SPI外设

    SD_SPI_ReadWriteByte(0xff);//启动传输
}

/**
  * @brief  设置SPI的速度
  * @param  SpeedSet:
  * SPI_BaudRatePrescaler_2   2分频   (SPI 36M@sys 72M)
  * SPI_BaudRatePrescaler_8   8分频   (SPI 9M@sys 72M)
  * SPI_BaudRatePrescaler_16  16分频  (SPI 4.5M@sys 72M)
  * SPI_BaudRatePrescaler_256 256分频 (SPI 281.25K@sys 72M)
  * @retval None
  */
void SD_SPI_SetSpeed(u8 SpeedSet)
{
    SPI_InitStructure.SPI_BaudRatePrescaler = SpeedSet ;
    SPI_Init(SD_SPI, &SPI_InitStructure);
    SPI_Cmd(SD_SPI, ENABLE);
}

/**
  * @brief  SPI读写一个字节。因为SPI为全双工协议,所以接收一个字节也需要时钟,
		    所以一般是写入一个字节同时读取一个字节。
  * @param  TxData: 写入的字节
  * @retval RxData:读到的字节
  */
u8 SD_SPI_ReadWriteByte(u8 TxData)
{
    u8 retry = 0;
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)  //检查指定的SPI标志位设置与否:发送缓存空标志位
    {
        retry++;
        if(retry > 200) return 0;
    }
    SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
    retry = 0;

//	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_BSY) == SET)  //ref: https://bbs.21ic.com/icview-440361-1-1.html
    {
        retry++;
        if(retry > 200)
            return 0;
    }
    return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
}

/* slave select */
static void SELECT(void)
{
    SD_CS_LOW();
}

/* slave deselect */
static void DESELECT(void)
{
    SD_CS_HIGH();
}

/* SPI transmit a byte */
static void SPI_TxByte(uint8_t data)
{
    SD_SPI_ReadWriteByte(data);
}

/* SPI transmit buffer */
static void SPI_TxBuffer(uint8_t* buffer, uint16_t len)
{
    for(int i=0; i<len; i++)
	{
		SD_SPI_ReadWriteByte(*buffer);
		buffer++;
	}
}

/* SPI receive a byte */
static uint8_t SPI_RxByte(void)
{
    uint8_t dummy, data;
    dummy = 0xFF;

    data = SD_SPI_ReadWriteByte(dummy);

    return data;
}

/* SPI receive a byte via pointer */
static void SPI_RxBytePtr(uint8_t* buff)
{
    *buff = SPI_RxByte();
}

/***************************************
 * SD functions
 **************************************/

/* wait SD ready */
static uint8_t SD_ReadyWait(void)
{
    uint8_t res;

    /* timeout 500ms */
    int32_t Timer2 = 0xffffff;

    /* if SD goes ready, receives 0xFF */
    do
    {
        res = SPI_RxByte();
		Timer2--;
    }
    while((res != 0xFF) && Timer2>0);

    return res;
}

/* power on */
static void SD_PowerOn(void)
{
    uint8_t args[6];
    uint32_t cnt = 0x1FFF;

    /* transmit bytes to wake up */
    DESELECT();
    for(int i = 0; i < 10; i++)
    {
        SPI_TxByte(0xFF);
    }

    /* slave select */
    SELECT();

    /* make idle state */
    args[0] = CMD0;   /* CMD0:GO_IDLE_STATE */
    args[1] = 0;
    args[2] = 0;
    args[3] = 0;
    args[4] = 0;
    args[5] = 0x95;   /* CRC */

    SPI_TxBuffer(args, sizeof(args));

    /* wait response */
    while((SPI_RxByte() != 0x01) && cnt)
    {
        cnt--;
    }

    DESELECT();
    SPI_TxByte(0XFF);

    PowerFlag = 1;
}

/* power off */
static void SD_PowerOff(void)
{
    PowerFlag = 0;
}

/* check power flag */
static uint8_t SD_CheckPower(void)
{
    return PowerFlag;
}

/* receive data block */
static bool SD_RxDataBlock(BYTE* buff, UINT len)
{
    uint8_t token;

    /* timeout 200ms */
    int32_t Timer1 = 0xffffff;

    /* loop until receive a response or timeout */
    do
    {
        token = SPI_RxByte();
		Timer1--;
    }
    while((token == 0xFF) && Timer1>0);

    /* invalid response */
    if(token != 0xFE) return FALSE;

    /* receive data */
    do
    {
        SPI_RxBytePtr(buff++);
    }
    while(len--);

    /* discard CRC */
    SPI_RxByte();
    SPI_RxByte();

    return TRUE;
}

/* transmit data block */
static bool SD_TxDataBlock(const uint8_t* buff, BYTE token)
{
    uint8_t resp;
    uint8_t i = 0;

    /* wait SD ready */
    if(SD_ReadyWait() != 0xFF) return FALSE;

    /* transmit token */
    SPI_TxByte(token);

    /* if it's not STOP token, transmit data */
    if(token != 0xFD)
    {
        SPI_TxBuffer((uint8_t*)buff, 512);

        /* discard CRC */
        SPI_RxByte();
        SPI_RxByte();

        /* receive response */
        while(i <= 64)
        {
            resp = SPI_RxByte();

            /* transmit 0x05 accepted */
            if((resp & 0x1F) == 0x05) break;
            i++;
        }

        /* recv buffer clear */
        while(SPI_RxByte() == 0);
    }

    /* transmit 0x05 accepted */
    if((resp & 0x1F) == 0x05) return TRUE;

    return FALSE;
}

/* transmit command */
static BYTE SD_SendCmd(BYTE cmd, uint32_t arg)
{
    uint8_t crc, res;

    /* wait SD ready */
    if(SD_ReadyWait() != 0xFF) return 0xFF;

    /* transmit command */
    SPI_TxByte(cmd);          /* Command */
    SPI_TxByte((uint8_t)(arg >> 24));   /* Argument[31..24] */
    SPI_TxByte((uint8_t)(arg >> 16));   /* Argument[23..16] */
    SPI_TxByte((uint8_t)(arg >> 8));  /* Argument[15..8] */
    SPI_TxByte((uint8_t)arg);       /* Argument[7..0] */

    /* prepare CRC */
    if(cmd == CMD0) crc = 0x95; /* CRC for CMD0(0) */
    else if(cmd == CMD8) crc = 0x87;  /* CRC for CMD8(0x1AA) */
    else crc = 1;

    /* transmit CRC */
    SPI_TxByte(crc);

    /* Skip a stuff byte when STOP_TRANSMISSION */
    if(cmd == CMD12) SPI_RxByte();

    /* receive response */
    uint8_t n = 10;
    do
    {
        res = SPI_RxByte();
    }
    while((res & 0x80) && --n);

    return res;
}

/***************************************
 * user_diskio.c functions
 **************************************/

/* initialize SD */
DSTATUS SD_disk_initialize(BYTE drv)
{
    uint8_t n, type, ocr[4];

    /* single drive, drv should be 0 */
    if(drv) return STA_NOINIT;

    /* no disk */
    if(Stat & STA_NODISK) return Stat;

    /* power on */
    SD_PowerOn();

    /* slave select */
    SELECT();

    /* check disk type */
    type = 0;

    /* send GO_IDLE_STATE command */
    if(SD_SendCmd(CMD0, 0) == 1)
    {
        /* timeout 1 sec */
        int32_t Timer1 = 0xfffff;

        /* SDC V2+ accept CMD8 command, http://elm-chan.org/docs/mmc/mmc_e.html */
        if(SD_SendCmd(CMD8, 0x1AA) == 1)
        {
            /* operation condition register */
            for(n = 0; n < 4; n++)
            {
                ocr[n] = SPI_RxByte();
            }

            /* voltage range 2.7-3.6V */
            if(ocr[2] == 0x01 && ocr[3] == 0xAA)
            {
                /* ACMD41 with HCS bit */
                do
                {
                    if(SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 1UL << 30) == 0) break;
					Timer1--;
                }
                while(Timer1>0);

                /* READ_OCR */
                if(Timer1>0 && SD_SendCmd(CMD58, 0) == 0)
                {
                    /* Check CCS bit */
                    for(n = 0; n < 4; n++)
                    {
                        ocr[n] = SPI_RxByte();
                    }

                    /* SDv2 (HC or SC) */
                    type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
                }
            }
        }
        else
        {
            /* SDC V1 or MMC */
            type = (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) <= 1) ? CT_SD1 : CT_MMC;
            do
            {
                if(type == CT_SD1)
                {
                    if(SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) == 0) break;  /* ACMD41 */
                }
                else
                {
                    if(SD_SendCmd(CMD1, 0) == 0) break;  /* CMD1 */
                }
				Timer1--;
            }
            while(Timer1>0);

            /* SET_BLOCKLEN */
            if(Timer1<=0 || SD_SendCmd(CMD16, 512) != 0) type = 0;
        }
    }

    CardType = type;

    /* Idle */
    DESELECT();
    SPI_RxByte();

    /* Clear STA_NOINIT */
    if(type)
    {
        Stat &= ~STA_NOINIT;
    }
    else
    {
        /* Initialization failed */
        SD_PowerOff();
    }

    return Stat;
}

/* return disk status */
DSTATUS SD_disk_status(BYTE drv)
{
    if(drv) return STA_NOINIT;
    return Stat;
}

/* read sector */
DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
    /* pdrv should be 0 */
    if(pdrv || !count) return RES_PARERR;

    /* no disk */
    if(Stat & STA_NOINIT) return RES_NOTRDY;

    /* convert to byte address */
    if(!(CardType & CT_SD2)) sector *= 512;

    SELECT();

    if(count == 1)
    {
        /* READ_SINGLE_BLOCK */
        if((SD_SendCmd(CMD17, sector) == 0) && SD_RxDataBlock(buff, 512)) count = 0;
    }
    else
    {
        /* READ_MULTIPLE_BLOCK */
        if(SD_SendCmd(CMD18, sector) == 0)
        {
            do
            {
                if(!SD_RxDataBlock(buff, 512)) break;
                buff += 512;
            }
            while(--count);

            /* STOP_TRANSMISSION */
            SD_SendCmd(CMD12, 0);
        }
    }

    /* Idle */
    DESELECT();
    SPI_RxByte();

    return count ? RES_ERROR : RES_OK;
}

/* write sector */
DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
    /* pdrv should be 0 */
    if(pdrv || !count) return RES_PARERR;

    /* no disk */
    if(Stat & STA_NOINIT) return RES_NOTRDY;

    /* write protection */
    if(Stat & STA_PROTECT) return RES_WRPRT;

    /* convert to byte address */
    if(!(CardType & CT_SD2)) sector *= 512;

    SELECT();

    if(count == 1)
    {
        /* WRITE_BLOCK */
        if((SD_SendCmd(CMD24, sector) == 0) && SD_TxDataBlock(buff, 0xFE))
            count = 0;
    }
    else
    {
        /* WRITE_MULTIPLE_BLOCK */
        if(CardType & CT_SD1)
        {
            SD_SendCmd(CMD55, 0);
            SD_SendCmd(CMD23, count); /* ACMD23 */
        }

        if(SD_SendCmd(CMD25, sector) == 0)
        {
            do
            {
                if(!SD_TxDataBlock(buff, 0xFC)) break;
                buff += 512;
            }
            while(--count);

            /* STOP_TRAN token */
            if(!SD_TxDataBlock(0, 0xFD))
            {
                count = 1;
            }
        }
    }

    /* Idle */
    DESELECT();
    SPI_RxByte();

    return count ? RES_ERROR : RES_OK;
}

/* ioctl */
DRESULT SD_disk_ioctl(BYTE drv, BYTE ctrl, void* buff)
{
    DRESULT res;
    uint8_t n, csd[16], *ptr = buff;
    WORD csize;

    /* pdrv should be 0 */
    if(drv) return RES_PARERR;
    res = RES_ERROR;

    if(ctrl == CTRL_POWER)
    {
        switch(*ptr)
        {
        case 0:
            SD_PowerOff();    /* Power Off */
            res = RES_OK;
            break;
        case 1:
            SD_PowerOn();   /* Power On */
            res = RES_OK;
            break;
        case 2:
            *(ptr + 1) = SD_CheckPower();
            res = RES_OK;   /* Power Check */
            break;
        default:
            res = RES_PARERR;
        }
    }
    else
    {
        /* no disk */
        if(Stat & STA_NOINIT) return RES_NOTRDY;

        SELECT();

        switch(ctrl)
        {
        case GET_SECTOR_COUNT:
            /* SEND_CSD */
            if((SD_SendCmd(CMD9, 0) == 0) && SD_RxDataBlock(csd, 16))
            {
                if((csd[0] >> 6) == 1)
                {
                    /* SDC V2 */
                    csize = csd[9] + ((WORD) csd[8] << 8) + 1;
                    *(DWORD*) buff = (DWORD) csize << 10;
                }
                else
                {
                    /* MMC or SDC V1 */
                    n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
                    csize = (csd[8] >> 6) + ((WORD) csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
                    *(DWORD*) buff = (DWORD) csize << (n - 9);
                }
                res = RES_OK;
            }
            break;
        case GET_SECTOR_SIZE:
            *(WORD*) buff = 512;
            res = RES_OK;
            break;
        case CTRL_SYNC:
            if(SD_ReadyWait() == 0xFF) res = RES_OK;
            break;
        case MMC_GET_CSD:
            /* SEND_CSD */
            if(SD_SendCmd(CMD9, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
            break;
        case MMC_GET_CID:
            /* SEND_CID */
            if(SD_SendCmd(CMD10, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
            break;
        case MMC_GET_OCR:
            /* READ_OCR */
            if(SD_SendCmd(CMD58, 0) == 0)
            {
                for(n = 0; n < 4; n++)
                {
                    *ptr++ = SPI_RxByte();
                }
                res = RES_OK;
            }
        default:
            res = RES_PARERR;
        }

        DESELECT();
        SPI_RxByte();
    }

    return res;
}

fatfs_sd_card.h

#ifndef __FATFS_SD_H
#define __FATFS_SD_H

#include "diskio.h"
#include "stm32f10x.h"                  // Device header

/* Definitions for MMC/SDC command */
#define CMD0     (0x40+0)       /* GO_IDLE_STATE */
#define CMD1     (0x40+1)       /* SEND_OP_COND */
#define CMD8     (0x40+8)       /* SEND_IF_COND */
#define CMD9     (0x40+9)       /* SEND_CSD */
#define CMD10    (0x40+10)      /* SEND_CID */
#define CMD12    (0x40+12)      /* STOP_TRANSMISSION */
#define CMD16    (0x40+16)      /* SET_BLOCKLEN */
#define CMD17    (0x40+17)      /* READ_SINGLE_BLOCK */
#define CMD18    (0x40+18)      /* READ_MULTIPLE_BLOCK */
#define CMD23    (0x40+23)      /* SET_BLOCK_COUNT */
#define CMD24    (0x40+24)      /* WRITE_BLOCK */
#define CMD25    (0x40+25)      /* WRITE_MULTIPLE_BLOCK */
#define CMD41    (0x40+41)      /* SEND_OP_COND (ACMD) */
#define CMD55    (0x40+55)      /* APP_CMD */
#define CMD58    (0x40+58)      /* READ_OCR */

/* MMC card type flags (MMC_GET_TYPE) */
#define CT_MMC    0x01    /* MMC ver 3 */
#define CT_SD1    0x02    /* SD ver 1 */
#define CT_SD2    0x04    /* SD ver 2 */
#define CT_SDC    0x06    /* SD */
#define CT_BLOCK  0x08    /* Block addressing */

/**
  * @brief  SD SPI Interface pins
  */
#define SD_SPI                           SPI2
#define SD_SPI_CLK                       RCC_APB1Periph_SPI2
#define SD_SPI_GPIO_PORT                 GPIOB                       /* GPIOB */
#define SD_SPI_GPIO_CLK                  RCC_APB2Periph_GPIOB
#define SD_SPI_SCK_PIN                   GPIO_Pin_13                 /* PB.13 */
#define SD_SPI_MISO_PIN                  GPIO_Pin_14                 /* PB.14 */
#define SD_SPI_MOSI_PIN                  GPIO_Pin_15                 /* PB.15 */
#define SD_SPI_CS_PIN                    GPIO_Pin_12                 /* PB.12 */

#define ENABLE_SD_SPI_CLK()              RCC_APB1PeriphClockCmd(SD_SPI_CLK, ENABLE)
#define ENABLE_SD_SPI_GPIO_CLK()         RCC_APB2PeriphClockCmd(SD_SPI_GPIO_CLK, ENABLE)

#define SD_CS_LOW()                      GPIO_ResetBits(SD_SPI_GPIO_PORT, SD_SPI_CS_PIN)
#define SD_CS_HIGH()                     GPIO_SetBits(SD_SPI_GPIO_PORT, SD_SPI_CS_PIN)

/**
  * @brief  SPI Interface function
  */
//初始化SPI外设
void SD_SPI_Init(void);
//设置SPI速度
void SD_SPI_SetSpeed(u8 SpeedSet);
//SPI读写一个字节
u8 SD_SPI_ReadWriteByte(u8 TxData);

/* Functions */
DSTATUS SD_disk_initialize(BYTE pdrv);
DSTATUS SD_disk_status(BYTE pdrv);
DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT SD_disk_ioctl(BYTE pdrv, BYTE cmd, void* buff);

#define SPI_TIMEOUT 100

#endif

直接编译试试。

在这里插入图片描述
在这里插入图片描述

可以看到,还有一些基本的数据类型没有typedef,千万别自作聪明自己去define,其实已经有了,就是ff.h文件里面,所以直接在diskio.h中包含即可

再次编译,发现剩下的就是diskio.h中预定义的函数未实现的报错了,这一部分非常简单,和上面HAL库一样,只需要调用已经定义好的函数即可:

当然,要记得先包含添加进去的头文件#include "fatfs_sd_card.h"

另外,考虑到获取时间的函数get_fattime还没有实现,这里再加上一个获取时间的函数,因为没有使用rtc外设,这里就直接return 0就行。

所以最后整个diskio.c文件如下所示。

/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs     (C)ChaN, 2019        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control modules to the FatFs module with a defined API.       */
/*-----------------------------------------------------------------------*/

#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "fatfs_sd_card.h"

/* Definitions of physical drive number for each drive */
#define DEV_RAM		0	/* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC		1	/* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB		2	/* Example: Map USB MSD to physical drive 2 */


/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	return SD_disk_status(pdrv);
}



/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	return SD_disk_initialize(pdrv);
}



/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	return SD_disk_read(pdrv, buff, sector, count);
}



/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	return SD_disk_write(pdrv, buff, sector, count);
}

#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	return SD_disk_ioctl(pdrv, cmd, buff);
}

DWORD get_fattime ()
{
	return 0;
}

到这里,编译一般是不会出错了,不妨用上面的测试函数测试一下,结果编译报错:
在这里插入图片描述
这其实是配置的问题,于是来到最后一步,改ff_conf.h文件。

  • #define FF_USE_STRFUNC 1 //这个配置的作用是使能f_puts等字符串读写函数
  • #define FF_CODE_PAGE 936 //这个的作用主要是使用中文,经过测试,文件名和内容都是中文是可以的
  • #define FF_USE_LFN 1 //是否使能长文件名,实测对于C8T6来说,不能和上面的936一起使用,看HAL库使用的是850,测试发现确实可以,所以更推荐的是850+使能长文件名。

在这里插入图片描述

这就是内存太小了,需要扩大一下,在startup_stm32_f10x_md.s文件中把stacksize改大:
在这里插入图片描述
要注意,FIL这个数据结构好像占内存很大,所以如果要在函数中定义局部变量,要考虑将芯片的栈大小加大。

串口显示以下内容,而且sd卡中也有相应名称的文件,表明文件读写成功

在这里插入图片描述

在这里插入图片描述
显然,还有一堆函数还没定义呢,主要就是diskio.c这个文件。因为它内部调用的一些函数我们还没实现,必然会报错。

所以,移植的第一步,就是将上面HAL库的代码转换成标准库下的代码,主要是SPI协议部分的代码。修改完成之后如下所示。

``

然后是标准库移植,相比于参考网上的那些代码,我更想直接参考这个代码来自己实现一个标准库,这样基本实现模块化操作,感觉比较有价值。
最好是可以完全不用修改的那种,直接添加到已有的项目中,这样移植还是非常方便的。

关于标准库移植这个,有些教程建议参考标准库中的例程,但经过我实践觉得好像也不是很好,因为这个库已经非常老了,甚至都没有2.0版本卡的分辨,所以不推荐。

简单描述一下做法,便于有缘人去实践。就是

4.3 遇到的问题和解决方案

  • 【标准库】在Keil中勾选Micro Lib导致f_mount函数执行失败
    这个问题花了我2天时间。。。因为途省事,重定向printf函数的时候直接勾选了使用微库,导致f_mount每次执行都出错,所以可以换一种方式重定向printf函数,具体可以参考这篇文章。

  • 【标准库】堆栈溢出:具体表现为加上f_open函数之后,代码直接不执行,串口输出内容的代码也不执行了
    解决:将FIL fil;的定义放在全局变量
    参考:https://blog.youkuaiyun.com/zyxhangiian123456789/article/details/80260324

如何修改:上面展示了HAL库如何配置,非常简单,如果是标准库,需要手动修改启动文件,如下所示
在这里插入图片描述

堆(Heap)和栈(Stack)的区别:
栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。
堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。 一般使用malloc函数占用的就是堆区。

  • 关于驱动器编号(Drive Number)的确定

5 扩展阅读

STM32是意法半导体(STMicroelectronics)开发的32位微控制器(MCU)系列,具有丰富的外设和强大的性能。SPI(Serial Peripheral Interface)是一种串行外设接口,用于在MCU和外部设备之间传输数据。SD(Secure Digital)是一种常见的存储标准,用于在移动设备、数码相机和其他电子设备中存储和传输数据。 STM32可以通过SPI接口连接到SD,实现对SD读写操作。SPI接口通常由四根线组成:时钟线(CLK)、主设备输出主动引脚(MOSI)、主设备输入被动引脚(MISO)和片选线(CS)。在STM32中,SPI接口是通过SPI外设进行配置和控制的。 要使用STM32连接SD,需要完成以下步骤: 1. 配置STM32SPI外设,包括设置时钟频率和通信模式等参数。 2. 使用GPIO外设配置引脚,将对应的SPI引脚连接到SD的对应引脚。 3. 初始化SD,通过向SD发送初始化命令来初始化SD的配置参数。 4. 发送读写命令,通过SPI接口向SD发送读写命令,并接收SD返回的数据。 在读写数据时,首先需要通过SPI接口发送读写命令和数据地址,等待SD返回应答后,再发送数据或接收数据。读取数据时,STM32通过SPI接口从SD读取数据;写入数据时,STM32将待写入的数据发送给SD。 通过STM32 SPI接口连接SD,可以实现高速、可靠的数据传输。同时,STM32还提供了丰富的软件库和开发工具,方便开发者进行SD读写操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记录无知岁月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值