STM32 TFT学习笔记——SD卡读写

主机环境:Windows 7 SP1

开发环境:MDK5.14

目标板:ST NUCLEO-F303RE

TFT型号:2.4英寸,带触摸,SD卡,240*320分辨率,26万色

驱动IC:ILI9325

ST库版本:STM32Cube_FW_F3_V1.1.0

SD卡:Kingston 16GB Micro SDHC Class 10


本TFT模块是带有SD卡插槽的,之前连线一直没接,现在可以使用了,对于该TFT模块来说一副图片需要的空间为150K(240*320*2),如果图片都存入FALSH空间肯定是存放不了多少图片资源的,因此我们可以把图片资源存入SD卡中等到需要时再从SD卡读取来显示,这样只要SD卡空间够大我们就可以显示很多图片资源了,因此来研究看SD卡的读写吧,这里是没有为SD卡做文件系统的,那个是以后的事情了。

对于SD卡的操作跟LCD类似,EVAL的固件库里面同样有写好了的SD库函数stm32303e_eval_sd.c/h,如下

STM32 TFT学习笔记——SD卡读写_第1张图片

基于SD卡资料文档配合库函数就可以完成我们SD卡的操作了,由于NUCLEO-F303RE的SPI1接口中的MOSI脚被用于LED2的操作,因此这里使用IO口模拟SPI通信来对SD卡进行操作,接口声明如下

#define SPI_SDCARD_nCS_GPIO_CLK_ENABLE()	__GPIOC_CLK_ENABLE()
#define SPI_SDCARD_SCK_GPIO_CLK_ENABLE()	__GPIOC_CLK_ENABLE()
#define SPI_SDCARD_MISO_GPIO_CLK_ENABLE()	__GPIOC_CLK_ENABLE()
#define SPI_SDCARD_MOSI_GPIO_CLK_ENABLE()	__GPIOC_CLK_ENABLE()

#define SPI_SDCARD_SCK_PIN                 	GPIO_PIN_6
#define SPI_SDCARD_SCK_GPIO_PORT           	GPIOC
#define SPI_SDCARD_MISO_PIN                	GPIO_PIN_8
#define SPI_SDCARD_MISO_GPIO_PORT          	GPIOC

#define SPI_SDCARD_MOSI_PIN                	GPIO_PIN_5
#define SPI_SDCARD_MOSI_GPIO_PORT          	GPIOC


#define SPI_SDCARD_nCS_PIN					GPIO_PIN_9
#define SPI_SDCARD_nCS_GPIO_PORT			GPIOC

#define SPI_SDCARD_nCS_Set_Low()			(GPIOC->BRR = GPIO_PIN_9)
#define SPI_SDCARD_nCS_Set_High()			(GPIOC->BSRRL = GPIO_PIN_9)

#define SPI_SDCARD_SCK_Set_Low()			{__NOP();__NOP();	\
											__NOP();__NOP();	\
											GPIOC->BRR = GPIO_PIN_6;	\
											__NOP();__NOP();	\
											__NOP();__NOP();}

#define SPI_SDCARD_SCK_Set_High()			{__NOP();__NOP();	\
											__NOP();__NOP();	\
											GPIOC->BSRRL = GPIO_PIN_6;	\
											__NOP();__NOP();}

#define SPI_SDCARD_MOSI_Set_Low()			(GPIOC->BRR = GPIO_PIN_5)
#define SPI_SDCARD_MOSI_Set_High()			(GPIOC->BSRRL = GPIO_PIN_5)

#define SPI_SDCARD_MISO_Read()				(GPIOC->IDR & GPIO_PIN_8)

SD卡是有9个引脚的,分配如下


当使用SPI模式时我们只用到了4个引脚,CLK、CS、DataIn、DataOut


SD里面是有一个接口控制器的,如下图

STM32 TFT学习笔记——SD卡读写_第2张图片

接口里面有7个寄存器CID、RCA、SCD、SCR、OCR、CSR(Card Status)、SSR(SD Status)。


但是这些寄存器在不同的模式下有些许不同,大体上是一致的,SPI模式下RCA寄存器是不支持的。上面5个寄存器的内容有很多,也很重要,文档提了一大坨,用到时再来看吧。

SD卡系统特性如下


时钟限制在0~25MHz,电压限制在2.0~3.6V,一般是使用3.3V供电电压。在使用SPI通信时数据在CLK的上升沿锁存,CLK空闲状态是低电平,MOSI在空闲状态下为1

STM32 TFT学习笔记——SD卡读写_第3张图片

