大家好,又见面了,我是你们的朋友全栈君。
最近学习一了下SD卡的驱动,网上程序的版本很多,使用的MCU和SD卡的型号千奇百怪,学起来反而没有方向,感觉上乱七八糟的,直到现在才知到我们平常说的SD卡实际上有很多中类别。0到2G的SD卡,最普通的卡;2G到32G的SDHC卡,也就是现在最常用的大容量SD卡;还有我没有见过的SDXC卡,容量好像在32G以上。同时还有手机上的TF卡,实际上也是SD卡 只不过做工不同而已,MMC卡。学习的时候走了很多弯路,SD卡的官方data sheet感觉上写的相当坑爹,网上的学习资料还是给了很大的帮助,但是由于网上的版本很多,程序流程还是要参考官方相对应的SD卡初始化流程。这两天闲下来,抓紧时间整理一下笔记。
首先说一下我自己使用的卡,它是SanDisk 4G SDHC Card,速度等级为4,算比较快的一种大容量SD卡。MCU选取了STC12C5A60S2,反馈信息的显示采用最常用的1602液晶屏,当然大部分网上的代码使用的是串口。SD卡有两种传输模式,SD模式和SPI模式,SD模式需要4跟数据线,而我们一般都采用SPI模式,也就是常说的串行通信模式,这种方式需要的通信线比较少,一根数据输入D_IN,一根数据输出D_OUT,CS片选线,CLK时钟,此外还有电源3.6V和GND地线,其他的引脚按照说明悬空或者接地即可。
其次,SD卡的初始化过程根据卡的不同有不同的方式,我们按照官方给出的流程来说。关于命令的具体参数和返回值的类型说明放在下一篇笔记中,这里只记录流程。
第一步,首先上电,将CS片选信号拉低,在这个基础上对CLK操作,给SD卡发送至少74个时钟周期,让SD卡完成自身检查和初始化,进入空闲状态(IDLE)。之后,对SD卡发送CMD0使其进入SPI模式。不论你是什么卡,第一步的工作都是相同的,这个时候可以观察一下SD卡从D_OUT线上的返回值,如果是0x01,说明CMD0操作是成功的,此时SD卡还处在IDLE状态。
第二步,发送CMD8这一步新的SD卡和老版本的SD卡是有区别的,CMD8是检测SD卡版本的命令,如果SD卡对此命令不识别,那么说明你的SD卡为老版本的,如果SD卡对CMD8做出了正确的返回值(前提你命令格式要对),则说明你的SD卡的硬件层版本是2.0的,支持大容量储存,也就是SDHC卡。我所使用的卡就是SDHC卡,所以有6个字节的返回值,这个在后面说明。另外要说的一点,我曾经直接跳过了CMD8的发送,直接进行了下一步命令,SD卡返回了错误的信息,没有进入正确的读写准备状态。所以这个命令还是按照官方的建议,发送检测。
第三步,CMD8有了返回值以后,则需要进一步让卡从IDLE状态进入读写就绪的状态,也就是发送ACMD41命令。这里要注意的是,SD卡有两种命令CMD和ACMD,如果直接发送命令,SD卡会将命令默认为CMD,如果你想发送ACMD,则要特殊的说明一下,CMD55就是这个功能,它可以提醒SD卡进行接着CMD55后的下一条命令为ACMD。第三步的操作即首先发送CMD55命令,接收到正常的返回值0X01后接着发送ACMD41,完成卡从IDLE状态到读写状态的初始化进程。如果操作正常,SD卡退出IDLE状态,最后的返回值为0X00,此外任何其他的返回值都是不正常的。
第四步,发送CMD58,读取OCR寄存器,OCR寄存器记录了SD卡可识别的电压范围;SD卡是否支持大容量存储,即SDHC;和SD卡上点状态。发送了CMD58命令后,SD卡的下一组返回值为R1返回值+OCR寄存器的内容。根据datasheet我们可以得到很多信息,上面已经提到,具体的位置手册上很明白。手册上推荐发送这个命令,主要功能是你可以知道你的V2.0SD卡是标准版本的,还是大容量的SD卡,大容量的SD卡读写操作时按照块(512BYTE)进行的,所以读写地址的方法有所不同。判断正常的方法,CMD58的返回值类型为R3 ,即R1类型+OCR寄存器内容,如果一切就绪,那么OCR的最高四位为1100。从这个命令以后,初始化的工作就全部进入了,SD卡进入读写准备状态,接下来就可以任意读取目标地址,对其进行读写操作了。
读写数据的过程: 无论读写数据还是接收发送CMD,我们都会用到两个最基本的函数,一个是read_byte(),即从SD卡的DATA_OUT引脚上读取8bit(1byte)的数据;另一个是write_byte(),向SD卡的DATA_IN引脚写一个字节的数据。命令,数据和返回值都是由多字节组合成的,所以在一个操作中会多次调用这两个基本的函数。如SD_Read_Sector()这个函数的主要功能就是从指定的地址中读取512字节的数据,那我们在发送了读的命令后相应的要调用512次read_byte()函数。
读写函数的时序图:向SD卡写数据时,时钟上升沿时数据有效;从SD卡读数据时,时钟在高电平时,MCU读到的数据有效,根据这个写两个基本函数就没有问题。
/****************************************************************************************
** 函数名称: void write_byte()
** 功能描述: 对SD卡写一个字节的数据
** 输 入: 要写入的字节
** 输 出: 无
****************************************************************************************/
void write_byte( uchar _data)
{
uchar i;
for(i=0; i<8; i++)
{
SD_CLK_CLR();
SD_DAIN = (_data & 0x80); //位操作必须有与运算以免不必要的错误
if(is_init) //是否进入高速模式,初始化时低速,is_init=1
DelayMs(4); //读写数据时高速,is_init=0;
_data <<= 1; //有的人为了提高速度,会把for循环拆开一句一句写
SD_CLK_SET();
if(is_init)
DelayMs(4);
}
SD_CLK_CLR(); //8bit数据传输完后拉低时钟线
SD_DAIN_SET();
}
/****************************************************************************************
** 函数名称: uchar read_byte()
** 功能描述: 从DATA_OUT线上读取一字节的数据
** 输 入: 无
** 输 出: 读出的一字节数据
****************************************************************************************/
uchar read_byte()
{
uchar _data,i;
SD_DAOUT_SET(); //读取前先要拉高数据线
for(i=0; i<8; i++)
{
SD_CLK_CLR();
if(is_init)
DelayMs(4);
SD_CLK_SET();
if(is_init)
DelayMs(4);
_data <<= 1;
if(SD_DAOUT == 1)
{
_data = _data | 0x01;
}
}
SD_CLK_CLR();
return _data;
}
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/128715.html原文链接:https://javaforall.cn