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