7 模拟SPI进行TF卡操作+Fatfs文件系统移植

2018-10-17  本文已影响251人  Savior2016

FATFS版本FATFS R0.13b
SD卡容量16G

概述

本文的重点是进行Fatfs文件系统的移植和初步的使用。TF卡的操作实际上是指令操作,即你想它发送固定的CMD指令,它接收到指令给你返回,因此只要是它支持的通讯方式,其实操作起来原理都是差不多的。后面会稍微展开介绍一些。
这里用的是SPI通讯,模拟SPI通讯,就是用普通的IO口模拟SPI的时序,当作SPI跟SD卡通讯。就是在这个部分,抄了一个别人开源的程序,导致我在找bug的过程中,搞清楚了整个文件系统,甚至是扇区的一些东西。
所以总结成这篇文章作为纪念。

1 模拟SPI

具体时序是什么,网上已经有很多了。因为是某个大神已经公开的代码,所以我就直接贴在这里。网上其他代码大多都是转的他的,不过不知道是历史原因还是什么,他代码里有一个错误。
发送指令时,对应的地址他做了移位操作(addr<<9),这里是他的文章链接:nios ii之Micro SD卡(TF卡)spi。这个操作的意思是,一个扇区512个字节,读写某个扇区,起始地址就是扇区*512,也就是传入的addr是扇区号,指令需要的是实际的起始地址。但是经过测试发现,现在指令传入的地址,SD卡死直接作为扇区号去操作的,因此不需要移位。
SD_spi_Solution.h

#ifndef SD_SPI_SOLUTION_H_
#define SD_SPI_SOLUTION_H_
#include "mico.h"       //头文件,根据芯片更改

 
#define CMD0    0   /* GO_IDLE_STATE */
#define CMD55   55  /* APP_CMD */
#define ACMD41  41  /* SEND_OP_COND (ACMD) */
#define CMD1    1   /* SEND_OP_COND */
#define CMD17   17  /* READ_SINGLE_BLOCK */
#define CMD8    8   /* SEND_IF_COND */
#define CMD18   18  /* READ_MULTIPLE_BLOCK */
#define CMD12   12  /* STOP_TRANSMISSION */
#define CMD24   24  /* WRITE_BLOCK */
#define CMD25   25  /* WRITE_MULTIPLE_BLOCK */
#define CMD13   13  /* SEND_STATUS */
#define CMD9    9   /* SEND_CSD */
#define CMD10   10  /* SEND_CID */
 
#define CSD     9
#define CID     10
 
 //这部分应根据具体的连线来修改!
//SD_MISO(38脚),SD_CLK(37脚),SD_MOSI(36脚),SD_CS(35脚)                    
#define SD_CS        (MICO_GPIO_35)
#define SD_MISO      (MICO_GPIO_38)
#define SD_CLK       (MICO_GPIO_37)
#define SD_MOSI      (MICO_GPIO_36)
//MISO                             
#define  SPI_MI (MicoGpioInputGet((mico_gpio_t)SD_MISO))
//CS
#define  SPI_CS_L   (MicoGpioOutputLow( (mico_gpio_t)SD_CS ))          
#define  SPI_CS_H   (MicoGpioOutputHigh( (mico_gpio_t)SD_CS ))
//CLK
#define  SPI_CLK_L  (MicoGpioOutputLow( (mico_gpio_t)SD_CLK ))            
#define  SPI_CLK_H  (MicoGpioOutputHigh( (mico_gpio_t)SD_CLK ))
//MOSI
#define  SPI_MO_L   (MicoGpioOutputLow( (mico_gpio_t)SD_MOSI ))           
#define  SPI_MO_H   (MicoGpioOutputHigh( (mico_gpio_t)SD_MOSI ))
//delay 1us(actually not,it maybe is several us,I don't test it)
void usleep(u16 i);
 
//set CS low
void CS_Enable();
 
//set CS high and send 8 clocks
void CS_Disable();
 
//write a byte
void SDWriteByte(u8 data);
 
//read a byte
u8 SDReadByte();
 
//send a command and send back the response
u8  SDSendCmd(u8 cmd,u32 arg,u8 crc);
 
