SD nand与SD卡 SPI模式驱动

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卡的通讯,网上也有相关资料,但描述均不是很清楚或完整,因此特整理此博客,以作记录及分享。

本博文以 CS 创世 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卡
  3. 发送循环指令CMD55+ACMD41,(CMD55用来表示后面的CMD41为ACMD命令),读取R1响应,直到响应0x00表示SD 2.0卡初始化完成
  4. 发送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,发送CMD8时,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接口驱动,移植文件系统,导入日志系统等等,后续有机会有时间我也会继续做整理分享。

希望本篇博文能帮助到你对于如何使用SPI实现SD nand的驱动也有大致清晰的了解,创作不易,转载请注明出处,点赞收藏+关注,找我不迷路!

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
目  录 第1章 引言 1 1.1 演进 1 1.2 gnu copyleft 2 1.3 kernel.org 2 1.4 邮件列表和论坛 3 1.5 linux发行版 3 1.6 查看源代码 4 1.7 编译内核 7 1.8 可加载的模块 8 1.9 整装待发 9 第2章 内核 11 2.1 启动过程 11 2.1.1 bios-provided physical ram map 12 2.1.2 758mb lowmem available 14 2.1.3 kernel command line: ro root=/dev/hda1 14 2.1.4 calibrating delay...1197.46 .bogomips (lpj=2394935) 15 2.1.5 checking hlt instruction 16 2.1.6 net: registered protocol family 2 17 2.1.7 freeing initrd memory: 387k freed 17 2.1.8 io scheduler anticipatory registered (default) 18 2.1.9 setting up standard pci resources 18 2.1.10 ext3-fs: mounted filesystem 19 2.1.11 init: version 2.85 booting 19 2.2 内核模式和用户模式 20 2.3 进程上下文和中断上下文 20 2.4 内核定时器 21 2.4.1 hz和jiffies 21 2.4.2 长延时 22 2.4.3 短延时 24 2.4.4 pentium时间戳计数器 24 2.4.5 实时钟 25 2.5 内核中的并发 26 2.5.1 自旋锁和互斥体 26 2.5.2 原子操作 30 2.5.3 读—写锁 31 2.5.4 调试 32 2.6 proc文件系统 32 2.7 内存分配 33 2.8 查看源代码 34 第3章 内核组件 37 3.1 内核线程 37 3.1.1 创建内核线程 37 3.1.2 进程状态和等待队列 41 3.1.3 用户模式辅助程序 42 3.2 辅助接口 43 3.2.1 链表 44 3.2.2 散列链表 49 3.2.3 工作队列 49 3.2.4 通知链 51 3.2.5 完成接口 54 3.2.6 kthread辅助接口 56 3.2.7 错误处理助手 57 3.3 查看源代码 58 第4章 基本概念 61 4.1 设备和驱动程序介绍 61 4.2 中断处理 63 4.2.1 中断上下文 63 4.2.2 分配irq号 64 4.2.3 设备实例:导航杆 65 4.2.4 softirq和tasklet 68 4.3 linux设备模型 71 4.3.1 udev 71 4.3.2 sysfs、kobject和设备类 73 4.3.3 热插拔和冷插拔 76 4.3.4 微码下载 76 4.3.5 模块自动加载 77 4.4 内存屏障 78 4.5 电源管理 79 4.6 查看源代码 79 第5章 字符设备驱动程序 81 5.1 字符设备驱动程序基础 81 5.2 设备实例:系统cmos 82 5.2.1 驱动程序初始化 83 5.2.2 打开与释放 86 5.2.3 数据交换 88 5.2.4 查找 92 5.2.5 控制 94 5.3 检测数据可用性 95 5.3.1 轮询 95 5.3.2 fasync 98 5.4 和并行端口交互 99 5.5 rtc子系统 108 5.6 伪字符驱动程序 109 5.7 混杂驱动程序 110 5.8 字符设备驱动程序注意事项 115 5.9 查看源代码 115 第6章 串行设备驱动程序 118 6.1 层次架构 119 6.2 uart驱动程序 121 6.2.1 设备实例:手机 122 6.2.2 rs-485 132 6.3 tty驱动程序 132 6.4 线路规程 134 6.5 查看源代码 141 第7章 输入设备驱动程序 143 7.1 输入事件驱动程序 144 7.2 输入设备驱动程序 150 7.2.1 serio 150 7.2.2 键盘 150 7.2.3 鼠标 152 7.2.4 触摸控制器 157 7.2.5 加速度传感器 158 7.2.6 输出事件 158 7.3 调试 159 7.4 查看源代码 160 第8章 i2c协议 161 8.1 i2c/smbus是什么 161 8.2 i2c核心 162 8.3 总线事务 164 8.4 设备实例:eeprom 164 8.4.1 初始化 165 8.4.2 探测设备 167 8.4.3 检查适配器的功能 169 8.4.4 访问设备 169 8.4.5 其他函数 170 8.5 设备实例:实时时钟 171 8.6 i2c-dev 174 8.7 使用lm-sensors监控硬件 174 8.8 spi总线 174 8.9 1-wire总线 176 8.10 调试 176 8.11 查看源代码 176 第9章 pcmcia和cf 179 9.1 pcmcia/cf是什么 179 9.2 linux-pcmcia子系统 181 9.3 主机控制器驱动程序 183 9.4 pcmcia核心 183 9.5 驱动程序服务 183 9.6 客户驱动程序 183 9.6.1 数据结构 184 9.6.2 设备实例:pcmcia卡 185 9.7 将零件组装在一起 188 9.8 pcmcia存储 189 9.9 串行pcmcia 189 9.10 调试 191 9.11 查看源代码 191 第10章 pci 193 10.1 pci系列 193 10.2 寻址和识别 195 10.3 访问pci 198 10.3.1 配置区 198 10.3.2 i/o和内存 199 10.4 dma 200 10.5 设备实例:以太网—调制解调器卡 203 10.5.1 初始化和探测 203 10.5.2 数据传输 209 10.6 调试 214 10.7 查看源代码 214 第11章 usb 216 11.1 usb体系架构 216 11.1.1 总线速度 218 11.1.2 主机控制器 218 11.1.3 传输模式 219 11.1.4 寻址 219 11.2 linux-usb子系统 220 11.3 驱动程序的数据结构 221 11.3.1 usb_device结构体 221 11.3.2 urb 222 11.3.3 管道 223 11.3.4 描述符结构 223 11.4 枚举 225 11.5 设备实例:遥测卡 225 11.5.1 初始化和探测过程 226 11.5.2 卡寄存器的访问 230 11.5.3 数据传输 233 11.6 类驱动程序 236 11.6.1 大容量存储设备 236 11.6.2 usb-串行端口转换器 241 11.6.3 人机接口设备 243 11.6.4 蓝牙 243 11.7 gadget驱动程序 243 11.8 调试 244 11.9 查看源代码 245 第12章 视频驱动程序 247 12.1 显示架构 247 12.2 linux视频子系统 249 12.3 显示参数 251 12.4 帧缓冲api 252 12.5 帧缓冲驱动程序 254 12.6 控制台驱动程序 265 12.6.1 设备实例:手机 266 12.6.2 启动logo 270 12.7 调试 270 12.8 查看源代码 271 第13章 音频驱动程序 273 13.1 音频架构 273 13.2 linux声音子系统 275 13.3 设备实例:mp3播放器 277 13.3.1 驱动程序函数和结构体 278 13.3.2 alsa编程 287 13.4 调试 288 13.5 查看源代码 289 第14章 块设备驱动程序 291 14.1 存储技术 291 14.2 linux块i/o层 295 14.3 i/o调度器 295 14.4 块驱动程序数据结构和方法 296 14.5 设备实例:简单存储控制器 298 14.5.1 初始化 299 14.5.2 块设备操作 301 14.5.3 磁盘访问 302 14.6 高级主题 304 14.7 调试 306 14.8 查看源代码 306 第15章 网络接口卡 308 15.1 驱动程序数据结构 308 15.1.1 套接字缓冲区 309 15.1.2 网络设备接口 310 15.1.3 激活 311 15.1.4 数据传输 311 15.1.5 看门狗 311 15.1.6 统计 312 15.1.7 配置 313 15.1.8 总线相关内容 314 15.2 与协议层会话 314 15.2.1 接收路径 314 15.2.2 发送路径 315 15.2.3 流量控制 315 15.3 缓冲区管理和并发控制 315 15.4 设备实例:以太网nic 316 15.5 isa网络驱动程序 321 15.6 atm 321 15.7 网络吞吐量 322 15.7.1 驱动程序性能 322 15.7.2 协议性能 323 15.8 查看源代码 324 第16章 linux无线设备驱动 326 16.1 蓝牙 327 16.1.1 bluez 328 16.1.2 设备实例:cf卡 329 16.1.3 设备实例:usb适配器 330 16.1.4 rfcomm 331 16.1.5 网络 332 16.1.6 hid 334 16.1.7 音频 334 16.1.8 调试 334 16.1.9 关于源代码 334 16.2 红外 335 16.2.1 linux-irda 335 16.2.2 设备实例:超级i/o芯片 337 16.2.3 设备实例:ir dongle 338 16.2.4 ircomm 340 16.2.5 联网 340 16.2.6 irda套接字 341 16.2.7 lirc 341 16.2.8 查看源代码 342 16.3 wifi 343 16.3.1 配置 343 16.3.2 设备驱动程序 346 16.3.3 查看源代码 347 16.4 蜂窝网络 347 16.4.1 gprs 347 16.4.2 cdma 349 16.5 当前趋势 350 第17章 存储技术设备 352 17.1 什么是闪存 352 17.2 linux-mtd子系统 353 17.3 映射驱动程序 353 17.4 nor芯片驱动程序 358 17.5 nand芯片驱动程序 359 17.6 用户模块 361 17.6.1 块设备模拟 361 17.6.2 字符设备模拟 361 17.6.3 jffs2 362 17.6.4 yaffs2 363 17.7 mtd工具 363 17.8 配置mtd 363 17.9 xip 364 17.10 fwh 364 17.11 调试 367 17.12 查看源代码 367 第18章 嵌入式linux 369 18.1 挑战 369 18.2 元器件选择 370 18.3 工具链 371 18.4 bootloader 372 18.5 内存布局 374 18.6 内核移植 375 18.7 嵌入式驱动程序 376 18.7.1 闪存 377 18.7.2 uart 377 18.7.3 按钮和滚轮 378 18.7.4 pcmcia/cf 378 18.7.5 sd/mmc 378 18.7.6 usb 378 18.7.7 rtc 378 18.7.8 音频 378 18.7.9 触摸屏 379 18.7.10 视频 379 18.7.11 cpld/fpga 379 18.7.12 连接性 379 18.7.13 专用领域电子器件 380 18.7.14 更多驱动程序 380 18.8 根文件系统 380 18.8.1 nfs挂载的根文件系统 381 18.8.2 紧凑型中间件 382 18.9 测试基础设施 383 18.10 调试 383 18.10.1 电路板返工 384 18.10.2 调试器 385 第19章 用户空间的驱动程序 386 19.1 进程调度和响应时间 387 19.1.1 原先的调度器 387 19.1.2 o(1)调度器 387 19.1.3 cfs 388 19.1.4 响应时间 388 19.2 访问i/o区域 390 19.3 访问内存区域 393 19.4 用户模式scsi 395 19.5 用户模式usb 397 19.6 用户模式i2c 400 19.7 uio 401 19.8 查看源代码 402 第20章 其他设备和驱动程序 403 20.1 ecc报告 403 20.2 频率调整 407 20.3 嵌入式控制器 408 20.4 acpi 408 20.5 isa与mca 410 20.6 火线 410 20.7 智能输入/输出 411 20.8 业余无线电 411 20.9 voip 411 20.10 高速互联 412 20.10.1 infiniband 413 20.10.2 rapidio 413 20.10.3 光纤通道 413 20.10.4 iscsi 413 第21章 调试设备驱动程序 414 21.1 kdb 414 21.1.1 进入调试器 415 21.1.2 kdb 415 21.1.3 kgdb 417 21.1.4 gdb 420 21.1.5 jtag调试器 421 21.1.6 下载 423 21.2 内核探测器 423 21.2.1 kprobe 423 21.2.2 jprobe 427 21.2.3 返回探针 429 21.2.4 局限性 431 21.2.5 查看源代码 431 21.3 kexec与kdump 431 21.3.1 kexec 432 21.3.2 kdump与kexec协同工作 432 21.3.3 kdump 433 21.3.4 查看源代码 437 21.4 性能剖析 437 21.4.1 利用oprofile剖析内核性能 438 21.4.2 利用gprof剖析应用程序性能 440 21.5 跟踪 441 21.6 ltp 444 21.7 uml 444 21.8 诊断工具 444 21.9 内核修改配置选项 444 21.10 测试设备 445 第22章 维护与发布 446 22.1 代码风格 446 22.2 修改标记 446 22.3 版本控制 447 22.4 一致性检查 447 22.5 构建脚本 448 22.6 可移植代码 450 第23章 结束语 451 23.1 流程一览表 451 23.2 下一步该做什么 452 附录a linux汇编 453 附录b linux与bios 457 附录c seq文件 461

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱出名的狗腿子

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值