MSP430F5438A单片机基于SPI的FatFs移植笔记(二)

上回说到,CMD0命令的实现,通过它完整的实现了命令发送动作,好下一步让我们继续回到初始化的过程当中:



初始化的过程在CMD0 之前,有一个Power ON 注意这不是简简单单的上个电这么容易的

你需要在Power ON 这一个过程当中:

1. 正确连接各个IO

2. 插入SD卡(不要笑,当你调试蒙了的时候真的可能就忘了)

3. 通电

4, 拉高CS引脚电平

5. 在保持输出为高的前提下,发送至少74个时钟周期(发送一个0xFF是8个时钟周期,我为了保险发了10次,那么就是80个,够了)

6. 拉低CS引脚电平

7. 等待一会儿(我索性就又发了16个时钟周期,也就是2个0xFF,实际证明是OK的)

这些都做完就可以开始发送CMD0了

发送CMD0过后,假如你收到的response是0x01,恭喜你完成了第一步

判断卡片版本的CMD8

接下来需要发送的是CMD8,它的作用是判断这个SD卡是1.0还是2.0的版本的,注意这个版本判断主要用于在后期通过CMD9和ACMD13获取卡片的总大小以及block size时判断返回response的内容,因为1.0版本和2.0版本的卡返回内容有所不同。要特别强调的是,这个1.0啊2.0似乎和WinHex读你的SD卡的时候显示的固件版本不是同一个东西,我手头这个8G的卡winhex显示的固件版本是1.0,通过CMD8查出来却是2.0的卡,还奇怪了好一阵子


前面说过,CMD8的返回值是R7,而且CRC也不是0x95,我是在开发过程中,为了便于调试所以给CMD8单独编了个函数,其实它的过程是和其他CMD差不多的

只不过CMD8的argument的格式是这样的(在410文档的7.3.1.4有说明,顺便一提大家百度CMD8 SD卡出来的那个百度文库的说明对CMD8的回复格式的说明有些不严谨,以我的为准!哈哈哈):

首先是 0x48命令头

然后是32 bits 的 argument

31-12位保留,填入0即可

11-8 供电电压(这个供电电压是你向SD卡说我提供的电压是否符合你的要求,你可以设计一个AD进行一下检验,如果自信没问题可以直接写 0001b通过)

7-0 最不同的就是这8bit,是一个 check pattern,这几位你发的是什么,CMD8的response的特定几位就会返回什么

最后是CRC校验,假如你的check pattern写的是0xAA,那么这里的CRC写0x87就可以了,要是check pattern 写了别的就拿CRC计算器去算一下吧


然后,假如你的CMD8收到了以0x01开头的回复,表明卡片支持CMD8,否则可能会收到别的东西,表明不支持。

支持CMD8的是2.0的卡,反之为1.0


再次判断卡片工作电压的CMD58

这里的标准流程是运行CMD58并通过卡片回复再次确认是否工作在正常电压下,但是我这里第一因为是调试,第二因为工作环境理想,电压可以保证,所以就省略了这一步了,发现不会对初始化造成任何不良影响

正式启动卡片初始化流程的ACMD41

发送ACMD与原来的CMD基本一样,不过你要首先发送一个CMD55,这是一个ACMD的先导命令,表明紧跟着发送的是一个ACMD命令,否则ACMD13和CMD13的命令号都是13,无法区分
唯一要注意的是ACMD41发送的过程中,它的argument的bit 30 给个1,表明发送方是主控芯片(连蒙带猜应该是这个意思)


再次发送CMD58判断卡片是SDSC还是SDHC/XC

这个步骤:能省略!!!!但是我就是省略了结果2了,倒不是说我不知道自己手里的是SDHC卡,上面那么大字写着呢我开始觉得也没什么,默认HC就行了,现在谁还有2G往下的SD卡,但是问题其实不在这里,而是密歇根大学那个哥们也偷懒了,但是那个家伙的的报告是04年写的啊04年!劳资还没上大学呢啊!有木有!有些地方他默认卡是SDSC啊,我就悲剧了啊!
虽然这个命令可以省略,初始化也能通过,可是你确实需要直到你手里的是什么卡这个非常重要,影响到了后面的读写函数