//reset SD card
u8 SDReset();
 
//initial SD card
u8 SDInit();
 
//read a single sector
u8 SDReadSector(u32 addr,u8 * buffer);
 
//read multiple sectors
u8 SDReadMultiSector(u32 addr,u8 sector_num,u8 * buffer);
 
//write a single sector
u8 SDWriteSector(u32 addr,u8 * buffer);
 
//write multiple sectors
u8 SDWriteMultiSector(u32 addr,u8 sector_num,u8 * buffer);
 
//get CID or CSD
u8 SDGetCIDCSD(u8 cid_csd,u8 * buffer);
 
//spi speed(0-255),0 is fastest
//u8 spi_speed;
//SD IO init
void SDIOinit(void);
 
#endif /* SD_SPI_SOLUTION_H_ */

SD_spi_Solution.c

#include"SD_spi_Solution.h"

u16 spi_speed = 200;//the spi speed(0-255),0 is fastest
u8 sd_state=0;
//delay 1us(actually not,it maybe is several us,I don't test it)
void usleep(u16 i)
{
    while(i --);
}
 
//set CS low
void CS_Enable()
{
    //set CS low
    SPI_CS_L;
}
 
//set CS high and send 8 clocks
void CS_Disable()
{
    //set CS high
    SPI_CS_H;
    //send 8 clocks
    SDWriteByte(0xff);
}
 
//write a byte
void SDWriteByte(u8 data)
{
    u8 i;
 
    //write 8 bits(MSB)
    for(i = 0;i < 8;i ++)
    {
        SPI_CLK_L;
        usleep(spi_speed);
        if(data & 0x80) 
                  SPI_MO_H;
        else SPI_MO_L;
        data <<= 1;
        SPI_CLK_H;
        usleep(spi_speed);
    }
 
    //when DI is free,it should be set high
    SPI_MO_H;
}
 
//read a byte
u8 SDReadByte()
{
    u8 data = 0x00,i;
 
    //read 8 bit(MSB)
    for(i = 0;i < 8;i ++)
    {
        SPI_CLK_L;
        usleep(spi_speed);
        SPI_CLK_H;
        data <<= 1;
        if(SPI_MI) 
            data |= 0x01;
        usleep(spi_speed);
    }
 
    return data;
}
 
//send a command and send back the response
u8  SDSendCmd(u8 cmd,u32 arg,u8 crc)
{
    u8 r1,time = 0;
    //send the command,arguments and CRC
    SDWriteByte((cmd & 0x3f) | 0x40);
    SDWriteByte(arg >> 24);
    SDWriteByte(arg >> 16);
    SDWriteByte(arg >> 8);
    SDWriteByte(arg);
    SDWriteByte(crc);

    //read the respond until responds is not '0xff' or timeout
    do{
        r1 = SDReadByte();
        time ++;
        //if time out,return
        if(time > 254) break;
    }while(r1 == 0xff);
 
    return r1;
}
 
