废话不多说,直接上代码!
#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;
}
/// 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();
}
SD SPI 驱动代码 /
//取消选择,释放SPI总线
void SD_DisSelect(void)
{
SD_CS_H();
SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
}
/**
* 选中SD卡并等待卡准备好
* @return 0:成功 1:失败
*/
uint8_t SD_Select(void)
{
SD_CS_L();
if (SD_WaitReady() == 0)return 0; //等待成功
SD_DisSelect();
return 1;//等待失败
}
/**
* 等待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;
}
/**
* 等待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;//正确回应
}
/**
* 接收来自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;//读取成功
}
//向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;//写入成功
}
/**
* 向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;
}
/**
* 查询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;
}
/**
* 查询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;
}
/**
* 获取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;
}
/// 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;//成功
}
/// 初始化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;//其他错误
}
/**
* 读取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;//
}
/**
* 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;//
}
#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