×
嵌入式 > 技术百科 > 详情

【青风带你学stm32f051系列教程】第13课 通过SPI读写SD卡

发布时间:2020-06-17 发布时间:
|

第13课 通过SPI读写SD卡

很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有U盘,FLASH芯片,SD卡等。他们各有优点,综合比较,最适合单片机系统的莫过于SD卡了,它不仅容量可以做到很大(32Gb以上),而且支持SPI接口,方便移动,有几种体积的尺寸可供选择(标准的SD卡尺寸,以及TF卡尺寸),能满足不同应用的要求。只需要4个IO口,就可以外扩一个最大达32GB以上的外部存储器,容量选择尺度很大,更换也很方便,而且方便移动,编程也比较简单,是单片机大容量外部存储器的首选。SD卡(Secure Digital Memory Card)中文翻译为安全数码卡,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。Sd卡的通信接口这里采用的是SPI,SPI接口的使用在前面读写W25X16时已经有了分析,这里来讨论下使用SPI来读写SD卡。

本节内容没有加入文件系统,直接采用SPI读写SD卡,从软硬件两个方面来学习如何配置:

硬件准备:

开发板的SD卡设置在液晶转接板上,其硬件电路如下图所示:

在主板上的TFT接口上分配了4个端口给SD卡读写:

其端口配置入下所示:

硬件连接:

SD_DIN---PB15

SD_OUT---PB14

SD_SCK---PB13

SD_CS---PB12

其中SD_CS:sd卡片选管脚,低电平有效

SD_SCK:sd卡的时钟管脚

SD_DIN:sd卡的spi输入管脚。

SD_OUT:sd 卡的spi输出管脚。

软件准备:

打开keil编译环境,设置系统工程树如下图所示:

如上所示,用户需要编写SD卡驱动函数,首先我们先来看看一个简单的SD卡测试主函数:

[c]

SD_Error Status = SD_RESPONSE_NO_ERROR ;
SD_CardInfo SDCardInfo;

int main (void)
{
SystemInit();
LCD_init(); // 液晶显示器初始化
LCD_Clear(ORANGE); // 全屏显示白色
POINT_COLOR =BLACK; // 定义笔的颜色为黑色
BACK_COLOR = WHITE ; // 定义笔的背景色为白色
/*-------------------------- SD Init ----------------------------- */
Status = SD_Init();

if (Status == SD_RESPONSE_NO_ERROR )
{
/*----------------- Read CSD/CID MSD registers ------------------*/
LCD_ShowString(20,20, "SD_Init is ok");
Status = SD_GetCardInfo(&SDCardInfo);
}
else
{
LCD_ShowString(20,20, "SD_Init is error");
}
}

[/c]

函数首先对SD卡进行了初始化,调用了sd卡初始化代码SD_Init(),然后读取sd卡信息状态。首先我们来看看SD卡的初始化,代码如下:

[c]

SD_Error SD_Init(void)
{
uint32_t i = 0;

/*!< 初始化SD_SPI */
SD_SPI_Init();

/*!< SD 片选写高 */
SD_CS_HIGH();

/*!< 发送无效字节0xFF, CS 至高10 */
/*!< CS和MOSI上升为80个时钟周期*/
for (i = 0; i <= 9; i++)
{
/*!< Send dummy byte 0xFF */
SD_WriteByte(SD_DUMMY_BYTE);
}

/*------------Put SD in SPI mode--------------*/
/*!< SD initialized and set to SPI mode properly */
return (SD_GoIdleState());
}

[/c]

SD卡的初始化,首先要对SD卡的硬件接口进行设置,SD卡采用SPI2,接口为PB12--PB15

采用器复用功能0,下面对其进行设置:

[c]

void SD_SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;

/*!< 初始化SD卡使用的IO端口的时钟 */
RCC_AHBPeriphClockCmd(SD_CS_GPIO_CLK | SD_SPI_MOSI_GPIO_CLK | SD_SPI_MISO_GPIO_CLK |SD_SPI_SCK_GPIO_CLK , ENABLE);

/*!< SD_SPI 外设时钟使能 */
RCC_APB1PeriphClockCmd(SD_SPI_CLK, ENABLE);

/*!< 配置SD_SPI 管脚: SCK */
GPIO_InitStructure.GPIO_Pin = SD_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(SD_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);

