打印
[应用相关]

STM32使用HAL库IO模拟SPI驱动SD卡

[复制链接]
708|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
paotangsan|  楼主 | 2021-7-4 16:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
#include "mmc_sd.h"
#include "delay.h"

uint8_t SD_Type = 0; //SD卡的类型

/// 移植修改区函数 //

/**
* SD卡SPI接口读写一个字节
* @param  TxData 待写入的字节
* @return        来自SPI的接收
*/
uint8_t SD_SPI_ReadWriteByte(uint8_t TxData)
{
    int i = 0;
    uint8_t RxData = 0;
    HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, GPIO_PIN_RESET);
    for(i = 7; i >= 0; i--)
    {
        HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, GPIO_PIN_RESET);
        if(TxData & (1 << i))
        {
            HAL_GPIO_WritePin(SPI_MOSI_GPIO_Port, SPI_MOSI_Pin, GPIO_PIN_SET);
        }
        else
        {
            HAL_GPIO_WritePin(SPI_MOSI_GPIO_Port, SPI_MOSI_Pin, GPIO_PIN_RESET);
        }
        delay_us(1);
        HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, GPIO_PIN_SET);
        RxData <<= 1;
        RxData |= HAL_GPIO_ReadPin(SPI_MISO_GPIO_Port, SPI_MISO_Pin);
        delay_us(1);
    }
    HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, GPIO_PIN_RESET);
    return RxData;
}



使用特权

评论回复
沙发
paotangsan|  楼主 | 2021-7-4 16:50 | 只看该作者
/// SPI硬件层初始化
void SD_SPI_Init(void)
{
    HAL_GPIO_WritePin(SPI_CLK_GPIO_Port, SPI_CLK_Pin, GPIO_PIN_RESET);    /* 时钟空闲为低电平 */
    SD_SPI_ReadWriteByte(0xFF);
    SD_CS_H();
}


使用特权

评论回复
板凳
paotangsan|  楼主 | 2021-7-4 16:51 | 只看该作者
  SD SPI 驱动代码  ///取消选择,释放SPI总线
void SD_DisSelect(void)
{   
SD_CS_H();   
SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
}



使用特权

评论回复
地板
paotangsan|  楼主 | 2021-7-4 16:52 | 只看该作者
/**
* 选中SD卡并等待卡准备好
* @return  0:成功  1:失败
*/
uint8_t SD_Select(void)
{
    SD_CS_L();
    if (SD_WaitReady() == 0)return 0; //等待成功
    SD_DisSelect();
    return 1;//等待失败
}


使用特权

评论回复
5
paotangsan|  楼主 | 2021-7-4 16:52 | 只看该作者
/**
* 等待SD卡准备好
* @return  0:成功  other:失败
*/
uint8_t SD_WaitReady(void)
{
    uint32_t t = 0;
    do
    {
        if (SD_SPI_ReadWriteByte(0XFF) == 0XFF)return 0; //OK
        t++;
    }
    while (t < 0XFFFFFF); //等待
    return 1;
}


使用特权

评论回复
6
paotangsan|  楼主 | 2021-7-4 16:52 | 只看该作者
/**
* 等待SD卡回应
* @param  Response 要得到的回应值
* @return          0:成功  other:失败
*/
uint8_t SD_GetResponse(uint8_t Response)
{
    uint16_t Count = 0xFFF; //等待次数
    while ((SD_SPI_ReadWriteByte(0XFF) != Response) && Count)Count--; //等待得到准确的回应
    if (Count == 0)return MSD_RESPONSE_FAILURE; //得到回应失败
    else return MSD_RESPONSE_NO_ERROR;//正确回应
}


使用特权

评论回复
7
paotangsan|  楼主 | 2021-7-4 16:52 | 只看该作者
/**
* 接收来自SD卡的一包数据
* @param  buf 存放接收的数据
* @param  len 接收的数据长度
* @return     0:成功  other:失败
*/
uint8_t SD_RecvData(uint8_t*buf, uint16_t len)
{
    if (SD_GetResponse(0xFE))return 1; //等待SD卡发回数据起始令牌0xFE
    while (len--) //开始接收数据
    {
        *buf = SD_SPI_ReadWriteByte(0xFF);
        buf++;
    }
    //下面是2个伪CRC(dummy CRC)
    SD_SPI_ReadWriteByte(0xFF);
    SD_SPI_ReadWriteByte(0xFF);
    return 0;//读取成功
}


