GPIO模拟SDIO,四通道模式读取SD卡

用单片机读取SD卡常用SPI模式,特别是在没有SDIO的单片机,几乎成了默认的方法!但SPI模式速度最多25Mbit,很多单片机的spi速率还远达不到25Mbit,很多时候只能忍受Kbit级的速度!而SDIO模式可以4四通模式,这样即使用gpio模拟也很易达到Mbyte的读取速率!

本例用stc32g12k128单片机,24M主频时已经过Mbyte的平均速率,如用spi,只有用24M高高速spi的DMA模式才可以达到!本人找了网上好久没有找到相关的例程,所以自已码了一个,其中参考了前辈们关于sd的知识,代码纯手敲,给各位有须要的参考,如用得到拿去用,不喜勿喷!

核心代码共四个文件:

sdio.h设置接口等,如下。

--------------------------------------------------------分割线--------------------------------------------------#ifndef __SDIO_H
#define __SDIO_H    


#include "..\sys\sys.h"
//#define MAIN_Fosc 24000000UL
//接口定义
sbit SD_D0=P7^0;
sbit SD_D1=P7^1;
sbit SD_D2=P7^2;
sbit SD_D3=P7^3;
sbit SD_da=P7^4;  //读数据指示
sbit SD_EN=P7^5;  //卡插入检测
sbit SD_CMD=P7^6;
sbit SD_SCK=P7^7;

#define SDin P7&0x0f  //用于并行读写
#define SDout P7      //用于并行读写
#define CMDdelay 15  //用于初始化命令时钟脉冲调整。使时钟速率在要求的100-400KHZ;  

void SDioinit(void);
u8 SDread(void);
void SDwrite(u8 dat);
void SDsendcmd(u8 CMD);  //低速速模式发命令
void SDHisendcmd(u8 CMD);//高速模式发命令
void watit_8CR();
u8 SDRScmd(void);
void SDRS_response(u8 dat[17]);
unsigned char crc7_mmc(unsigned char *dat,int length);


#endif

--------------------------------------------------------分割线--------------------------------------------------

sdio.c用gpio模拟sdio时序,如下::::::::::::::::::::::::

/*
此源码用于用gpoi形成基本sdio时序。支持四通道读取,写入。
由于4通道写入的CRC16计算太费资源使得写入对比SPI模式没有优势,所以此例只
使用4通道读取功能,写入没有实现。
*/
#include "sdio.h"
u8 temp=0;
void sdiodelay(void) //用于延时调节命令的时钟,初始化时时钟要在100-400KHZ。

    u8 us=CMDdelay;
    while(--us);
}    

void SDioinit(void)
{
  SDout=0xff;

}


u8 SDread(void) //四通道读取1byte数据

    SD_SCK=0;
    SD_SCK=1;
  temp=SDout&0x0f;
    SD_SCK=0;
    temp<<=4;
  SD_SCK=1;
    return(SDout&0x0f|temp);

}


void SDwrite(u8 dat) //四通道写入取1byte数据

  
    SD_SCK=0;
    SDout=SDout&0xf0|(dat>>4);
    SD_SCK=1;
    dat&=0x0f;
    SD_SCK=0;
    SDout=SDout&0xf0|dat;
  SD_SCK=1;
    

}

void SDsendcmd(u8 CMD)  //限速发送接收,用于初始化时使用

    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    sdiodelay();
    SD_SCK=1;
  sdiodelay();    
    CMD<<=1;    
    
}
void SDHisendcmd(u8 CMD)   //全速发送命令

    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    SD_SCK=0;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    SD_SCK=1;
  CMD<<=1;
    
}


void watit_8CR()  //等待8个时钟周期。

  u8 temp=0xff,CMD=0xff,i=8;
    
    while(i--)
    {
    SD_SCK=1;
  (CMD&0x80)?SD_CMD=1:SD_CMD=0;
    temp<<=1;    
    sdiodelay();
    SD_SCK=0;    
    sdiodelay();
    temp|=SD_CMD;
    CMD<<=1;    

    }

    
}
u8 SDRScmd(void)  //接收回复,用于初始化时和设置卡。



  u8 temp;
    
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    SD_SCK=0;    
  sdiodelay();    
  temp<<=1;
    SD_SCK=1;
  sdiodelay();
    temp|=SD_CMD;
    
    return temp;
    
}


