芯片芯语 发表于 2023-5-12 11:33

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卡,也称之为内存卡,在早些年间的手机上出现过,主要用来存储数据;
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjMGyZvq


[*]SD nand,贴片式SD卡,使用起来和SD卡一致,不同的是采用,通常采用LGA-8封装,尺寸为8mm x 6mm x 0.75mm,重点是采用贴片封装,可以直接贴在板卡上,直接解决了SD卡固定问题,再也不用为SD卡的接触稳定性操心!
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjniH7ds


[*]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定义
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjmOOIeq


[*]SD卡 SPI接口及SD接口模式IO定义
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjnAe1r5


[*]2.3 注意事项
[*]此外对于使用SPI接口需要注意的是,SPI接口只是定义了物理传输层,并没有定义完整的数据传输协议,因此上层软件还是需要遵循SD接口协议!
[*]3. SD接口协议
[*]在2.3中我们重点强调了,SPI接口只是定义了物理层,也即硬件链路层,关于协议层并没有定义,写一次依旧遵循SD接口协议,因此我们需要首先了解下SD总线协议的内容。
[*]SD 总线协议由SD卡协议定义,是一个通用的标准协议。首先说明的是,SD总线协议不仅仅只适用于SD卡,还支持IO卡,MMC卡等等,而且对这些不同类型的设备均能做出区分的!有点像USB一样牛X!
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjlde53u


[*]我们首先来了解下SD总线协议中的命令及响应。
3.1 命令
[*]命令由主机发出,分为广播命令和寻址命令
[*]广播命令是针对与SD主机连接的所有设备发出的
[*]寻址命令是指定某个地址的设备进行命令传输
[*]3.1.1 命令格式
[*]命令由48bit位(6字节)组成,格式如下:
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjkVHPOh


[*]起始位: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模式请自行查阅手册)
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjjjKI6a

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjjAmhQ6

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjiUI9O1

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjhsdEQ6

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjfd6uUs


[*]上图中,命令序号对应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 响应格式
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjf0vYF7

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhje9Wirm

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjcdRCYi

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjgLiq1g


[*]其中重点讲下R1响应,在上图中我们可以看到R1返回的内容为卡的状态,关于卡状态的描述如下,每个bit均代表着对应的含义,如下图中所示:
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjcGiODJ

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjb5uwGs


[*]上图是SD nand的内部结构,与SD卡完全类似,主要有五个部分组成,这里就不细述了,不然此篇文章会过于臃长,关于这块大家可以上网查找,需要重点注意的是内部有7个寄存器,主要用来对卡片进行配置和读取卡片有关的信息,描述如下,其中SD接口有些命令就指定了读取哪个寄存器的内容!
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjZeyW7u

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写了数据之后,回复不是马上的,以及在必要的数据之间需要增加间隔,我们挑几个重点看下,在实际开发中有需要注意的在后文对应处有描述,不用过于担心。
[*]1.主机发送命令给卡,卡响应,注意图中的NCR,NCR最小不是0,因此主机发送了命令之后,SD nand不是马上就响应的
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjZ73w92


[*]2.卡连续响应两个指令之间需要有间隔,如图中的NRC
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjYACU75


[*]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卡选择了对应的模式之后不可切换,如果需要重新切换,需要重新上电
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjXgiVaW


[*]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);elseGPIO_ResetBits(GPIOA, GPIO_Pin_8);}2.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 //SDHCextern 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 0xFFuint8_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 = {0};/* command bit7 is always 1, bit6 is always 0, see SD manual. */data &= ~(0x80);data = cmd | 0x40;data = (uint8_t)(arg >> 24);data = (uint8_t)(arg >> 16);data = (uint8_t)(arg >> 8);data = (uint8_t)(arg);data = crc;for (int i = 0; i < 6; i ++)_spi_read_write_byte(data);}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;elsereturn 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;elsereturn 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响应值,直到卡空闲

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjTYyl6Y

关于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;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 = _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==0x01 && R7R3_Resp==0xAA) { //判断该卡是否支持2.7-3.6V电压count = 200; //支持电压范围,可以操作do { //发卡初始化指令CMD55+ACMD41sd_send_cmd(SD_CMD_APP_CMD, 0, 0xFF); //CMD55,以强调下面的是ACMD命令if (sd_read_response(SD_RESPONSE_NO_ERROR)) // SD_IN_IDLE_STATEreturn 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 = _spi_read_write_byte(SD_DUMMY_BYTE);}//检查接收到OCR中的bit30(CCS)//CCS = 0:SDSC CCS = 1:SDHCif(R7R3_Resp&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可以直接写入,不需要先擦除才能写入!!!牛XPlus吧!哈哈!5.3.1 数据写入数据分为单块写入和多块写入,多块写入可循环执行多块写入实现。单个块写入使用CMD24,多个块写入使用CMD25,注意此处,SD nand的操作与SD卡可能会有所不一样,在对应位置有详细描述。单块写入步骤如下:1.发送CMD24,读取响应值R1,判断卡无错误2.发送写开始指令 0xFE(SD协议中未找到此描述,此应该是SD nand所特有)3.依次传输写入数据4.发送两个字节的CRC校验,由于SPI默认没有开启CRC,因此填充为0xFFFF5.读取卡的状态判断是否有误,结束https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjQYiAAo

5.3.2 数据读取数据读取也分为单块读取和多块读取,多块读取可采用循环执行单块读取逻辑实现。单块数据读取步骤如下:
[*]1.发送CMD17,读取响应值R1,判断有无错误
[*]2.等待SD nand发送数据输出开始标志 0xFE
[*]3.依次读取数据
[*]4.多读取两位CRC值,结束
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjT1xbbl


[*]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 = {0};
[*]uint8_t rx_data = {0};
[*]for (i = 0; i < 512; i ++)
[*]tx_data = 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 != rx_data)
[*]break;
[*]printf("%d ", rx_data);
[*]}
[*]if (i == 512) {
[*]printf("sd card 读写测试成功\n");
[*]} else {
[*]printf("sd card 读写测试失败, i:%d\n", i);
[*]}
[*]}代码运行如下,测试通过:
https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjRNh8qL

https://i1.go2yd.com/image.php?url=YD_cnt_75_01MFhjQ2IyBL6. 总结

[*]综上,便是关于使用SPI接口驱动SD nand的全部说明了,确实花费了不少时间整理说明,关于SD nand的驱动**还有很多,比如采用SD接口驱动,移植文件系统,导入日志系统等等,后续有机会有时间我也会继续做整理分享。
[*]希望本篇博文能帮助到你对于如何使用SPI实现SD nand的驱动也有大致清晰的了解。
页: [1]
查看完整版本: SD nand 与 SD卡的SPI模式驱动