打印
[应用相关]

玩一下SD卡 SPI总线

[复制链接]
2083|18
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

SD 卡又两种操作方式,一种是 SDIO 模式,还有一种是 SPI 模式,我介绍的是使用 SPI 模式。

沙发
junpeng324|  楼主 | 2018-5-31 19:11 | 只看该作者
SD 卡简介
SD 卡是由 MMC 卡的基础上发展而来的,它是一种基于半导体快闪**器
的新一代**设备,被广泛应用于各种便携设备,如手机、数码相机、多媒体播
放器等。

使用特权

评论回复
板凳
junpeng324|  楼主 | 2018-5-31 19:17 | 只看该作者
SD 卡可以分为三类:SD 卡、SDHC 卡、SDXC 卡

使用特权

评论回复
地板
junpeng324|  楼主 | 2018-5-31 19:24 | 只看该作者
它的通信协议也分为两种,一种是 V1.0 版本和 V2.0 版本,所以不同的版本
操作过程中会有一定的差异。对于 SD 卡和 SDHC 卡来说,协议基本是兼容的,
不过 SDXC 卡,区别就大了。在这里我们主要讨论的是 SD 卡和 SDHC 卡。而
SD 卡的通信接口也有两种模式,一种是 SDIO 模式,还有一种是 SPI 模式,这
里我们主要讨论的是 SPI 模式下的 SD 卡。使用 STM32 的 SPI 驱动 SD 卡,最高
通信速度可达 18Mbps,每秒可传输数据 2M 字节以上,对于一般的应用足够了。

使用特权

评论回复
5
junpeng324|  楼主 | 2018-5-31 19:24 | 只看该作者
SD 卡的操作
要操作 SD 卡,最重要的就是如何初始化 SD 卡、读 SD 卡、写 SD 卡。其他
的读取 SD 卡的其他型号信息等,都是次要的,只有偶尔才会用到。而学习初始
化 SD 卡,读 SD 卡之前,我们要先学会怎么向 SD 写命令。

使用特权

评论回复
6
junpeng324|  楼主 | 2018-5-31 19:25 | 只看该作者
1. SD 卡写命令操作
SD 卡的命令格式如下:


使用特权

评论回复
7
junpeng324|  楼主 | 2018-5-31 19:27 | 只看该作者
长度为 48 位,一共分为第一个字节的最高 2 位必须是 01。接下来 6 位是命
令号,比如:CMD0 其实就是 0x00;CMD16 其实是 0x10。第 2 个字节到第 5
个字节是命令参数,命令参数是特定的命令才会有的。剩下第 6 个字节是一个 7
位的 CRC 效验和最低位为 1 组成。

使用特权

评论回复
8
junpeng324|  楼主 | 2018-5-31 20:02 | 只看该作者
SD 卡的一些命令参数

使用特权

评论回复
9
junpeng324|  楼主 | 2018-5-31 21:11 | 只看该作者
写命令的操作步骤为:
1) 如果上次片选没有取消,那么先取消片选,取消片选之后提供格外
的 8 个时钟周期,方便 SD 卡准备好。
2) 选择片选。
3) 检测 SD 卡是否准备好,也就是一直发送 0xFF,直到接收到非 0xFF
为止。
4) 发送 6 个字节的命令格式。包括 1 个字节命令序号,4 个字节的命令
参数,1 个字节的 CRC 效验。注意第 1 个字节最高两位为 01。第 6
个字节最低位为 1。
5) 如果是 CMD12 停止数据传输命令的话,多发送 8 个时钟周期。也就
是发送 0xFF 一次,为了避免影响之后检测是否发送成功。
6) 检测是否发送命令成功。因为我们不知道回应有多少个字节,所以
一直读取回应数据,直到最高两位为 00 时,说明发送成功。

使用特权

评论回复
10
junpeng324|  楼主 | 2018-5-31 21:13 | 只看该作者
初始化SD使用的IO和SPI
static void SD_GPIO_Config(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
       
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOG, ENABLE);
       
        /* SD_CS PG14/ FLASH_CS PG13 */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

        GPIO_Init(GPIOG, &GPIO_InitStructure);
        GPIO_SetBits(GPIOG, GPIO_Pin_14);
        GPIO_SetBits(GPIOG, GPIO_Pin_13);
       
        /* ENC28J60_CS PB12 */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

        GPIO_Init(GPIOB, &GPIO_InitStructure);
        GPIO_SetBits(GPIOB, GPIO_Pin_12); //避免影响SD卡

        SPI2_Config();
}

使用特权