SDCard的通讯协议分三块Command、Response、Data-block,形式如下:

STM32 TFT学习笔记——SD卡读写_第4张图片

发送一个命令后SD卡的控制器回返回一个response,之后传输数据且带有CRC校验码。但是SPI模式下CRC校验默认是禁止的。

SD卡上电初始化时序

STM32 TFT学习笔记——SD卡读写_第5张图片

上电时需要74个时钟周期来稳定。SD卡在上电后默认是SD模式,要想使用SPI模式需要发送命令给SD卡的控制器来进行模式切换

STM32 TFT学习笔记——SD卡读写_第6张图片

即上电之后需要发送CMD0命令来实现模式的切换,文中提到虽然SPI模式下CRC校验是禁止的,但是CMD0命令的发送还是要带上正确的7位CRC校验值0x4A,毕竟此时还不是SPI模式,发送CMD0命令完毕后,SD卡会进入SPI模式同时会返回一个R1 response(0x01)。SD卡是支持单block的读写和多blocks读写。之前已经看到了单block的读操作,现在看以下多block的读操作:


多block读和单block读基本相似,唯一不同的是多block读在最后需要发送一个Stop命令(CMD12),再来看以下写操作


写操作与读操作类似,但是读操作的频率是大于写操作的频率,因为数据一般都在SD卡中写好了的。写操作需要注意的是数据写完之后MCU需要去发送SEND_STATUS命令CMD13来检测数据写入是否正确。此外还有读取CID/CSD寄存器命令,SPI模式下与SD模式下读取寄存器是不一样的。


在SPI模式下读取寄存器跟读取block数据是一致的。

SPI通信还需要注意以下几点:

STM32 TFT学习笔记——SD卡读写_第7张图片

即所有操作完毕后,MCU仍需要提供8个clock给SD控制器以便其在MCU停止时钟之前完成其操作。以上是SD卡在SPI模式下的操作流程,下面就说说其命令格式


命令共有6个字节,且以MSB形式传输,第一个字节为Command后面会提到命令列表,bit[7:6]=01b固定,即Command只占用了6个字节,即最多有64个命令;中间4个字节为命令参数(Command Argument)命令不需要参数时该值为0,最后一个字节为CRC值,bit[0]=1b固定,不使用CRC时写1即可,前面提到使SD卡进入SPI模式是发送CMD1其CRC值为0x4A(1001010b),再加上bit[0]=1即为10010101b,CMD1不需要参数,因此具体发送的数据为0x40 0x00 0x00 0x00 0x00 0x95。文档中亦给出了CRC的计算方式


由于SPI模式下CRC默认禁止了就没去仔细研究这个,以后用到了再细看吧。SD卡的命令分为了10类:class0-class9

命令列表如下


由于命令列表比较长,意思一下就行了。用到的时候再查文档就可以了。

来看以下SPI的回码R1

STM32 TFT学习笔记——SD卡读写_第8张图片

R1是用于响应Command(除了SEND_STATUS之外),最高位固定为0,response还有R1b、R2、R3,此外还有data response如下

STM32 TFT学习笔记——SD卡读写_第9张图片

data token如下

STM32 TFT学习笔记——SD卡读写_第10张图片

还有data err token等,这里就不细说了,不然太冗长了。。。,开始对比EVAL的库函数以及文档来编辑我们的SD卡操作,首先是SPI的驱动函数

由于是模拟spi因此只需要初始化io口就够了

/**********************************************************************
函数:HAL_SPI_SDCARD_MspInit()
函数作用:sd卡spi资源初始化
参数:无
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-02
说明: SD卡通讯接口为模拟SPI
**********************************************************************/
void HAL_SPI_SDCARD_MspInit(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	SPI_SDCARD_SCK_GPIO_CLK_ENABLE();
	SPI_SDCARD_MISO_GPIO_CLK_ENABLE();
	SPI_SDCARD_MOSI_GPIO_CLK_ENABLE();
	SPI_SDCARD_nCS_GPIO_CLK_ENABLE();

	GPIO_InitStruct.Pin 	  = SPI_SDCARD_SCK_PIN;
	GPIO_InitStruct.Mode      = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull      = GPIO_PULLUP;
	GPIO_InitStruct.Speed     = GPIO_SPEED_HIGH;
	HAL_GPIO_Init(SPI_SDCARD_SCK_GPIO_PORT, &GPIO_InitStruct);

	GPIO_InitStruct.Pin 	  = SPI_SDCARD_MOSI_PIN;
	HAL_GPIO_Init(SPI_SDCARD_MOSI_GPIO_PORT, &GPIO_InitStruct);

	GPIO_InitStruct.Pin 	  = SPI_SDCARD_nCS_PIN;
	HAL_GPIO_Init(SPI_SDCARD_nCS_GPIO_PORT, &GPIO_InitStruct);

	GPIO_InitStruct.Pin 	  = SPI_SDCARD_MISO_PIN;
	GPIO_InitStruct.Mode	  = GPIO_MODE_INPUT;
	HAL_GPIO_Init(SPI_SDCARD_MISO_GPIO_PORT, &GPIO_InitStruct);
	
}

