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;
}
Provided by 昨夜三更雨, see https://www.cnblogs.com/zysgy/p/16820254.html