分立元件 发表于 2020-11-14 15:33:45

【分享】51单片机读写SD/TF卡测试程序

本帖最后由 分立元件 于 2020-11-14 15:35 编辑



测试2G和2G以下的TF卡可以使用,4G和8G的卡不能使用,不容易找了几天:L

#include<reg52.h>

//替换关键字
typedef unsigned long u32;
typedef unsigned char u8;
typedef unsigned int u16;
//接口定义
sbit SD_CS                = P1^0;
sbit SPI_MOSI        = P1^2;
sbit SPI_MISO        = P1^3;
sbit SPI_CLK         = P1^1;
//测试平台:STC89C516和STC12C5A60S2
//内存卡大小:128MB和2GB
//晶振:均为12MHz
//定义512字节数据缓冲区
u8 xdata BUFFER_DATA={0};

//使用位操作加快SPI读写速度
u8 bdata Byte_data;
sbit Byte_data7=Byte_data^7;
sbit Byte_data6=Byte_data^6;
sbit Byte_data5=Byte_data^5;
sbit Byte_data4=Byte_data^4;
sbit Byte_data3=Byte_data^3;
sbit Byte_data2=Byte_data^2;
sbit Byte_data1=Byte_data^1;
sbit Byte_data0=Byte_data^0;

//SPI写入一个字节(字节数据)
void SPI_write_Byte(u8 Byte)
{
        SPI_MOSI=1;
        Byte_data=Byte;
        SPI_CLK=0;SPI_MOSI=Byte_data7;SPI_CLK=1;
        SPI_CLK=0;SPI_MOSI=Byte_data6;SPI_CLK=1;
        SPI_CLK=0;SPI_MOSI=Byte_data5;SPI_CLK=1;
        SPI_CLK=0;SPI_MOSI=Byte_data4;SPI_CLK=1;
        SPI_CLK=0;SPI_MOSI=Byte_data3;SPI_CLK=1;
        SPI_CLK=0;SPI_MOSI=Byte_data2;SPI_CLK=1;
        SPI_CLK=0;SPI_MOSI=Byte_data1;SPI_CLK=1;
        SPI_CLK=0;SPI_MOSI=Byte_data0;SPI_CLK=1;
        SPI_MOSI=1;
}

//SPI读取一个字节(返回值为数据)
u8 SPI_read_Byte()
{
        SPI_MISO=1;
        SPI_CLK=0;Byte_data7=SPI_MISO;SPI_CLK=1;
        SPI_CLK=0;Byte_data6=SPI_MISO;SPI_CLK=1;
        SPI_CLK=0;Byte_data5=SPI_MISO;SPI_CLK=1;
        SPI_CLK=0;Byte_data4=SPI_MISO;SPI_CLK=1;
        SPI_CLK=0;Byte_data3=SPI_MISO;SPI_CLK=1;
        SPI_CLK=0;Byte_data2=SPI_MISO;SPI_CLK=1;
        SPI_CLK=0;Byte_data1=SPI_MISO;SPI_CLK=1;
        SPI_CLK=0;Byte_data0=SPI_MISO;SPI_CLK=1;
        SPI_MISO=1;
        return Byte_data;
}

//检测SD卡的响应
u8 SD_response()
{
        u8 i;
        u8 response;

        for(i=0;i<9;++i)
        {
                response=SPI_read_Byte();
                if((response==0x00)||(response==0x01)) break;
        }
        return response;
}

//MCU向SD卡写入命令
void MCU_write_SD_command(u8 command,u32 argument,u8 CRC)
{
        SPI_write_Byte(command|0x40);
        SPI_write_Byte(((u8 *)&argument));
        SPI_write_Byte(((u8 *)&argument));
        SPI_write_Byte(((u8 *)&argument));
        SPI_write_Byte(((u8 *)&argument));
        SPI_write_Byte(CRC);
}

//SD卡初始化
u8 SD_init()
{
        u8 i;
        u8 response=0x01;
       
        SD_CS=1;
        for(i=0;i<10;++i) SPI_write_Byte(0xFF);
        SD_CS=0;
       
        MCU_write_SD_command(0x00,0x00000000,0x95);//发送命令0将MMC置于SPI模式

        response=SD_response();

        if(response!=0x01) return 0;
        while(response)
        {
                SD_CS=1;
                SPI_write_Byte(0xFF);
                SD_CS=0;
                MCU_write_SD_command(0x01,0x00FFC000,0xFF);
                response=SD_response();
        }
        SD_CS=1;
        SPI_write_Byte(0xFF);
        return 1;
}