使用特权

评论回复
8
paotangsan|  楼主 | 2021-7-4 16:52 | 只看该作者
//向sd卡写入一个数据包的内容 512字节
//buf:数据缓存区
//cmd:指令
//返回值:0,成功;其他,失败;

/**
* 向SD卡写入一扇区数据
* @param  buf 待写入的数据,size=512
* @param  cmd 指令
* @return     0:成功  other:失败
*/
uint8_t SD_SendBlock(uint8_t*buf, uint8_t cmd)
{
    uint16_t t;
    if (SD_WaitReady())return 1; //等待准备失效
    SD_SPI_ReadWriteByte(cmd);
    if (cmd != 0XFD) //不是结束指令
    {
        for (t = 0; t < 512; t++)SD_SPI_ReadWriteByte(buf[t]); //提高速度,减少函数传参时间
        SD_SPI_ReadWriteByte(0xFF);//忽略crc
        SD_SPI_ReadWriteByte(0xFF);
        t = SD_SPI_ReadWriteByte(0xFF); //接收响应
        if ((t & 0x1F) != 0x05)return 2; //响应错误
    }
    return 0;//写入成功
}


使用特权

评论回复
9
paotangsan|  楼主 | 2021-7-4 16:52 | 只看该作者
/**
* 向SD卡发送命令
* @param  cmd 待发送的命令
* @param  arg 参数
* @param  crc crc校验值
* @return     SD卡返回的响应值
*/
uint8_t SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
    uint8_t r1;
    uint8_t Retry = 0;
    SD_DisSelect();//取消上次片选
    if (SD_Select())return 0XFF; //片选失效
    //发送
    SD_SPI_ReadWriteByte(cmd | 0x40);//分别写入命令
    SD_SPI_ReadWriteByte(arg >> 24);
    SD_SPI_ReadWriteByte(arg >> 16);
    SD_SPI_ReadWriteByte(arg >> 8);
    SD_SPI_ReadWriteByte(arg);
    SD_SPI_ReadWriteByte(crc);
    if (cmd == CMD12)SD_SPI_ReadWriteByte(0xff); //Skip a stuff byte when stop reading
    //等待响应,或超时退出
    Retry = 0X1F;
    do
    {
        r1 = SD_SPI_ReadWriteByte(0xFF);
    }
    while ((r1 & 0X80) && Retry--);
    //返回状态值
    return r1;
}


使用特权

评论回复
10
paotangsan|  楼主 | 2021-7-4 16:53 | 只看该作者
/**
* 查询SD卡的CID信息,包括制造商信息
* @param  cid_data 存放CID数据,至少16字节
* @return          0:成功  1:失败
*/
uint8_t SD_GetCID(uint8_t *cid_data)
{
    uint8_t r1;
    //发CMD10命令,读CID
    r1 = SD_SendCmd(CMD10, 0, 0x01);
    if (r1 == 0x00)
    {
        r1 = SD_RecvData(cid_data, 16); //接收16个字节的数据
    }
    SD_DisSelect();//取消片选
    if (r1)return 1;
    else return 0;
}


使用特权

评论回复
11
paotangsan|  楼主 | 2021-7-4 16:53 | 只看该作者
/**
* 查询SD卡CID信息,包括制造商信息
* @param  csd_data 存放CID信息,至少16字节
* @return          0:成功  1:失败
*/
uint8_t SD_GetCSD(uint8_t *csd_data)
{
    uint8_t r1;
    r1 = SD_SendCmd(CMD9, 0, 0x01); //发CMD9命令,读CSD
    if (r1 == 0)
    {
        r1 = SD_RecvData(csd_data, 16); //接收16个字节的数据
    }
    SD_DisSelect();//取消片选
    if (r1)return 1;
    else return 0;
}


使用特权

评论回复
12
paotangsan|  楼主 | 2021-7-4 16:53 | 只看该作者
/**
* 获取SD卡扇区数
* @return  0:获取出错  other:SD卡扇区数
*/
uint32_t SD_GetSectorCount(void)
{
    uint8_t csd[16];
    uint32_t Capacity;
    uint8_t n;
    uint16_t csize;
    //取CSD信息,如果期间出错,返回0
    if (SD_GetCSD(csd) != 0) return 0;
    //如果为SDHC卡,按照下面方式计算
    if ((csd[0] & 0xC0) == 0x40)         //V2.00的卡
    {
        csize = csd[9] + ((uint16_t)csd[8] << 8) + 1;
        Capacity = (uint32_t)csize << 10;//得到扇区数
    }
    else //V1.XX的卡
    {
        n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
        csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
        Capacity = (uint32_t)csize << (n - 9); //得到扇区数
    }
    return Capacity;
}


