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

发布者:shiwanyongbing最新更新时间:2020-05-13 来源: eefocus关键字:MSP430F5438A  单片机  SPI  FatFs移植 手机看文章 扫描二维码
随时随地手机看文章

怎么说呢……太费劲了,前面的博客还烂尾了,主要是觉得自己在调试的过程当中思维太混乱。虽然说自己挖的坑,含着泪也要填上,这几个就先不填了吧我重新开个坑把调通的说清楚。


不管移植什么程序,最重要的就是,


不要自以为是

一定要先查资料,花一周查资料,查到查不到为止,否则你编了一半的程序再参考别人的,直接后果是你下不了决心推翻重来

1. FatFs移植要点:

相信能看到这个博客的都知道FatFs是什么了,目前应该是0.11版本,我就不多废话了,一个开源的文件系统,不全面的说,作用就是让你编程序操作写SD卡的内容能够被PC机读出来(有不对的话懂的大神请指正)

它的好处就是只要写底层的几个硬件驱动函数就OK了,上层的函数都已经写好了,清楚格式直接调用就可以了。

所谓“硬件驱动”函数,就是告诉单片机,完成一个动作(比如初始化)具体需要哪个IO口怎样变化,哪个IO口该高,哪个IO口该低,通信端口选哪个,发什么东西,这些基本动作组合在一起,就能够完成初始化。


FatFs所需要的驱动函数共有五个,diskio.h 文件里面看就是这样的:


DSTATUS disk_initialize (BYTE pdrv);

DSTATUS disk_status (BYTE pdrv);

DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);

DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);

DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);


函数返回值:


DSTATUS


是一个枚举类型变量,包含5个元素,依次是成功、读写错误、写保护、未准备好、参数错误:


/* Results of Disk Functions */

typedef enum {

RES_OK = 0, /* 0: Successful */

RES_ERROR, /* 1: R/W Error */

RES_WRPRT, /* 2: Write Protected */

RES_NOTRDY, /* 3: Not Ready */

RES_PARERR /* 4: Invalid Parameter */

} DRESULT;


实际在写程序的时候返回值分得细有助于快速定位问题的所在,但是我移植的时候图省事只用了成功、读写错误、参数错误三个,原因是写保护我没有编写专门的函数去判断,而未准备好出现的概率很低。


各个函数的输入参数具体到每个函数再进行一一说明


那么下面首先以初始化程序

DSTATUS disk_initialize (BYTE pdrv);

具体说一下:通过TI的单片机MSP430F5438A进行函数的实现步骤


这里需要参考的良心文档以及网站有:


1.FatFs官方网站:http://www.elm-chan.org/fsw/ff/00index_e.html

说明很浅显,优点是易懂缺点是靠他说的那点儿说明实现简直不可能


2.一个叫Tilen Majerle的老外的网站,基于STM32系列单片机开发的FatFs:http://stm32f4-discovery.com/2014/07/library-21-read-sd-card-fatfs-stm32f4xx-devices/

注意这个哥们把全套的实现文件分成了好几个下载链接,分类的效果挺好,一开始看的人就晕了,这个文件下载下来,代码里面的函数在另外一个下载链接里……不过是真的很全,虽然可能跟你需要实现的平台不大一样,但是对于了解实现过程和时序很有帮助


3.一个密歇根大学的人写的技术报告,MSP430F149写的程序,但是只实现了单次读写,并且使用了DMA(内存直读)这种高端的功能(我会上载到资料,待补全)


4.SD卡的官方协议说明书,这个基本上太重要了,名字是part1_410,看来只是整个的一部分,不过很详细,说得很明白,只要你耐心细心,很多东西都可以直接找到,如果你关心的是SPI模式的话第7章是重点!

2. disk_initialize函数实现

这个函数在diskio.c当中的函数体是这样的:

/*-----------------------------------------------------------------------*/

/* Inidialize a Drive                                                    */

/*-----------------------------------------------------------------------*/

 

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;

}


可以看到FatFs很“贴心”的提供了ATA、MMC、USB三款接口存储器的初始化程序,可是你要是真的认为它贴心你就错了……

首先来说输入参数pdrv,这个具体可以理解为你要操作的存储器编号,我们操作单片机的通常也就一个存储器(我项目里面是SD卡),所以在ffconf.h的定义中:


#define _VOLUMES 1


这样一来,所有的输入参数pdrv就都是0了,而ATA MMC USB的定义是这样的:


/* Definitions of physical drive number for each drive */

#define ATA 0 /* Example: Map ATA harddisk to physical drive 0 */

#define MMC 1 /* Example: Map MMC/SD card to physical drive 1 */

#define USB 2 /* Example: Map USB MSD to physical drive 2 */


你妹啊亏我一开始还天真的认为我的是SD卡,不过脑子就只实现了

MMC_disk_initialize();

哪知道单个存储器条件下人家根本执行不到这儿啊!


所以,如果你们只有一个盘,一定要实现的是ATA那个,管他名字是什么,这里我最后实现的时候折衷了一下,把

MMC_disk_initialize();

拷贝到ATA下面了,也算是偷了个懒。那么

MMC_disk_initialize();

这个函数怎么实现呢?

下面的思路是一步一步深入到最底层,所以可能你一眼看不完这个函数的实现过程,不要着急

我们主要通过这个函数的实现过程一步一步搞清楚所有的运作机制


就用到了我上面提到的几个良心文件了

流程其实很多人都提到了也很清楚,这里先上一张经典流程图,来自part1_410.pdf(171/202)(以下我就简称这个文档是410文档了)

可见,第一步是发送命令CMD0,关于各个命令的说明410文档中同样有叙述(179/202),注意SPI用户要看第七章的表格

一个命令的组成分以下部分:

一个命令一共是6个byte


第一个byte是命令头

最高位是起始位,0,第二位是传输位,1,后面6位就是command index了,上面那个流程图里面是CMD几,这里换算就可以了,例如我们首先发送CMD0,那么这头一个字节就是

0x40 ( 0100 0000b )

需要说明,SD卡所支持的SPI通信的发送方式是3pin 8bit MSB First,也就是高位先发送,一次8位,所以CMD0最先发的就是0x40


第二个到第五个共4 bytes 是 命令参数 argument

这个对应的命令都能够在410当中找到argument的含义,CMD0的argument标注的是stuff bits填充位,一律写0就OK


第六个byte是7位CRC校验位和最后一个始终是1的end bit

这个CRC校验位的计算过程我没有理会,因为SPI模式下,除了CMD0和后面提到的CMD8,其它的校验位SD卡不会去管对错的


这里对于CMD0,加入你的argument全0,那么这个最后一个byte固定为

0x95,固定即可不用管它


说完了命令组成,那么具体来说一个命令是如何发送的呢?下面用MSP430F5438A举例子来说

这个单片机的SPI接口有好几个,我用的是P3.1,P3.2,P3.3组成的3pin的SPI,具体配置方法应该很容易查到,TI的user's guide当中也会有的

主要来说说SPI的通信机制,其实和我们常见的RS232有类似也有不同


SPI的3个pin分别是 输入,输出,时钟,此外,SD卡还有一个片选端口CS需要连接一个GPIO,只有这个片选端口为低电平的时候,SD卡才有反应(当然各个阶段也有需要在CS为高电平的时候需要干活的情况)


通信中,Master(单片机)提供时钟信号,Slave(SD卡)接收时钟信号,双方都是根据时钟的沿来分别采样输入或者输出线上根据所发送数据而变化的电压,从而实现数据的传输

与串口类似的是,如果你的单片机需要向SD卡发送数据,在合适的时间(后面会说什么时间合适)向发送缓冲区写需要发送的数据就OK

与串口不同的是,如果你的单片机需要接收SD卡发送的数据,接收1个byte的数据,单片机需要写入一个0xFF的数据将需要接收的数据“推过来”,知道了这个,剩下的就好办了


对于一个SPI通信端口,5438A配有:

一个发送中断标志位:UCTXIFG

一个接收中断标志位:UCRXIFG

一个发送缓冲器:UCxTXBUF

一个接收缓冲器:UCxRXBUF


中断标志位虽然可以手动清零,但是我个人不推荐。因为TI有自动的机制,我们查询就可以了,

发送数据:

不断查询确认发送中断标志位UCTXIFG,直到发送中断标志位为1,表示发送缓冲器可以写入数据,这时将需要发送的1byte数据写入发送缓冲器即可,写入后发送中断标志位自动归0,发送完成后重新变为1

