主机环境: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_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里面是有一个接口控制器的,如下图
接口里面有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; }
这里有个提示当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;
/********************************************************************** 函数: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; } }
/********************************************************************** 函数: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"); }
不再是单纯的发送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; }
#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卡由于我没有所以就没测试过。。。