BFMIPBWVFP 发表于 2024-7-26 15:57:35

stc32g12k128 模拟SDIO四通道读取SD卡

本帖最后由 BFMIPBWVFP 于 2024-7-26 16:34 编辑

废话不多说直接上源码。

本例用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);
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) //接收回复
{u8 i=500,j=8;

    while(i--)
    {
SD_SCK=0;   
sdiodelay();   
    SD_SCK=1;
    sdiodelay();
      if(SD_CMD==0)//返回开始
            {
            dat<<=1;
                  dat|=SD_CMD;
                  SD_SCK=0;
                  sdiodelay();

                  SD_SCK=1;
                  sdiodelay();
                  dat<<=1;
                  dat|=SD_CMD;
                                    SD_SCK=0;
                  sdiodelay();
                  SD_SCK=1;
                  sdiodelay();
                  dat<<=1;
                  dat|=SD_CMD;
                                    SD_SCK=0;
                  sdiodelay();
                  SD_SCK=1;
                  sdiodelay();
                  dat<<=1;
                  dat|=SD_CMD;
                                    SD_SCK=0;
                  sdiodelay();
                  SD_SCK=1;
                  sdiodelay();
                  dat<<=1;
                  dat|=SD_CMD;
                                        SD_SCK=0;
                  sdiodelay();
                  SD_SCK=1;
                  sdiodelay();
                  dat<<=1;
                  dat|=SD_CMD;
                                        SD_SCK=0;
                  sdiodelay();
                  SD_SCK=1;
                  sdiodelay();
                  dat<<=1;
                  dat|=SD_CMD;
                                        SD_SCK=0;
                  sdiodelay();
                  SD_SCK=1;
                  sdiodelay();
                  dat<<=1;
                  dat|=SD_CMD;


                for(j=1;j<17;j++)dat=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;//命令返回数组

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

//初始化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==0x01&&Response==0xaa)//卡是否支持2.7~3.6V
            {

                retry=64;
         do
                        {
                            SD_SendCmd(55,0x00000000);
                            Response=0x00;
                        SD_SendCmd(41,0x403c0000);
                        }while(((Response&0x80)!=0x80)&& retry--);//OCR=1 时上电操作是否完成

                        //OCR=1 时,OCR (CCS) 指示了卡是否为大容量卡。
                        //CMD8 无响应,ACMD41 响应的 OCR=0 :SD 1.X 卡。
                        //CMD8 有响应,ACMD41 响应的 OCR=0 :SD 2.0 非大容量卡。
                        //CMD8 有响应,ACMD41 响应的 OCR=1 :SDHC 2.0 大容量卡。

                        if(Response&0x40)SD_Type=SD_TYPE_V2HC;//CMD8 有响应,ACMD41 响应的 OCR=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<<8)|Response; // 获取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;
    if(SD_Type!=SD_TYPE_V2HC)add <<= 9;//转换为字节地址
      dat=(u8)(17 | 0x40);
      dat=(u8)(add>>24);
      dat=(u8)(add>>16);
      dat=(u8)(add>>8);
      dat=(u8)(add);
      CRC=(crc7_mmc(dat,5)<<1)+1;

    SDHisendcmd(dat);//发送读命令
    SDHisendcmd(dat);
SDHisendcmd(dat);
SDHisendcmd(dat);
SDHisendcmd(dat);
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=SDout;
                  SD_SCK=0;
                  buf<<=4;
                  SD_SCK=1;
                  buf|=(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;
    if(SD_Type!=SD_TYPE_V2HC)add <<= 9;//转换为字节地址
      dat=(u8)(18 | 0x40);
      dat=(u8)(add>>24);
      dat=(u8)(add>>16);
      dat=(u8)(add>>8);
      dat=(u8)(add);
      CRC=(crc7_mmc(dat,5)<<1)+1;

    SDHisendcmd(dat);//发送读命令
    SDHisendcmd(dat);
SDHisendcmd(dat);
SDHisendcmd(dat);
SDHisendcmd(dat);
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=SDout;
                  SD_SCK=0;
                  buf<<=4;
                  SD_SCK=1;
                  buf|=(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;

            }
    }

}
————————————————


原文链接:https://blog.csdn.net/bfmipbwvfp/article/details/140374767,也是本人发的,上面有附件。

xxxevery 发表于 2024-7-27 17:21:23

谢谢分享

soma 发表于 2024-7-27 18:20:42

这是qspi接口的程序

_NCY_ 发表于 2024-7-29 07:09:02

SD卡模拟qspi比spi硬件dma快多少?
如果flash也用qspi了话能快很多吗?

小涵子爸爸 发表于 2024-7-29 07:58:31

感谢分享

BFMIPBWVFP 发表于 2024-7-29 08:14:57

soma 发表于 2024-7-27 18:20
这是qspi接口的程序

并不是,QSPI比sdio少一条CMD命令线路。

dyojSTC 发表于 2025-3-13 15:49:42

感谢分享
页: [1]
查看完整版本: stc32g12k128 模拟SDIO四通道读取SD卡