评论回复
11
junpeng324|  楼主 | 2018-5-31 21:14 | 只看该作者
向SD卡发送一个命令.
static uint8_t SD_WriteCmd(uint8_t cmd, uint32_t dat, uint8_t crc)
{         
        uint8_t r1 = 0;
        uint16_t i = 0;

        /* 复位SD卡,取消上次片选 */
        SD_CS_SET;
        SPI2_WriteReadData(0xFF);         //额外提供8个时钟

        SD_CS_CLR;
        while(SPI2_WriteReadData(0xFF) != 0xFF) //等待卡是否准备好
        {
                i++;
                if(i > 100)
                {
                        return 0xFF;                         //等待失败返回
                }
        }

        /* 发送数据 */
        SPI2_WriteReadData(cmd | 0x40);

        SPI2_WriteReadData(dat >> 24);     //发送Dat的最高8位
        SPI2_WriteReadData(dat >> 16);
        SPI2_WriteReadData(dat >> 8);
        SPI2_WriteReadData(dat & 0x00FF);

        SPI2_WriteReadData(crc);
        if(cmd == SD_CMD12)                 //如果是停止数据传输命令,额外多发一个时钟
        {
                SPI2_WriteReadData(0xFF);
        }

        i = 0;
        do
        {
                r1 = SPI2_WriteReadData(0xFF);       
                i++;
                if(i > 100)
                {
                        return 0xFF;
                }
        }
        while((r1 & 0x80) != 0);   //发送成功的最高位是0

        return r1;
}

使用特权

评论回复
12
junpeng324|  楼主 | 2018-5-31 21:14 | 只看该作者
初始化SD卡
int8_t SD_Init(void)
{
        uint8_t r1, buf[4];
        uint16_t i = 0;

        SD_GPIO_Config();
       
        /* 将SD卡通信模式转换为SPI模式,上电默认是用DAT0作数据线 */
        /* 接到CMD0时,CS信号有效,SPI模式启动 */
        for(i=0; i<0x0F; i++)//初始时,先发送至少74个时钟,这个是必须的。
        {
                SPI2_WriteReadData(0xFF);         //发送8*16个
        }

        //当读取到0x01的时候表示初始化成功
        i = 0;
        while(SD_WriteCmd(SD_CMD0, 0, 0x95) != 0x01)
        {
                i++;
                if(i > 100)
                {
                        return 0xFF;        //初始化失败返回0
                }
        }
               
        /* 发送CMD8,检测是否SD V2.0 */
        i = 0;
        do
        {
                i++;
                if(i > 100)        //若是发送超过次数跳出循环管
                {
                        break;
                }
                r1 = SD_WriteCmd(SD_CMD8, 0x01AA, 0x87);
        }
        while(r1 != 0x01); //发送CMD8
       
        if(r1 == 0x01)     //如果CMD8有回应说明是SD V2.0协议
        {
                /* 读取CMD8的返回值,检测是否支持电压 */
                /* 读取CMD8的返回值,检测是否支持电压 */
                for(i=0; i<4; i++)
                {
                        buf[i] = SPI2_WriteReadData(0xFF);
                }
               
                /* 卡电压不支持电压,返回错误 */
                if((buf[2] != 0x01) || (buf[3] != 0xAA))
                {
                        return 0xFF;
                }
               
                /* 初始化SD卡 */
                i = 0;
                do
                {
                        i++;
                        if(i > 100)
                        {
                                return 0xFF;   //返回失败
                        }

                        SD_WriteCmd(SD_CMD55, 0, 0x01);
                        r1 = SD_WriteCmd(SD_CMD41, 0x40000000, 0x01);
                }
                while(r1 != 0);

                /* 检测是SDHC卡还是SD卡 */
                i = 0;
                while(SD_WriteCmd(SD_CMD58, 0, 0x01) != 0)
                {
                        i++;
                        if(i > 100)
                        {
                                SD_TYPE = SD_TYPE_ERR;
                                break;
                        }               
                }

                /* 读取OCR */
                for(i=0; i<4; i++)
                {
                        buf[i] = SPI2_WriteReadData(0xFF);
                }

                if(buf[0] & 0x40)
                {
                        SD_TYPE = SD_TYPE_V2HC;
                }
                else
                {
                        SD_TYPE = SD_TYPE_V2;
                }       
        }
                       
        else //否则就是SD V1.0或者MMC V3
        {
                SD_WriteCmd(SD_CMD55, 0x00, 0x01);
                r1 = SD_WriteCmd(SD_CMD41, 0x00, 0x01);

                if(r1 <= 1)           //对CMD41有回应说明是SD V1.0
                {
                        SD_TYPE = SD_TYPE_V1;         //是V1.0卡
                        i = 0;
                        do
                        {
                                if(i > 100)
                                {
                                        return 0xFF;
                                }

                                SD_WriteCmd(SD_CMD55, 0x00, 0x01);
                                r1 = SD_WriteCmd(SD_CMD41, 0x00, 0x01);       
                        }
                        while(r1 != 0);       
                }

                else                  //没有回应说明是MMC V3
                {
                        SD_TYPE = SD_TYPE_MMC;         //卡类型是MMC卡
                        i = 0;
                        while(SD_WriteCmd(SD_CMD1, 0, 0x01) != 0)
                        {
                                i++;
                                if(i > 100)
                                {
                                        return 0xFF;
                                }
                        }       
                }
        }               

        SD_CS_SET;                   //取消片选
        SPI2_WriteReadData(0xFF);
//        SPI2_SetSpeed(SPI_BaudRatePrescaler_2);

        return 0;
}

使用特权

评论回复
13
junpeng324|  楼主 | 2018-5-31 21:15 | 只看该作者
读取SD卡的内存大小
int8_t SD_ReadCapacity(uint32_t *capacity)
{
        uint8_t csdValue[16];
        uint16_t n, i = 0;

        /* 发送命令 */
        while(SD_WriteCmd(SD_CMD9, 0, 0x01) != 0);
        {
                i++;
                if(i > 100)
                {
                        return 0xFF;  //发送命令失败
                }
        }

        /* 等待起始令牌0XFE */
        i = 0;
        while(SPI2_WriteReadData(0xFF) != 0xFE)
        {
                i++;
                if(i > 500)
                {
                        return 0xFF;
                }
        }

               
        /* 读取数据 */
        for(i=0; i<16; i++)
        {
                csdValue[i] = SPI2_WriteReadData(0xFF);
        }

        /* 读取两位CRC效验 */
        SPI2_WriteReadData(0xFF);          //RCC
        SPI2_WriteReadData(0xFF);
       
        /* 取消片选 */
        SD_CS_SET;                          
        SPI2_WriteReadData(0xFF);

        /* SD V2.0的卡CSD第一个数据是0x40 */
        if((csdValue[0] & 0xC0) == 0x40)
        {
                /* 计算C_SIZE,在CSD[69:48] */
                *capacity = csdValue[9] + ((uint16_t)csdValue[8] << 8) + 1;

                /* 实际上就是乘以1024 */
                *capacity = (*capacity) << 10;//得到扇区数                       
        }
        else   
        {
                /* 内存算法是 capacity = BLOCKNR * BLOCK_LEN */
                /* BLOCKNR = (C_SIZE + 1) * MULT; */
                /* BLOCK_LEN = (READ_BL_LEN < 12) 或2^(READ_BL_LEN) */

                /* 计算BLOCK_LEN,C_SIZE_MULT在CSD[49:47];READ_BL_LEN在CSD[83:80] */
                n = (csdValue[5] & 0x0A) + ((csdValue[10] & 0x80) >> 7)
                         + ((csdValue[9] & 0x03) << 1) + 2;

                /* 计算C_SIZE,C_SIZE在CSD[73:62] */
                *capacity = (csdValue[8] >> 6) + ((uint16_t)csdValue[7] << 2)
                            + ((uint16_t)(csdValue[6] & 3) << 10) + 1;
                *capacity = (*capacity) << (n - 9);//得到扇区数       
        }

        return 0;               
}

使用特权

评论回复
14
junpeng324|  楼主 | 2018-5-31 21:23 | 只看该作者
读取512个数据数据
static int8_t SD_ReadData(uint8_t *buf)
{
        uint16_t i;

        /* 等待起始令牌0XFE */
        i = 0;
        while(SPI2_WriteReadData(0xFF) != 0xFE)
        {
                i++;
                if(i > 0x0FFF)
                {
                        return 0xFF;
                }
        }
       
        /* 接收数据 */
        for(i=0; i<512; i++)
        {
                *buf = SPI2_WriteReadData(0xFF);
                buf++;       
        }

        /* 读完数据再读两位CRC效验,但是我们可以不需要它们 */
        SPI2_WriteReadData(0xFF);
        SPI2_WriteReadData(0xFF);
       
        return 0;                                                 
}

使用特权

