- void SD_SPI_CSSetLev(uint8_t lev)
- {
- if(lev == 0)
- LL_GPIO_ResetOutputPin(SPI1_CS_GPIO_Port,SPI1_CS_Pin);
- else
- LL_GPIO_SetOutputPin(SPI1_CS_GPIO_Port,SPI1_CS_Pin);
- }
- uint8_t SD_SPI_WriteByte(uint8_t data)
- {
- uint8_t rev = 0;
- while(!LL_SPI_IsActiveFlag_TXE(SPI1));
- LL_SPI_TransmitData8(SPI1,data);
- while(!LL_SPI_IsActiveFlag_RXNE(SPI1));
- rev = LL_SPI_ReceiveData8(SPI1);
- return rev;
- }
- uint8_t SD_SPI_ReadByte(void)
- {
- return SD_SPI_WriteByte(0xFF);
- }
- void SD_SPI_WriteDatas(uint8_t *datas, uint16_t len)
- {
- while(len > 0)
- {
- SD_SPI_WriteByte(*datas);
- datas += 1;
- len -= 1;
- }
- }
- void SD_SPI_ReadDatas(uint8_t *datas, uint16_t len)
- {
- while(len > 0)
- {
- *datas = SD_SPI_ReadByte();
- datas += 1;
- len -= 1;
- }
- }
通电后需要等待SD卡电压稳定后再进行后续通讯
通过SPI向SD卡发送的命令格式如下
涉及到的部分命令码如下
几种应答的数据格式:
R1,1字节,R1b与R1格式相同,但可选地添加了忙信号。忙信号令牌可以是任意数量的字节。零值表示卡正忙。非零值表示卡已准备好下一个命令。
R2,2字节
R3,5字节,用于获取OCR寄存器的数据。高8位为R1令牌,低4字节为OCR寄存器的数据。
R7,共5字节,主要用于获取SD卡工作电压信息,高8位为R1令牌。
在SPI模式中,只有CMD0和CMD8需要CRC,其他命令的CRC无效直接填0xFF即可
封装命令发送方法
- /**
- * [url=home.php?mod=space&uid=247401]@brief[/url] SD卡命令
- */
- #define SD_CMD0_GO_IDLE_STATE 0 //复位所有的卡到idle状态
- #define SD_CMD1_SEND_OP_COND 1 //发送主机支持的电压操作范围
- #define SD_CMD8_SEND_IF_COND 8 //发送SD卡接口条件,包含主机支持的电压信息,并询问卡是否支持
- #define SD_CMD9_SEND_CSD 9 //要求卡发送其CSD寄存器内容
- #define SD_CMD10_SEND_CID 10 //要求卡发送其CID寄存器内容
- #define SD_CMD12_STOP_TRANSMISSION 12 //强制卡停止传输,可用于多块读写时表示结束
- #define SD_CMD13_SEND_STATUS 13 //要求卡发送状态寄存器内容
- #define SD_CMD16_SET_BLOCKLEN 16 //对于标准SD卡设置块命令长度,SDHC卡固定512
- #define SD_CMD17_READ_SINGLE_BLOCK 17 //对于标准SD卡读取指定长度的块,SDHC卡固定读取512
- #define SD_CMD18_READ_MULT_BLOCK 18 //连续从SD卡读取数据块,直到被CMD12打断
- #define SD_CMD24_WRITE_SINGLE_BLOCK 24 //对于标准SD卡设置写入CMD16设置长度的数据,SDHC卡固定512
- #define SD_CMD25_WRITE_MULT_BLOCK 25 //连续向SD卡写入数据,直到被CMD12打断
- #define SD_CMD27_PROG_CSD 27 //对CSD的可编程位进行编程
- #define SD_CMD30_SEND_WRITE_PROT 30 //要求卡发送写保护状态
- #define SD_CMD32_SD_ERASE_GRP_START 32 //设置擦除的起始块地址
- #define SD_CMD33_SD_ERASE_GRP_END 33 //设置擦除的结束块地址
- #define SD_CMD38_ERASE 38 //擦除选定的块
- #define SD_CMD58_READ_OCR 58 //要求返回OCR寄存器的值
- #define SD_CMD55_APP_CMD 55 //发送ACMD前发送此命令,返回0x01
- #define SD_ACMD23_SE_SET_BLOCK_COUNT 23 //设置预擦除块数
- #define SD_ACMD41_SD_SEND_OP_COND 41 //返回0x00
- typedef enum {
- /**
- * [url=home.php?mod=space&uid=247401]@brief[/url] SD 响应及错误标志
- */
- SD_RESPONSE_NO_ERROR = (0x00),
- SD_IN_IDLE_STATE = (0x01),
- SD_ERASE_RESET = (0x02),
- SD_ILLEGAL_COMMAND = (0x04),
- SD_COM_CRC_ERROR = (0x08),
- SD_ERASE_SEQUENCE_ERROR = (0x10),
- SD_ADDRESS_ERROR = (0x20),
- SD_PARAMETER_ERROR = (0x40),
- SD_RESPONSE_FAILURE = (0xFF),
- /**
- * @brief 数据响应类型
- */
- SD_DATA_OK = (0x05),
- SD_DATA_CRC_ERROR = (0x0B),
- SD_DATA_WRITE_ERROR = (0x0D),
- SD_DATA_OTHER_ERROR = (0xFF)
- } SD_Error;
- enum {
- SD_RESPONSE_TYPE_NONE = 0,
- SD_RESPONSE_TYPE_R1,
- SD_RESPONSE_TYPE_R1b,
- SD_RESPONSE_TYPE_R2,
- SD_RESPONSE_TYPE_R3,
- SD_RESPONSE_TYPE_R7,
- };
- uint8_t SD_GetCMResponseType(uint8_t cmd)
- {
- switch(cmd)
- {
- case SD_CMD0_GO_IDLE_STATE:
- case SD_CMD1_SEND_OP_COND:
- case SD_CMD9_SEND_CSD:
- case SD_CMD10_SEND_CID:
- case SD_CMD16_SET_BLOCKLEN:
- case SD_CMD17_READ_SINGLE_BLOCK:
- case SD_CMD18_READ_MULT_BLOCK:
- case SD_CMD24_WRITE_SINGLE_BLOCK:
- case SD_CMD25_WRITE_MULT_BLOCK:
- case SD_CMD27_PROG_CSD:
- case SD_CMD32_SD_ERASE_GRP_START:
- case SD_CMD33_SD_ERASE_GRP_END:
- case SD_CMD55_APP_CMD:
- case SD_ACMD41_SD_SEND_OP_COND:
- return SD_RESPONSE_TYPE_R1;
- break;
- case SD_CMD12_STOP_TRANSMISSION:
- return SD_RESPONSE_TYPE_R1b;
- break;
- case SD_CMD13_SEND_STATUS:
- return SD_RESPONSE_TYPE_R2;
- break;
- case SD_CMD58_READ_OCR:
- return SD_RESPONSE_TYPE_R3;
- break;
- case SD_CMD8_SEND_IF_COND:
- return SD_RESPONSE_TYPE_R7;
- break;
- default:
- break;
- }
- return SD_RESPONSE_TYPE_NONE;
- }
- void SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc,uint8_t *res,uint8_t release_cs)
- {
- uint8_t datas[6];
- uint16_t i = 0xFFF;
- SD_SPI_CSSetLev(0);
- datas[0] = (cmd | 0x40); /*!< Construct byte 1 */
- datas[1] = (uint8_t)(arg >> 24); /*!< Construct byte 2 */
- datas[2] = (uint8_t)(arg >> 16); /*!< Construct byte 3 */
- datas[3] = (uint8_t)(arg >> 8); /*!< Construct byte 4 */
- datas[4] = (uint8_t)(arg); /*!< Construct byte 5 */
- datas[5] = (crc); /*!< Construct CRC: byte 6 */
- SD_SPI_WriteDatas(datas,6);
- do
- {
- res[0] = SD_SPI_ReadByte();
- if(i == 0)
- {
- res[0]= SD_RESPONSE_FAILURE;
- return;
- }
- i -= 1;
- }while(res[0]&0x80);
- if(SD_GetCMResponseType(cmd) == SD_RESPONSE_TYPE_R2)
- {
- res[1]= SD_SPI_ReadByte();
- }
- else if(SD_GetCMResponseType(cmd) == SD_RESPONSE_TYPE_R3 || SD_GetCMResponseType(cmd) == SD_RESPONSE_TYPE_R7)
- {
- i = 1;
- while(i<5)
- {
- res[i] = SD_SPI_ReadByte();
- i += 1;
- }
- }
- if(release_cs == 0)
- return;
- SD_SPI_CSSetLev(1);
- SD_SPI_WriteByte(0xFF);
- }
SD卡初始化时,拉低CS并发送CMD0使SD卡进入SPI模式,之后通过CMD8判断SD卡版本,如果是2.0版本再通过CMD58判断是不是SDHC,完整流程如图
完成初始化流程后读取CSD寄存器,里面有SD卡大小的信息,CSD结构如图
根据C_SIZE就能计算出SD卡大小
我这里为了方便只判断是否符合SDHC,有兴趣的可以在此基础上实现完整的初始化过程,代码如下
- uint32_t sdcard_block_num = 0;
- /**
- * @brief 初始化 SD/SD 卡
- * @param None
- * @retval SD 响应:
- * - SD_RESPONSE_FAILURE: 初始化失败
- * - SD_RESPONSE_NO_ERROR: 初始化成功
- */
- uint8_t SD_Init(void)
- {
- uint8_t i;
- uint8_t res[5];
- uint8_t csd[16];
- SD_SPI_CSSetLev(1);
- for (i = 0; i < 10; i++)
- {
- SD_SPI_WriteByte(0xFF);
- }
- //进入空闲模式
- SD_SendCmd(SD_CMD0_GO_IDLE_STATE, 0, 0x95,res,1);
- if(res[0] != SD_IN_IDLE_STATE)
- return SD_RESPONSE_FAILURE;
- SD_SendCmd(SD_CMD8_SEND_IF_COND, 0x1AA, 0x87,res,1);
- if(res[0] != SD_IN_IDLE_STATE)
- return SD_RESPONSE_FAILURE;
- if (res[3]!=0x01 || res[4]!=0xAA)
- return SD_RESPONSE_FAILURE;
- i = 200;
- do
- {
- SD_SendCmd(SD_CMD55_APP_CMD, 0, 0xFF,res,1);
- if(res[0] != SD_IN_IDLE_STATE)
- return SD_RESPONSE_FAILURE;
- SD_SendCmd(SD_ACMD41_SD_SEND_OP_COND, 0x40000000, 0xFF,res,1);
- if (i == 0)
- return SD_RESPONSE_FAILURE;
- i-=1;
- } while (res[0] != SD_RESPONSE_NO_ERROR);
- i = 200;
- do
- {
- SD_SendCmd(SD_CMD58_READ_OCR, 0, 0xFF,res,1);
- if (i == 0)
- return SD_RESPONSE_FAILURE;
- i-=1;
- } while (res[0] != SD_RESPONSE_NO_ERROR);
- if((res[1]&0x40) == 0)
- return SD_RESPONSE_FAILURE;
- //读CSD
- SD_SendCmd(SD_CMD9_SEND_CSD, 0, 0xFF,res,0);
- if(res[0] == SD_RESPONSE_NO_ERROR)
- {
- SD_SPI_ReadBlockDatas(csd,16);
- }
- sdcard_block_num = (csd[9] + ((uint16_t)csd[8] << 8) + 1)*1024;
- SD_SPI_CSSetLev(1);
- SD_SPI_WriteByte(0xFF);
- return SD_RESPONSE_NO_ERROR;
- }
需要注意的是,在SD卡初始化过程中SPI时钟频率配置在400kHz以下,保证卡能够稳定地进入工作状态,初始化完成后再切换为高频率,所以在main中应该这样调用
- LL_GPIO_SetOutputPin(LED_GPIO_Port, LED_Pin);
- LL_SPI_SetBaudRatePrescaler(SPI1,LL_SPI_BAUDRATEPRESCALER_DIV256);
- if(SD_Init() == SD_RESPONSE_NO_ERROR)
- LL_GPIO_ResetOutputPin(LED_GPIO_Port, LED_Pin);
- LL_SPI_SetBaudRatePrescaler(SPI1,LL_SPI_BAUDRATEPRESCALER_DIV4);
SD初始化完成后就可以对数据进行读写了,SD卡的读写都是基于块进行的,对于SDHC来说每块的长度固定为512,读数据可以单块或多块读
单块读取:
1.发送CMD17,等待回应
2.等待读取到0xFE,
3.连续读512的数据和2字节的CRC
多块读取:
1.发送CMD17,等待回应
2.等待读取到0xFE,
3.连续读512的数据和2字节的CRC
4.重复2~3
5.读取完数据后发送CMD12结束
单块写入:
1.发送CMD24,等待回应
2.发送0xFE
3.发送512字节的数据和2字节CRC
4.读取回应,回应的低5位如果为0x05说明数据发送成功,之后SD卡进入忙碌状态会将MISO拉低,这期间不能对SD卡进行操作
多块写入:
1.发送CMD25,等待回应
2.发送0xFC
3.发送512字节的数据和2字节CRC
4.读取回应,回应的低5位如果为0x05说明数据发送成功,之后SD卡进入忙碌状态会将MISO拉低,这期间不能对SD卡进行操作
5.重复2~4
6.发送完数据后发送0xFD结束写入
代码实现如下
- uint8_t SD_SPI_ReadBlockDatas(uint8_t *datas, uint16_t len)
- {
- uint16_t i = 0xFFF;
- while(SD_SPI_ReadByte() != 0xFE)
- {
- if(i == 0)
- return SD_RESPONSE_FAILURE;
- i -= 1;
- }
-
- SD_SPI_ReadDatas(datas,len);
- SD_SPI_ReadByte();
- SD_SPI_ReadByte();
- return SD_RESPONSE_NO_ERROR;
- }
- uint8_t SD_SPI_WriteBlockDatas(uint8_t flag,uint8_t *datas, uint16_t len)
- {
- uint16_t i = 64;
- SD_SPI_WriteByte(0xFF);
- SD_SPI_WriteByte(flag);
- if(flag == 0xFE || flag == 0xFC)
- {
- SD_SPI_WriteDatas(datas,len);
- SD_SPI_WriteByte(0xFF);
- SD_SPI_WriteByte(0xFF);
- while((SD_SPI_ReadByte()&0x1F) != SD_DATA_OK)
- {
- if(i == 0)
- return SD_RESPONSE_FAILURE;
- i -= 1;
- }
- }
- while(SD_SPI_ReadByte() != 0xFF);
- return SD_RESPONSE_NO_ERROR;
- }
- uint8_t SD_ReadBlocks(uint32_t block_addr,uint8_t *data,uint32_t block_num)
- {
- uint8_t res;
- if(sdcard_block_num == 0 || data == NULL || block_num == 0)
- return SD_RESPONSE_FAILURE;
- if(block_num == 1)
- {
- SD_SendCmd(SD_CMD17_READ_SINGLE_BLOCK, block_addr, 0xFF,&res,0);
- if(res != SD_RESPONSE_NO_ERROR)
- return SD_RESPONSE_FAILURE;
- SD_SPI_ReadBlockDatas(data,512);
- }
- else
- {
- SD_SendCmd(SD_CMD18_READ_MULT_BLOCK, block_addr, 0xFF,&res,0);
- if(res != SD_RESPONSE_NO_ERROR)
- return SD_RESPONSE_FAILURE;
- while(block_num > 0 && res == SD_RESPONSE_NO_ERROR)
- {
- res = SD_SPI_ReadBlockDatas(data,512);
- data += 512;
- block_num -= 1;
- }
- SD_SendCmd(SD_CMD12_STOP_TRANSMISSION, 0, 0xFF,&res,0);
- }
-
- SD_SPI_CSSetLev(1);
- SD_SPI_WriteByte(0xFF);
- return SD_RESPONSE_NO_ERROR;
- }
- uint8_t SD_WriteBlocks(uint32_t block_addr,uint8_t *data,uint32_t block_num)
- {
- uint8_t res;
- if(sdcard_block_num == 0 || data == NULL || block_num == 0)
- return SD_RESPONSE_FAILURE;
- if(block_num == 1)
- {
- SD_SendCmd(SD_CMD24_WRITE_SINGLE_BLOCK, block_addr, 0xFF,&res,0);
- if(res != SD_RESPONSE_NO_ERROR)
- return SD_RESPONSE_FAILURE;
- SD_SPI_WriteBlockDatas(0xFE,data,512);
- }
- else
- {
- SD_SendCmd(SD_CMD25_WRITE_MULT_BLOCK, block_addr, 0xFF,&res,0);
- if(res != SD_RESPONSE_NO_ERROR)
- return SD_RESPONSE_FAILURE;
- while(block_num > 0 && res == SD_RESPONSE_NO_ERROR)
- {
- res = SD_SPI_WriteBlockDatas(0xFC,data,512);
- data += 512;
- block_num -= 1;
- }
- SD_SPI_WriteBlockDatas(0xFD,NULL,0);
- }
- SD_SPI_CSSetLev(1);
- SD_SPI_WriteByte(0xFF);
- return SD_RESPONSE_NO_ERROR;
- }
接下来修改USB代码,让电脑可以正确识别SD卡容量并读写SD卡
打开usbd_storage_if.c这个文件,找到STORAGE_GetCapacity_FS这个方法,这个方法告诉电脑存储设备的块大小与块数量,修改如下
- int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
- {
- /* USER CODE BEGIN 3 */
- *block_num = sdcard_block_num;
- *block_size = STORAGE_BLK_SIZ;
- return (USBD_OK);
- /* USER CODE END 3 */
- }
找到STORAGE_Read_FS这个方法,这个方法在电脑读取时会被调用,修改如下
- int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
- {
- /* USER CODE BEGIN 6 */
- SD_ReadBlocks(blk_addr,buf,blk_len);
- return (USBD_OK);
- /* USER CODE END 6 */
- }
找到STORAGE_Write_FS这个方法,这个方法在电脑读写入数据时会被调用,修改如下
- int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
- {
- /* USER CODE BEGIN 7 */
- SD_WriteBlocks(blk_addr,buf,blk_len);
- return (USBD_OK);
- /* USER CODE END 7 */
- }
最后在main中将USB初始化方法放到SD卡初始化方法之后
将SD卡模块与核心板连接好,编译烧录后一个简单的读卡器就做好了,运行效果如下
以上就是一个简单的读卡器实现过程,能够正常的对SD卡进行读写,就是速度有点慢,有兴趣的伙伴可以在此基础上进行扩展