void SDRS_response(u8 dat[17]) //接收回复
{u8 i=500,j=8;
    
    while(i--)
    {
  SD_SCK=0;    
  sdiodelay();    
    SD_SCK=1;
    sdiodelay();
        if(SD_CMD==0)//返回开始
            {  
              dat[0]<<=1;
                    dat[0]|=SD_CMD;
                    SD_SCK=0;
                    sdiodelay();
                
                    SD_SCK=1;
                    sdiodelay();
                  dat[0]<<=1;
                    dat[0]|=SD_CMD;
                                    SD_SCK=0;
                    sdiodelay();
                    SD_SCK=1;
                    sdiodelay();
                  dat[0]<<=1;
                    dat[0]|=SD_CMD;
                                    SD_SCK=0;
                    sdiodelay();
                    SD_SCK=1;
                    sdiodelay();
                  dat[0]<<=1;
                    dat[0]|=SD_CMD;
                                    SD_SCK=0;
                    sdiodelay();
                    SD_SCK=1;
                    sdiodelay();
                  dat[0]<<=1;
                    dat[0]|=SD_CMD;
                                        SD_SCK=0;
                    sdiodelay();
                    SD_SCK=1;
                    sdiodelay();
                  dat[0]<<=1;
                    dat[0]|=SD_CMD;
                                        SD_SCK=0;
                    sdiodelay();
                    SD_SCK=1;
                    sdiodelay();
                  dat[0]<<=1;
                    dat[0]|=SD_CMD;
                                        SD_SCK=0;
                    sdiodelay();
                    SD_SCK=1;
                    sdiodelay();
                  dat[0]<<=1;
                    dat[0]|=SD_CMD;
        
            
                for(j=1;j<17;j++)dat[j]=SDRScmd();
                break;
            }
        
    }
    
    
}    

unsigned char crc7_mmc(unsigned char *dat,int length) //用于计算发送命令时的CRC7
{
    u8 i;
    u8 crc = 0;        // Initial value
    while (length--)
    {
        crc ^= *dat++;        // crc ^= *data; data++;
        for (i = 0; i < 8; i++)
        {
            if (crc & 0x80)
                crc = (crc << 1) ^ 0x12;        // 0x12 = 0x09<<(8-7) 多项式 x7+x3+1 0x85
            else
                crc <<= 1;
        }
    }
    
    return crc >> 1;
}    


u16 crc16_ccitt(u8 *dat, u16 length)  //CRC-16/CCITT        x16+x12+x5+1
{
    u8 i;
    u16 crc = 0;        // Initial value
    while(length--)
    {
        crc ^= *dat++;        // crc ^= *dat; dat++;
        for (i = 0; i < 8; ++i)
        {
            if (crc & 1)
                crc = (crc >> 1) ^ 0x8408;        // 0x8408 = reverse 0x1021
            else
                crc = (crc >> 1);
        }
    }
    return crc;
}

sd.h为sd卡的基本设置:

#ifndef __SD_H
#define __SD_H
#include "sdio.h"

extern u8 SD_Type;//SD卡的类型 
extern u8 Response[17];//命令返回数组

//错误码定义
//-------------------------------------------------------------
#define INIT_CMD0_ERROR     0x01 //CMD0错误
#define INIT_CMD1_ERROR     0x02 //CMD1错误
#define WRITE_BLOCK_ERROR   0x03 //写块错误
#define READ_BLOCK_ERROR    0x04 //读块错误
//-------------------------------------------------------------


// SD卡类型定义  
#define SD_TYPE_ERR     0X00
#define SD_TYPE_V2      0X04
#define SD_TYPE_V2HC    0X06

//SD传输数据结束后是否释放总线宏定义  
#define NO_RELEASE      0
#define RELEASE         1    


//SD卡回应标记字
#define MSD_RESPONSE_NO_ERROR      0x00
#define MSD_IN_IDLE_STATE          0x01
#define MSD_ERASE_RESET            0x02
#define MSD_ILLEGAL_COMMAND        0x04
#define MSD_COM_CRC_ERROR          0x08
#define MSD_ERASE_SEQUENCE_ERROR   0x10
#define MSD_ADDRESS_ERROR          0x20
#define MSD_PARAMETER_ERROR        0x40
#define MSD_RESPONSE_FAILURE       0xFF     

// SD卡指令表         
#define CMD0    0       //卡复位
#define CMD1    1
#define CMD2    2       //获取CID寄存器
#define CMD3    3       //获取CID寄存器
#define CMD7    7       //获取CID寄存器
#define CMD8    8       //命令8 ,SEND_IF_COND
#define CMD9    9       //命令9 ,读CSD数据
#define CMD10   10      //命令10,读CID数据
#define CMD12   12      //命令12,停止数据传输
#define CMD16   16      //命令16,设置SectorSize 应返回0x00
#define CMD17   17      //命令17,读sector
#define CMD18   18      //命令18,读Multi sector
#define CMD23   23      //命令23,设置多sector写入前预先擦除N个block
#define CMD24   24      //命令24,写sector
#define CMD25   25      //命令25,写Multi sector
#define CMD41   41      //命令41,应返回0x00
#define CMD55   55      //命令55,应返回0x01
#define CMD58   58      //命令58,读OCR信息
#define CMD59   59      //命令59,使能/禁止CRC,应返回0x00
#define TRY_TIME 10    //向SD卡写入命令之后,读取SD卡的回应次数,即读TRY_TIME次,如果在TRY_TIME次中读不到回应,产生超时错误,命令写入失败