其读、写、初始化操作如下

/**********************************************************************
函数:SPI_SDCARD_Init()
函数作用:sd卡spi初始化
参数:无
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-02
说明: SD卡通讯接口为模拟SPI
**********************************************************************/
void SPI_SDCARD_Init(void)
{
	HAL_SPI_SDCARD_MspInit();
	
	SPI_SDCARD_nCS_Set_High();
	SPI_SDCARD_SCK_Set_Low();
	SPI_SDCARD_MOSI_Set_High();
}
/**********************************************************************
函数:SPI_SDCARD_Write()
函数作用:SPI SDCARD发送一个字节数据
参数:
		uint8_t value----------------------------待发送的字节数据
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-07-31
说明: 上升沿锁存数据,MSB
**********************************************************************/
void SPI_SDCARD_Write(uint8_t value)
{
	uint8_t i = 0;
	for(i = 0; i < 8; i++)
	{
		SPI_SDCARD_SCK_Set_Low();
		
		if(value&0x80)
		{
			SPI_SDCARD_MOSI_Set_High();
		}
		else
		{
			SPI_SDCARD_MOSI_Set_Low();
		}
		value<<=1;
		SPI_SDCARD_SCK_Set_High();//锁存数据
	}
	SPI_SDCARD_MOSI_Set_High();//释放数据总线
}
/**********************************************************************
函数:SPI_SDCARD_Read()
函数作用:SPI SDCARD接收一个字节数据
参数:无
返回值:读取的字节内容
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-02
说明: 上升沿锁存数据,MSB
**********************************************************************/
uint8_t SPI_SDCARD_Read(void)
{
	uint8_t i = 0,value = 0;
	for(i = 0; i < 8; i++)
	{
		SPI_SDCARD_SCK_Set_Low();
		SPI_SDCARD_SCK_Set_High();//锁存数据
		value<<=1;
		if(SPI_SDCARD_MISO_Read())
		{
			value|=0x01;
		}
	}
	return value;
}

spi驱动搞完后就是我们的SDCARD操作了,首先是初始化


这里有个提示当SD卡处于IDLE模式时只有CMD1、ACMD41、CDM59、CDM58命令是有效的,MCU需持续的发送CMD1命令,直到SD卡退出IDLE模式

拷贝EVAL库函数中的命令列表以及response码如下

#define SDCARD_DUMMY_BYTE			0xFF

/**
  * @brief  Commands: CMDxx = CMD-number | 0x40
  */
#define SDCARD_CMD_GO_IDLE_STATE          	0   /* CMD0 = 0x40 */
#define SDCARD_CMD_SEND_OP_COND           	1   /* CMD1 = 0x41 */
#define SDCARD_CMD_SEND_CSD               	9   /* CMD9 = 0x49 */
#define SDCARD_CMD_SEND_CID               	10  /* CMD10 = 0x4A */
#define SDCARD_CMD_STOP_TRANSMISSION      	12  /* CMD12 = 0x4C */
#define SDCARD_CMD_SEND_STATUS            	13  /* CMD13 = 0x4D */
#define SDCARD_CMD_SET_BLOCKLEN           	16  /* CMD16 = 0x50 */
#define SDCARD_CMD_READ_SINGLE_BLOCK      	17  /* CMD17 = 0x51 */
#define SDCARD_CMD_READ_MULT_BLOCK        	18  /* CMD18 = 0x52 */
#define SDCARD_CMD_SET_BLOCK_COUNT        	23  /* CMD23 = 0x57 */
#define SDCARD_CMD_WRITE_SINGLE_BLOCK     	24  /* CMD24 = 0x58 */
#define SDCARD_CMD_WRITE_MULT_BLOCK       	25  /* CMD25 = 0x59 */
#define SDCARD_CMD_PROG_CSD               	27  /* CMD27 = 0x5B */
#define SDCARD_CMD_SET_WRITE_PROT         	28  /* CMD28 = 0x5C */
#define SDCARD_CMD_CLR_WRITE_PROT         	29  /* CMD29 = 0x5D */
#define SDCARD_CMD_SEND_WRITE_PROT        	30  /* CMD30 = 0x5E */
#define SDCARD_CMD_SDCARD_ERASE_GRP_START   32  /* CMD32 = 0x60 */
#define SDCARD_CMD_SDCARD_ERASE_GRP_END     33  /* CMD33 = 0x61 */
#define SDCARD_CMD_UNTAG_SECTOR           	34  /* CMD34 = 0x62 */
#define SDCARD_CMD_ERASE_GRP_START        	35  /* CMD35 = 0x63 */
#define SDCARD_CMD_ERASE_GRP_END          	36  /* CMD36 = 0x64 */
#define SDCARD_CMD_UNTAG_ERASE_GROUP      	37  /* CMD37 = 0x65 */
#define SDCARD_CMD_ERASE                  	38  /* CMD38 = 0x66 */


