分享

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

 weikong66 2013-03-28
【青风带你学stm32f051系列教程】第13课 通过SPI读写SD卡
2013年02月19日 ? 教程 ? 暂无评论 ? 被围观 667+

第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卡测试主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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卡的初始化,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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,下面对其进行设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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包含的信息,判断信息序列是否正确,可以按照下面方式进行编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
* @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

1
2
3
4
5
6
7
{
 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文件中,我们需要对这些命令进行定义,这样在操作函数中可以直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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卡的操作可以分为下面三种类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*!<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个字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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卡的应答结构如下图所示:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
 * @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进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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卡操作代码,我的任务就算完成了。谢谢大家指正。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多