评论回复
15
junpeng324|  楼主 | 2018-5-31 21:25 | 只看该作者
读取扇区的数据
int8_t SD_ReadDisk(uint8_t *buf, uint32_t sector, uint8_t num)
{
        uint16_t i;

        if(SD_TYPE != SD_TYPE_V2HC)
        {
                sector <<= 9; //转换位字节地址
        }
       
        if(num == 1)
        {
                /* 发送读取命令 */
                i = 0;
                while(SD_WriteCmd(SD_CMD17, sector, 0x01) != 0);
                {
                        i++;
                        if(i > 100)
                        {
                                return 0xFF;  //命令无反应,表明发送命令失败
                        }       
                }

                /*  接收数据  */
                if(SD_ReadData(buf) != 0)
                {
                        return 0xFF;
                }               
        }
        else
        {
                /* 发送连续读取命令 */
                i = 0;
                while(SD_WriteCmd(SD_CMD18, sector, 0x01) != 0);
                {
                        i++;
                        if(i > 100)
                        {
                                return 0xFF;  //命令无反应,表明发送命令失败
                        }       
                }
               
                /* 接收数据 */
                while(num--)
                {
                        /*  接收数据  */
                        if(SD_ReadData(buf) != 0)
                        {
                                return 0xFF;
                        }
                        buf += 512;                       
                }
                SD_WriteCmd(SD_CMD12, 0, 0x01); //发送停止信号
        }
       
        /* 取消片选 */
        SD_CS_SET;
        SPI2_WriteReadData(0xFF);       

        return 0;
}

使用特权

评论回复
16
junpeng324|  楼主 | 2018-5-31 21:26 | 只看该作者
写512个数据数据
static int8_t SD_WriteData(uint8_t *buf, uint8_t cmd)
{
        uint16_t i;

        /*  发送若干个时钟,等待SD卡准备好  */
        i = 0;
        while(SPI2_WriteReadData(0xFF) != 0xFF)
        {
                if(i > 0x0FFF)
                {
                        return 0xFF;       
                }       
        }

        /* 发送命令 */
        SPI2_WriteReadData(cmd);

        /* 开始写入数据 */
        for(i = 0; i<512; i++)
        {
                SPI2_WriteReadData(*buf);
                buf++;
        }

        /* 发送两位CRC效验码,随便发 */
        SPI2_WriteReadData(0xFF);
        SPI2_WriteReadData(0xFF);

        /* 读取返回值 */
        i = SPI2_WriteReadData(0xFF);

        if((i & 0x1F) != 0x05)          //判断是否写成功
        {
                 SD_CS_SET;
                SPI2_WriteReadData(0xFF);
                return 0xFF;
        }

        return 0;
}

使用特权

评论回复
17
junpeng324|  楼主 | 2018-5-31 21:26 | 只看该作者
写多个扇区
int8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint8_t num)
{
        uint8_t i;

        if(SD_TYPE != SD_TYPE_V2HC)
        {
                sector <<= 9; //转换位字节地址
        }
       
        /* - 只写一个扇区 - */
        if(num == 1)
        {
                /* 发送写命令 */
                i = 0;
                while(SD_WriteCmd(SD_CMD24, sector, 0x01) != 0);
                {
                        i++;
                        if(i > 100)
                        {
                                return 0xFF;  //命令无反应,表明发送命令失败
                        }       
                }
       
                if(SD_WriteData(buf, 0xFE) != 0)
                {
                        return 0xFF;       
                }
        }

        /* - 写多个扇区 - */
        else
        {
                if(SD_TYPE == SD_TYPE_MMC)                  //如果是MMC卡
                {
                        SD_WriteCmd(SD_CMD55, 0, 0X01);       
                        SD_WriteCmd(SD_CMD23, num, 0X01); //写入前先擦除num个扇区里面的数据       
                }
                /* 发送连续写命令 */
                i = 0;
                while(SD_WriteCmd(SD_CMD25, sector, 0x01) != 0);
                {
                        i++;
                        if(i > 100)
                        {
                                return 0xFF;  //命令无反应,表明发送命令失败
                        }       
                }

                /* - 开始写数据 - */
                while(num--)
                {
                        if(SD_WriteData(buf, 0xFC) != 0)
                        {
                                return 0xFF;       
                        }
                        buf += 512;       
                }

                /*  发送若干个时钟,等待SD卡准备好  */
                i = 0;
                while(SPI2_WriteReadData(0xFF) != 0xFF)
                {
                        if(i > 0x0FFF)
                        {
                                return 0xFF;       
                        }       
                }
       
                /* 发送结束命令 */
                SPI2_WriteReadData(0xFD);

        }

        SD_CS_SET;
        SPI2_WriteReadData(0xFF);

        return 0;
}

使用特权

评论回复
18
chongdongchen| | 2018-6-2 08:43 | 只看该作者
顶一下!

使用特权

评论回复
19
hwh132| | 2018-6-2 09:11 | 只看该作者
学习了,谢谢分享!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

37

主题

1130

帖子

8

粉丝