typedef enum
{
	/**
 	 * @brief  SD reponses and error flags
 	 */
	SDCARD_RESPONSE_NO_ERROR      = (0x00),
	SDCARD_IN_IDLE_STATE          = (0x01),
	SDCARD_ERASE_RESET            = (0x02),
	SDCARD_ILLEGAL_COMMAND        = (0x04),
	SDCARD_COM_CRC_ERROR          = (0x08),
	SDCARD_ERASE_SEQUENCE_ERROR   = (0x10),
	SDCARD_ADDRESS_ERROR          = (0x20),
	SDCARD_PARAMETER_ERROR        = (0x40),
	SDCARD_RESPONSE_FAILURE       = (0xFF),

	/**
	* @brief  Data response error
	*/
	SDCARD_DATA_OK                = (0x05),
	SDCARD_DATA_CRC_ERROR         = (0x0B),
	SDCARD_DATA_WRITE_ERROR       = (0x0D),
	SDCARD_DATA_OTHER_ERROR       = (0xFF)
}SDCARD_Info;

这些都是我们后面要用到的,初始化SD卡就要发送命令,且要获取响应码,因此编辑SDCARD_WriteCmd函数先

/**********************************************************************
函数:SDCARD_WriteCmd()
函数作用:发送SD卡命令
参数:
		uint8_t cmd---------------------------------------发送的命令
		uint32_t args-----------------------------------------命令参数
		uint8_t crc_value--------------------------------------CRC校验码
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-02
说明: 需使用SPI模式对SD卡进行读写操作
**********************************************************************/
void SDCARD_WriteCmd(uint8_t cmd, uint32_t args, uint8_t crc_value)
{
	
	uint8_t i = 0x00;
	uint8_t frame[6];

	/* Prepare Frame to send */
	frame[0] = (cmd | 0x40);		   	/* Construct byte 1 */
	frame[1] = (uint8_t)(args >> 24); 	/* Construct byte 2 */
	frame[2] = (uint8_t)(args >> 16); 	/* Construct byte 3 */
	frame[3] = (uint8_t)(args >> 8);  	/* Construct byte 4 */
	frame[4] = (uint8_t)(args);	   		/* Construct byte 5 */
	frame[5] = (crc_value); 			/* Construct byte 6 */

	/* Send Frame */
	for (i = 0; i < 6; i++)
	{
		SPI_SDCARD_Write(frame[i]); /* Send the Cmd bytes */
	}
}
/**********************************************************************
函数:SDCARD_WaitResponse()
函数作用:获取SD卡回码
参数:
		uint8_t response-----------------------------------指定的回码
返回值:0:成功-1:失败
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-02
说明: 需使用SPI模式对SD卡进行读写操作
**********************************************************************/
int8_t SDCARD_WaitResponse(uint8_t response)
{
	uint16_t time_out = 0xFFFF;
	/* Check if response is got or a timeout is happen */
	while ((SPI_SDCARD_Read() != response) && time_out)
	{
		time_out--;
	}

	if (time_out == 0)
	{
		/* After time out */
		return (int8_t)-1;
	}
	else
	{
		/* Right response got */
		return 0;
	}
}

使用这两个函数就可以实现我们的sd卡初始化了,如下