/*!< 配置 SD_SPI 管脚: MISO */
GPIO_InitStructure.GPIO_Pin = SD_SPI_MISO_PIN;
GPIO_Init(SD_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);

/*!< 配置 SD_SPI 管脚: MOSI */
GPIO_InitStructure.GPIO_Pin = SD_SPI_MOSI_PIN;
GPIO_Init(SD_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);

/*!<配置 SD_SPI_CS_PIN 管脚: SD Card CS pin */
GPIO_InitStructure.GPIO_Pin = SD_CS_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SD_CS_GPIO_PORT, &GPIO_InitStructure);

/*配置SPI复用*/
GPIO_PinAFConfig(SD_SPI_SCK_GPIO_PORT, SD_SPI_SCK_SOURCE, SD_SPI_SCK_AF);
GPIO_PinAFConfig(SD_SPI_MISO_GPIO_PORT, SD_SPI_MISO_SOURCE, SD_SPI_MISO_AF);
GPIO_PinAFConfig(SD_SPI_MOSI_GPIO_PORT, SD_SPI_MOSI_SOURCE, SD_SPI_MOSI_AF);

/*!< SD_SPI配置参数 */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SD_SPI, &SPI_InitStructure);

SPI_RxFIFOThresholdConfig(SD_SPI, SPI_RxFIFOThreshold_QF);

SPI_Cmd(SD_SPI, ENABLE); /*!< SD_SPI enable */
}

[/c]

然后编写SD卡信息检测函数,依次检测sd包含的信息,判断信息序列是否正确,可以按照下面方式进行编写:

[c]

* @brief 返回有关特定卡的信息
* @param cardinfo: pointer to a SD_CardInfo structure that contains all SD
* card information.
* @retval The SD Response:
* - SD_RESPONSE_FAILURE: Sequence failed
* - SD_RESPONSE_NO_ERROR: Sequence succeed
*/
SD_Error SD_GetCardInfo(SD_CardInfo *cardinfo)
{
SD_Error status = SD_RESPONSE_FAILURE;

SD_GetCSDRegister(&(cardinfo->SD_csd));
status = SD_GetCIDRegister(&(cardinfo->SD_cid));
cardinfo->CardCapacity = (cardinfo->SD_csd.DeviceSize + 1) ;
cardinfo->CardCapacity *= (1 << (cardinfo->SD_csd.DeviceSizeMul + 2));
cardinfo->CardBlockSize = 1 << (cardinfo->SD_csd.RdBlockLen);
cardinfo->CardCapacity *= cardinfo->CardBlockSize;

/*!< Returns the reponse */
return status;
}

[/c]

大家注意,SD卡的整个信息,我们在sd.h中采用一个结构体表示SD_CardInfo来表示:

typedef struct

[c]

{
SD_CSD SD_csd; /*!< 卡的具体数据 */
SD_CID SD_cid; /*!< 存储卡标识数据 */
uint32_t CardCapacity; /*!< 卡片容量 */
uint32_t CardBlockSize; /*!< 卡的块大小 */
} SD_CardInfo;

[/c]

这个结构体中的成员SD_CSD SD_csd,SD_CID SD_cid我们也写成结构体的类型,这里表示了SD卡的几个重要信息。其详细定义可以在文件 《SD卡协议(物理层)》中找到详细说明,这里就不再罗嗦了。

SD卡给出一些基本操作命令,我们列出部分如下表所示:

在SD.H文件中,我们需要对这些命令进行定义,这样在操作函数中可以直接使用:

[c]

#define SD_CMD_GO_IDLE_STATE 0 /*!< CMD0 = 0x40 */
#define SD_CMD_SEND_OP_COND 1 /*!< CMD1 = 0x41 */
#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 */

[/c]

完成这些定义之后,我们就就来编写SD卡的操作函数了。根据《SD卡协议(物理层)》文件中的说明,SD卡的操作可以分为下面三种类型:

[c]

/*! SD_Error SD_ReadBlock(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t BlockSize);
SD_Error SD_ReadMultiBlocks(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks);
SD_Error SD_WriteBlock(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t BlockSize);
SD_Error SD_WriteMultiBlocks(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks);

/*! SD_Error SD_GetCSDRegister(SD_CSD* SD_csd);
SD_Error SD_GetCIDRegister(SD_CID* SD_cid);
void SD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc);
SD_Error SD_GetResponse(uint8_t Response);
uint8_t SD_GetDataResponse(void);
SD_Error SD_GoIdleState(void);
uint16_t SD_GetStatus(void);

/*! uint8_t SD_WriteByte(uint8_t byte);
uint8_t SD_ReadByte(void);

[/c]

下面我们来举其中一个例子, 从SD卡读取块数据,首先我们需要详细阅读《SD卡协议(物理层)》,文件中给出了读取块数据的基本操作步骤如下图所示:

首先要发送读取命令,sd卡应答无错误后开始传输数据,数据传输结束后再返回结束应答。基本就这3步。

根据这个方式编写发送命令函数,含6个字节:

[c]

void SD_SendCmd(uint8_t Cmd, uint32_t Arg, uint8_t Crc)
{
uint32_t i = 0x00;

uint8_t Frame[6];

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); /*!< Construct CRC: byte 6 */

for (i = 0; i < 6; i++)
{
SD_WriteByte(Frame[i]); /*!< Send the Cmd bytes */
}
}

[/c]

Sd卡的应答结构如下图所示:

因此根据上面所分析的三个步骤,读单个块数据的子函数编写代码如下图所示:

[c]

/**
* @brief 从SD卡读取块数据.
* @param pBuffer:指向从sd卡读出的接收数据缓冲指针
* @param ReadAddr:sd卡读取的内部地址.
* @param BlockSize: sd卡块的大小.
* @retval The SD Response:
* - SD_RESPONSE_FAILURE: Sequence failed
* - SD_RESPONSE_NO_ERROR: Sequence succeed
*/
SD_Error SD_ReadBlock(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t BlockSize)
{
uint32_t i = 0;
SD_Error rvalue = SD_RESPONSE_FAILURE;

/*!< SD 片选置低*/
SD_CS_LOW();

/*!< 发送命令CMD17 (SD_CMD_READ_SINGLE_BLOCK) 去读取一个块 */
SD_SendCmd(SD_CMD_READ_SINGLE_BLOCK, ReadAddr, 0xFF);

/*!< 监测SD卡识别读块命令: R1 response (0x00: no errors) */
if (!SD_GetResponse(SD_RESPONSE_NO_ERROR))
{
/*!< 标示数据传送开始 */
if (!SD_GetResponse(SD_START_DATA_SINGLE_BLOCK_READ))
{
/*!< 读取SD卡块数据 */
for (i = 0; i < BlockSize; i++)
{
/*!保存接收数据值缓冲 */
*pBuffer = SD_ReadByte();
pBuffer++;
}
/*!< Get CRC bytes (not really needed by us, but required by SD) */
SD_ReadByte();
SD_ReadByte();
/*!< 设置相应成功*/
rvalue = SD_RESPONSE_NO_ERROR;
}
}
/*!< SD 片选为高 */
SD_CS_HIGH();

/*!< 发送空字节: 8个时钟脉冲延迟 */
SD_WriteByte(SD_DUMMY_BYTE);

/*!< 返回相应 */
return rvalue;
}

[/c]

主函数对SD进行测试:

[c]

#include "stm32f0xx.h"
#include "sd.h"
#include "ili9328.h"
SD_Error Status = SD_RESPONSE_NO_ERROR ;
SD_CardInfo SDCardInfo;

int main (void)
{
SystemInit();
LCD_init(); // 液晶显示器初始化
LCD_Clear(ORANGE); // 全屏显示白色
POINT_COLOR =BLACK; // 定义笔的颜色为黑色
BACK_COLOR = WHITE ; // 定义笔的背景色为白色
/*-------------------------- SD Init ----------------------------- */
Status = SD_Init();

if (Status == SD_RESPONSE_NO_ERROR )
{
/*----------------- Read CSD/CID MSD registers ------------------*/
LCD_ShowString(20,20, "SD_Init is ok");
Status = SD_GetCardInfo(&SDCardInfo);
}
else
{
LCD_ShowString(20,20, "SD_Init is error");
}
}

[/c]

这里面就简要的举了一个读取SD块的例子,整个SD卡的操作要严格按照其协议规定的时序进行书写,每个SD卡的操作都有相应的操作命令,大家自己编写代码的时候需要参考《SD卡协议(物理层)》文件,在这里大家弄懂了我们怎么更加SD卡协议书写SD卡操作代码,我的任务就算完成了。谢谢大家指正。


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
NTMD6N03R2G的技术参数