使用特权

评论回复
13
paotangsan|  楼主 | 2021-7-4 16:53 | 只看该作者
/// SD卡进入闲置状态
uint8_t SD_Idle_Sta(void)
{
    uint16_t i;
    uint8_t retry;
    for (i = 0; i < 0xf00; i++); //纯延时,等待SD卡上电完成
    //先产生>74个脉冲,让SD卡自己初始化完成
    for (i = 0; i < 10; i++)SD_SPI_ReadWriteByte(0xFF);
    //-----------------SD卡复位到idle开始-----------------
    //循环连续发送CMD0,直到SD卡返回0x01,进入IDLE状态
    //超时则直接退出
    retry = 0;
    do
    {
        //发送CMD0,让SD卡进入IDLE状态
        i = SD_SendCmd(CMD0, 0, 0x95);
        retry++;
    }
    while ((i != 0x01) && (retry < 200));
    //跳出循环后,检查原因:初始化成功?or 重试超时?
    if (retry == 200)return 1; //失败
    return 0;//成功
}


使用特权

评论回复
14
paotangsan|  楼主 | 2021-7-4 16:53 | 只看该作者
/// 初始化SD卡
uint8_t SD_Initialize(void)
{
    uint8_t r1;      // 存放SD卡的返回值
    uint16_t retry;  // 用来进行超时计数
    uint8_t buf[4];
    uint16_t i;

    SD_SPI_Init();                //初始化IO
    for (i = 0; i < 10; i++)SD_SPI_ReadWriteByte(0XFF); //发送最少74个脉冲
    retry = 20;
    do
    {
        r1 = SD_SendCmd(CMD0, 0, 0x95); //进入IDLE状态
    }
    while ((r1 != 0X01) && retry--);
    SD_Type = 0; //默认无卡
    if (r1 == 0X01)
    {
        if (SD_SendCmd(CMD8, 0x1AA, 0x87) == 1) //SD V2.0
        {
            for (i = 0; i < 4; i++)buf[i] = SD_SPI_ReadWriteByte(0XFF);        //Get trailing return value of R7 resp
            if (buf[2] == 0X01 && buf[3] == 0XAA) //卡是否支持2.7~3.6V
            {
                retry = 0XFFFE;
                do
                {
                    SD_SendCmd(CMD55, 0, 0X01);        //发送CMD55
                    r1 = SD_SendCmd(CMD41, 0x40000000, 0X01); //发送CMD41
                }
                while (r1 && retry--);
                if (retry && SD_SendCmd(CMD58, 0, 0X01) == 0) //鉴别SD2.0卡版本开始
                {
                    for (i = 0; i < 4; i++)buf[i] = SD_SPI_ReadWriteByte(0XFF); //得到OCR值
                    if (buf[0] & 0x40)SD_Type = SD_TYPE_V2HC; //检查CCS
                    else SD_Type = SD_TYPE_V2;
                }
            }
        }
        else //SD V1.x/ MMC        V3
        {
            SD_SendCmd(CMD55, 0, 0X01);                //发送CMD55
            r1 = SD_SendCmd(CMD41, 0, 0X01);        //发送CMD41
            if (r1 <= 1)
            {
                SD_Type = SD_TYPE_V1;
                retry = 0XFFFE;
                do //等待退出IDLE模式
                {
                    SD_SendCmd(CMD55, 0, 0X01);        //发送CMD55
                    r1 = SD_SendCmd(CMD41, 0, 0X01); //发送CMD41
                }
                while (r1 && retry--);
            }
            else
            {
                SD_Type = SD_TYPE_MMC; //MMC V3
                retry = 0XFFFE;
                do //等待退出IDLE模式
                {
                    r1 = SD_SendCmd(CMD1, 0, 0X01); //发送CMD1
                }
                while (r1 && retry--);
            }
            if (retry == 0 || SD_SendCmd(CMD16, 512, 0X01) != 0)SD_Type = SD_TYPE_ERR; //错误的卡
        }
    }
    SD_DisSelect();//取消片选
    if (SD_Type)return 0;
    else if (r1)return r1;
    return 0xaa;//其他错误
}


使用特权