/**********************************************************************
函数:SDCARD_Init()
函数作用:SD卡初始化
参数:无
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-03
说明: 需使用SPI模式对SD卡进行读写操作
**********************************************************************/
void SDCARD_Init(void)
{
	uint8_t i = 0;
	SPI_SDCARD_nCS_Set_High();

	//发送80 个clks
	for(i = 0; i < 10; i++)
	{
		SPI_SDCARD_Write(SDCARD_DUMMY_BYTE);
	}

	SPI_SDCARD_nCS_Set_Low();
	SDCARD_WriteCmd(SDCARD_CMD_GO_IDLE_STATE,0x00000000,0x95);

	if(0 != SDCARD_WaitResponse(SDCARD_IN_IDLE_STATE))
	{
		//进入IDLE模式失败
		printf("response err!\r\n");
	}
	
	SPI_SDCARD_nCS_Set_High();
	SPI_SDCARD_Write(SDCARD_DUMMY_BYTE);
	printf("response success!\r\n");
	SPI_SDCARD_nCS_Set_Low();

	do
	{
		SDCARD_WriteCmd(SDCARD_CMD_SEND_OP_COND,0x00000000,0xFF);
	}while(0 != SDCARD_WaitResponse(SDCARD_RESPONSE_NO_ERROR));
	
	printf("sdcard init over!\r\n");
}

下载代码到NUCLEO_F303RE目标板运行,发现只输出了“response success”,SD卡是进入了IDLE模式但是退不出来,理想很丰满,现实很骨感。。。试过改动一些效果依然,可能是文档不对头吧,买TFT模块提供的SD卡文档是SanDisk Secure Digital Card product Manual Version2.2还是2004年的,而我的卡是Kingston的,但是找Kingston文档找不到,就找SD规范组织的一个文档SD Specifications part1 Physical Layer Simplified Specification Version 4.10这个就比较新了是2013年的,这个文档就比较全面了,且提出了SDHC、SDXC,SDHC跟SDSC会有些许不同这个看文档就晓得了。在初始化时有了不同的流程,之前我们在IDLE模式下不停的发送CMD1来使其退出IDLE模式,行不通。现在来看一下新文档中有关SD卡初始化的流程


不再是单纯的发送CMD1了而是多出了CMD8命令操作,在IDLE模式下发送CMD8命令,该命令是带有参数的用来确认SD卡的接口操作条件,如果该命令发送完毕后返回illegal command,表明其是Ver1.X规范的SD卡或者不是SD卡,如果该命令响应正确表明该SD卡是符合Ver2.X规范就可以执行其他操作了CMD58、ACMD41。官方文档中不建议发送CMD1给SD卡了,因为该命令很难让Host分辨出所接的是SD卡还是MMC卡。文档中提到CMD8命令的CRC是一直使能的,因此CMD8和CMD1一样,都要带有正确的CRC值,现在来看一下CMD8的命令详解

STM32 TFT学习笔记——SD卡读写_第11张图片
该命令参数中Arg[11:0]为有效位,但重要的是VHS域,来确定电压提供范围,这里当然选择1了——2.7-3.6V,Check pattern为任意值,谷歌一下很多人写的是0xAA,所以这里就写一样的值吧,CRC=0x87,有关CRC的计算可以去网上下载计算工具,有很多,不然手动计算很费力的。CMD8的响应码为R7,5个字节,如下

STM32 TFT学习笔记——SD卡读写_第12张图片

现在重写一下SDCARD_Init函数来看看其返回值

/**********************************************************************
函数:SDCARD_Init()
函数作用:SD卡初始化
参数:无
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-05
说明: 需使用SPI模式对SD卡进行读写操作
**********************************************************************/
void SDCARD_Init(void)
{
	uint8_t i = 0;
	SPI_SDCARD_nCS_Set_High();

	//发送80 个clks
	for(i = 0; i < 10; i++)
	{
		SPI_SDCARD_Write(SDCARD_DUMMY_BYTE);
	}

	SPI_SDCARD_nCS_Set_Low();
	SDCARD_WriteCmd(SDCARD_CMD_GO_IDLE_STATE,0x00000000,0x95);

	if(0 != SDCARD_WaitResponse(SDCARD_IN_IDLE_STATE))
	{
		//进入IDLE模式失败
		printf("response err!\r\n");
	}
	
	SPI_SDCARD_nCS_Set_High();
	SPI_SDCARD_Write(SDCARD_DUMMY_BYTE);
	printf("response success!\r\n");
	SPI_SDCARD_nCS_Set_Low();

	SDCARD_WriteCmd(SDCARD_CMD_SEND_IF_COND,0x1AA,0x87);//verify SD Card interface operating condition
	while(1)
	{
		printf("ack:%02X\r\n",SPI_SDCARD_Read());
		i++;
		if(i >= 255)
		{
			break;
		}
	}

	printf("sdcard init over!\r\n");
}

下载到目标板查看串口输出如下:


将结果跟R7对比,0xFF是无效值不用去管它,R7=0x01 0x00 0x00 0x01 0xAA共5个字节,第一个字节为R1为IDLE模式,command version=0x00,voltage accepted=0x01,check pattern=0xAA,可以看到返回结果跟文档匹配。说明SD卡是支持CMD8命令同时可以在该电压下工作,而且check pattern验证正确。啊,看到了一丝曙光那。。。

接下来对比前面的SPI模式初始化流程图该发送CMD58(READ OCR)命令,流程图中该框框是虚框即该步骤是可以省略的。CMD58命令格式如下:


其响应码为R3,R3的格式如下:

STM32 TFT学习笔记——SD卡读写_第13张图片

R3回码由R1+OCR寄存器组成。

编辑SDCARD_Init函数增加读取OCR寄存器代码如下

SDCARD_WriteCmd (SDCARD_CMD_READ_OCR,0x00000000,SDCARD_NOCRC_BYTE);//read ocr register

while(1)
{
	printf("acc:%02X\r\n",SPI_SDCARD_Read ());
	i++;
	if(i >= 250)
	{
		break;
	}
}

查看返回值,如下:

STM32 TFT学习笔记——SD卡读写_第14张图片

在SPI模式下读取寄存器是跟读取block数据是一致的,返回值是一个字节响应码和几个字节的data block,OCR寄存器的详细内容如下:

STM32 TFT学习笔记——SD卡读写_第15张图片

OCR寄存器是32bit共4个字节,对比我们串口收到的回码:0x01 0x00 0xFF 0x80 0x00,对比OCR寄存器可以发现OCR[23:15]=111111111b。UHS-II Card Status=0(只有UHS-I Card支持此位),CCS=0,busy=0(上电流程未完成),此时发送CMD58命令只能确认其电压范围无法得知SD卡类型,等SD卡上电初始化完成之后再发送该命令就可以得知SD卡类型了通过CCS位来判断。CCS=0:SD卡为SDSC,CCS=1:SD卡为SDHC/SDXC。CCS位只在busy=1时有效。因此在上电初始化时我们就省略这一步,直接操作下一步ACMD41命令。ACMD41命令是用来启动初始化流程并用来检测SD卡的初始化流程是否完成,文档中提到CMD8是一定要优先于ACMD41命令发送的,SD卡接收CMD8命令后(如果有效)会扩展CMD58和ACMD41指令。

启动初始化时用户需要不断的发送ACMD41命令给SD卡直到初始化完成即R1=0x00.ACMD41看名字就知道和CMD41不一样,ACMD41命令是application specific command,在发送该命令之前需要发送CMD55命令(不带参数)来高速SD卡的控制器下一条命令是application specific command。

ACMD41命令格式如下:


其是带有参数的,有效参数位为HCS,详细信息如下


SPI模式下和SD模式下ACMD41的返回不一样,在SPI模式下我们只需要将HCS位置1即可,表明支持高容量SD卡,再次修改SDCARD_Init函数

while(1)
{
	SDCARD_WriteCmd (SDCARD_CMD_APP,0x00000000,SDCARD_NOCRC_BYTE);
	SDCARD_GetResponse ();
	SDCARD_WriteCmd (SDCARD_ACMD_SEND_OP_COND,0x40000000,SDCARD_NOCRC_BYTE);
	while(1)
	{
		ack = SDCARD_GetResponse ();
		printf("acc:%02X\r\n",ack);
		i++;
		if( i >= 5 || ack == 0x00)
		{
			break;
		}
	}
	if(ack == 0x00)
	{
		break;
	}
}

查看其返回结果,如下:

STM32 TFT学习笔记——SD卡读写_第16张图片

已经初始化完毕,现在我们可以执行CMD58命令来检测SD卡类型,结果如下:

STM32 TFT学习笔记——SD卡读写_第17张图片

可以看到此时OCR寄存器中CCS位和busy位都是1,即初始化完成且SD卡类型为SDHC/SDXC。整合一下代码,目前初始化的完整代码如下

