SD nand 与 SD卡的SPI模式驱动
文章目录
- SD nand 与 SD卡的SPI模式驱动
- 1. 概述
- 2. SPI接口模式与SD接口模式区别
- 2.1 接口模式区别
- 2.2 硬件引脚
- 2.3 注意事项
- 3. SD接口协议
- 3.1 命令
- 3.1.1 命令格式
- 3.1.2 命令类型
- 3.2 响应
- 3.2.1 响应格式
- 4. SD nand(SD卡)结构描述
- 5. SD nand SPI通讯
- 5.1 SD nand SPI 通讯概述
- 5.2 SPI 时序
- 5.3 上电初始化及模式切换
- 5.3.1 初始化及模式切换流程说明
- 5.3.2 代码实现
- 5.4 识别过程
- 5.4.1 识别流程说明
- 5.4.2 代码实现
- 5.3 数据传输
- 5.3.1 数据写入
- 5.3.2 数据读取
- 5.3.3 代码实现
- 6. 总结
1. 概述
首先简单介绍下SD卡和SD nand:
-
SD卡,也称之为内存卡,在早些年间的手机上出现过,主要用来存储数据;
-
SD nand,贴片式SD卡,使用起来和SD卡一致,不同的是采用,通常采用LGA-8封装,尺寸为8mm x 6mm x 0.75mm,重点是采用贴片封装,可以直接贴在板卡上,直接解决了SD卡固定问题,再也不用为SD卡的接触稳定性操心!
SD nand 与 SD卡除了封装上的区别,使用起来基本没什么不一样,因此下文中不再做区分,统一以SD nand作为描述。
SD nand 和 SD 卡、SPI Nor flash、 nand flash、eeprom一样,都是嵌入式系统中常见的用来存储数据所使用的存储芯片,这几种存储芯片主要的区别在于存储数据容量不一样、操作的大小不一样,价格不一样,因此在实际产品设计中,往往需要根据自身产品的需求来选择对应的存储芯片。
SD nand存储空间大小在上述存储系列芯片中属于偏大的,其存储空间小到 1Gb(256MB) 起步,大到可以到32G,最小读写单元通常是 512 Byte,与SD卡一样,均支持SD接口模式以及SPI接口模式(后文会详细描述其区别)。
关于采用SPI接口模式完成于SD nand和SD卡的通讯,网上也有相关资料,但描述均不是很清楚或完整,因此特整理此博客,以作记录及分享。
本博文以创世 CSNPGCR01-AOW 这颗IC为例,着重描述如何通过SPI接口完成SD nand(SD卡)的读写驱动。
2. SPI接口模式与SD接口模式区别
2.1 接口模式区别
SD nand同时支持SPI接口和SD接口,接下来主要从以下几个维度分析二者的区别:
- 硬件资源角度:
- SD接口需要控制器具有SDIO外设硬件支持
- SPI接口如果控制器具有SPI硬件外设那就最好了,没有也可以使用软件模式SPI
- 传输效率:
- SD接口支持四线同时传输
- SPI只有MOSI一根总线
- 且接口速度上SD接口速度通常要大于SPI接口,因此SD效率远高于SPI接口
- 控制难度:
- SPI协议比较简单,也是嵌入式开发中最常使用的协议之一,只有MISO和MOSI两根数据总线,因此控制难度简单;
- SD协议相对SPI要复杂,且需要控制的引脚多,内部还存在状态机,相比SPI较为复杂
综上分析,SD接口效率更高,但是需要芯片有对应外设支持,而SPI接口虽然效率比不上SD接口,但是控制起来简单,且对芯片外设硬件依赖不高,对于低端的控制器,亦可使用软件模式SPI来驱动SD nand。
2.2 硬件引脚
SD nand以及SD 卡在SPI接口以及SD接口模式下,硬件引脚如下图所示:
-
SD nand SPI接口及SD接口模式IO定义
-
SD卡 SPI接口及SD接口模式IO定义
2.3 注意事项
此外对于使用SPI接口需要注意的是,SPI接口只是定义了物理传输层,并没有定义完整的数据传输协议,因此上层软件还是需要遵循SD接口协议!
3. SD接口协议
在2.3中我们重点强调了,SPI接口只是定义了物理层,也即硬件链路层,关于协议层并没有定义,写一次依旧遵循SD接口协议,因此我们需要首先了解下SD总线协议的内容。
SD 总线协议由SD卡协议定义,是一个通用的标准协议。首先说明的是,SD总线协议不仅仅只适用于SD卡,还支持IO卡,MMC卡等等,而且对这些不同类型的设备均能做出区分的!有点像USB一样牛逼!
我们首先来了解下SD总线协议中的命令及响应。
3.1 命令
命令由主机发出,分为广播命令和寻址命令
-
广播命令是针对与SD主机连接的所有设备发出的
-
寻址命令是指定某个地址的设备进行命令传输
3.1.1 命令格式
命令由48bit位(6字节)组成,格式如下:
- 起始位:1bit 固定为0
- 传输位:1bit 主要用于区分传输方向,1代表主机发送给从机的命令,0代表从机响应的主机命令
- 命令号:6bit 命令号索引,总共能表示2^6=64个命令
- 命令参数:32bit 命令所包含的参数信息
- CRC7:7bit CRC校验位,用于保证数据传输的正确性,生成器多项式为:
G(x) = x^7 + x^3 + 1
3.1.2 命令类型
命令主要有4种类型:
- bc:无响应广播命令
- bcr:有响应广播命令
- ac:寻址命令,发送到选定卡,DAT线没有数据传输
- adtc:寻址数据传输命令,发送到选定的卡,且DAT线有数据传输
在SD总线协议中,经常见到的CMDx,代表的就是命令号,后面的x代表命令索引,在3.1.1中命令格式组成中描述了命令号总共占6bit,所以CMDx的范围是CMD0 - CMD63,CMD后面的数字代表的就是命令号command index的值。
对于SD这么复杂的协议,64种命令类型通常还不能涵盖所有类型的数据,因此SD协会在制定此协议的时候将命令继续细化,分了两种类型的命令:CMD和ACMD,CMD代表常规命令,ACMD代表特定应用的命令,ACMD通常为制造商特定使用的。
那么SD协议又是如何区分CMD和ACMD命令的呢?
在发送ACMD命令之前必须发送特定的CMD命令(APP_CMD)表明接下来的一帧命令是ACMD命令,在SD协议种规定此特定命令名称叫APP_CMD,也就是CMD55。
需要注意的是,CMD命令类型这么多,但实际上并没有都使用,针对SD nand(SD卡)的命令也就那么几条(注意SD模式命令的响应和SPI模式命令的响应有些许不同,SD模式请自行查阅手册)
上图中,命令序号对应3.1.1节命令格式中的命令号 command index,参数对应3.1.1节命令格式中的命令参数argument。
3.2 响应
针对需要响应的命令(bcr),SD nand(SD卡)在接收到命令之后会做出响应,根据命令的不同,响应的类型也不相同,其中命令中已规定哪个命令需要响应,并且返回什么类型的响应。
响应总共分为7中类型,分别是R1~R7,需要注意的是,SD nand(SD卡)没有R4、R5类型的响应。
响应的数据长度也并非完全一样,响应根据内容长度分为短响应和长响应,短响应长度为48bit(6Byte),长响应长度为136bit(17Byte),其中只有R2属于长响应,其他均属于短响应。
3.2.1 响应格式
其中重点讲下R1响应,在上图中我们可以看到R1返回的内容为卡的状态,关于卡状态的描述如下,每个bit均代表着对应的含义,如下图中所示:
4. SD nand(SD卡)结构描述
上图是SD nand的内部结构,与SD卡完全类似,主要有五个部分组成,这里就不细述了,不然此篇文章会过于臃长,关于这块大家可以上网查找,需要重点注意的是内部有7个寄存器,主要用来对卡片进行配置和读取卡片有关的信息,描述如下,其中SD接口有些命令就指定了读取哪个寄存器的内容!
5. SD nand SPI通讯
主要参考资料:官方文档《Part_1_Pjysical_Layer_Specification_Ver2.0.0pdf》
建议大家有时间的话也可以读一读,还是有收获的,如果没时间的话也可以先参考本博文
5.1 SD nand SPI 通讯概述
SD nand SPI通讯接口完成驱动主要可以分为三大部分:
- 上电初始化以及模式切换
- SD nand(SD卡)识别
- 数据传输两大步
在以上三大部分中,每个部分均有命令传输,从3.1.1中我们可以知道发送给SD nand的命令为48bit,也就是8字节,那么SPI模式下与SD nand通讯,发送命令其实就是采用SPI总线往SD nand传输8个字节的数据,大家把握这这个思路去理解下文的通讯过程也就简单多了。
需要注意的是:
- SD nand或SD卡上电默认均为SD模式,需要对齐完成初始化以及模式切换后才能切换到SPI模式。
- SD 模式,所有命令默认开启CRC校验,因此没有切换到SPI模式之前,所有命令都必须携带正确的CRC校验值
- 进入SPI模式后,默认关闭CRC校验,此时CRC校验字段默认填充1即可,当然也可以通过命令配置打开SPI模式的CRC校验
5.2 SPI 时序
在开始进行通讯读写前,我们先来看下SPI时序,使用SPI完成于SD nand(SD卡)的通讯与我们平常使用SPI与其他设备通讯会有一点点小小的区别,主要在于往SD nand写了数据之后,回复不是马上的,以及在必要的数据之间需要增加间隔,我们挑几个重点看下,在实际开发中有需要注意的在后文对应处有描述,不用过于担心。
- 主机发送命令给卡,卡响应,注意图中的NCR,NCR最小不是0,因此主机发送了命令之后,SD nand不是马上就响应的
- 卡连续响应两个指令之间需要有间隔,如图中的NRC
5.3 上电初始化及模式切换
5.3.1 初始化及模式切换流程说明
- 首先配置控制器SPI外设
- SD nand(SD卡)电源应该在250ms内到大VCC,这是硬件电路要求
- 同时保持CS引脚为高电平状态,CLK时钟引脚至少发送74个时钟给SD nand已启动SD nand
- 之后SD nand进入空闲状态,发送CMD0命令至SD卡切换进入SPI模式
- 注意务必保证CMD0是第一包命令
- SD卡选择了对应的模式之后不可切换,如果需要重新切换,需要重新上电
5.3.2 代码实现
- SPI外设配置代码如下:
#ifndef __BSP_SPI_H__
#define __BSP_SPI_H__
#include "stm32f10x.h"
#define PIN_HIGH 1
#define PIN_LOW 0
int sd_spi_config(void);
void set_sd_spi_cs_pin(uint8_t state);
#endif /* __BSP_SPI_H__ */
#include "./spi/bsp_spi.h"
/**
* @brief spi gpio configuration
*
* @note CLK:PA5 MISO:PA6 MOSI:PA7 CS:PA8
*
*/
static void _spi_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* Configure SD_SPI pins: SCK */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure SD_SPI pins: MOSI */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure SD_SPI pins: MISO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*!< Configure SD_SPI_CS_PIN pin: SD Card CS pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief configer spi1 peripher.
*
* @note Data rising edge acquisition.
*/
static void _spi_config(void)
{
SPI_InitTypeDef SPI_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/*!< SD_SPI Config */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 0;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
int sd_spi_config(void)
{
_spi_gpio_init();
_spi_config();
return 0;
}
void set_sd_spi_cs_pin(uint8_t state)
{
if (state)
GPIO_SetBits(GPIOA, GPIO_Pin_8);
else
GPIO_ResetBits(GPIOA, GPIO_Pin_8);
}
- SD初始化代码如下,
set_sd_to_idle_state
函数向SD nand发送CMD0指令,同时由于发送CMD0时,SD nand还处于SD模式,因此手动计算CRC结果为0x95
并发送,发送完CMD0之后等待SD nand的R1响应,并根据响应内容,知道SD nand操作完成。
#ifndef __SD_SPI_DRV_H__
#define __SD_SPI_DRV_H__
#include "stm32f10x.h"
/**
* @brief Commands: CMDxx = CMD-number | 0x40
*/
#define SD_CMD_GO_IDLE_STATE 0 /*!< CMD0 = 0x40 */
#define SD_CMD_SEND_OP_COND 1 /*!< CMD1 = 0x41 */
#define SD_CMD_SEND_IF_COND 8 /*!< CMD8 = 0x48 */
#define SD_CMD_SEND_CSD 9 /*!< CMD9 = 0x49 */
#define SD_CMD_SEND_CID 10 /*!< CMD10 = 0x4A */
#define SD_CMD_STOP_TRANSMISSION 12 /*!< CMD12 = 0x4C */
#define SD_CMD_SEND_STATUS 13 /*!< CMD13 = 0x4D */
#define SD_CMD_SET_BLOCKLEN 16 /*!< CMD16 = 0x50 */
#define SD_CMD_READ_SINGLE_BLOCK 17 /*!< CMD17 = 0x51 */
#define SD_CMD_READ_MULT_BLOCK 18 /*!< CMD18 = 0x52 */
#define SD_CMD_SET_BLOCK_COUNT 23 /*!< CMD23 = 0x57 */
#define SD_CMD_WRITE_SINGLE_BLOCK 24 /*!< CMD24 = 0x58 */
#define SD_CMD_WRITE_MULT_BLOCK 25 /*!< CMD25 = 0x59 */
#define SD_CMD_PROG_CSD 27 /*!< CMD27 = 0x5B */
#define SD_CMD_SET_WRITE_PROT 28 /*!< CMD28 = 0x5C */
#define SD_CMD_CLR_WRITE_PROT 29 /*!< CMD29 = 0x5D */
#define SD_CMD_SEND_WRITE_PROT 30 /*!< CMD30 = 0x5E */
#define SD_CMD_SD_ERASE_GRP_START 32 /*!< CMD32 = 0x60 */
#define SD_CMD_SD_ERASE_GRP_END 33 /*!< CMD33 = 0x61 */
#define SD_CMD_UNTAG_SECTOR 34 /*!< CMD34 = 0x62 */
#define SD_CMD_ERASE_GRP_START 35 /*!< CMD35 = 0x63 */
#define SD_CMD_ERASE_GRP_END 36 /*!< CMD36 = 0x64 */
#define SD_CMD_UNTAG_ERASE_GROUP 37 /*!< CMD37 = 0x65 */
#define SD_CMD_ERASE 38 /*!< CMD38 = 0x66 */
#define SD_CMD_READ_OCR 58 /*!< CMD58 */
#define SD_CMD_APP_CMD 55 /*!< CMD55 返回0x01*/
#define SD_ACMD_SD_SEND_OP_COND 41 /*!< ACMD41 返回0x00*/
typedef enum {
/**
* @brief SD reponses and error flags
*/
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 Data response error
*/
SD_DATA_OK = (0x05),
SD_DATA_CRC_ERROR = (0x0B),
SD_DATA_WRITE_ERROR = (0x0D),
SD_DATA_OTHER_ERROR = (0xFF)
} SD_ERROR;
//SD卡的类型
#define SD_TYPE_NOT_SD 0 //非SD卡
#define SD_TYPE_V1 1 //V1.0的卡
#define SD_TYPE_V2 2 //SDSC
#define SD_TYPE_V2HC 4 //SDHC
extern uint8_t SD_Type;
void sd_power_on(void);
SD_ERROR set_sd_to_idle_state(void);
SD_ERROR get_sd_card_type(void);
#endif /* __SD_SPI_DRV_H__ */
#include "./sd_nand/sd_spi_drv.h"
#include "./spi/bsp_spi.h"
#define SD_SPI SPI1
#define SD_DUMMY_BYTE 0xFF
uint8_t SD_Type = 0;
static uint8_t _spi_read_write_byte(uint8_t data)
{
while(SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SD_SPI, data);
while(SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SD_SPI);
}
static void sd_send_cmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
uint8_t data[6] = {0};
/* command bit7 is always 1, bit6 is always 0, see SD manual. */
data[0] &= ~(0x80);
data[0] = cmd | 0x40;
data[1] = (uint8_t)(arg >> 24);
data[2] = (uint8_t)(arg >> 16);
data[3] = (uint8_t)(arg >> 8);
data[4] = (uint8_t)(arg);
data[5] = crc;
for (int i = 0; i < 6; i ++)
_spi_read_write_byte(data[i]);
}
static uint8_t sd_read_response(uint8_t response)
{
uint32_t repeat = 0xfff;
while (repeat --) {
if (_spi_read_write_byte(SD_DUMMY_BYTE) == response)
break;
}
if (repeat)
return SD_RESPONSE_NO_ERROR;
else
return SD_RESPONSE_FAILURE;
}
void sd_power_on(void)
{
set_sd_spi_cs_pin(PIN_HIGH);
uint32_t i = 0;
for (i = 0; i <= 9; i++) {
_spi_read_write_byte(SD_DUMMY_BYTE);
}
}
SD_ERROR set_sd_to_idle_state(void)
{
uint32_t repeat = 0xfff;
set_sd_spi_cs_pin(PIN_LOW);
sd_send_cmd(SD_CMD_GO_IDLE_STATE, 0, 0x95);
if (sd_read_response(SD_IN_IDLE_STATE)) //查询卡是否处于空闲状态
return SD_RESPONSE_FAILURE;
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE); //释放卡
if (repeat == 0)
return SD_RESPONSE_FAILURE;
else
return SD_RESPONSE_NO_ERROR;
}
5.4 识别过程
SD nand的识别过程颇为复杂,需要参考下图所示状态机。
其复杂的原因是,随着科技的发展,SD卡也迭代了好几轮,但是协议需要兼容所有版本的卡,因此看上去会复杂很多。
我们采用的SD nand 型号为 CSNPGCR01-AOW,为V2.0.0的卡,且容量为1Gb,因此整体识别路线为中间那条线路。
5.4.1 识别流程说明
V2.0卡识别流程:
- SD nand上电首先完成初始化,并发送CMD0配置为SPI模式
- 之后发送CMD8命令,读取R7响应,判断SD nand的版本
- 如果响应值为0x01则判断为V2.0的卡(此时是这个)
- 如果响应值非0x01则需要进一步判断时V1.0的卡还是MMC卡
- 发送循环指令CMD55+ACMD41,读取R1响应,直到响应0x00表示SD 2.0卡初始化完成
- 发送CMD58命令,读取R3响应,R3中包含OCR寄存器的值,OCR寄存器的第31位(bit30)描述了此卡类型是否为SDHC类型,根据此位判断此卡属于标准容量卡还是高容量卡
V1.0卡识别流程:
- SD nand上电首先完成初始化,并发送CMD0配置为SPI模式
- 之后发送CMD8命令判断SD nand的版本
- 如果响应值为0x01则判断为V2.0的卡
- 如果响应值非0x01则需要进一步判断时V1.0的卡还是MMC卡(此时是这个)
- 发送CMD58命令,并判断响应值R3,如果没有返回则不是SD V1.0的卡
- 发送ACMD41(argument为置0),并判断R1响应值,直到卡空闲
关于CMD8指令,此处重点说明:
CMD8命令的参数中主要包含两个部分,Voltage Supplied(VHS)和check pattern,发送CMD7时,VHS参数应设置为主机支持的电压范围,我们的控制器通常是3.3V,因此此处设置为0001b; check pattern可以设置为任意值,当SD nand(SD卡)接收到此CMD8指令之后会返回R7响应,如果SD nand支持此电压等级,SD nand会回显 VHS 和check pattern的内容在R7中,如果SD nand不支持此电压等级,SD nand将不会返回,并始终保持在空闲状态。
5.4.2 代码实现
SD nand识别代码如下:
SD_ERROR get_sd_card_type(void)
{
uint32_t i = 0;
uint32_t count = 0xFFF;
uint8_t R7R3_Resp[4];
uint8_t R1_Resp;
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
sd_send_cmd(SD_CMD_SEND_IF_COND, 0x1AA, 0x87);
/*!< Check if response is got or a timeout is happen */
while (( (R1_Resp = _spi_read_write_byte(SD_DUMMY_BYTE)) == 0xFF) && count) {
count--;
}
if (count == 0) {
/*!< After time out */
return 1;
}
//响应 = 0x05 非V2.0的卡
if(R1_Resp == (SD_IN_IDLE_STATE|SD_ILLEGAL_COMMAND)) {
/*----------Activates the card initialization process-----------*/
count = 0xfff;
do {
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
/*!< 发送CMD1完成V1 版本卡的初始化 */
sd_send_cmd(SD_CMD_SEND_OP_COND, 0, 0xFF);
/*!< Wait for no error Response (R1 Format) equal to 0x00 */
if (sd_read_response(SD_RESPONSE_NO_ERROR))
break;
} while (count --);
if (count == 0) {
return 2;
}
SD_Type = SD_TYPE_V1;
//不处理MMC卡
//初始化正常
} else if (R1_Resp == 0x01) { //响应 0x01 V2.0的卡
/*!< 读取CMD8 的R7响应 */
for (i = 0; i < 4; i++) {
R7R3_Resp[i] = _spi_read_write_byte(SD_DUMMY_BYTE);
}
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
if(R7R3_Resp[2]==0x01 && R7R3_Resp[3]==0xAA) { //判断该卡是否支持2.7-3.6V电压
count = 200; //支持电压范围,可以操作
do { //发卡初始化指令CMD55+ACMD41
sd_send_cmd(SD_CMD_APP_CMD, 0, 0xFF); //CMD55,以强调下面的是ACMD命令
if (sd_read_response(SD_RESPONSE_NO_ERROR)) // SD_IN_IDLE_STATE
return 3; //超时返回
sd_send_cmd(SD_ACMD_SD_SEND_OP_COND, 0x40000000, 0xFF); //ACMD41命令带HCS检查位
if (sd_read_response(SD_RESPONSE_NO_ERROR))
break;
}while(count--);
if(count == 0)
return 4; //重试次数超时
//初始化指令完成,读取OCR信息,CMD58
//-----------鉴别SDSC SDHC卡类型开始-----------
count = 200;
do {
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
sd_send_cmd(SD_CMD_READ_OCR, 0, 0xFF);
if (!sd_read_response(SD_RESPONSE_NO_ERROR))
break;
} while (count--);
if(count == 0)
return 5; //重试次数超时
//响应正常,读取R3响应
/*!< 读取CMD58的R3响应 */
for (i = 0; i < 4; i++) {
R7R3_Resp[i] = _spi_read_write_byte(SD_DUMMY_BYTE);
}
//检查接收到OCR中的bit30(CCS)
//CCS = 0:SDSC CCS = 1:SDHC
if(R7R3_Resp[0]&0x40) { //检查CCS标志 {
SD_Type = SD_TYPE_V2HC;
} else {
SD_Type = SD_TYPE_V2;
}
//-----------鉴别SDSC SDHC版本卡的流程结束-----------
}
}
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
//初始化正常返回
return SD_RESPONSE_NO_ERROR;
}
5.3 数据传输
在完成卡识别之后,便进入了数据传输过程,在输出传输过程内即可完成数据的读写操作。
SD NAND单个块为512字节,擦除、读写都是以块为单位进行的,而且SD NAND可以直接写入,不需要先擦除才能写入!!!牛逼Plus吧!哈哈!
5.3.1 数据写入
数据分为单块写入和多块写入,多块写入可循环执行多块写入实现。单个块写入使用CMD24,多个块写入使用CMD25,注意此处,SD nand的操作与SD卡可能会有所不一样,在对应位置有详细描述。
单块写入步骤如下:
- 发送CMD24,读取响应值R1,判断卡无错误
- 发送写开始指令 0xFE(SD协议中未找到此描述,此应该是SD nand所特有)
- 依次传输写入数据
- 发送两个字节的CRC校验,由于SPI默认没有开启CRC,因此填充为0xFFFF
- 读取卡的状态判断是否有误,结束
5.3.2 数据读取
数据读取也分为单块读取和多块读取,多块读取可采用循环执行单块读取逻辑实现。
单块数据读取步骤如下:
- 发送CMD17,读取响应值R1,判断有无错误
- 等待SD nand发送数据输出开始标志 0xFE
- 依次读取数据
- 多读取两位CRC值,结束
5.3.3 代码实现
#define SD_START_DATA_SINGLE_BLOCK_READ 0xFE /*!< Data token start byte, Start Single Block Read */
#define SD_START_DATA_MULTIPLE_BLOCK_READ 0xFE /*!< Data token start byte, Start Multiple Block Read */
#define SD_START_DATA_SINGLE_BLOCK_WRITE 0xFE /*!< Data token start byte, Start Single Block Write */
#define SD_START_DATA_MULTIPLE_BLOCK_WRITE 0xFD /*!< Data token start byte, Start Multiple Block Write */
#define SD_STOP_DATA_MULTIPLE_BLOCK_WRITE 0xFD /*!< Data toke stop byte, Stop Multiple Block Write */
SD_ERROR sd_write_block(uint8_t* pbuf, uint64_t addr, uint16_t size)
{
uint32_t i = 0;
SD_ERROR ret = SD_RESPONSE_FAILURE;
//SDHC卡块大小固定为512,且写命令中的地址的单位是sector
if (SD_Type == SD_TYPE_V2HC) {
size = 512;
addr /= 512;
}
/*!< SD chip select low */
set_sd_spi_cs_pin(PIN_LOW);
/*!< Send CMD24 (SD_CMD_WRITE_SINGLE_BLOCK) to write multiple block */
sd_send_cmd(SD_CMD_WRITE_SINGLE_BLOCK, addr, 0xFF);
/*!< Check if the SD acknowledged the write block command: R1 response (0x00: no errors) */
if (!sd_read_response(SD_RESPONSE_NO_ERROR)) {
/*!< Send a dummy byte */
_spi_read_write_byte(SD_DUMMY_BYTE);
/*!< Send the data token to signify the start of the data */
_spi_read_write_byte(SD_START_DATA_SINGLE_BLOCK_WRITE);
/*!< Write the block data to SD : write count data by block */
for (i = 0; i < size; i++) {
/*!< Send the pointed byte */
_spi_read_write_byte(*pbuf);
/*!< Point to the next location where the byte read will be saved */
pbuf++;
}
/*!< Put CRC bytes (not really needed by us, but required by SD) */
_spi_read_write_byte(SD_DUMMY_BYTE);
_spi_read_write_byte(SD_DUMMY_BYTE);
/*!< Read data response */
if (sd_get_data_response() == SD_DATA_OK) {
ret = SD_RESPONSE_NO_ERROR;
}
}
/*!< SD chip select high */
set_sd_spi_cs_pin(PIN_HIGH);
/*!< Send dummy byte: 8 Clock pulses of delay */
_spi_read_write_byte(SD_DUMMY_BYTE);
/*!< Returns the reponse */
return ret;
}
SD_ERROR sd_read_block(uint8_t* pbuf, uint64_t addr, uint16_t size)
{
uint32_t i = 0;
SD_ERROR ret = SD_RESPONSE_FAILURE;
//SDHC卡块大小固定为512,且读命令中的地址的单位是sector
if (SD_Type == SD_TYPE_V2HC) {
size = 512;
addr /= 512;
}
/*!< SD chip select low */
set_sd_spi_cs_pin(PIN_LOW);
/*!< Send CMD17 (SD_CMD_READ_SINGLE_BLOCK) to read one block */
sd_send_cmd(SD_CMD_READ_SINGLE_BLOCK, addr, 0xFF);
/*!< Check if the SD acknowledged the read block command: R1 response (0x00: no errors) */
if (!sd_read_response(SD_RESPONSE_NO_ERROR)) {
/*!< Now look for the data token to signify the start of the data */
if (!sd_read_response(SD_START_DATA_SINGLE_BLOCK_READ)) {
/*!< Read the SD block data : read NumByteToRead data */
for (i = 0; i < size; i++) {
/*!< Save the received data */
*pbuf = _spi_read_write_byte(SD_DUMMY_BYTE);
/*!< Point to the next location where the byte read will be saved */
pbuf++;
}
/*!< Get CRC bytes (not really needed by us, but required by SD) */
_spi_read_write_byte(SD_DUMMY_BYTE);
_spi_read_write_byte(SD_DUMMY_BYTE);
/*!< Set response value to success */
ret = SD_RESPONSE_NO_ERROR;
}
}
/*!< SD chip select high */
set_sd_spi_cs_pin(PIN_HIGH);
/*!< Send dummy byte: 8 Clock pulses of delay */
_spi_read_write_byte(SD_DUMMY_BYTE);
/*!< Returns the reponse */
return ret;
}
此外,为了验证以上代码正常运行,编写简单测试程序进行测试,代码如下:
int main(void)
{
USART1_Config();
LED_GPIO_Config();
sd_spi_config();
printf("sd card test!\n");
sd_init();
uint8_t tx_data[512] = {0};
uint8_t rx_data[512] = {0};
for (i = 0; i < 512; i ++)
tx_data[i] = 512-i;
sd_write_block(tx_data, 0, sizeof(tx_data));
sd_read_block(rx_data, 0, sizeof(rx_data));
for (i = 0; i < 512; i ++) {
if (tx_data[i] != rx_data[i])
break;
printf("%d ", rx_data[i]);
}
if (i == 512) {
printf("sd card 读写测试成功\n");
} else {
printf("sd card 读写测试失败, i:%d\n", i);
}
}
代码运行如下,测试通过:
6. 总结
综上,便是关于使用SPI接口驱动SD nand的全部说明了,确实花费了不少时间整理说明,关于SD nand的驱动玩法还有很多,比如采用SD接口驱动,移植文件系统,导入日志系统等等,后续有机会有时间我也会继续做整理分享。
关于博客中的实例代码也只是简单的demo,结构还不是很完美,后续再更新优化。
希望本篇博文能帮助到你对于如何使用SPI实现SD nand的驱动也有大致清晰的了解,创作不易,转载请注明出处,点赞收藏+关注,找我不迷路!