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

时间:2021-10-23 06:36:37

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

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


初始化的过程在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机上,奉劝大家别嫌麻烦,串口很简单的,搞定了以后比仿真器有优势,可以一定程度上避免你单步调试中对时序的影响