如果你成功的执行了以上所有命令那么恭喜你初始化成功了

这是源代码

char SD_once_disk_initialize(char* SD_type)
{
	char i,j;
	// ===0===
	// 设置通信频率为约400kHz
	// 默认SPI初始化的时候进行设置了,这里不再重复
        
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("Init-Building Argument...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
        
	// ===1===
	// 构造argument
	for (i = 0; i < 4; i++)
	{
          argument[i] = 0;
	}

	// ===2===
	// 延迟至少74个时钟周期,期间:
	// (1) 保持CS持续高
	// (2) 保持信号线持续高
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("Waiting 74 Cicles...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	SPI_CS_HIGH();
	SPI_SD_Wait(100); //至少10个,这里保险起见给了100

	// ===3===
	// 将CS拉低
	// 根据 Application Note Secure Digital Card Interface for the MPS430
	// 这里拉低之后加入一个延时
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CS become low...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	SPI_CS_LOW();
	SPI_SD_Wait(2);

	// ===3===
	// 发送 CMD0 命令
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CMD0...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	if (SD_Send_Command(CMD0, CMD0_R, response, argument) == 0)
		return 0;

        
	// 发送命令 CMD8 这个命令的CRC不是0x95,如果checkpattern是0x0A那CRC是0x87
	// 为了判断 SD 卡的版本,这里比Application Note多了这个过程
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("Checking Version...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	if (SD_Check_Card_Version() == 0)
        {
          //============================================
          // DEBUG4
          //--------------------------------------------
          sd_RS232TX_PROC("Checking Version Finished");
          sd_RS232TX_PROC(sd_NewRow);
          //============================================
          return 0;
        }
	else
	{
          if (response[2] == 0x01)// 判断SD卡版本
          {
            //============================================
            // DEBUG4
            //--------------------------------------------
            sd_RS232TX_PROC("Version 2");
            sd_RS232TX_PROC(sd_NewRow);
            //============================================
            *SD_type = SD_TYPE2;// 新版SD
          }
          else
          {
            //============================================
            // DEBUG4
            //--------------------------------------------
            sd_RS232TX_PROC("Version 1");
            sd_RS232TX_PROC(sd_NewRow);
            //============================================
            *SD_type = SD_TYPE1;// 旧版SD
          }
	}
        

	// ===5===
        // 之前省略了CMD58看电压
	// 发送CMD0成功之后,进一步发送ACMD41命令,读取SD卡的OCR寄存器
	// 本项目操作的SD卡为SDHC,根据规定,若是SDHC或者SDHX,argument第30位需要置1

	argument[3] = 0x40;
        argument[2] = 0x00;
        argument[1] = 0x00;
        argument[0] = 0x00;

	j=0;
	do
	{
		j++;
		// 首先发送CMD55
		if (SD_Send_Command(CMD55, CMD55_R, response, argument) == 1)
			SD_Send_Command(ACMD41, ACMD41_R, response, argument);
		else
			j = SD_IDLE_WAIT_MAX;
	}
	while( ((response[0] & MSK_IDLE)==MSK_IDLE) && (j<SD_IDLE_WAIT_MAX) );
	// 如果超过查询次数阈值而没有结果,那么直接返回0
	if (j >= SD_IDLE_WAIT_MAX)
	{
		return 0;
	}

	// ===6===
	// 之后本来是查询卡种,命令为CMD58这里不查了

	

	return 1;
}

其中那个CMD8的发送命令是这样的:

// 通过CMD8命令检测SD卡的版本
char SD_Check_Card_Version(void)
{
	int i;
	char response_length;
	unsigned char tmp;
	unsigned char check_pattern;

        // CMD8也加入一个唤醒过程
        // 根据振南的建议,添加一个唤醒的过程:
        SPI_CS_HIGH();
        SPI_SendByte(0xFF);
        // 等待不忙
        while(0xFF != SPI_RcveByte());
	// 发送命令前将CS置低
	SPI_CS_LOW();

        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CMD8-Head...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	// 发送CMD头
	tmp = 0x40 + 8;			//CMD8
	SPI_SendByte(tmp);

	// 发送Argument
	// CMD8 的 Argument 是有意义的,从高到低如下:
	// 最低位 argument[0],check pattern,CMD8 发什么 收的 responce 就是什么
	// 随便给,这里设个0xAA
	check_pattern = 0xAA;
	argument[0] = check_pattern;
	// 第二位 argument[1],低4位供电电压,高4位保留(写0)
	// 供电电压那四位,如果电压在2.7~3.6V之间,写 0001b 就可以了
	argument[1] = 0x01;
	// 第三位 和 最高位 argument[2~3], 全部保留写0:
	argument[2] = 0x00;
	argument[3] = 0x00;
	// 发送
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CMD8-Arguments...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	for (i = 3; i >= 0; i--)
	{
		SPI_SendByte(argument[i]);
		argument[i] = 0x00;
	}

	// 发送CRC
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CMD8-CRC...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	SPI_SendByte(0x87);		//CMD8 的 CRC 校验是 0x87 (在 check pattern 是 0xAA 的情况下)

	// CMD8 的回复种类是R7,6个字节(包括CRC)
	response_length = 5;

	// 等待回复-有效回复的第一位是0,所以要设置一个有退出机制的循环来等待这个0开头的回复byte
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CMD8-Waiting Valid Response...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	i=0;
	do
	{
		tmp = SPI_RcveByte();
		i++;
	}
	while( ((tmp&0x80)!=0) && (i<SD_MAX_CMD_RETRY) );	// 满足两个条件,继续等:第一,首位非零;第二,没有超出等待极限

	// 如果失败只能返回0退出
	if ( i >= SD_MAX_CMD_RETRY )
	{
          //============================================
          // DEBUG4
          //--------------------------------------------
          sd_RS232TX_PROC("CMD8-Waiting Fail...");
          sd_RS232TX_PROC(sd_NewRow);
          //============================================
		SPI_CS_HIGH();
		return 0;
	}

	// 如果成功,接收剩下的 5 个字节
	// 其中 response[0] 最低位是 CRC 
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CMD8-Recv Rest Responses...");
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
        response[5] = tmp;
        //============================================
        // DEBUG4
        //--------------------------------------------
        sd_RS232TX_PROC("CMD8-Resp[5]: ");
        sd_RS232TX_PROC("0x");
        sprintf(sd_DEBUG_STR, "%02X",tmp);
        sd_RS232TX_PROC(sd_DEBUG_STR);
        sd_RS232TX_PROC(sd_NewRow);
        //============================================
	for (i=response_length-1; i>=0; i--)
	{// 这里还是有这个多收了的问题
          tmp = SPI_RcveByte();
          //============================================
          // DEBUG4
          //--------------------------------------------
          sd_RS232TX_PROC("CMD8-Resp");
          sprintf(sd_DEBUG_STR, "[%d]: ",i);
          sd_RS232TX_PROC(sd_DEBUG_STR);
          sd_RS232TX_PROC("0x");
          sprintf(sd_DEBUG_STR, "%02X",tmp);
          sd_RS232TX_PROC(sd_DEBUG_STR);
          sd_RS232TX_PROC(sd_NewRow);
          //============================================
          response[i] = tmp;
	}

	// 回复内容解析放在这个函数的外面比较合适
	// 接收完成之后,直接返回接收成功
        SPI_CS_HIGH();
	return 1;
}

这里还有点儿BUG和其他的需要说明,为了保险
我的response这个static unsigned char 的全局变量数组给了8个元素,CMD8命令结束之后我读了6个byte多读了一个,也没什么大问题,最后一个是0xFF罢了我就没有改

另外,在初始化的函数当中所有注释了DEBUG的语句其实是我开通了串口,通过串口把调试结果实时发送到PC机上,奉劝大家别嫌麻烦,串口很简单的,搞定了以后比仿真器有优势,可以一定程度上避免你单步调试中对时序的影响

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值