主机环境: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的SDIO用4位总线24MHZDMA模式操作SHDC卡
下一篇:STM32CubeMx + SD Card + FatFs 读写SD卡死等问题
推荐阅读最新更新时间:2024-03-16 16:09