SD卡从容量上讲分两种:标准容量和大容量,最小的是标准容量,小于等于2G
其中的访问关系如下:
SD卡分为两种模式:认证模式和传输模式,每一个模式包含着不同的状态,如下
以下主要讲其初始化过程:
SD卡初始化主要包含5条命令:CMD0,CMD8,ACMD41,CMD2,CMD3,初始化在认证模式下进行,此模式只用到CMDLine。
上电之后,所有卡均处于空闲状态,此时主机并不知道卡的适用电压是多少,所以,主机先假定一个电压并用其发送CMD0。
CMD0:reset指令,处于inactive状态的卡会无视掉这条命令,接收到这条命令或者上电之后,所有的卡处于输入模式,并处于一个默认的设置:RCA=0x0000,速度最低状态,最高的驱动电流忍耐力。
CMD8:用来验证接口操作条件(比如电压之类的,好吧,这个词我是直译的),什么意思呢,卡根据主机发过来的CMD8命令的参数来分析操作环境,同样,主机会根据CMD8的RESPONSE来再次确定操作环境的有效性。
但是卡有可能不会对CMD8命令进行一个RESPONSE,第一种原因:电压不符,因为主机并不知道卡的实际电压,只是假设了一个电压。第二种原因:卡的版本是2.0之前的。协议中说在2.00版本的协议强制在ACMD41之前发送CMD8,也就是说2.00之前的版本不需要发送,自然不会对这个命令做出响应。
CMD8命令告诉卡主机支持2.00版本协议,所以卡可以使能新功能。
ACMD41:是用来舍弃那些无法再主机想要的电压范围进行工作的卡,被舍弃的卡进入inactive状态。
如果没有得到CMD8的回应,则ACMD41的HCS(host capacity support)选项设置为0。
如果得到了CMD8的回应,则HCS可以设置为0也可以设置为1,但是大容量的卡如果接收到ACMD41命令发现HCS为0,则不会进入就绪(见前面的图,标准容量的主机不能访问大容量的卡)。标准容量的卡则不会关注HCS选项。所以如果主机支持大容量,则应该将HCS设置为1。
关于ACMD41的RESPONSE,含有CCS字段,若CCS==1,则卡是大容量,如果CCS==0,则卡是标准容量。
CMD2:CID回显
CMD3:设置RCA
具体的图如下所示:
---------------------------------------------------------------------------------------------------
工作图如下:
过了认证模式,进入传输模式之后,工作就如上图了。
主要解释一下各种命令:
CMD4:广播命令,广播给所有卡,功能为修改DSR寄存器,从而来设置驱动阶段的参数(总线宽度,卡的数量,传输频率从Fod到Fpp)
SDR寄存器的解释如下:
可以看出,对DSR的设置是可以选择的,也就是说这个指令并不是非选不可。
CMD7:主要功能为让卡在传输状态和stand-by state之间切换,卡接到命令之后,根据参数中的RCA:如果和自己的RCA相等,则选中自己,如果和自己的不同,则取消选中自己。
特殊情况:CMD7中RCA为零,取消选中所有卡。在这之后可能会做两件事:1)再次选择一个特定RCA的卡2)重新发送CMD3给卡,这种是为了应对初始化完后才加入的卡,这样的卡没经历认证过程,故没有RCA。这样做比全部reset的方式效率要高。
CMD16:设置BLOCK大小,在标容卡中:标准容量的SD卡默认的BLOCK大小是512Bytes。仅有当CSD中PARTIAL BLOCK READ OPERATION设置为允许的时候,标容卡才允许修改LENGTH。在大容量卡中:这条命令不影响读写的LENGTH,读写的LENGTH皆为512BYTES,如果强制修改则错误,但是大容量SD卡这条CMD16指令影响LOCK_UNLOCK指令。
CMD18 CMD12:这是一对指令,CMD18用来读连续的多个块,CMD12用来结束传输。
CMD17:CMD17用来读单个块。当数据线上没有数据传输的时候,数据线为高电平,每个传输的数据块需要以1或4为低电平为start bits,需要以1-4位高电平为end bits。
CMD24-27 42 56:均为写指令。当sd卡目前正在写入,写缓冲区已经满了的时候,会将DAT0置为低电平以示劳资很忙。不准写写保护区域。
CMD13:要求CARD发送状态,CARD响应的状态中有READY_FOR_DATA位,来显示CARD是否可以接受新的数据,如果卡不能接受新数据,则主机会通过CMD7指令来发送取消选中卡。并将卡置入未连接状态以及释放数据线。
ACMD23:此命令推荐在CMD25(写多个块)之前用,它可以定义将要写的块数,相比直接CMD25,可以加快速度。
ACMD22:得到有多少个块完好的写入,一般用于出错的时候。
(读写 具体看代码有待补齐)
三个和擦除有关的指令:CMD32(设置擦除起始地址),CMD33(设置擦除结束地址),CMD38(开始擦除),三个指令必须按照这个顺序一起用。如果擦除的范围中包含写保护区域,那么就只有其中的非保护区域可以被擦除。同样,当卡正在擦除的时候,也会把DAT线置为低电平(空闲的时候为高电平),检测到低电平的主机会对卡取消选中或者直接将其置为失去连接状态。SCR寄存器中的第55位规定了卡的擦除后的区域是0还是1(不同的制造商不一样)。
代码如下:
按块计算地址:
设置起始地址
设置终止地址
发送擦除指令
等待卡工作完毕
CMD42:上锁解锁命令,这个指令功能很强大,先上图一张:
3位:强制擦除,如有此位则忽略其他位
2位:0为lock 1为unlock
1位:为1则清除密码
0位:为1则设置密码
注意:在使用这个命令之前应该先设置block大小。
主要介绍一下四个操作:
1) 设置密码
①.CMD7命令,选中一个卡
②发送CMD16.
③发送LOCK/UNLOCK命令,如果修改密码的话需要同时提供旧密码新密码
④如果旧密码错误则将状态寄存器中的相应位置一
2) 重置密码
3) 锁定卡
4) 解锁卡
5)强制擦除
忘记密码者的福音,不研究了,看了这么多天文档,累了,用的时候再研究吧。
---------------------------------------------------------------------------------------------------
SD卡的内部构造看上去很简单,先上图一张,看到下面这张图我就有中豁然开朗的感觉:
感觉so nb,这么复杂的器件就只有6个寄存器。
然后看它的指令:
指令一共有四种:第一种,无回复广播指令,CMD0应该就是这种指令,第二种,有回复广播指令,这种指令只有在CMDline是分开的时候才可以用,第三种,点对点指令,第四种,
点对点数据传输指令。
指令的格式都一样的,48位,但是不同的指令的RESPONSE不同,每个指令起于起始位,终止于终止位,transmission bit代表传输方向,就是是主机到卡还是卡到主机,这几个再加上后面的CRC校验位都是硬件自动生成的,我们需要用软件生成的是COMMAND INDEX 和 ARGUMENT:
下面逐条详细介绍初始化过程中的命令:
CMD0
它的功能是复位所有的卡,从说明上可以看出它不需要回复。
对应到驱动里,其对命令的发送是修改stm32中的CMD寄存器和ARG寄存器:
Cmd寄存器分布如下,其最后的6位为命令的index:
ARG寄存器分布如下
我们软件填充好这两个寄存器的值,剩下的就是硬件SDIO要做的了。
其接受函数通过判断STA寄存器(状态寄存器)中的命令发送位来返回,如下:
CMD8
其参数为保留位(后面要求保留位要为零),VHS,和CHECK PATTERN,其中CHECK PATTERN会在CMD8的RESPONSE中再次被发回来,以证明没错。
可以看到CMD8的RESPONSE为R7,我们再去看一下R7是什么样的。
可以看出R7也是48位的,其中包含支持电压,以及CHECK PATTERN的回复那么所谓的支持电压时如何表现出来的呢,
这是驱动中接受R7 RESPONSE的函数
其实这个函数就是不停的去检查STA的0,2,6位,这几个位的意思如下:
好吧,可以看出接受RESPONSE的工作硬件SDIO全帮咱们完成了,咱们只是知道接没接收到RESPONSE以及其CRC校验是否正确。当然,对于CMD8来说,知道有没有RESPONSE就够了,从这一点就已经能判断出卡的类型了。
ACMD41
发送这条命令之前首先要发送CMD55,来证明这是一条应用命令。
这条指令首先通过HCS来告诉卡主机的容量,只有当卡接受到CMD8的时候HCS才被卡视为有效的。这条命令的RESPONSE是R3。
根据R3 RESPONSE,返回的是OCR寄存器的值,赶脚sd卡肯定是一群不和谐工程师设计的,RESPONSE没有感受到一丝一毫的规律,还是我太笨了?反正ACMD41主要就是要求卡返回OCR寄存器的值。
OCR寄存器存储着很多东西,但更多的是保留位。。。。。。。。。
OCR的15到24位里面存储着卡的支持电压
OCR第30位存储着card是标准容量还是大容量
OCR的第31位标志着卡现在是否是busy状态
寄存器分布如下:
别的都大同小异,在此把读取ACMD41 RESPONSE的驱动代码贴出来:
我猛然意识到,处理响应和读取响应的参数应该是分开的,上面的驱动代码只是处理了响应,而并没有读取响应的返回的OCR寄存器的值。
这才是读取响应中OCR的值得函数
根据地址,查找stm32中文参考手册
读取返回的寄存器的值,就是在stm32查询上面的那个寄存器了,咱们是短响应,所以只有第一个用得上。
CMD2
没啥说的,CID回显,R7如下:
要把回复设置为长类型:
CMD3
重新生成RCA
额。。看到这有个疑问,卡要怎么生成RCA,不同的卡怎么知道RCA不冲突呢?
。。。。。。。。算了。。。。。不纠结了,先看R6中16-31位是新的RCA,但是低16位是什么呢,他说让看***表,那就看呗:
看之前先说几句,这里面是卡的状态的0-12,19,22,23位:
之后再接受的时候要去判断这些位,如果都为零说明没有出错card的状态正常才可以。
---------------------------------------------------------------------------------------------------
我就擦了,写数据块的驱动写了好久,就是不能过,妈蛋,一开始貌似还能写进去,现在写都写不进去了,一直是缓冲区下溢错误,官方历程也只能跑DMA,妈蛋妈蛋妈蛋,靠!!!!!!!!!!!
擦出的驱动(核心代码):
就是三个命令依次发过去 ,按理说还应该有个读状态的命令,来读SD卡是否擦除完毕,不过图省事,没写
SD_Error qqk_erase()
{
SDIO_CmdInitStructure.SDIO_Argument = (u32)0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;//R1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/*!< Send CMD33 SD_ERASE_GRP_END with argument as addr */
SDIO_CmdInitStructure.SDIO_Argument = (u32)512;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_END);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/*!< Send CMD38 ERASE */
SDIO_CmdInitStructure.SDIO_Argument = 0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_ERASE);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
return(SD_OK);
结果图片:
读取CSD
经测试,貌似只能在未选中作为参数的RCA的卡的时候使用这个命令,也就是在使用CMD7选中以这个RCA为相对地址的卡之前,原因:因为CMD9是用在STAND-BY STATE状态的,而选中之后就进入了传输态。
SDIO_CmdInitStructure.SDIO_Argument = 557252608;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
while (!( SDIO->STA & (SDIO_FLAG_CCRCFAIL | SDIO_FLAG_CTIMEOUT | SDIO_FLAG_CMDREND)));
printf("\r\nµ°ÌÛ²âÊÔ¿ªÊ¼\r\n");
if(SDIO->STA & SDIO_FLAG_CMDREND)
{
printf("\r\n%32x\r\n",SDIO_GetResponse(SDIO_RESP1));
printf("\r\n%32x\r\n",SDIO_GetResponse(SDIO_RESP2));
printf("\r\n%32x\r\n",SDIO_GetResponse(SDIO_RESP3));
printf("\r\n%32x\r\n",SDIO_GetResponse(SDIO_RESP4));
}
}