u8 SD_WaitReady(void);                        //等待SD卡准备
u8 SD_Init(void);                            //初始化
u8 SD_SendCmd(u8 cmd, u32 arg);        //写命令
u8 SDRScmd(void);
void SD_readsent(u32 add);
void SD_readblock(u8 buf[],u32 add);
void SD_readMblock(u8 buf[],u32 add,u32 num);//读多个扇区
#endif

--------------------------------------------------------分割线--------------------------------------------------

sd.c为sd卡的初始化和读取函数实现。

/*
此源码用于用gpoi模拟sdio实现sd卡使用4通道读取功能,写入没有实现(CRC16校检太费时,没有优势)。
由于现市面上的卡都是2.0以上的大容量卡,所以本例只支持2.0版以上SD卡。
*/
#include "sd.h"
//#include ".\src\uart1.h"
//#include "delay.h"

u8 SD_Type=0;//SD卡的类型 
u8 Response[17]={0xff,0xff,0xff,0xff,0xff,0xff,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};//命令返回数组
u16 SD_RCA;  //卡自荐的地址,host 需要保存下来以便后续使用 CMD7 来选中此卡。

///


//等待卡准备好
//返回值:0,准备好了;其他,错误代码
u8 SD_WaitReady(void)
{
    u32 t=500;
  while(t--)SDsendcmd(0xff); 
    return 1;
}


//向SD卡发送一个命令
//输入: u8 cmd   命令 
//      u32 arg  命令参数
//      u8 crc   crc校验值       
//返回值:SD卡返回的响应                                                              
u8 SD_SendCmd(u8 cmd, u32 arg)
{

    u8 Retry=0; 
    u8 crc;
  u8 dat[5];
        Response[0]=0xff;
        dat[0]=(u8)(cmd | 0x40);
        dat[1]=(u8)(arg>>24);
        dat[2]=(u8)(arg>>16);
        dat[3]=(u8)(arg>>8);
        dat[4]=(u8)(arg);
      crc=(crc7_mmc(dat,5)<<1)+1;
    SDsendcmd(dat[0]);       //分别写入命令    最高2位固定为1  
    SDsendcmd(dat[1]);        //命令参数 2-5字节  4个字节 32位
    SDsendcmd(dat[2]);
    SDsendcmd(dat[3]);
    SDsendcmd(dat[4]);      
    SDsendcmd(crc); 
                                     //停止传数据命令
    //if(cmd==CMD12)SDsendcmd(0xff);//Skip a stuff byte when stop reading
      SDRS_response(Response);
      watit_8CR();
    return Response[0];
}

//初始化SD卡
u8 SD_Init(void)
{
  u8 r1;      // 存放SD卡的返回值
  u16 retry;  // 用来进行超时计数

    
     SD_WaitReady();
    r1=SD_SendCmd(CMD0,0);//进入IDLE状态      即复位sd卡 空闲状态
  watit_8CR();
    

     
     SD_Type=0;//默认无卡
     
    
        SD_SendCmd(CMD8,0x1AA);//SD V2.0      发送接口状态命令                                          
                                          //如果返回值为1 则是 SDV2.0版本
      
            if(Response[3]==0x01&&Response[4]==0xaa)//卡是否支持2.7~3.6V
            {  
                 
                retry=64;
         do
                        {
                            SD_SendCmd(55,0x00000000);
                            Response[1]=0x00;
                          SD_SendCmd(41,0x403c0000);
                        }while(((Response[1]&0x80)!=0x80)&& retry--);//OCR[31]=1 时上电操作是否完成
                        
                        //OCR[31]=1 时,OCR[30] (CCS) 指示了卡是否为大容量卡。
                        //CMD8 无响应,ACMD41 响应的 OCR[30]=0 :SD 1.X 卡。
                        //CMD8 有响应,ACMD41 响应的 OCR[30]=0 :SD 2.0 非大容量卡。
                        //CMD8 有响应,ACMD41 响应的 OCR[30]=1 :SDHC 2.0 大容量卡。
                        
                        if(Response[1]&0x40)SD_Type=SD_TYPE_V2HC;//CMD8 有响应,ACMD41 响应的 OCR[30]=1 :SDHC 2.0 大容量卡。    
                        else SD_Type=SD_TYPE_V2; 
    
                    r1=SD_SendCmd(CMD2,0x00000000); //获取CID寄存器    
                                       //R2 类响应136BIT,其中包含了 CID 寄存器 (如图13) ,是该卡的识别号,host 可以忽略 CID 的内容。
            
                    r1=SD_SendCmd(CMD3,0x00000000);//进入数据传输模式。
                        
                    SD_RCA=((u16)Response[1]<<8)|Response[2]; // 获取RCA (16bit) ,这是卡自荐的地址,host 需要保存下来以便后续使用 CMD7 来选中此卡。
                        
          r1=SD_SendCmd(CMD7,(u32)(SD_RCA)<<16);    //选中卡    
                        
                    r1=SD_SendCmd(55,(u32)(SD_RCA)<<16);  //CMD55,参数必须是RCA
                    r1=SD_SendCmd(6, 0x00000002);    //ACMD6,打开4bit传输模式选中卡
          r1=SD_SendCmd(CMD16,0x00000200);    //设置块为512byte,不能设为其它。
                     //SD_WaitReady();    
                    
            }
        

    
    SDout=0xff;
    if(SD_Type)return 0;    //如果没有采集到SD卡版本 返回0成功。
    else if(r1)return r1; //初始化失败。       
    return 0xaa;//其他错误
}
    