//MCU向SD卡写入数据,一次最多512字节(扇区,数据长度(最大为512,建议填512))
u8 MCU_write_SD_512Byte(u32 block,u16 data_length)
{
        u16 i;
        u8 *block_data_pointer;//扇区数据指针
        u8 dataResp;
       
        block_data_pointer=BUFFER_DATA;//指针指向缓冲数据
       
        SD_CS=0;
       
        MCU_write_SD_command(0x18,512*block,0xFF);//发送写入命令

        if(!SD_response())
        {
                SPI_write_Byte(0xFF);
                SPI_write_Byte(0xFF);
                SPI_write_Byte(0xFF);
                //命令成功-现在MCU向SD卡写入数据
                SPI_write_Byte(0xFE);//数据将在发送0xFE后开始写入
                //现在MCU向SD卡写入数据
                for(i=0;i<data_length;++i)
                {
                        SPI_write_Byte(*block_data_pointer);
                        ++block_data_pointer;
                }
                for(;i<512;++i) SPI_write_Byte(0x00);
               
                //数据块发送-现在发送校验和
                SPI_write_Byte(0xFF);//两字节CRC校验,为0XFFFF,不考虑CRC
                SPI_write_Byte(0xFF);

                dataResp=SPI_read_Byte();//现在读取数据响应
                //在数据响应之后是一个繁忙的字节数,一个零字节表示MMC忙

                while(!SPI_read_Byte());

                dataResp=dataResp&0x0F; //屏蔽数据响应令牌的高字节
               
                SD_CS=1;
               
                SPI_write_Byte(0xFF);
                if(dataResp==0x0B) return 0;//数据不被CARD接受
                if(dataResp==0x05) return 1;//无效的数据响应
                return 0;
        }
        //写入命令0x18没有被MMC接收
        return 0;
}

//MCU向SD卡读取数据,一次最多512字节(扇区,数据长度(最大为512,建议填512))
u8 MCU_read_SD_512Byte(u32 block,u16 data_length)
{
        u16 i;
        u8 *block_data_pointer;//扇区数据指针
       
        block_data_pointer=BUFFER_DATA;//指针指向缓冲数据
       
        SD_CS=0;
        MCU_write_SD_command(0x11,512*block,0xFF);//然后发送写命令

        if(!SD_response())
        {
                while(SPI_read_Byte()!=0xFE);//发送命令成功,现在发送数据,数据在接收0xFE之后

                for(i=0;i<data_length;++i)
                {
                       
                        *block_data_pointer=SPI_read_Byte();
                        ++block_data_pointer;
                }
                for(;i<512;++i) SPI_read_Byte();

                //数据块发送-现在发送校验和
                SPI_read_Byte();
                SPI_read_Byte();
                //现在读入数据响应
                SD_CS=1;
                SPI_read_Byte();
                return 1;
        }
        //读命令0x11没有被MMC的接收
        return 0;
}
void main()
{
        unsigned int i;

        SD_init();//SD卡初始化
       
        for(i=0;i<512;++i)
        BUFFER_DATA=0x7F;

        MCU_write_SD_512Byte(960,512);//MCU向SD卡写入数据,一次最多512字节(扇区,数据长度(最大为512,建议填512))

        for(i=0;i<512;++i) //清零数组,准备接收SD卡数据
        BUFFER_DATA=0;

        MCU_read_SD_512Byte(960,512);//MCU向SD卡读取数据,一次最多512字节(扇区,数据长度(最大为512,建议填512))
        P0=BUFFER_DATA;
       
        while(1);
}