评论回复
15
paotangsan|  楼主 | 2021-7-4 16:54 | 只看该作者
/**
* 读取SD卡一个扇区
* @param  buf    存放读取的数据
* @param  sector 扇区编号
* @param  cnt    要读取的扇区个数
* @return        0:成功  other:失败
*/
uint8_t SD_ReadDisk(uint8_t*buf, uint32_t sector, uint8_t cnt)
{
    uint8_t r1;
    if (SD_Type != SD_TYPE_V2HC)sector <<= 9; //转换为字节地址
    if (cnt == 1)
    {
        r1 = SD_SendCmd(CMD17, sector, 0X01); //读命令
        if (r1 == 0) //指令发送成功
        {
            r1 = SD_RecvData(buf, 512); //接收512个字节
        }
    }
    else
    {
        r1 = SD_SendCmd(CMD18, sector, 0X01); //连续读命令
        do
        {
            r1 = SD_RecvData(buf, 512); //接收512个字节
            buf += 512;
        }
        while (--cnt && r1 == 0);
        SD_SendCmd(CMD12, 0, 0X01);        //发送停止命令
    }
    SD_DisSelect();//取消片选
    return r1;//
}


使用特权

评论回复
16
paotangsan|  楼主 | 2021-7-4 16:54 | 只看该作者
/**
* SD卡写一个扇区的数据
* @param  buf    存放待写入的数据
* @param  sector 扇区编号
* @param  cnt    要写入的扇区个数
* @return        0:成功  other:失败
*/
uint8_t SD_WriteDisk(uint8_t*buf, uint32_t sector, uint8_t cnt)
{
    uint8_t r1;
    if (SD_Type != SD_TYPE_V2HC)sector *= 512; //转换为字节地址
    if (cnt == 1)
    {
        r1 = SD_SendCmd(CMD24, sector, 0X01); //读命令
        if (r1 == 0) //指令发送成功
        {
            r1 = SD_SendBlock(buf, 0xFE); //写512个字节
        }
    }
    else
    {
        if (SD_Type != SD_TYPE_MMC)
        {
            SD_SendCmd(CMD55, 0, 0X01);
            SD_SendCmd(CMD23, cnt, 0X01); //发送指令
        }
        r1 = SD_SendCmd(CMD25, sector, 0X01); //连续读命令
        if (r1 == 0)
        {
            do
            {
                r1 = SD_SendBlock(buf, 0xFC); //接收512个字节
                buf += 512;
            }
            while (--cnt && r1 == 0);
            r1 = SD_SendBlock(0, 0xFD); //接收512个字节
        }
    }
    SD_DisSelect();//取消片选
    return r1;//
}


使用特权

评论回复
17
paotangsan|  楼主 | 2021-7-4 16:54 | 只看该作者
#ifndef _MMC_SD_H_
#define _MMC_SD_H_

#include "main.h"

// SD卡扇区大小
#define SD_SECTOR_SIZE  512U

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

// SD卡指令表
#define CMD0    0       //卡复位
#define CMD1    1
#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 MSD_DATA_OK                0x05
#define MSD_DATA_CRC_ERROR         0x0B
#define MSD_DATA_WRITE_ERROR       0x0D
#define MSD_DATA_OTHER_ERROR       0xFF

//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卡片选引脚控制,使用STM32CUBEMX配置SD卡片选引脚为输出模式并设置为高电平
#define SD_CS_H()                HAL_GPIO_WritePin(SD_CS_GPIO_Port, SD_CS_Pin, GPIO_PIN_SET)
#define SD_CS_L()                HAL_GPIO_WritePin(SD_CS_GPIO_Port, SD_CS_Pin, GPIO_PIN_RESET)

extern uint8_t  SD_Type;                        //SD卡的类型

//函数申明区
uint8_t SD_WaitReady(void);                                                        //等待SD卡准备
uint8_t SD_GetResponse(uint8_t Response);                                        //获得相应
uint8_t SD_Initialize(void);                                                        //初始化
uint8_t SD_ReadDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);                //读块
uint8_t SD_WriteDisk(uint8_t*buf,uint32_t sector,uint8_t cnt);                //写块
uint32_t SD_GetSectorCount(void);                                           //读扇区数
uint8_t SD_GetCID(uint8_t *cid_data);                     //读SD卡CID
uint8_t SD_GetCSD(uint8_t *csd_data);                     //读SD卡CSD

#endif


使用特权

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

本版积分规则

53

主题

4119

帖子

0

粉丝