SPI驱动SD卡及FATFS移植

Posted on 2022-10-24 05:31  昨夜三更雨  阅读(446)  评论(0编辑  收藏  举报

1. 基本思路

采用SPI驱动SD卡,要点如下:

  • SPI时钟频率不能太高,测试中发现时钟频率不宜超过20Mhz;

  • 为方便移植,可将SD卡驱动分为两部分:

    • sd_spi_cfg.c/h: 配置底层SPI读写函数和片选CS信号控制,移植时仅需改动该文件;
    • sd_spi.c/h: 包含SD卡的主要驱动函数。

2. 实现方式

sd_spi_cfg.c/h主要包含两个函数:

  • uint8_t sd_spi_cfg_write_read(uint8_t _byte)
  • void sd_spi_cfg_delay(uint16_t _ms)

sd_spi.c/h主要包含如下函数:

  • uint8_t sd_spi_init(void)
  • uint8_t sd_spi_get_status(void)
  • uint8_t sd_spi_read_blocks(uint8_t *_pbuf, uint32_t _saddr, uint32_t _blknbr)
  • uint8_t sd_spi_write_blocks(uint8_t *_pbuf, uint32_t _saddr, uint32_t _blknbr)
  • uint16_t sd_spi_get_block_size(void)
  • uint32_t sd_spi_get_block_number(void)
  • static uint8_t sd_spi_read_neq(uint8_t _value)
  • static uint8_t sd_spi_read_eq(uint8_t _value)
  • static sd_response_typedef sd_spi_send_cmd ( uint8_t _cmd, uint32_t _arg, uint8_t _crc, uint8_t _expr )
  • static uint8_t sd_spi_get_csd(sd_csd_typedef* _csd)
  • static uint8_t sd_spi_get_cid(sd_cid_typedef* _cid)

3. 完整代码

基于STM32的sd_spi_cfg.c/h文件示例如下:

  • sd_spi_cfg.h
/**
 *******************************************************************************
 * @file    sd_spi_cfg.h
 * @author  xixizhk
 *******************************************************************************
 * @version V2022 @ Oct 21, 2022 \n
 * Initial version.
 *******************************************************************************
 */


/* Define to prevent recursive inclusion **************************************/
#ifndef _SD_SPI_CFG_H
#define _SD_SPI_CFG_H

#ifdef __cplusplus
extern "C" {
#endif

/**
 *******************************************************************************
 * @addtogroup Includes
 * @{
 */

#include "stdint.h"

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Definitions
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Types
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Constants
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Variables
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Macros
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Functions
 * @{
 */

uint8_t sd_spi_cfg_write_read(uint8_t _byte);
void sd_spi_cfg_delay(uint16_t _ms);

/**
 * @}
 */


#ifdef __cplusplus
}
#endif


#endif /* _SD_SPI_CFG_H */

/**************************** ALL RIGHTS RESERVED *****************************/
  • sd_spi_cfg.c
/**
 *******************************************************************************
 * @file    sd_spi_cfg.c
 * @author  xixizhk
 *******************************************************************************
 * @version V2022 @ Oct 21, 2022 \n
 * Initial version.
 *******************************************************************************
 */


/**
 *******************************************************************************
 * @addtogroup Includes
 * @{
 */

#include "sd_spi_cfg.h"
#include "spi.h"
#include "main.h"

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Definitions
 * @{
 */

#define _TIMEOUT    100U

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Types
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Constants
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Variables
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Macros
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Functions
 * @{
 */

/**
 * @brief   Write buffer to the card.
 * @param   _byte [In]: byte to be written.
 * @retval  Date read.
 */
uint8_t sd_spi_cfg_write_read(uint8_t _byte)
{
    uint8_t byte = _byte;

    HAL_GPIO_WritePin(TF_CS_GPIO_Port, TF_CS_Pin, GPIO_PIN_RESET);
    HAL_SPI_TransmitReceive(&hspi3, &_byte, &byte, 1, _TIMEOUT);
    HAL_GPIO_WritePin(TF_CS_GPIO_Port, TF_CS_Pin, GPIO_PIN_SET);

    return byte;
}

/**
 * @brief   Delay in ms
 * @param   _ms [In]: ms to be delayed. 
 * @retval  None.
 */
void sd_spi_cfg_delay(uint16_t _ms)
{
    /* Functions based on interrupt such as HAL_Delay() not recommended,
     * because it may result in blocking. Here, approximate delay is employed.
     */
    uint32_t i, j;
    for (i = 0; i < _ms; i++)
    {
        for (j = 0; j < 17000; j++)
        {
            ;
        }
    }
}

/**
 * @}
 */


/**************************** ALL RIGHTS RESERVED *****************************/

sd_spi.c/h基本与所采用的芯片平台无关,如下:

  • sd_spi.h
/**
 *******************************************************************************
 * @file    sd_spi.h
 * @author  xixizhk
 *******************************************************************************
 * @version V2022 @ Oct 21, 2022 \n
 * Initial version.
 *******************************************************************************
 */


/* Define to prevent recursive inclusion **************************************/
#ifndef _SD_SPI_H
#define _SD_SPI_H

#ifdef __cplusplus
extern "C" {
#endif

/**
 *******************************************************************************
 * @addtogroup Includes
 * @{
 */

#include "stdint.h"

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Definitions
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Types
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Constants
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Variables
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Macros
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Functions
 * @{
 */

uint8_t sd_spi_init(void);
uint8_t sd_spi_get_status(void);
uint8_t sd_spi_read_blocks(uint8_t *_pbuf, uint32_t _saddr, uint32_t _blknbr);
uint8_t sd_spi_write_blocks(uint8_t *_pbuf, uint32_t _saddr, uint32_t _blknbr);
uint16_t sd_spi_get_block_size(void);
uint32_t sd_spi_get_block_number(void);

/**
 * @}
 */


#ifdef __cplusplus
}
#endif


#endif /* _SD_SPI_H */

/**************************** ALL RIGHTS RESERVED *****************************/
  • sd_spi.c
/**
 *******************************************************************************
 * @file    sd_spi.c
 * @author  xixizhk
 *******************************************************************************
 * @version V2022 @ Oct 21, 2022 \n
 * Initial version.
 *******************************************************************************
 */


/**
 *******************************************************************************
 * @addtogroup Includes
 * @{
 */

#include "sd_spi.h"
#include "sd_spi_cfg.h"

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Definitions
 * @{
 */

#define _SD_TRY_MAX                 (100)

#define _SD_CMD_LENGTH              (6)

#define _SD_DUMMY                   (0xFF)

#define _SD_BLOCK_SIZE              (512)

/**
 * @brief SD Card type
 */
#define _SD_V1X                     (0x0)
#define _SD_V2_SC                   (0x1)
#define _SD_V2_HC                   (0x2)

/**
 * @brief Definitions for the expected response.
 */
#define _SD_RESPONSE_EXPECTED_R1    (0x01)
#define _SD_RESPONSE_EXPECTED_R1B   (0x1B)
#define _SD_RESPONSE_EXPECTED_R2    (0x02)
#define _SD_RESPONSE_EXPECTED_R3    (0x03)
#define _SD_RESPONSE_EXPECTED_R4R5  (0x45)
#define _SD_RESPONSE_EXPECTED_R7    (0x07)

/**
 * @brief Definitions of commands: CMDxx = CMD-number | 0x40.
 */
#define _SD_CMD_GO_IDLE_STATE       ( 0)  /* CMD0  = 0x40 */
#define _SD_CMD_SEND_OP_COND        ( 1)  /* CMD1  = 0x41 */
#define _SD_CMD_SEND_IF_COND        ( 8)  /* CMD8  = 0x48 */
#define _SD_CMD_SEND_CSD            ( 9)  /* CMD9  = 0x49 */
#define _SD_CMD_SEND_CID            (10)  /* CMD10 = 0x4A */
#define _SD_CMD_STOP_TRANSMISSION   (12)  /* CMD12 = 0x4C */
#define _SD_CMD_SEND_STATUS         (13)  /* CMD13 = 0x4D */
#define _SD_CMD_SET_BLOCKLEN        (16)  /* CMD16 = 0x50 */
#define _SD_CMD_READ_SINGLE_BLOCK   (17)  /* CMD17 = 0x51 */
#define _SD_CMD_READ_MULT_BLOCK     (18)  /* CMD18 = 0x52 */
#define _SD_CMD_SET_BLOCK_COUNT     (23)  /* CMD23 = 0x57 */
#define _SD_CMD_WRITE_SINGLE_BLOCK  (24)  /* CMD24 = 0x58 */
#define _SD_CMD_WRITE_MULT_BLOCK    (25)  /* CMD25 = 0x59 */
#define _SD_CMD_PROG_CSD            (27)  /* CMD27 = 0x5B */
#define _SD_CMD_SET_WRITE_PROT      (28)  /* CMD28 = 0x5C */
#define _SD_CMD_CLR_WRITE_PROT      (29)  /* CMD29 = 0x5D */
#define _SD_CMD_SEND_WRITE_PROT     (30)  /* CMD30 = 0x5E */
#define _SD_CMD_SD_ERASE_GRP_START  (32)  /* CMD32 = 0x60 */
#define _SD_CMD_SD_ERASE_GRP_END    (33)  /* CMD33 = 0x61 */
#define _SD_CMD_UNTAG_SECTOR        (34)  /* CMD34 = 0x62 */
#define _SD_CMD_ERASE_GRP_START     (35)  /* CMD35 = 0x63 */
#define _SD_CMD_ERASE_GRP_END       (36)  /* CMD36 = 0x64 */
#define _SD_CMD_UNTAG_ERASE_GROUP   (37)  /* CMD37 = 0x65 */
#define _SD_CMD_ERASE               (38)  /* CMD38 = 0x66 */
#define _SD_CMD_SD_APP_OP_COND      (41)  /* CMD41 = 0x69 */
#define _SD_CMD_APP_CMD             (55)  /* CMD55 = 0x77 */
#define _SD_CMD_READ_OCR            (58)  /* CMD55 = 0x79 */

/**
 * @brief Definitions of SD card response.
 */
/* R1 answer value */
#define _SD_R1_NO_ERROR             (0x00)
#define _SD_R1_IN_IDLE_STATE        (0x01)
#define _SD_R1_ERASE_RESET          (0x02)
#define _SD_R1_ILLEGAL_COMMAND      (0x04)
#define _SD_R1_COM_CRC_ERROR        (0x08)
#define _SD_R1_ERASE_SEQUENCE_ERROR (0x10)
#define _SD_R1_ADDRESS_ERROR        (0x20)
#define _SD_R1_PARAMETER_ERROR      (0x40)
/* R2 answer value */
#define _SD_R2_NO_ERROR             (0x00)
#define _SD_R2_CARD_LOCKED          (0x01)
#define _SD_R2_LOCKUNLOCK_ERROR     (0x02)
#define _SD_R2_ERROR                (0x04)
#define _SD_R2_CC_ERROR             (0x08)
#define _SD_R2_CARD_ECC_FAILED      (0x10)
#define _SD_R2_WP_VIOLATION         (0x20)
#define _SD_R2_ERASE_PARAM          (0x40)
#define _SD_R2_OUTOFRANGE           (0x80)
/* Data response error */
#define _SD_DATA_OK                 (0x05)
#define _SD_DATA_CRC_ERROR          (0x0B)
#define _SD_DATA_WRITE_ERROR        (0x0D)
#define _SD_DATA_OTHER_ERROR        (0xFF)

/**
 * @brief  Start Data tokens:
 *         Tokens (necessary because at nop/idle (and CS active) only 0xff is 
 *         on the data/command line)
 */
/* Data token start byte, Start Single Block Read */
#define _SD_TOKEN_START_DATA_SINGLE_BLOCK_READ    0xFE
/* Data token start byte, Start Multiple Block Read */
#define _SD_TOKEN_START_DATA_MULTIPLE_BLOCK_READ  0xFE
/* Data token start byte, Start Single Block Write */
#define _SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE   0xFE
/* Data token start byte, Start Multiple Block Write */
#define _SD_TOKEN_START_DATA_MULTIPLE_BLOCK_WRITE 0xFD
/* Data token stop byte, Stop Multiple Block Write */
#define _SD_TOKEN_STOP_DATA_MULTIPLE_BLOCK_WRITE  0xFD

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Types
 * @{
 */

typedef struct {
    uint8_t r1;
    uint8_t r2;
    uint8_t r3;
    uint8_t r4;
    uint8_t r5;
} sd_response_typedef;

/* Card Specific Data: CSD Register */ 
typedef struct
{
    uint8_t  Reserved1:2;               /* Reserved */
    uint16_t DeviceSize:12;             /* Device Size */
    uint8_t  MaxRdCurrentVDDMin:3;      /* Max. read current @ VDD min */
    uint8_t  MaxRdCurrentVDDMax:3;      /* Max. read current @ VDD max */
    uint8_t  MaxWrCurrentVDDMin:3;      /* Max. write current @ VDD min */
    uint8_t  MaxWrCurrentVDDMax:3;      /* Max. write current @ VDD max */
    uint8_t  DeviceSizeMul:3;           /* Device size multiplier */
} sd_csd_part_v1;
typedef struct
{
    uint8_t  Reserved1:6;               /* Reserved */
    uint32_t DeviceSize:22;             /* Device Size */
    uint8_t  Reserved2:1;               /* Reserved */
} sd_csd_part_v2;
typedef struct
{
    /* Header part */
    uint8_t  CSDStruct:2;           /* CSD structure */
    uint8_t  Reserved1:6;           /* Reserved */
    uint8_t  TAAC:8;                /* Data read access-time 1 */
    uint8_t  NSAC:8;                /* Data read access-time 2 in CLK cycles */
    uint8_t  MaxBusClkFrec:8;       /* Max. bus clock frequency */
    uint16_t CardComdClasses:12;    /* Card command classes */
    uint8_t  RdBlockLen:4;          /* Max. read data block length */
    uint8_t  PartBlockRead:1;       /* Partial blocks for read allowed */
    uint8_t  WrBlockMisalign:1;     /* Write block misalignment */
    uint8_t  RdBlockMisalign:1;     /* Read block misalignment */
    uint8_t  DSRImpl:1;             /* DSR implemented */
    /* v1 or v2 struct */
    union csd_version
    {
        sd_csd_part_v1 v1;
        sd_csd_part_v2 v2;
    } PartCSD;
    uint8_t  EraseSingleBlockEnable:1;  /* Erase single block enable */
    uint8_t  EraseSectorSize:7;         /* Erase group size multiplier */
    uint8_t  WrProtectGrSize:7;         /* Write protect group size */
    uint8_t  WrProtectGrEnable:1;       /* Write protect group enable */
    uint8_t  Reserved2:2;               /* Reserved */
    uint8_t  WrSpeedFact:3;             /* Write speed factor */
    uint8_t  MaxWrBlockLen:4;           /* Max. write data block length */
    uint8_t  WriteBlockPartial:1;       /* Partial blocks for write allowed */
    uint8_t  Reserved3:5;               /* Reserved */
    uint8_t  FileFormatGrouop:1;        /* File format group */
    uint8_t  CopyFlag:1;                /* Copy flag (OTP) */
    uint8_t  PermWrProtect:1;           /* Permanent write protection */
    uint8_t  TempWrProtect:1;           /* Temporary write protection */
    uint8_t  FileFormat:2;              /* File Format */
    uint8_t  Reserved4:2;               /* Reserved */
    uint8_t  CRC:7;                     /* Reserved */
    uint8_t  Reserved5:1;               /* always 1*/
} sd_csd_typedef;

/* Card Identification Data: CID Register */
typedef struct
{
    uint8_t  ManufacturerID;       /* ManufacturerID */
    uint16_t OEM_AppliID;          /* OEM/Application ID */
    uint32_t ProdName1;            /* Product Name part1 */
    uint8_t  ProdName2;            /* Product Name part2*/
    uint8_t  ProdRev;              /* Product Revision */
    uint32_t ProdSN;               /* Product Serial Number */
    uint8_t  Reserved1;            /* Reserved1 */
    uint16_t ManufactDate;         /* Manufacturing Date */
    uint8_t  CID_CRC;              /* CID CRC */
    uint8_t  Reserved2;            /* always 1 */
} sd_cid_typedef;

/* SD Card information  */
typedef struct
{
    uint8_t Type;           /* 0: SDv1SC, 1: SDv2SC, 2: SDv2HC */
    sd_csd_typedef CSD;
    sd_cid_typedef CID;
    uint64_t CardCapacity;      /* Card capacity */
    uint16_t CardBlockSize;     /* Card block size for R/W in byte */
    uint32_t LogBlockNbr;       /* Specifies card logical capacity in block */
    uint16_t LogBlockSize;      /* Specifies logical block size in bytes */
} sd_info_typedef;

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Constants
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Variables
 * @{
 */

static sd_info_typedef sd_info;

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Macros
 * @{
 */

/**
 * @}
 */

/**
 *******************************************************************************
 * @addtogroup Functions
 * @{
 */

static uint8_t sd_spi_read_neq(uint8_t _value);
static uint8_t sd_spi_read_eq(uint8_t _value);
static sd_response_typedef sd_spi_send_cmd
(
    uint8_t _cmd,
    uint32_t _arg,
    uint8_t _crc,
    uint8_t _expr
);
static uint8_t sd_spi_get_csd(sd_csd_typedef* _csd);
static uint8_t sd_spi_get_cid(sd_cid_typedef* _cid);


/**
 * @brief   Initialize the SD card and get related information.
 * @param   None.
 * @retval  0 if successful. 
 */
uint8_t sd_spi_init(void)
{
    sd_response_typedef res;
    volatile uint8_t cnt = 0;
    uint8_t retv;

    /* Step 1: Initialize SD card. */

    /* Send CMD0 (_SD_CMD_GO_IDLE_STATE) to put SD in SPI mode and
     * wait for In Idle State Response (R1 Format) equal to 0x01. */
    cnt = 0;
    do
    {
        cnt = cnt + 1;
        res = sd_spi_send_cmd
        (
            _SD_CMD_GO_IDLE_STATE,
            0,
            0x95,
            _SD_RESPONSE_EXPECTED_R1
        );
        if (cnt >= _SD_TRY_MAX)
        {
            return 1;
        }
        
    } while (res.r1 != _SD_R1_IN_IDLE_STATE);
    /* Send CMD8 (_SD_CMD_SEND_IF_COND) to check the power supply status
     * and wait until response (R7 Format) equal to 0xAA. */
    res = sd_spi_send_cmd
    (
        _SD_CMD_SEND_IF_COND,
        0x1AA,
        0x87,
        _SD_RESPONSE_EXPECTED_R7
    );
    if((res.r1 & _SD_R1_ILLEGAL_COMMAND) == _SD_R1_ILLEGAL_COMMAND)
    {
        /* Initialize card V1 */
        cnt = 0;
        do
        {
            cnt = cnt + 1;
            /* Send CMD55 (_SD_CMD_APP_CMD) before any ACMD command:
             * R1 response (0x00: no errors) */
            res = sd_spi_send_cmd
            (
                _SD_CMD_APP_CMD,
                0x00000000,
                0xFF,
                _SD_RESPONSE_EXPECTED_R1
            );
            /* Send ACMD41 (_SD_CMD_SD_APP_OP_COND) to initialize SDHC
             * or SDXC cards: R1 response (0x00: no errors) */
            res = sd_spi_send_cmd
            (
                _SD_CMD_SD_APP_OP_COND,
                0x00000000,
                0xFF,
                _SD_RESPONSE_EXPECTED_R1
            );
            if (cnt >= _SD_TRY_MAX)
            {
                return 2;
            }
        } while(res.r1 == _SD_R1_IN_IDLE_STATE);
        sd_info.Type = _SD_V1X;
    }
    else
    {
        if (res.r1 == _SD_R1_IN_IDLE_STATE)
        {
            /* Initialize card V2 */
            cnt = 0;
            do
            {
                cnt = cnt + 1;
                /* Send CMD55 (_SD_CMD_APP_CMD) before any ACMD command:
                 * R1 response (0x00: no errors) */
                res = sd_spi_send_cmd
                (
                    _SD_CMD_APP_CMD,
                    0,
                    0xFF,
                    _SD_RESPONSE_EXPECTED_R1
                );
                /* Send ACMD41 (_SD_CMD_SD_APP_OP_COND) to initialize SDHC
                 * or SDXC cards: R1 response (0x00: no errors) */
                res = sd_spi_send_cmd
                (
                    _SD_CMD_SD_APP_OP_COND,
                    0x40000000,
                    0xFF,
                    _SD_RESPONSE_EXPECTED_R1
                );
                if (cnt >= _SD_TRY_MAX)
                {
                    return 3;
                }
            } while(res.r1 == _SD_R1_IN_IDLE_STATE);
            if((res.r1 & _SD_R1_ILLEGAL_COMMAND) == _SD_R1_ILLEGAL_COMMAND)
            {
                cnt = 0;
                do
                {
                    cnt = cnt + 1;
                    /* Send CMD55 (_SD_CMD_APP_CMD) before any ACMD command:
                     * R1 response (0x00: no errors) */
                    res = sd_spi_send_cmd
                    (
                        _SD_CMD_APP_CMD,
                        0,
                        0xFF,
                        _SD_RESPONSE_EXPECTED_R1
                    );
                    /* Send ACMD41 (_SD_CMD_SD_APP_OP_COND) to initialize SDHC
                     * or SDXC cards: R1 response (0x00: no errors) */
                    res = sd_spi_send_cmd
                    (
                        _SD_CMD_SD_APP_OP_COND,
                        0x00000000,
                        0xFF,
                        _SD_RESPONSE_EXPECTED_R1
                    );
                    if (cnt >= _SD_TRY_MAX)
                    {
                        return 4;
                    }
                } while(res.r1 == _SD_R1_IN_IDLE_STATE);
            }
            /* Send CMD58 (_SD_CMD_READ_OCR) to initialize SDHC or SDXC cards:
            * R3 response (0x00: no errors) */
            res = sd_spi_send_cmd
            (
                _SD_CMD_READ_OCR,
                0x00000000,
                0xFF,
                _SD_RESPONSE_EXPECTED_R3
            );
            if(res.r1 != _SD_R1_NO_ERROR)
            {
                return 5;
            }
            if (((res.r2 & 0x40) >> 6) == 0)
            {
                sd_info.Type = _SD_V2_SC;
            }
            else
            {
                sd_info.Type = _SD_V2_HC;
            }
        }
        else
        {
            return 6;
        }
    }

    /* Step 2: Get CSD. */
    cnt = 0;
    do
    {
        cnt = cnt + 1;
        retv = sd_spi_get_csd(&(sd_info.CSD));
        if (cnt >= _SD_TRY_MAX)
        {
            return 7;
        }
    } while (retv != 0);
    
    /* Step 3: Get CID. */
    cnt = 0;
    do
    {
        cnt = cnt + 1;
        retv = sd_spi_get_cid(&(sd_info.CID));
        if (cnt >= _SD_TRY_MAX)
        {
            return 8;
        }
    } while (retv != 0);

    /* Step 4: Calculate key information. */
    sd_info.LogBlockSize = _SD_BLOCK_SIZE;
    if(sd_info.Type == _SD_V2_HC)
    {
        sd_info.CardBlockSize = 512;
        sd_info.CardCapacity =
            (sd_info.CSD.PartCSD.v2.DeviceSize + 1) * 1024 * 512;
        sd_info.LogBlockNbr = (sd_info.CardCapacity) / (sd_info.LogBlockSize);
    }
    else
    {
        sd_info.CardCapacity = (sd_info.CSD.PartCSD.v1.DeviceSize + 1) ;
        sd_info.CardCapacity *=
            (1 << (sd_info.CSD.PartCSD.v1.DeviceSizeMul + 2));
        sd_info.CardBlockSize = 1 << (sd_info.CSD.RdBlockLen);
        sd_info.CardCapacity *= sd_info.CardBlockSize;
        sd_info.LogBlockNbr = (sd_info.CardCapacity) / (sd_info.LogBlockSize);
    }

    /* Step 5: Wait for a while. */
    sd_spi_cfg_delay(1000);

    return 0;
}

/**
 * @brief   Get the status of the card.
 * @param   None.
 * @return  0 if OK. 
 */
uint8_t sd_spi_get_status(void)
{
    sd_response_typedef res;

    /* Send CMD13 (_SD_SEND_STATUS) to get SD status */
    res = sd_spi_send_cmd
    (
        _SD_CMD_SEND_STATUS,
        0,
        0xFF,
        _SD_RESPONSE_EXPECTED_R2
    );
    /* Find SD status according to card state */
    if(( res.r1 == _SD_R1_NO_ERROR) && ( res.r2 == _SD_R2_NO_ERROR))
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

/**
 * @brief   Read multiblocks.
 * @param   _pbuf [Out]: pointer to the buffer to save the read data. 
 * @param   _saddr [In]: address from where data is to be read. The address is
 *              counted in blocks of 512bytes.
 * @param   _blknbr [In]: number of the blocks to be read.
 * @return  0 if successful. 
 */
uint8_t sd_spi_read_blocks(uint8_t *_pbuf, uint32_t _saddr, uint32_t _blknbr)
{
    uint8_t retv;
    uint16_t i, j, step;
    sd_response_typedef res;

    if (_saddr + _blknbr >= sd_info.LogBlockNbr)
    {
        return 1;
    }

    /* For CMD17, the address is in block for SDHC,
     * while it is in byte for SDSC. */
    if ((sd_info.Type == _SD_V1X) || (sd_info.Type ==_SD_V2_SC))
    {
        _saddr = _saddr * _SD_BLOCK_SIZE;
        step = _SD_BLOCK_SIZE;
    }
    else
    {
        step = 1;
    }

    /* Send CMD16 (_SD_CMD_SET_BLOCKLEN) to set the size of the block and
     * check if the SD acknowledged the set block length command:
     * R1 response (0x00: no errors) */
    res = sd_spi_send_cmd
    (
        _SD_CMD_SET_BLOCKLEN,
        _SD_BLOCK_SIZE,
        0xFF,
        _SD_RESPONSE_EXPECTED_R1
    );
    if (res.r1 != _SD_R1_NO_ERROR)
    {
        return 2;
    }

    /* Data transfer */
    for (i = 0; i < _blknbr; i++)
    {
        /* Send CMD17 (_SD_CMD_READ_SINGLE_BLOCK) to read one block and
         * check if the SD acknowledged the read block command:
         * R1 response (0x00: no errors) */
        res = sd_spi_send_cmd
        (
            _SD_CMD_READ_SINGLE_BLOCK,
            _saddr,
            0xFF,
            _SD_RESPONSE_EXPECTED_R1
        );
        if (res.r1 != _SD_R1_NO_ERROR)
        {
            return 3;
        }
        /* Now look for the data token to signify the start of the data */
        retv = sd_spi_read_eq(_SD_TOKEN_START_DATA_SINGLE_BLOCK_READ);
        if (retv == _SD_TOKEN_START_DATA_SINGLE_BLOCK_READ)
        {
            /* Read block data */
            for (j = 0; j < _SD_BLOCK_SIZE; j++)
            {
                *_pbuf = sd_spi_cfg_write_read(_SD_DUMMY);
                _pbuf = _pbuf + 1;
            }
            /* Get CRC bytes (not really needed by us, but required by SD) */
            sd_spi_cfg_write_read(_SD_DUMMY);
            sd_spi_cfg_write_read(_SD_DUMMY);
        }
        else
        {
            return 4;
        }
        /* Get address of the next block */
        _saddr = _saddr + step;
    }

    return 0;
}

/**
 * @brief   Write multiblocks.
 * @param   _pbuf [In]: pointer to the buffer to be written to the vard. 
 * @param   _saddr [In]: address from where data is to be written. The address
 *              is counted in blocks of 512bytes.
 * @param   _blknbr [In]: number of the blocks to be written.
 * @return  0 if successful. 
 */
uint8_t sd_spi_write_blocks(uint8_t *_pbuf, uint32_t _saddr, uint32_t _blknbr)
{
    uint8_t retv;
    uint16_t i, j, step;
    sd_response_typedef res;

    if (_saddr + _blknbr >= sd_info.LogBlockNbr)
    {
        return 1;
    }

    /* For CMD17, the address is in block for SDHC,
     * while it is in byte for SDSC. */
    if ((sd_info.Type == _SD_V1X) || (sd_info.Type ==_SD_V2_SC))
    {
        _saddr = _saddr * _SD_BLOCK_SIZE;
        step = _SD_BLOCK_SIZE;
    }
    else
    {
        step = 1;
    }

    /* Send CMD16 (_SD_CMD_SET_BLOCKLEN) to set the size of the block and
     * check if the SD acknowledged the set block length command:
     * R1 response (0x00: no errors) */
    res = sd_spi_send_cmd
    (
        _SD_CMD_SET_BLOCKLEN,
        _SD_BLOCK_SIZE,
        0xFF,
        _SD_RESPONSE_EXPECTED_R1
    );
    if (res.r1 != _SD_R1_NO_ERROR)
    {
        return 2;
    }

    /* Data transfer */
    for (i = 0; i < _blknbr; i++)
    {
        /* Send CMD24 (_SD_CMD_WRITE_SINGLE_BLOCK) to write blocks and
         * check if the SD acknowledged the write block command:
         * R1 response (0x00: no errors) */
        res = sd_spi_send_cmd
        (
            _SD_CMD_WRITE_SINGLE_BLOCK,
            _saddr,
            0xFF,
            _SD_RESPONSE_EXPECTED_R1
        );
        if (res.r1 != _SD_R1_NO_ERROR)
        {
            return 3;
        }
        /* Send dummy byte for NWR timing : 1 byte between CMDWRITE and TOKEN */
        sd_spi_cfg_write_read(_SD_DUMMY);
        sd_spi_cfg_write_read(_SD_DUMMY);
        /* Send the data token to signify the start of the data */
        sd_spi_cfg_write_read(_SD_TOKEN_START_DATA_SINGLE_BLOCK_WRITE);
        /* Write the block data to SD */
        for (j = 0; j < _SD_BLOCK_SIZE; j++)
        {
            sd_spi_cfg_write_read(*_pbuf);
            _pbuf = _pbuf + 1;
        }
        /* Put CRC bytes (not really needed by us, but required by SD) */
        sd_spi_cfg_write_read(_SD_DUMMY);
        sd_spi_cfg_write_read(_SD_DUMMY);
        /* Read data response */
        retv = sd_spi_cfg_write_read(_SD_DUMMY);
        if ((retv & 0x1F) == 0x05)
        {
            /* Wait IO line return 0xFF */
            while (sd_spi_cfg_write_read(_SD_DUMMY) != 0xFF);
        }
        else
        {
            return 4;
        }
        /* Get address of the next block */
        _saddr = _saddr + step;
    }

    return 0;
}

/**
 * @brief   Return the block size.
 * @param   None.
 * @return  Block size of the card. 
 */
uint16_t sd_spi_get_block_size(void)
{
    return _SD_BLOCK_SIZE;
}

/**
 * @brief   Return the number of the blocks.
 * @param   None.
 * @return  Number of the blocks. 
 */
uint32_t sd_spi_get_block_number(void)
{
    return (sd_info.LogBlockNbr);
}

/**
 * @brief   Wait the byte different from specified value.
 * @param   _value [In]: specified value.
 * @retval  Byte read.
 */
static uint8_t sd_spi_read_neq(uint8_t _value)
{
    uint8_t res;
    uint32_t cnt = 0;
    do
    {
        cnt = cnt + 1;
        res = sd_spi_cfg_write_read(_SD_DUMMY);
        if (cnt >= _SD_TRY_MAX)
        {
            return _value;
        }
    } while (res == _value);
    return res;
}

/**
 * @brief   Wait the byte equal to specified value.
 * @param   _value [In]: specified value.
 * @retval  Byte read.
 */
static uint8_t sd_spi_read_eq(uint8_t _value)
{
    uint8_t res;
    uint16_t cnt = 0;
    do
    {
        cnt = cnt + 1;
        res = sd_spi_cfg_write_read(_SD_DUMMY);
        if (cnt >= _SD_TRY_MAX)
        {
            return (~_value);
        }
    } while (res != _value);
    return res;
}

/**
 * @brief   Sends 5 bytes command to the SD card and get response.
 * @param   _cmd [In]: the user expected command to send to SD card.
 * @param   _arg [In]: the command argument.
 * @param   _crc [In]: the CRC.
 * @param   _expr [In]: expected response.
 * @retval  Response of the card.
 */
static sd_response_typedef sd_spi_send_cmd
(
    uint8_t _cmd,
    uint32_t _arg,
    uint8_t _crc,
    uint8_t _expr
)
{ 
    uint8_t frame[_SD_CMD_LENGTH], tmp;
    sd_response_typedef res = {0xFF, 0xFF , 0xFF, 0xFF, 0xFF};

    /* R1 Length = NCS(0) + 6-B CMD + NCR(1~8) + 1-B response + NEC(0) = 15B */
    /* R1b identical to R1 + Busy information */
    /* R2 Length = NCS(0) + 6-B CMD + NCR(1~8) + 2-B response + NEC(0) = 16B */

    /* Prepare Frame to send. */
    frame[0] = (_cmd | 0x40);         /* Construct byte 1 */
    frame[1] = (uint8_t)(_arg >> 24); /* Construct byte 2 */
    frame[2] = (uint8_t)(_arg >> 16); /* Construct byte 3 */
    frame[3] = (uint8_t)(_arg >> 8);  /* Construct byte 4 */
    frame[4] = (uint8_t)(_arg);       /* Construct byte 5 */
    frame[5] = (_crc | 0x01);         /* Construct byte 6 */

    /* Send the command. */
    for (tmp = 0; tmp < _SD_CMD_LENGTH; tmp++)
    {
        sd_spi_cfg_write_read(frame[tmp]);
    }

    /* Receive the response. */
    switch (_expr)
    {
    case _SD_RESPONSE_EXPECTED_R1:
        res.r1 = sd_spi_read_neq(_SD_DUMMY);
        break;
    case _SD_RESPONSE_EXPECTED_R1B:
        res.r1 = sd_spi_read_neq(_SD_DUMMY);
        res.r2 = sd_spi_cfg_write_read(_SD_DUMMY);
        sd_spi_cfg_delay(10);
        /* Wait IO line return 0xFF */
        do
        {
            tmp = sd_spi_cfg_write_read(_SD_DUMMY);
        } while (tmp != 0xFF);
        break;
    case _SD_RESPONSE_EXPECTED_R2:
        res.r1 = sd_spi_read_neq(_SD_DUMMY);
        res.r2 = sd_spi_cfg_write_read(_SD_DUMMY);
        break;
    case _SD_RESPONSE_EXPECTED_R3:
    case _SD_RESPONSE_EXPECTED_R7:
        res.r1 = sd_spi_read_neq(_SD_DUMMY);
        res.r2 = sd_spi_cfg_write_read(_SD_DUMMY);
        res.r3 = sd_spi_cfg_write_read(_SD_DUMMY);
        res.r4 = sd_spi_cfg_write_read(_SD_DUMMY);
        res.r5 = sd_spi_cfg_write_read(_SD_DUMMY);
        break;
    default:
        break;
    }

    sd_spi_cfg_write_read(_SD_DUMMY);

    return res;
}

/**
 * @brief   Get CSD register.
 * @param   _csd [Out]: pointer to the structure to of CSD register.
 * @return  0 if successful. 
 */
static uint8_t sd_spi_get_csd(sd_csd_typedef* _csd)
{
    sd_response_typedef res;
    volatile uint8_t cnt = 0;
    uint8_t retv, csd_tab[16];

    /* Send CMD9 (CSD register) and wait for response
     * in the R1 format (0x00 is no errors) */
    res = sd_spi_send_cmd(_SD_CMD_SEND_CSD, 0, 0xFF, _SD_RESPONSE_EXPECTED_R1);
    if(res.r1 == _SD_R1_NO_ERROR)
    {
        retv = sd_spi_read_eq(_SD_TOKEN_START_DATA_SINGLE_BLOCK_READ);
        if (retv == _SD_TOKEN_START_DATA_SINGLE_BLOCK_READ)
        {
            for (cnt = 0; cnt < 16; cnt++)
            {
                /* Store CSD register value on csd_tab */
                csd_tab[cnt] = sd_spi_cfg_write_read(_SD_DUMMY);
            }
            /* Get CRC bytes (not really needed by us, but required by SD) */
            sd_spi_cfg_write_read(_SD_DUMMY);
            sd_spi_cfg_write_read(_SD_DUMMY);
            /* CSD header decoding */
            /* Byte 0 */
            _csd->CSDStruct = (csd_tab[0] & 0xC0) >> 6;
            _csd->Reserved1 =  csd_tab[0] & 0x3F;
            /* Byte 1 */
            _csd->TAAC = csd_tab[1];
            /* Byte 2 */
            _csd->NSAC = csd_tab[2];
            /* Byte 3 */
            _csd->MaxBusClkFrec = csd_tab[3];
            /* Byte 4/5 */
            _csd->CardComdClasses =
                (csd_tab[4] << 4) | ((csd_tab[5] & 0xF0) >> 4);
            _csd->RdBlockLen = csd_tab[5] & 0x0F;
            /* Byte 6 */
            _csd->PartBlockRead   = (csd_tab[6] & 0x80) >> 7;
            _csd->WrBlockMisalign = (csd_tab[6] & 0x40) >> 6;
            _csd->RdBlockMisalign = (csd_tab[6] & 0x20) >> 5;
            _csd->DSRImpl         = (csd_tab[6] & 0x10) >> 4;
            /* CSD v1/v2 decoding */
            if(sd_info.Type != _SD_V2_HC)
            {
                /* CSD V1: SD V1.X, or SD V2.00 SC */
                _csd->PartCSD.v1.Reserved1 = ((csd_tab[6] & 0x0C) >> 2);
                _csd->PartCSD.v1.DeviceSize =
                    ((csd_tab[6] & 0x03) << 10) | (csd_tab[7] << 2)
                    | ((csd_tab[8] & 0xC0) >> 6);
                _csd->PartCSD.v1.MaxRdCurrentVDDMin =
                    (csd_tab[8] & 0x38) >> 3;
                _csd->PartCSD.v1.MaxRdCurrentVDDMax = (csd_tab[8] & 0x07);
                _csd->PartCSD.v1.MaxWrCurrentVDDMin =
                    (csd_tab[9] & 0xE0) >> 5;
                _csd->PartCSD.v1.MaxWrCurrentVDDMax =
                    (csd_tab[9] & 0x1C) >> 2;
                _csd->PartCSD.v1.DeviceSizeMul =
                    ((csd_tab[9] & 0x03) << 1) | ((csd_tab[10] & 0x80) >> 7);
            }
            else
            {
                /* CSD V2: SD V2.00 HC */
                _csd->PartCSD.v2.Reserved1 =
                    ((csd_tab[6] & 0x0F) << 2) | ((csd_tab[7] & 0xC0) >> 6);
                _csd->PartCSD.v2.DeviceSize =
                    ((csd_tab[7] & 0x3F) << 16) | (csd_tab[8] << 8)
                    | csd_tab[9];
                _csd->PartCSD.v2.Reserved2 = ((csd_tab[10] & 0x80) >> 8);
            }
            _csd->EraseSingleBlockEnable = (csd_tab[10] & 0x40) >> 6;
            _csd->EraseSectorSize   =
                ((csd_tab[10] & 0x3F) << 1) | ((csd_tab[11] & 0x80) >> 7);
            _csd->WrProtectGrSize   = (csd_tab[11] & 0x7F);
            _csd->WrProtectGrEnable = (csd_tab[12] & 0x80) >> 7;
            _csd->Reserved2         = (csd_tab[12] & 0x60) >> 5;
            _csd->WrSpeedFact       = (csd_tab[12] & 0x1C) >> 2;
            _csd->MaxWrBlockLen     =
                ((csd_tab[12] & 0x03) << 2) | ((csd_tab[13] & 0xC0) >> 6);
            _csd->WriteBlockPartial = (csd_tab[13] & 0x20) >> 5;
            _csd->Reserved3         = (csd_tab[13] & 0x1F);
            _csd->FileFormatGrouop  = (csd_tab[14] & 0x80) >> 7;
            _csd->CopyFlag          = (csd_tab[14] & 0x40) >> 6;
            _csd->PermWrProtect     = (csd_tab[14] & 0x20) >> 5;
            _csd->TempWrProtect     = (csd_tab[14] & 0x10) >> 4;
            _csd->FileFormat        = (csd_tab[14] & 0x0C) >> 2;
            _csd->Reserved4         = (csd_tab[14] & 0x03);
            _csd->CRC               = (csd_tab[15] & 0xFE) >> 1;
            _csd->Reserved5         = (csd_tab[15] & 0x01);
        }
        else
        {
            return 1;
        }
    }
    else
    {
        return 2;
    }
    
    return 0;
}

/**
 * @brief   Get CID register.
 * @param   _cid [Out]: pointer to the structure to of CID register.
 * @return  0 if successful. 
 */
static uint8_t sd_spi_get_cid(sd_cid_typedef* _cid)
{
    sd_response_typedef res;
    volatile uint8_t cnt = 0;
    uint8_t retv, cid_tab[16];

    /* Send CMD10 (CID register) and wait for response
     * in the R1 format (0x00 is no errors) */
    res = sd_spi_send_cmd(_SD_CMD_SEND_CID, 0, 0xFF, _SD_RESPONSE_EXPECTED_R1);
    if(res.r1 == _SD_R1_NO_ERROR)
    {
        retv = sd_spi_read_eq(_SD_TOKEN_START_DATA_SINGLE_BLOCK_READ);
        if(retv == _SD_TOKEN_START_DATA_SINGLE_BLOCK_READ)
        {
            /* Store CID register value on cid_tab */
            for (cnt = 0; cnt < 16; cnt++)
            {
                cid_tab[cnt] = sd_spi_cfg_write_read(_SD_DUMMY);
            }
            /* Get CRC bytes (not really needed by us, but required by SD) */
            sd_spi_cfg_write_read(_SD_DUMMY);
            sd_spi_cfg_write_read(_SD_DUMMY);
            /* Byte 0 */
            _cid->ManufacturerID = cid_tab[0];
            /* Byte 1 */
            _cid->OEM_AppliID = cid_tab[1] << 8;
            /* Byte 2 */
            _cid->OEM_AppliID |= cid_tab[2];
            /* Byte 3 */
            _cid->ProdName1 = cid_tab[3] << 24;
            /* Byte 4 */
            _cid->ProdName1 |= cid_tab[4] << 16;
            /* Byte 5 */
            _cid->ProdName1 |= cid_tab[5] << 8;
            /* Byte 6 */
            _cid->ProdName1 |= cid_tab[6];
            /* Byte 7 */
            _cid->ProdName2 = cid_tab[7];
            /* Byte 8 */
            _cid->ProdRev = cid_tab[8];
            /* Byte 9 */
            _cid->ProdSN = cid_tab[9] << 24;
            /* Byte 10 */
            _cid->ProdSN |= cid_tab[10] << 16;
            /* Byte 11 */
            _cid->ProdSN |= cid_tab[11] << 8;
            /* Byte 12 */
            _cid->ProdSN |= cid_tab[12];
            /* Byte 13 */
            _cid->Reserved1 |= (cid_tab[13] & 0xF0) >> 4;
            _cid->ManufactDate = (cid_tab[13] & 0x0F) << 8;
            /* Byte 14 */
            _cid->ManufactDate |= cid_tab[14];
            /* Byte 15 */
            _cid->CID_CRC = (cid_tab[15] & 0xFE) >> 1;
            _cid->Reserved2 = 1;
        }
        else
        {
            return 1;
        }
    }
    else
    {
        return 2;
    }

    return 0;
}

/**
 * @}
 */


/**************************** ALL RIGHTS RESERVED *****************************/

4. FatFS移植

FatFS的配置文件由用户自行定义和修改,相关API建议先参看函数说明再使用(如f_mount函数中例如盘符的命名、f_mkfs中缓存的要求等)。以下给出移植主要需要修改的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 "sd_spi.h"

/* Definitions of physical drive number for each drive */
#define DEV_SD    0


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

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive number to identify the drive */
)
{
  DSTATUS stat;
	int result;

	switch (pdrv)
	{
	case DEV_SD:
	  result = sd_spi_get_status();
	  if (result == 0)
	  {
	    stat = 0;
	  }
	  else
	  {
	    stat = STA_NOINIT;
	  }
	  break;
	default:
	  stat = STA_NODISK;
	  break;
	}

	return stat;
}



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

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive number to identify the drive */
)
{
	DSTATUS stat;
	int result;

  switch (pdrv)
  {
  case DEV_SD:
    result = sd_spi_init();
    if (result == 0)
    {
      stat = 0;
    }
    else
    {
      stat = STA_NOINIT;
    }
    break;
  default:
    stat = STA_NODISK;
    break;
  }

	return stat;
}



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

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive number 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 */
)
{
	DRESULT res;
	int result;

  switch (pdrv)
  {
  case DEV_SD:
    result = sd_spi_read_blocks(buff, sector, count);
    if (result == 0)
    {
      res = RES_OK;
    }
    else
    {
      res = RES_ERROR;
    }
    break;
  default:
    res = RES_PARERR;
    break;
  }

  return res;
}



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

#if FF_FS_READONLY == 0

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive number 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 */
)
{
	DRESULT res;
	int result;

  switch (pdrv)
  {
  case DEV_SD:
    result = sd_spi_write_blocks((uint8_t *)buff, sector, count);
    if (result == 0)
    {
      res = RES_OK;
    }
    else
    {
      res = RES_ERROR;
    }
    break;
  default:
    res = RES_PARERR;
    break;
  }

  return res;
}

#endif


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

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

  switch (pdrv)
  {
  case DEV_SD:
    switch (cmd)
    {
    case GET_SECTOR_SIZE:
      *((WORD *)buff) = sd_spi_get_block_size();
      break;
    case GET_SECTOR_COUNT:
      *((LBA_t *)buff) = sd_spi_get_block_number();
      break;
    case GET_BLOCK_SIZE:
      *((WORD *)buff) = 1;
      break;
    default:
      break;
    }
    res = RES_OK;
    break;
  default:
    res = RES_PARERR;
    break;
  }

  return res;
}