/*
        unsigned int i;
       
        SD_init();//SD卡初始化
       
        {
                在使用SD卡模块前,先初始化SD卡
        }
       
        for(i=0;i<512;++i) BUFFER_DATA=0x7F;
        MCU_write_SD_512Byte(960,512);//MCU向SD卡写入数据,一次最多512字节(扇区,数据长度(最大为512,建议填512))
       
        {
                将数据写入到SD卡:
                先将数据写入到数据缓冲数组BUFFER_DATA中(这里的数据我全部都写0x7F),
                然后运行MCU_write_SD_512Byte(960,512);
                960为SD卡的扇区,表示将数据写入到SD卡的960扇区,512为一次性写入的数据大小,为512字节,建议填512字节,
                因为1个扇区的大小为512字节
        }
       
        MCU_read_SD_512Byte(960,512);//MCU向SD卡读取数据,一次最多512字节(扇区,数据长度(最大为512,建议填512))
        P0=BUFFER_DATA;
       
        {
                从SD卡读取数据:
                先运行MCU_read_SD_512Byte(960,512);
                这里的960为你要读的SD扇区,512位一次性读取的数据大小,为512字节,建议填512字节,因为1个扇区的大小为512字节
                完成后数据存放在数据缓冲数组BUFFER_DATA中
               
                因为没有现象的话不好判断SD卡读写是否成功,所以把P0BUFFER_DATA赋给P0,P0接LED灯,就可以看见现象了
        }

        {
                注意:将程序下载到开发板后可能会出现P0口显示的数据和SD卡写入数据不一致的情况
                这时,
                        要将开发板和SD卡模块断电
                        要将开发板和SD卡模块断电
                        要将开发板和SD卡模块断电
                        不是复位
                        不是复位
                        不是复位
                将开发板断电后再上电,就可以看见现象了
                如果还不成功,
                        检查连线是否正确
                        检查连线是否正确
                        检查连线是否正确
                或尝试插拔SD卡
               
                SD卡读写成功后,可以修改扇区和SD的写入数据多实验几次,确保万无一失
        }
*/

iffi123 发表于 2020-11-14 20:28:16

本帖最后由 iffi123 于 2020-11-14 20:29 编辑

网上很多程序,针对1.x版本的tf/sd卡, 通过CMD0进入空闲状态,然后发送CMD1进行初始化

而2.0(大于4G容量)需改成CMD8初始化,我是用16G卡做试验

分立元件 发表于 2020-11-14 21:05:48

iffi123 发表于 2020-11-14 20:28
网上很多程序,针对1.x版本的tf/sd卡, 通过CMD0进入空闲状态,然后发送CMD1进行初始化

而2.0(大于4G容量 ...

这样额,我试试看!谢谢啦

分立元件 发表于 2020-11-15 00:39:34

iffi123 发表于 2020-11-14 20:28
网上很多程序,针对1.x版本的tf/sd卡, 通过CMD0进入空闲状态,然后发送CMD1进行初始化

而2.0(大于4G容量 ...

刚才试了一下原来的4G和8G内存卡,把CDM1改CDM8(0X48,0X000001AA,0X78),也不能正常工作,用我新买的4G内存卡居然可以在CDM1下正常工作,好奇怪?

t3486784401 发表于 2020-11-15 01:21:16

本帖最后由 t3486784401 于 2020-11-15 01:22 编辑

我看了 Arduino 的 SD 库(4G/8G 都支持),的确提到了通过 CMD8 来区分 SD1 和 SD2 类型的卡。
但是 CMD8 的数据 CRC 校验是 0x87,与 LZ 在 4L 提到的不符,望确认。

Arduino 库里 CMD8 发送的是: 0x48, 0x1AA, 0x87

附上源码截图:



------------------------------------------------------------------------------------------------------

Arduino 的库直接在 FAT 层面搞的,底层平时都没咋看过

iffi123 发表于 2020-11-15 07:53:21

本帖最后由 iffi123 于 2020-11-15 08:09 编辑

分立元件 发表于 2020-11-15 00:39
刚才试了一下原来的4G和8G内存卡,把CDM1改CDM8(0X48,0X000001AA,0X78),也不能正常工作,用我新买的4G内 ...

SD规范对初始化的流程, 图看起来比较杂


昨天我回复时疏忽了,CMD8是识别卡是否支持2.0的,

如果支持,后续真正初始化是ACMD41, 执行成功后,卡就进入ready状态,可以正常读写操作, 此时若再发送CMD58,可进一步识别是否是大容量卡;
不支持CMD8的话,就是1.x卡,改用CMD1初始化(手上没有小于4G卡,自己未试过CMD1)

分立元件 发表于 2020-11-15 08:41:37

t3486784401 发表于 2020-11-15 01:21
我看了 Arduino 的 SD 库(4G/8G 都支持),的确提到了通过 CMD8 来区分 SD1 和 SD2 类型的卡。
但是 CMD8 ...

不好意思是我打错数字了,87写成78了!
页: [1]
查看完整版本: 【分享】51单片机读写SD/TF卡测试程序