//reset SD card
u8 SDReset()
{
    u8 i,r1,time = 0;
 
    //set CS high
    CS_Disable();
 
    //send 128 clocks
    for(i = 0;i < 16;i ++)
    {
        SDWriteByte(0xff);
    }
 
    //set CS low
    CS_Enable();
 
    //send CMD0 till the response is 0x01
    do{
        r1 = SDSendCmd(CMD0,0,0x95);
        time ++;
        //if time out,set CS high and return r1
        if(time > 254)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while(r1 != 0x01);
 
    //set CS high and send 8 clocks
    CS_Disable();
 
    return 0;
}
 
//initial SD card(send CMD55+ACMD41 or CMD1)
void SDIOinit(void)
{
     //IO初始化
    MicoGpioInitialize((mico_gpio_t)SD_CS, OUTPUT_PUSH_PULL);
    MicoGpioInitialize((mico_gpio_t)SD_MISO, INPUT_PULL_UP);
    MicoGpioInitialize((mico_gpio_t)SD_CLK, OUTPUT_PUSH_PULL);
    MicoGpioInitialize((mico_gpio_t)SD_MOSI, OUTPUT_PUSH_PULL);
}
u8 SDInit()
{
    u8 r1,time = 0;
    
   

    SDReset();

    //set CS low
    CS_Enable();
 
    //check interface operating condition
    r1 = SDSendCmd(CMD8,0x000001aa,0x87);
    //if support Ver1.x,but do not support Ver2.0,set CS high and return r1
    if(r1 == 0x05)
    {
        //set CS high and send 8 clocks
        CS_Disable();
        return r1;
    }
    //read the other 4 bytes of response(the response of CMD8 is 5 bytes)
    r1=SDReadByte();
    r1=SDReadByte();
    r1=SDReadByte();
    r1=SDReadByte();
 
    do{
        //send CMD55+ACMD41 to initial SD card
        do{
            r1 = SDSendCmd(CMD55,0,0xff);
            time ++;
            //if time out,set CS high and return r1
            if(time > 254)
            {
                //set CS high and send 8 clocks
                CS_Disable();
                return r1;
            }
        }while(r1 != 0x01);
 
        r1 = SDSendCmd(ACMD41,0x40000000,0xff);
 
        //send CMD1 to initial SD card
        //r1 = SDSendCmd(CMD1,0x00ffc000,0xff);
        time ++;
 
        //if time out,set CS high and return r1
        if(time > 254)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while(r1 != 0x00);
 
    //set CS high and send 8 clocks
    CS_Disable();
 
    return 0;
}
 
//read a single sector
u8 SDReadSector(u32 addr,u8 * buffer)
{
    u8 r1;
    u16 i,time = 0;
 
    //set CS low
    CS_Enable();
 
    //send CMD17 for single block read
       // addr=addr*512;
    r1 = SDSendCmd(CMD17,addr,0x55);
    //if CMD17 fail,return
    if(r1 != 0x00)
    {
        //set CS high and send 8 clocks
        CS_Disable();
        return r1;
    }
 
    //continually read till get the start byte 0xfe
    do{
        r1 = SDReadByte();
        time ++;
        //if time out,set CS high and return r1
        if(time > 30000)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while(r1 != 0xfe);
 
    //read 512 Bits of data
    for(i = 0;i < 512;i ++)
    {
        buffer[i] = SDReadByte();
    }
 
    //read two bits of CRC
    SDReadByte();
    SDReadByte();
 
    //set CS high and send 8 clocks
    CS_Disable();
 
    return 0;
}
 
//read multiple sectors
u8 SDReadMultiSector(u32 addr,u8 sector_num,u8 * buffer)
{
    u16 i,time = 0;
    u8 r1;
 
    //set CS low
    CS_Enable();
 
    //send CMD18 for multiple blocks read
    r1 = SDSendCmd(CMD18,addr,0xff);
    //if CMD18 fail,return
    if(r1 != 0x00)
    {
        //set CS high and send 8 clocks
        CS_Disable();
        return r1;
    }
 
    //read sector_num sector
    do{
        //continually read till get start byte
        do{
            r1 = SDReadByte();
            time ++;
            //if time out,set CS high and return r1
            if(time > 30000 || ((r1 & 0xf0) == 0x00 && (r1 & 0x0f)))
            {
                //set CS high and send 8 clocks
                CS_Disable();
                return r1;
            }
        }while(r1 != 0xfe);
        time = 0;
 
        //read 512 Bits of data
        for(i = 0;i < 512;i ++)
        {
            *buffer ++ = SDReadByte();
        }
 
        //read two bits of CRC
        SDReadByte();
        SDReadByte();
    }while( -- sector_num);
    time = 0;
 
    //stop multiple reading
    r1 = SDSendCmd(CMD12,0,0xff);
 
    //set CS high and send 8 clocks
    CS_Disable();
 
    return 0;
}
 
//write a single sector
u8 SDWriteSector(u32 addr,u8 * buffer)
{
    u16 i,time = 0;
    u8 r1;
 
    //set CS low
    CS_Enable();
 
    do{
        do{
            //send CMD24 for single block write
            r1 = SDSendCmd(CMD24,addr,0xff);
            time ++;
            //if time out,set CS high and return r1
            if(time > 60000)
            {
                //set CS high and send 8 clocks
                CS_Disable();
                return r1;
            }
        }while(r1 != 0x00);
        time = 0;
 
        //send some dummy clocks
        for(i = 0;i < 5;i ++)
        {
            SDWriteByte(0xff);
        }
 
        //write start byte
        SDWriteByte(0xfe);
 
        //write 512 bytes of data
        for(i = 0;i < 512;i ++)
        {
            SDWriteByte(buffer[i]);
        }
 
        //write 2 bytes of CRC
        SDWriteByte(0xff);
        SDWriteByte(0xff);
 
        //read response
        r1 = SDReadByte();
        time ++;
        //if time out,set CS high and return r1
        if(time > 60000)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while((r1 & 0x1f)!= 0x05);
    time = 0;
 
    //check busy
    do{
        r1 = SDReadByte();
        time ++;
        //if time out,set CS high and return r1
        if(time > 60000)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while(r1 != 0xff);
 
    //set CS high and send 8 clocks
    CS_Disable();
 
    return 0;
}
 
//write several blocks
u8 SDWriteMultiSector(u32 addr,u8 sector_num,u8 * buffer)
{
    u16 i,time = 0;
    u8 r1;
 
    //set CS low
    CS_Enable();
 
    //send CMD25 for multiple block read
    r1 = SDSendCmd(CMD25,addr,0xff);
    //if CMD25 fail,return
    if(r1 != 0x00)
    {
        //set CS high and send 8 clocks
        CS_Disable();
        return r1;
    }
 
    do{
        do{
            //send several dummy clocks
            for(i = 0;i < 5;i ++)
            {
                SDWriteByte(0xff);
            }
 
            //write start byte
            SDWriteByte(0xfc);
 
            //write 512 byte of data
            for(i = 0;i < 512;i ++)
            {
                SDWriteByte(*buffer ++);
            }
 
            //write 2 byte of CRC
            SDWriteByte(0xff);
            SDWriteByte(0xff);
 
            //read response
            r1 = SDReadByte();
            time ++;
            //if time out,set CS high and return r1
            if(time > 254)
            {
                //set CS high and send 8 clocks
                CS_Disable();
                return r1;
            }
        }while((r1 & 0x1f)!= 0x05);
        time = 0;
 
        //check busy
        do{
            r1 = SDReadByte();printf("n%d",r1);
            time ++;
            //if time out,set CS high and return r1
            if(time > 30000)
            {
                //set CS high and send 8 clocks
                CS_Disable();
                return r1;
            }
        }while(r1 != 0xff);
        time = 0;
    }while(-- sector_num);
 
    //send stop byte
    SDWriteByte(0xfd);
 
    //check busy
    do{
        r1 = SDReadByte();
        time ++;
        //if time out,set CS high and return r1
        if(time > 30000)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while(r1 != 0xff);
 
    //set CS high and send 8 clocks
    CS_Disable();
 
    return 0;
}
 
//get CID or CSD
u8 SDGetCIDCSD(u8 cid_csd,u8 * buffer)
{
    u8 r1;
    u16 i,time = 0;
 
    //set CS low
    CS_Enable();
 
    //send CMD10 for CID read or CMD9 for CSD
    do{
        if(cid_csd == CID)
            r1 = SDSendCmd(CMD10,0,0xff);
        else
            r1 = SDSendCmd(CMD9,0,0xff);
        time ++;
        //if time out,set CS high and return r1
        if(time > 254)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while(r1 != 0x00);
    time = 0;
 
    //continually read till get 0xfe
    do{
        r1 = SDReadByte();
        time ++;
        //if time out,set CS high and return r1
        if(time > 30000)
        {
            //set CS high and send 8 clocks
            CS_Disable();
            return r1;
        }
    }while(r1 != 0xfe);
 
    //read 512 Bits of data
    for(i = 0;i < 16;i ++)
    {
        *buffer ++ = SDReadByte();
    }
 
    //read two bits of CRC
    SDReadByte();
    SDReadByte();
 
    //set CS high and send 8 clocks
    CS_Disable();
 
    return 0;
}

使用的时候需要修改的是void SDIOinit(void);进行相应端口的初始化,头文件上方宏定义中关于IO口电平的操作也需要做自己的修改。
这里附获取SD卡容量的程序供调试用:

         u8 csddata[16] = {0};
    SDGetCIDCSD(CSD, csddata);
    u32 csize = csddata[9] + ((uint32_t)csddata[8] << 8) + ((uint32_t)(csddata[7] & 0x3f) << 16) + 1;
    u32 Capacity = csize << 9;

Capacity单位是Kbyte。

2 SD卡扇区简介

知道一些扇区的知识,有利于程序调试。下载一个DiskGenius,SD卡插在电脑上,就可以看到SD卡的磁盘信息。


RD1

这里要特别特别注意的是,每一步操作都确认一下是SD卡还是你自己的电脑硬盘,不要重演我的悲剧。

2.1 格式化

此格式化非彼格式化,这里有更多的选项,顺便熟悉一些概念。
选中SD卡(点上方的蓝条),然后点工具栏里的delete,删除当前分区(有几个删除几个)。


delete

然后点工具栏里最左边的Save All,再点击New Partition,会弹出来一个框,点下面的Advanced>>,展开高级页面:


New Partition
按图中圈的部分改好,第二个圈让它对其8个扇区,因为Fatfs可以设置读取块的大小,最大是4096,我估计这个选项会有影响,没有具体测试。
比较重要的是三号圈,这里是begining sector,就是FAT32的起始扇区,就是这里存储着SD卡相关的配置信息。
改好之后点OK,然后再点Save All。

2.2 FAT32信息

sector

点dector edit,再点offset,出现下面这个框:


offset

按图上选,点OK就会跳转到2048页扇区了。


0
扇区以0xEB开头,表示活跃,Fatfs会检查这一位,以0x55,0xAA结尾,软件中两个分割线之间就是一个扇区,512个字节。在中间可以占到FAT32字样,FATFS就是通过这个来初步判断是什么文件系统类型的。
最新版本的FATFS可以自己来找这一页,所以不用太担心这个offset,R0.1版本之前的好像只会在0页查找。写在其他页会找不到文件系统。

3 Fatfs移植

3.1 获取源码

FatFs官网下载源码。

源码
移植要用到的是source里面的文件,把它们添加到工程里。

3.2 移植

这里只写简单移植使用的部分,更多的功能可以结合文档去探索。

3.2.1 头文件修改

include 包含的头文件,有一些是windows的,酌情清除掉,添加自己芯片的头文件,一般不用添加。

#ifndef FF_INTEGER
#define FF_INTEGER
#include "mico.h"
        /* Embedded platform */

/* These types MUST be 16-bit or 32-bit */
typedef int             INT;
typedef unsigned int    UINT;

/* This type MUST be 8-bit */
typedef unsigned char   BYTE;

/* These types MUST be 16-bit */
typedef short           SHORT;
typedef unsigned short  WORD;
typedef unsigned short  WCHAR;

/* These types MUST be 32-bit */
typedef long            LONG;
typedef unsigned long   DWORD;

/* This type MUST be 64-bit (Remove this for ANSI C (C89) compatibility) */
typedef unsigned long long QWORD;

#endif

3.2.2 实现diskio.c

这个文件就是主要的功能上实现了,在这里对接上SPI操作和FatFs,主要是实现以下函数。

DSTATUS disk_status(
    BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
    DSTATUS stat;
    stat = RES_OK;
    return stat;
}
DSTATUS disk_initialize(
    BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
    DSTATUS stat;
    int result;
    result = 0;//SDInit();

    if (result == 0)
    {
        stat = RES_OK;
    }
    else
    {
        stat = RES_ERROR;
    }

    return stat;
}

参数BYTE pdrv是设备号,如果只有一个设备就不用管它,如果挂载了其他设备,就自己做一下判断。

DRESULT disk_read(
    BYTE pdrv,  /* Physical drive nmuber to identify the drive */
    BYTE *buff,   /* Data buffer to store read data */
    DWORD sector, /* Start sector in LBA */
    UINT count  /* Number of sectors to read */
)
{

    DRESULT stat;
    int result;
    //BYTE buffd[512]={0};
    
    if(count==1)
    {
        result=SDReadSector(sector,buff);
    }
    else
    {
            result = SDReadMultiSector(sector, count, buff);
    }
    if (result == 0)
    {
        stat = RES_OK;
    }
    else
    {
        stat = RES_ERROR;
    }
}
DRESULT disk_write(
    BYTE pdrv,        /* Physical drive nmuber to identify the drive */
    const BYTE *buff, /* Data to be written */
    DWORD sector,    /* Start sector in LBA */
    UINT count        /* Number of sectors to write */
)
{

    DRESULT stat;
    int result;
    if(count==1)
    {
        result=SDWriteSector(sector,(uint8_t *)buff);
    }
    else
    {
            result = SDWriteMultiSector(sector, count, (uint8_t *)buff);
    }
    
    
    if (result == 0)
    {
        stat = RES_OK;
    }
    else
    {
        stat = RES_ERROR;
    }
}
DRESULT disk_ioctl(
    BYTE pdrv, /* Physical drive nmuber (0..) */
    BYTE cmd,  /* Control code */
    void *buff /* Buffer to send/receive control data */
)
{
    DRESULT stat;
    
    switch (cmd)
    {
    case CTRL_SYNC:
        stat = RES_OK;
        break;
    case GET_SECTOR_COUNT:
          u8 csddata[16] = {0};
    SDGetCIDCSD(CSD, csddata);
    u32 csize = csddata[9] + ((uint32_t)csddata[8] << 8) + ((uint32_t)(csddata[7] & 0x3f) << 16) + 1;
    u32 Capacity = csize << 9;
        *((DWORD *)buff) = Capacity;
        stat = RES_OK;
        break;
    case GET_SECTOR_SIZE:

        *(WORD *)buff = 512; //spi flash的扇区大小是 512 Bytes
        return RES_OK;
    case GET_BLOCK_SIZE:
        *((DWORD *)buff) = 4096;
        stat = RES_OK;
        break;

    default:
        stat = RES_PARERR;
        break;
    }

    return stat;
}
DWORD get_fattime(void)
{
    
    return 0;
}

4 简单实使用

经过上面的修改就差不多可以用了。不能用就找其他移植笔记看看缺了什么没有,主要是要注意现在SD卡不能挂载在0上面了。

4.1 挂载

 FATFS ffs;   /* Work area (filesystem object) for logical drive */
  FRESULT fr;
  fr=f_mount(&ffs, "1:/", 1);

f_mount的最后一个参数是1,就会立即挂载,是0就等需要的时候才挂载。

4.2 读写文件

    FATFS ffs;   /* Work area (filesystem object) for logical drive */
    FRESULT fr;
    fr=f_mount(&ffs, "1:/", 1);
    //准备要写入的数据
    u8 buf[512]={0};
     for(int j=0;j<512;j++)
     {
       buf[j]='a';
     }
   //接收数据的数组
   u8 bufb[512]={0};
   fr = f_open( &fil, "1:/test2.txt",  FA_OPEN_APPEND|FA_WRITE);
   // int fsizei=0;
   //fsizei=f_size(&fil);//读取文件大小
   // fr = f_lseek(&fil, f_size(&fil));//移动光标
   fr = f_write(&fil, buf, 5, &bw);//写入5个字符
   f_close(&fil);
   fr=f_open(&fil, "1:/test2.txt", FA_OPEN_EXISTING | FA_READ);
   fr=f_read(&fil, bufb, 5, &br);
   f_close(&fil);

FA_OPEN_APPEND:文件存在就打开,并将光标移动到文件末尾,便于添加新内容。文件不存在就新建文件。
关于后面的参数选项官网有详细的解释:


open

这里与时俱进,不需要自己去移动光标了,可以直接通过参数打开文件并追加内容。

5 问题

因为我这是事后做的记录,没有重新去再移植一遍,所以记录上可能有疏忽。如果遇到什么问题可以给我反馈一下。

上一篇下一篇

猜你喜欢

热点阅读