接收数据:

不断查询接收中断标志位UCRXIFG,为1表明接收缓冲器中的数据有效,1byte可进行读取,读取后,接收中断标志位自动归零,直到再次接收到完整数据


有了这个流程,那么单个byte的发送和接收也不是问题了,我们可以顺利的发送命令CMD0了? NO,还有一个时序的问题


这里参考了另一个良心文献,振南的SD说明书

久违的中文啊哈哈,时序是这样的


首先在CS为高电平的情况下,输入8个唤醒时钟(可能落下了周期两个字,应该是8个时钟周期)(我理解是保险起见吧?)输入唤醒时钟周期的方法是,8个唤醒时钟周期就向写入缓冲器写入一个0xFF就可以了,因为SI是高的所以写FF,写入的时候时钟就运行了


然后拉低CS电平


然后读入nx8个时钟,同样是读入字节,方法是写入0xFF然后查输入寄存器,然后读数,注意如果SD卡就绪那么读入的数应该是0xFF


确定就绪后,写入6个byte的命令,之后保持CS为低电平,直到读入的返回值非0xFF表明命令处理完毕,SD卡开始发送回复数据,根据命令的恢复类型不同,读取不同byte数目的回复,读取完成之后将CS信号设为高电平,一次命令发送过程结束!


回复类型分为 R1,R1b,R2,R3,R7等等等等,同样可以在410当中找到


CMD的命令为回复为R1,长度1个byte,包含SD卡的状态,初始化时的正常回复为Idle:0x01,表明SD卡空闲!


发送CMD的完整代码如下:


// 发送CMD命令

char SD_Send_Command(unsigned char cmd, 

unsigned char response_type, 

unsigned char *response, 

unsigned char *argument)

