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

发布者:浅唱梦幻最新更新时间:2018-07-19 来源: eefocus关键字: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,如下


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


#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里面是有一个接口控制器的,如下图


接口里面有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


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


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

SD卡上电初始化时序


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


即上电之后需要发送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通信还需要注意以下几点:


即所有操作完毕后,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


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


data token如下


还有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的命令详解


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

现在重写一下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的格式如下:


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;

}

}

查看返回值,如下:


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


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;

}

}

查看其返回结果,如下:


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


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

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

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

我们的读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]);

}

结果如下:


一看到最后的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卡读写 引用地址:STM32 TFT学习笔记——SD卡读写

上一篇:基于STM32的SDIO用4位总线24MHZDMA模式操作SHDC卡
下一篇:STM32CubeMx + SD Card + FatFs 读写SD卡死等问题

推荐阅读最新更新时间:2024-03-16 16:09

STM32单片机学习---PWM输出
实现功能:采用定时器2的通道2,使PA1输出频率1K,占空比40的PWM波形,用PA8随意延时取反led灯,指示程序运行。 首先熟悉一下定时器的PWM相关部分。 其实PWM就是定时器的一个比较功能而已。 CNT里的值不断++,一旦加到与CCRX寄存器值相等,那么就产生相应的动作。这点和AVR单片机很类似。既然这样,我们要产生需要的PWM信号,就需要设定PWM的频率和PWM的占空比。 首先说频率的确定。由于通用定时器的时钟来源是PCLK1,而我又喜欢用固件库的默认设置,那么定时器的时钟频率就这样来确定了,如下: AHB(72MHz)→APB1分频器(默认2)→APB1时钟信号(36MHz)→倍频器(*2倍)→通用定时
[单片机]
STM32学习笔记之对PWM频率和占空比都可调测试
基于战舰开发板 修改的可以对频率和占空比同时调节的一个简单程序。 span style= font-size:18px; void TIM3_PWM_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //ʹÄܶ¨Ê±Æ÷3
[单片机]
stm32专题十三:DMA(三)存储器到外设
DMA的存储器到外设的配置,其实和存储器到存储器的配置非常类似。 只是需要注意一点,就是外设寄存器的地址如何获得?比如USART- DR数据寄存器,我们可以这样定义(基址 + 偏移) // 外设寄存器地址 #define USART_DR_ADDRESS (USART1_BASE + 0x04) 然后的配置就跟USART和DMA非常类似,直接上初始化过程: bsp_dma.c #include bsp_dma.h #include stdio.h uint8_t SendBuff ; void USART_Config(void) { GPIO_InitTypeDef GPIO_InitStr
[单片机]
<font color='red'>stm32</font>专题十三:DMA(三)存储器到外设
STM32通过两个按键变量控制多种花样灯亮法源程序
我写的一个矩阵按键,只用两个按键就可以实现控制多种led灯流水灯亮法 单片机源程序如下: #include sys.h #include delay.h #include usart.h #include led.h #include key.h int main(void) { u8 s=1; u8 m=1; HAL_Init(); //初始化HAL库 Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz delay_init(180); //初始化延时函数 uart_init(115200); /
[单片机]
STM32单片的启动过程解析
一、STM32启动文件详细解析 STM32启动文件详细解析(V3.5.0) 以:startup_stm32f10x_hd.s为例 ;********************(C)COPYRIGHT2011STMicroelectronics******************** ;*FileName:startup_stm32f10x_hd.s ;*Author:MCDApplicationTeam ;*Version:V3.5.0 ;*Date:11-March-2011 ;*Description:STM32F10xHighDensityDevicesvectortableforMDK-ARM ;*toolchain. ;*
[单片机]
STM32学习笔记(6.2):LCD的显示
7. 程序源代码 main.c文件中的代码: #include stm32f10x_lib.h #include stm32f10x_lcd.h extern unsigned char LCD_Image_BIT ; extern unsigned char LCD_Image_HIT ; void RCC_cfg(); void FSMC_cfg(); void LCD_cfg(); void GPIO_cfg(); void LCD_Show(unsigned char * LCD_Image); int main() { RCC_cfg(); GP
[单片机]
stm32之TIM-基本定时器应用实例(详细)
开发环境:Window 7 开发工具:Keil uVision4 硬件:STM32F103VCT6 定时器最基本的功能就是定时处理事情。比如定时发送USART数据、定时采集AD数据、定时检测IO口电位、还可以通过IO口输出波形等。可以实现非常丰富的功能。 STM32系列的定时器分为基本定时器、通用定时器、高级控制定时器。后者包括前者的全部功能。所以先掌握基本定时器可以更好理解后面功能繁多的定时器。 通常地,STM32高级定时器TIM1、TIM8,通用定时器TIM2、TIM3、TIM4、TIM5,基本定时器TIM6、TIM7。 有用过STM32的话都知道,STM32所有的外设初始化都是使用标准库里的初始化结构体和初始化函
[单片机]
<font color='red'>stm32</font>之TIM-基本定时器应用实例(详细)
RyanMqtt移植指南
测试环境:stm32F401RCT6、RT-Thread版本: v4.1.0、RT-Thread Studio版本: 2.2.6、网络硬件使用ec800m移植at_socket使用sal框架。 1、移植介绍 RyanMqtt 库希望应用程序为以下接口提供实现: system 接口 RyanMqtt 需要 RTOS 支持,必须实现如下接口才可以保证 mqtt 客户端的正常运行 network 接口 RyanMqtt 依赖于底层传输接口 API,必须实现该接口 API 才能在网络上发送和接收数据包 MQTT 协议要求基础传输层能够提供有序的、可靠的、双向传输(从客户端到服务端 和从服务端到客户端)的字节流 time 接口
[单片机]
RyanMqtt移植指南
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved