SD nand与SD卡 SPI模式驱动

Source

SD nand 与 SD卡的SPI模式驱动

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通讯接口完成驱动主要可以分为三大部分:

  1. 上电初始化以及模式切换
  2. SD nand(SD卡)识别
  3. 数据传输两大步

在以上三大部分中,每个部分均有命令传输,从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写了数据之后,回复不是马上的,以及在必要的数据之间需要增加间隔,我们挑几个重点看下,在实际开发中有需要注意的在后文对应处有描述,不用过于担心。

  1. 主机发送命令给卡,卡响应,注意图中的NCR,NCR最小不是0,因此主机发送了命令之后,SD nand不是马上就响应的在这里插入图片描述
  2. 卡连续响应两个指令之间需要有间隔,如图中的NRC在这里插入图片描述

5.3 上电初始化及模式切换

5.3.1 初始化及模式切换流程说明

  1. 首先配置控制器SPI外设
  2. SD nand(SD卡)电源应该在250ms内到大VCC,这是硬件电路要求
  3. 同时保持CS引脚为高电平状态,CLK时钟引脚至少发送74个时钟给SD nand已启动SD nand
  4. 之后SD nand进入空闲状态,发送CMD0命令至SD卡切换进入SPI模式
    • 注意务必保证CMD0是第一包命令
    • SD卡选择了对应的模式之后不可切换,如果需要重新切换,需要重新上电
      在这里插入图片描述

5.3.2 代码实现

  1. 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);
}
  1. 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卡识别流程:

  1. SD nand上电首先完成初始化,并发送CMD0配置为SPI模式
  2. 之后发送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卡识别流程:

  1. SD nand上电首先完成初始化,并发送CMD0配置为SPI模式
  2. 之后发送CMD8命令判断SD nand的版本
    • 如果响应值为0x01则判断为V2.0的卡
    • 如果响应值非0x01则需要进一步判断时V1.0的卡还是MMC卡(此时是这个)
  3. 发送CMD58命令,并判断响应值R3,如果没有返回则不是SD V1.0的卡
  4. 发送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卡可能会有所不一样,在对应位置有详细描述。

单块写入步骤如下:

  1. 发送CMD24,读取响应值R1,判断卡无错误
  2. 发送写开始指令 0xFE(SD协议中未找到此描述,此应该是SD nand所特有)
  3. 依次传输写入数据
  4. 发送两个字节的CRC校验,由于SPI默认没有开启CRC,因此填充为0xFFFF
  5. 读取卡的状态判断是否有误,结束
    在这里插入图片描述

5.3.2 数据读取

数据读取也分为单块读取和多块读取,多块读取可采用循环执行单块读取逻辑实现。

单块数据读取步骤如下:

  1. 发送CMD17,读取响应值R1,判断有无错误
  2. 等待SD nand发送数据输出开始标志 0xFE
  3. 依次读取数据
  4. 多读取两位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的驱动也有大致清晰的了解,创作不易,转载请注明出处,点赞收藏+关注,找我不迷路!