{

int i;

char response_length;

unsigned char tmp;

 

        // 根据振南的建议,添加一个唤醒的过程:

        SPI_CS_HIGH();

        SPI_SendByte(0xFF);

        //--------------------------------------------

        

        while(0xFF != SPI_RcveByte());

// 发送命令前将CS置低

        

SPI_CS_LOW();

 

// 发送CMD头

tmp = 0x40 + cmd;

SPI_SendByte(tmp);

 

// 发送Argument

for (i=3; i>=0; i--) //注意!这里是MSB First,但是不太明白这么写到底有Argument的是如何排列的,参见 Application Note P15

{

          SPI_SendByte(argument[i]);

}

 

// 发送CRC

SPI_SendByte(0x95); //说白了只有CMD0需要,其它之后的都不用了,所以这里索性把所有的CRC都写成CMD0的

 

// 确定回复种类

response_length = 0;

switch (response_type)

{

case R1:

case R1B:

response_length = 1;

break;

case R2:

response_length = 2;

break;

case R3:

response_length = 5;

break;

default:

break;

}

        

// 等待回复-有效回复的第一位是0,所以要设置一个有退出机制的循环来等待这个0开头的回复byte

i=0;

do

{

tmp = SPI_RcveByte();

i++;

}

while( ((tmp&0x80)!=0) && (i        

        

        

// 如果失败只能返回0退出

if ( i >= SD_MAX_CMD_RETRY )

{

          // DEBUG4

          /*

          if(cmd == 13)

          {

            sd_RS232TX_PROC("ACMD13 FAIL!");

            sd_RS232TX_PROC(sd_NewRow);

          }

          */

SPI_CS_HIGH();

return 0;

}

 

// 如果成功

        

        i = response_length - 1;

        response[i] = tmp;

        i--;

        while (i>=0)

        {

          tmp = SPI_RcveByte();

          response[i] = tmp;

          i--;

        }

 

// 下面的内容涉及到:如果返回值是 R1B 即 BusyType,那么有一些额外的任务要做:

// SD 卡会输出一连串的 “0”,所以,任何非零回复是 BUSY 状态结束的标志

i=0;

if (response_type == R1B)

{

do

{

i++;

tmp = SPI_RcveByte();

}

while (tmp != 0xFF);

 

SPI_SendByte(0xFF);// 最后这句不知道什么意思,先这样

}

 

// 能到这里说明已经顺利完成命令执行并得到相应结果,返回退出

SPI_CS_HIGH();

return 1;

}




以上程序参考了密歇根大学的老兄,几点说明:

argument 和 response分别是函数所在的C文件中定义的全局static char类型数组

元素个数分别为4 和 5,因为R3和R7类型会返回5个byte的response


发送CRC时,没有考虑CMD8的情况,所以针对CMD8需要单独编写一个函数,或者在这里添加一个判断,都是没有问题的(CMD8干什么的后面再说)


关于R1 R1b 等等的定义其实都是一些便于区别的整数,按顺序从0开始定义就是了


关于输入变量cmd,可以在h文件中定义 CMD0 为 0, 其它同理,例如 CMD24 为 24 等等,即便是ACMD也按照排号定义就可以了,只不过ACMD命令之前要首先发送前缀命令CMD55


此外,函数中有一些地方比较匪夷所思,我的函数和密歇根那个哥们的也有一点区别,我这个是实际调试通过的,不知道和我自己用的开发板有关系没有,有兴趣的朋友可以重新看一下他的原版程序


以上就是发送命令的完整函数,以后发送各个命令就直接说“发送CMDxxx”了,不再详述过程(CMD8 和 ACMD怎么发会另外在后面说细一些)


今天先到这里

关键字:MSP430F5438A  单片机  SPI  FatFs移植 引用地址:MSP430F5438A单片机基于SPI的FatFs移植笔记(一)

上一篇:MSP430 大型数组 上电不启动
下一篇:MSP430 SD卡SPI读写操作(3) —— SD卡读写实现(以MSP430F5438A为例)

推荐阅读最新更新时间:2024-10-31 20:05

单片机中常用的负电压是怎样产生的
负电压的产生电路图原理 在电子电路中我们常常需要使用负电压,比如说我们在使用运放的时候常常需要建立一个负电压。下面就简单的以正5V电压到负电压5V为例说一下它的电路。 通常需要使用负电压时一般会选择使用专用的负压产生芯片,但这些芯片都比较贵,比如ICL7600,LT1054等。差点忘了MC34063了,这个芯片使用的最多了,关于34063的负压产生电路这里不说了,在datasheet中有的。下面请看我们在单片机电子电路中常用的两种负电压产生电路。 现在的单片机有很多都带有了PWM输出,在使用单片机的时候PWM很多时候是没有用到的,用它辅助产生负压是不错的选择。 上面的电路是一个最简单的负压产生电路了。使用的原件是
[单片机]
C51单片机 蜂鸣器两只蝴蝶代码
#include // 这是 单片机音乐代码生成器 生成的代码 #define uchar unsigned char sbit beepIO=P1^5; // 输出 为 P1.5 可以修改 成 其它 IO 口 uchar m,n; uchar code T ={{0,0}, {0xF8,0x8B},{0xF8,0xF2},{0xF9,0x5B},{0xF9,0xB7},{0xFA,0x14},{0xFA,0x66},{0xFA,0xB9},{0xFB,0x03},{0xFB,0x4A},{0xFB,0x8F},{0xFB,0xCF},{0xFC,0x0B}, {0xFC,0x43},{0xFC,0x
[单片机]
基于单片机的大功率太阳能LED路灯电路设计与仿真
    1  引言     LED 作为第四代照明光源, 正以其独有的优越性在城市美化、道路照明、庭院照明、室内照明以及其他各领域中得到越来越广的应用。尤其在偏远无电地区, 太阳能照明灯具以其不可阻挡的优势得到迅速的推广应用。某作者对LED 灯中的太阳能电池板的安装、控制器的功能、铅蓄电池的安置和恒流驱动电路等进行了探讨, 但没有给出控制器等关键电路的设计和仿真。另一些作者 从光源设计、驱动电路设计和散热设计3 方面说明了LED路灯设计中应遵循的原则, 但使用的电路没有实现智能化, 无法进行程序控制。     本文设计和仿真了一基于AT89C52 单片机智能控制的, 功率约为40W 太阳能LED 路灯。它采用了双电源供电模
[单片机]
基于<font color='red'>单片机</font>的大功率太阳能LED路灯电路设计与仿真
单片机驱动步进电机程序
NS-6型实验板驱动步进电机 控制输出为P1口 由8050,8550做功率输出驱动PH266-E1.2按K1键,电机由慢变快作加速度顺时针旋转按K2键,电机由慢变快作加速度逆时针旋转 注:本试验只为初学者掌握单片机驱动步进电机的原理,其硬件配置只做为短时间试验演示不可持续过长时间,亦不可实际中使用 电路如下图: ? ;尼士单片机 ;任风逍遥 ;步进电机驱动程序 ;2004/8/8 ;NS-1试验板驱动步进电机 ;K1驱动步进电机顺时针转动,K2驱动步进电机逆时针转动 org 000h ajmp aa org 010h ;马达正转 aa: mov 20h,#50 JB P3.6,Bb;如果P3.6为1则转到Bb如
[单片机]
基于EPP-CAN智能接口卡硬件电路设计
  mcp2510是 microchip公司的一款can协议控制器,完全支持can总线v2.0a/b技术规范,能够发送和接收标准和扩展报文。它还同时具备验收过滤以及报文管理功能。该器件包含3个发送缓冲器和两个接受缓冲器,减少了单片机的管理负担。单片机的通讯是通过行业标准串行外设接口spi来实现的,其数据传输速 率高达5mbps。can总线上可编程位传输速率最高可达1mbps。pc机中,每个并行口占用3个i/o端口地址,分别对应着各自的数据寄存器,控制寄存器和状态寄存器。数据寄存器地址为378h,用来暂存传送的数据,在双向通信环境中,包含读入和读出两个寄存器的双缓冲寄存器,对应 ad0~ad7双向数据/地址端口,输入操作时使用输入寄
[单片机]
基于EPP-CAN智能接口卡硬件电路设计
瑞萨电子推出支持以太网和MOST网络的MCU
~实现了单芯片车载信息娱乐系统控制和网络功能~ 瑞萨电子株式会社(TSE:6723,以下简称“瑞萨电子”)于2011年6月8日宣布推出六个新型第四代(X4代)、基于V850系列的S系列微控制器(MCU)。该微控制器适用于汽车信息娱乐系统和网络。新产品包括两个V850E2/Sx4-H系列100引脚的微控制器、两个V850E2/SJ4-H系列144引脚的微控制器、以及两个V850E2/SK4-H系列176引脚的微控制器。 车载信息娱乐系统和网络的发展需要借助协议的支持,如以太网和MOST(面向媒体的系统传输),以支持高带宽和高可靠性的数据传输。这对于完善的汽车网络可谓锦上添花,如瑞萨公司的V850E2/Sx4-H系列也支持CA
[单片机]
PCF8574 STC15单片机的驱动程序 LCD1602显示
网上找了很多的的1602程序,结果要么全是ARDUINO和STM32,要么没一个靠谱的,所以决定自己写,测了N久终于写出来了,看上去效果不错哈哈哈哈! (MCU是STC15F2K60S2) 制作出来的实物图如下: 单片机源程序如下: #include pcf8574.h #define WriteADDR 0x4e #define ReadADDR 0x4f #define u8 unsigned char #define CSSet 0x04//EN=1 #define CSReset 0x00//EN=0 #define COMSet 0x08 // RS=0 RW=0 #define DATSet 0x0
[单片机]
PCF8574 STC15<font color='red'>单片机</font>的驱动程序 LCD1602显示
智能语音拨号报警系统
随着人们生活水平的提高和安防意识的增强,急需开发面向大众、价格低廉、运行可靠的自动报警系统。鉴于住宅电话和移动通信设备的普及,以及电话语音报警的快捷、有效及价格低廉等优点,公共通信网成了报警系统的最佳传输媒介。本文介绍的报警系统就是在此基础上发展起来的一种智能语音拨号报警系统,该系统可广泛用于各种对安防要求较高的场合,如智能楼宇、商场、银行和工厂等。 电话自动报警的主要功能为:用户根据需要把自己的手机号码、办公室电话或报警监控中心的电话预存入报警主机。报警主机不断地对所监控的设备(门禁、烟雾探测器、窗磁、摄像头等)状况进行巡检,当有不安全情况(如火灾、非法入室、视频丢失等)发生时,报警主机拨通预先存入的电话号码,播放相应的
[单片机]
智能语音拨号报警系统
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved