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

作者: 青风
上传时间为: 2013-02-19 10:47 PM

第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卡块操作 */ 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寄存器相关操作 */ 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);

/*!<SD卡字节操作 */ 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卡操作代码,我的任务就算完成了。谢谢大家指正。

全部评论 ()

创建讨论帖子

登录 后参与评论
系统提示