void SD_readblock(u8 buf[],u32 add)//读取一个扇区数据
{
      int i=0,j=-1,cn=0;
        u8 CRC=0;
      u8 dat[5];
    if(SD_Type!=SD_TYPE_V2HC)add <<= 9;//转换为字节地址
        dat[0]=(u8)(17 | 0x40);
        dat[1]=(u8)(add>>24);
        dat[2]=(u8)(add>>16);
        dat[3]=(u8)(add>>8);
        dat[4]=(u8)(add);
      CRC=(crc7_mmc(dat,5)<<1)+1;
    
    SDHisendcmd(dat[0]);//发送读命令
    SDHisendcmd(dat[1]);
  SDHisendcmd(dat[2]);
  SDHisendcmd(dat[3]);
  SDHisendcmd(dat[4]);
  SDHisendcmd(CRC);
    P7=0xff;
    for(i=0;i<1024;i++) // 接收数据
     {
        
        SD_SCK=0;
        SD_SCK=1;    
        if(!SD_D0)
            {    
                  P74=0;
            for(j=0;j<512;j++)
                {
                    SD_SCK=0;
                    SD_SCK=1;
                    buf[j]=SDout;
                    SD_SCK=0;
                    buf[j]<<=4;
                    SD_SCK=1;
                    buf[j]|=(SDout&0x0f);                
                }
                P74=1;
                SDread();
                SDread();//接收crc16,丢弃。
                SDread();
                SDread();
                
                SDread();
                SDread();
                SDread();
                SDread();
                
                SDread();                        
                break;
            }
    }

}

void SD_readMblock(u8 buf[],u32 add,s32 num)//读取num个扇区数据
{
    
      u32 i,j,k=0;
        u8 CRC=0;
      u8 dat[5];
    if(SD_Type!=SD_TYPE_V2HC)add <<= 9;//转换为字节地址
        dat[0]=(u8)(18 | 0x40);
        dat[1]=(u8)(add>>24);
        dat[2]=(u8)(add>>16);
        dat[3]=(u8)(add>>8);
        dat[4]=(u8)(add);
      CRC=(crc7_mmc(dat,5)<<1)+1;
    
    SDHisendcmd(dat[0]);//发送读命令
    SDHisendcmd(dat[1]);
  SDHisendcmd(dat[2]);
  SDHisendcmd(dat[3]);
  SDHisendcmd(dat[4]);
  SDHisendcmd(CRC);
    SDout=0xff;
    num--;    
    for(i=0;i<10240;i++) // 接收数据
     {
        
        SD_SCK=0;
        SD_SCK=1;    
        if(!SD_D0)
            {    
                  P74=0;
            for(j=0;j<512;j++)
                {
                    SD_SCK=0;
                    SD_SCK=1;
                    buf[k]=SDout;
                    SD_SCK=0;
                    buf[k]<<=4;
                    SD_SCK=1;
                    buf[k]|=(SDout&0x0f);    
                    k++;
                }
                P74=1;
                SDread();
                SDread();//接收crc16,丢弃。
                SDread();
                SDread();
                
                SDread();
                SDread();
                SDread();
                SDread();
                
                SDread();                        
                if(num<1)break;
                num--;
            }
    }

        SDHisendcmd(0x4c);//发送读命令CMD12
        SDHisendcmd(0);
        SDHisendcmd(0);
        SDHisendcmd(0);
        SDHisendcmd(0);
        SDHisendcmd(0x61);
    
    for(i=0;i<1024;i++) // 接收数据
     {
        
        SD_SCK=0;
        SD_SCK=1;    
        if(SD_CMD==0)
            {    

             for(j=0;j<64;j++)
                {
                    SD_SCK=0;
                    SD_SCK=1;

                }
                
                break;
                
            }
    }

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值