/**********************************************************************
函数:SDCARD_Init()
函数作用:SD卡初始化
参数:无
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-05
说明: 需使用SPI模式对SD卡进行读写操作
**********************************************************************/
void SDCARD_Init(void)
{
	uint8_t i = 0;
	uint8_t ack = 0;
	SPI_SDCARD_nCS_Set_High();

	//发送80 个clks
	for(i = 0; i < 10; i++)
	{
		SPI_SDCARD_Write(SDCARD_DUMMY_BYTE);
	}

	SPI_SDCARD_nCS_Set_Low();
	SDCARD_WriteCmd(SDCARD_CMD_GO_IDLE_STATE,0x00000000,0x95);

	if(0 != SDCARD_WaitResponse(SDCARD_IN_IDLE_STATE))
	{
		//进入IDLE模式失败
		printf("response err!\r\n");
	}
	
	SPI_SDCARD_nCS_Set_High();
	SPI_SDCARD_Write(SDCARD_DUMMY_BYTE);
	printf("response success!\r\n");
	SPI_SDCARD_nCS_Set_Low();

	SDCARD_WriteCmd(SDCARD_CMD_SEND_IF_COND,0x1AA,0x87);//verify SD Card interface operating condition
	ack = SDCARD_GetResponse ();
	if(SDCARD_IN_IDLE_STATE != ack)
	{
		printf("get R1 err!\r\n");
		return;
	}
	ack = SDCARD_GetResponse ();
	ack = SDCARD_GetResponse ();
	ack = SDCARD_GetResponse ();
	if(0x01 != ack)
	{
		printf("can't accept the voltage!\r\n");
		return;
	}
	ack = SDCARD_GetResponse ();
	if(0xAA != ack)
	{
		printf("check pattern err!\r\n");
		return;
	}
	
	while(1)
	{
		SDCARD_WriteCmd (SDCARD_CMD_APP,0x00000000,SDCARD_NOCRC_BYTE);
		SDCARD_GetResponse ();
		SDCARD_WriteCmd (SDCARD_ACMD_SEND_OP_COND,0x40000000,SDCARD_NOCRC_BYTE);
		while(1)
		{
			ack = SDCARD_GetResponse ();
			i++;
			if( i >= 5 || ack == 0x00)
			{
				break;
			}
		}
		if(ack == 0x00)
		{
			break;
		}
	}
	
	SDCARD_WriteCmd (SDCARD_CMD_READ_OCR,0x00000000,SDCARD_NOCRC_BYTE);//read ocr register
	ack = SDCARD_GetResponse ();
	ack = SDCARD_GetResponse ();
	SDCARD_GetResponse ();
	SDCARD_GetResponse ();
	SDCARD_GetResponse ();
	
	SPI_SDCARD_nCS_Set_High ();
	SDCARD_WriteDummy ();

	if(0xC0 == ack)
	{
		printf("sdhc/sdxc init over!\r\n");
	}
	
}

初始化结果如下:

STM32 TFT学习笔记——SD卡读写_第18张图片

终于搞定。。。SD卡信息比较大,这个代码也是弄了三四天了吧大概,初始化完成之后剩下的就是读写操作了。这里需要注意的是SDSC卡的block size是可以设定的,而sdhc/sdxc卡的block size是固定512字节的不可修改。读写操作又可以参考EVAL库函数里面的读写函数,先看一下读写命令格式

STM32 TFT学习笔记——SD卡读写_第19张图片

可见参数为数据地址,这里SDSC和SDHC/SDXC又不同了,


对于SDSC来说地址单元是字节,对于SDHC/SDXC来说地址单元是block即512字节,(想想也就知道了只有32bit,按字节只能寻址4GB空间),发送完读写命令后除了R1回码外还有一个start block tokens and stop tran token。如下

STM32 TFT学习笔记——SD卡读写_第20张图片

我们的读block函数如下

/**********************************************************************
函数:SDCARD_ReadBlocks()
函数作用:读取SD卡块数据
参数:
		uint32_t blockStartAddr---------------------------块的起始地址
		uint8_t blockNumbers--------------------------------读取的块数
		uint8_t *context---------------------------------数据存储地址
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-05
说明: 需使用SPI模式对SD卡进行读写操作
**********************************************************************/
int8_t SDCARD_ReadBlocks(uint32_t blockStartAddr, uint8_t blockNumbers, uint8_t *context)
{
	uint32_t offset = 0;
	uint16_t i = 0;
	while(blockNumbers--)
	{
		SDCARD_WriteDummy ();
		
		SDCARD_WriteCmd (SDCARD_CMD_READ_SINGLE_BLOCK,blockStartAddr+offset,SDCARD_NOCRC_BYTE);

		if(0 != SDCARD_WaitResponse (SDCARD_RESPONSE_NO_ERROR))
		{
			return (int8_t)-1;
		}
		if(0 != SDCARD_WaitResponse (SDCARD_START_DATA_SINGLE_BLOCK_READ))
		{
			return (int8_t)-1;
		}
		for(i = 0; i < SDCARD_BLOCK_SIZE; i++)
		{
			*context = SPI_SDCARD_Read ();
			context++;
		}
		offset += SDCARD_BLOCK_OFFSET;
		/* get CRC bytes (not really needed by us, but required by SD) */
		SPI_SDCARD_Read ();
		SPI_SDCARD_Read ();
	}
	
	SDCARD_WriteDummy ();

	return 0;	
}

