IT技术互动交流平台

青风stm32f051系列教程:第13课 通过SPI读写SD卡

作者:青风  发布日期:2013-03-19 21:33:26

很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有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卡测试主函数:


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");
 }
}

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

 

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());
}

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

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


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 */
}

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

 

* @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;
}

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

typedef struct


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

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

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

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


#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 */

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

 

/*!<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);

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

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

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


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 */
 }
}

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

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


/**
 * @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;
}

主函数对SD进行测试:

 

#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");
 }
}

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

Tag标签: 青风   stm32f051   SPI   SD卡  
  • 专题推荐

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规