上回说到例程需要修改,则干脆直接写一个初始化的程序
SD卡初始化函数编写
初始化函数严格按照流程进行,函数名定为
disk_initialize
具体形式如下:
DSTATUS disk_initialize (BYTE pdrv);
这个DSTATUS是一个单独定义的变量
<span style="font-size:10px;">typedef BYTE DSTATUS;</span>
关于FatFs
初始化函数定义成这样的原因是,如果你需要用单片机在SD卡上写入数据,这些数据还能够在PC机上读出来,那么必须构建一个文件系统,比如FAT32,而刚好有一个开源的文件系统代码叫做FatFs,这里有说明链接
个人理解所谓文件系统其实就是在SD卡中开出一个区域做一个表,这个表中描述了目前有多少文件存在SD卡当中,这些文件分别多大,叫什么名字,整个SD卡如何划分等等,而这个表就是文件系统,可能并不全面但是大概就是这个意思。
既然是开源代码,所以有一部分肯定是人家写好的,比如文件系统一些固定的内容(如我所说“表”的固定格式等),但是单片机有所不同,SD卡也有所不同,所以这组代码当中有相当一部分函数是只有函数声明却没有函数实现(更准确的说是函数实现空着)的。例如这个disk_initialize,需要针对不同的平台进行编写。
函数的输入变量 BYTE pdry 表征的是所连接存储介质的连接方式,分为:
<span style="font-size:10px;">#define ATA 0
#define MMC 1
#define USB 2</span>
本文涉及的自然是MMC这种,那么在FatFs提供的函数框架:
<span style="font-size:10px;">DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
int result;
switch (pdrv) {
case ATA :
result = ATA_disk_initialize();
// translate the reslut code here
return stat;
case MMC :
result = MMC_disk_initialize();
// translate the reslut code here
return stat;
case USB :
result = USB_disk_initialize();
// translate the reslut code here
return stat;
}
return STA_NOINIT;
}</span><span style="font-size: 14px;">
</span>
只关注 case MMC 即可,即进一步编写
MMC_disk_initialize()
这个函数就可以了,但是这个初始化函数的返回值是什么
这一点在 How to Use MMC/SDC 当中有说明:
SD卡的回复格式
SPI Response
部分翻译一下,原文去链接中找
R1,R2,R3,R7是几种常见的命令回复,根据命令索引号的不同决定。
R1是大部分命令的回复,长度1个byte,结构如下图
各个位置基本都表征的是各种错误,最后一位是是否处于空闲状态,所以如果指令执行成功,有可能会回复0x00或者0x01
R3/R7的结构则是R1后面加上32位数据,只有CMD58和CMD8会采用这个格式
有一些指令花费的时间会比NCR(预定的指令响应时间阈值)长,这种情况下会回复一个R1b,这是一个R1后面跟一个busy flag(繁忙标志位,这东西是几位的长什么样没有说),在此期间,DO(原文写的是D0怀疑写错了原文确实写错了,后面0xFF证实)始终保持低电平。主控设备应该在此期间等待,直到DO变为高电平(收到0xFF)
所以这个初始化的回复是R1格式的,只需要对这个格式进行一下定义解析就好了,通常的常见回复是0x01
对SPI的初始化可以如(一)当中一样,但是分频系数要进行调整,改为100分频比较合适
但是:
1. 说明当中并没有详述CMD0究竟是怎样一个格式,发送缓冲区应该写什么
2. 例程当中并没有描述怎样取得收取的SPI通信数据
各个CMD的具体格式是什么
CMD命令 实际上由几部分组成:
一共6个bytes
Byte2~5
7 65~0 31~07~1 0
0 1Command Command ArgumentCRC 1
这么写出来基本看不懂,举个例子CMD0
其各个Byte如下:
Byte1: 0x40 二进制 0100 | 0000
Byte2~5: 0x000x00 0x000x00
Byte6: 0x95(这个 CRC 只在 CMD0 和 CMD8 的时候有用处,就这么记住不用懂了,0x95是给 CMD0 的,CMD8 和它的 byte 2-5 内容有关)
综上,发送一个CMD0需要连续向SD卡发送一个6bytes的信息(即向UCB0TXBUF写入)
{0x04, 0x00, 0x00, 0x00, 0x00, 0x95}
也可以在百度知道的一个问题中看到更多的示例(平常感觉根本帮不上忙的百度知道居然也可以用上)
参考了很多人的初始化程序编写,通常这样一个命令都会发个100次以上以确保收到
比如这个:
还有这个:
都是这样一种方法,但是在这个网页中,作者在最后提到:
- 向SD卡写入一个CMD或者ACMD指令的过程是这样的:
首先使CS为低电平,SD卡使能;其次在SD卡的Din写入指令;写入指令后还要附加8个填充时钟,是SD卡完成内部操作;之后在SD卡的Dout上接受回应;回应接受完毕使CS为低电平,再附加8个填充时钟。
“附加8个填充时钟”的方法也值得一试
事实证明这个填充时钟的方法才是正确的,多发几次实际上达到了相同的效果
MSP430如何收取SPI信息
首先来讲,目前接触到的通信中对接收数据的处理无非就两种方式,中断与查询,不知道SPI采用哪一种
中断在下面简要介绍,实际并不使用,原因是:SPI这种通信方式是:在SCLK也即通信时钟信号存在的情况时,同时进行收发,所以在发送命令完成等待SD卡回复的过程当中,需要继续向发送寄存器写入高电平:0xFF,以便能够在SD卡接收完命令,响应一端时间之后收到从SD卡发回的回复,SD卡基本上不会主动向主控芯片发送数据,所以,查询方式可以胜任而且更容易控制时序
中断方式
MSP430当中内置了一个SPI的接受缓冲器UCxRXBUF
发过来的数据都存放在这里
通过中断进行收取:
中断通过查找5438A的中断向量表(在datasheet的第16页)可知其中断优先级为56,实际上在头文件里面已经进行了定义,接收中断函数的结构如下:
#pragma vector = USCI_B0_VECTOR
__interrupt void USCI_B0_ISR(void)
{
switch(__even_in_range(UCB0IV,4)) // 这个__even_in_range(a,b)是一个仅对变量a在0~b之间的偶数才调用switch的表达方式
// UCB0IV 是SPI接收中断对应的中断向量,其为2时表明出发了接收中断
{
case 0:break ; // Vector 0 - no interrupt
case 2: // Vector 2 - RXIFG
SPIRXBuffer = UCB0RXBUF ; // 从缓冲区中读出数据!
break ;
case 4:break ; // Vector 4 - TXIFG
default: break ;
}
}
USCI_B0_VECTOR
是在头文件“msp430x54x.h”当中定义的中断优先级56的中断向量
查询方式
相比之下查询方式就要简单得多
仍以P3.1,2,3三个端口组成的SPI通信为例,中断标志寄存器是:
UCB0IFG
该寄存器的最低两位
bit1:UCTXIFG:发送中断标志位,当其为1时,表示发送缓冲寄存器 UCB0TXBUF 可以写入待发送数据,发送过程中为0,发送完成硬件置1
bit0:UCRXIFG:接收中断标志位,当其为1时,表示接收缓冲寄存器 UCB0RXBUF 中的数据可以读出,读出数据后硬件清零
当然两个寄存器也可以通过软件进行写入操作
以下分别是SPI发送函数和SPI接收函数
发送:
void SPI_SendByte(char txbyte) // 通过SPI发送一个字节
{
// 首先等待发送中断标志位为1,之后方可发送
while (0==(UCB0IFG & UCTXIFG))
;
// 向发送寄存器写入需要发送的字符
UCB0TXBUF = txbyte;
}
接收:
unsigned char SPI_RcveByte() //
{
unsigned char tmp;
// 清接收中断标志位
UCB0IFG &= ~UCRXIFG;
// 让时钟运转以便接收数据
SPI_SendByte(0xFF);
// 等待数据接收中断
while(0==(UCB0IFG & UCRXIFG))
;
// 读取接收缓冲区当中的数据
tmp = UCB0RXBUF;
// 函数返回
return tmp;
}
那么这部分先介绍到这里,将会在(三)当中给出初始化的完整流程!