需要注意的是SDCARD_BLOCK_OFFSET在SDHC/SDXC中就不是512而是1了,如果sd卡类型是SDSC的话SDCARD_BLOCK_OFFSET就是可变的,由CMD16指令来确定,默认的话一般是512,

#define SDCARD_DUMMY_BYTE			0xFF
#define SDCARD_NOCRC_BYTE			0xFF
#define SDCARD_BLOCK_SIZE			0x200
#define SDCARD_BLOCK_OFFSET			1

在主函数调用该函数,

SPI_SDCARD_nCS_Set_Low ();
if(0 != SDCARD_ReadBlocks (0x00,1,arr))
{
	printf("read err!\r\n");
}
SPI_SDCARD_nCS_Set_High ();
for(i = 0; i < 512; i++)
{
	if(0 == i%16)
	{
		printf("\r\n");
	}
	printf("%02X ",arr[i]);
}

结果如下:

STM32 TFT学习笔记——SD卡读写_第21张图片

一看到最后的55 AA就晓得这个读block函数是成功的,这个对比可以将SD卡通过读卡器查到PC机上然后使用winhex软件来查看其数据或者通过后面的写block函数来对比。读block函数搞定后,写block就easy了,如下

/**********************************************************************
函数:SDCARD_WriteBlocks()
函数作用:向SD卡写入块数据
参数:
		uint32_t blockStartAddr---------------------------块的起始地址
		uint8_t blockNumbers--------------------------------写入的块数
		uint8_t *context---------------------------------数据存储地址
返回值:无
上一版本:无
当前版本:1.0
作者:anobodykey
最后修改时间:2015-08-05
说明: 需使用SPI模式对SD卡进行读写操作
**********************************************************************/
int8_t SDCARD_WriteBlocks(uint32_t blockStartAddr, uint8_t blockNumbers, uint8_t *context)
{
	uint16_t i = 0;
	uint32_t offset = 0;
	uint8_t ack = 0;
	while(blockNumbers--)
	{
		SDCARD_WriteDummy ();
		
		SDCARD_WriteCmd (SDCARD_CMD_WRITE_SINGLE_BLOCK,blockStartAddr+offset,SDCARD_NOCRC_BYTE);
		if(0 != SDCARD_WaitResponse (SDCARD_RESPONSE_NO_ERROR))
		{
			return (int8_t)-1;
		}
		/* Send the data token to signify the start of the data */
    	SPI_SDCARD_Write (SDCARD_START_DATA_SINGLE_BLOCK_WRITE);

		for(i = 0; i < SDCARD_BLOCK_SIZE; i++)
		{
			SPI_SDCARD_Write (*context);
			context++;
		}
		
		offset+=SDCARD_BLOCK_OFFSET;
		
    	/* get CRC bytes (not really needed by us, but required by SD) */
		SPI_SDCARD_Read ();
		SPI_SDCARD_Read ();

		ack = SDCARD_GetResponse ();//get data response
		if(SDCARD_DATA_OK != ack)
		{
			return (int8_t)-1;
		}
	}
	
	SDCARD_WriteDummy ();
	
	return 0;
}

在主函数调用该函数,

for(i = 0; i < 512; i++)
{
	wri[i] = i;
}
SPI_SDCARD_nCS_Set_Low ();
if(0 != SDCARD_WriteBlocks (0x01,1,wri))
{
	printf("write err!\r\n");
}
SPI_SDCARD_nCS_Set_High ();
SPI_SDCARD_nCS_Set_Low ();
if(0 != SDCARD_ReadBlocks (0x01,1,arr))
{
	printf("read err!\r\n");
}
SPI_SDCARD_nCS_Set_High ();
for(i = 0; i < 512; i++)
{
	if(0 == i%16)
	{
		printf("\r\n");
	}
	printf("%02X ",arr[i]);
}

不仅检测了写函数同时还检测了读函数,测试结果如下:


好了,SD卡的基本读写也已经完了,剩下的都是完善代码了,三四天的感悟:多看文档尤其是最新的文档!以上代码只针对SDHC卡,SDSC卡和SDXC卡由于我没有所以就没测试过。。。

你可能感兴趣的:(STM32 TFT学习笔记——SD卡读写)