本实验所使用的源代码已同步至个人主页的资源处,可供读者自行学习......
什么是SD NAND?
1.SD NAND 卡介绍
SD NAND 卡是一种基于 NAND 闪存技术的存储设备,其外观和接口类似于标准的 SD 卡。它将 NAND 闪存芯片和必要的控制电路集成在一个小型的封装内,以 SD 卡的形式提供数据存储功能。
2.SD NAND 卡与其他SD卡的对比

与原始的 NAND(闪存)相比,该产品有许多优势,它有具有以下功能
嵌入式坏块管理功能:这意味着它可以自己检测和标记坏块,在数据存储和读取过程中自动避开这些坏块。例如,当写入数据时,产品会先检查目标块是否为坏块,如果是,就会选择其他正常的块来存储数据,从而提高了数据存储的可靠性和效率。
更强的嵌入式纠错码(ECC)功能:ECC 是一种用于检测和纠正数据传输或存储过程中错误的技术。在 NAND 闪存中,数据可能会因为各种因素(如电气干扰、闪存单元老化等)而出现错误。原始的 NAND 闪存可能只有基本的 ECC 功能或者需要依赖外部的 ECC 机制。该产品具有更强的嵌入式 ECC 功能,能够更有效地检测和纠正更多的数据错误。例如,它可以纠正多位数据错误,确保从闪存中读取的数据的准确性,降低数据出错导致系统故障或数据丢失的风险。
即使是异常断电,它仍然能保持你的数据安全。
3.SD NAND卡的物理结构如下:
SD 卡主要由以下几个部分组成,存储单元作为存储数据的核心部件,通过存储单元接口与卡控制单元实现数据传输。电源检测单元能够确保 SD 卡工作在适宜的电压范围内,一旦出现掉电或上电的情况,它会促使控制单元和存储单元接口进行复位操作。卡及接口控制单元负责掌控 SD 卡的运行状态,其中包含6个寄存器。接口驱动器则对 SD 卡引脚的输入输出进行控制。

4.引脚分配图如下:

SD NAND卡的读写控制主要有两种模式: SD模式和SPI模式
在SD模式下:SD 卡共使用到 SCLK、CMD、SDD[3:0]六根 信号线;SDIO 总线与多张 SD 卡连接时,可以共用 SCLK 时钟信号线,对于 CMD、SDD[3:0]信号线,每张 SD 卡都要独立连接.
在 SPI 模式下,SD 卡共使用到 CS、SCLK、DI(MISO)、DO(MOSI)四 根信号线;SPI 总线与多张 SD 卡连接时,除 CS 片选信号线不可共用外,其他信号均可共用。

5.SD卡的寄存器

对于SD卡寄存器的详细介绍,读者可参考以下文章 《Part1_Physical_Layer_Simplified_Specification_Ver7.10》
6.SD NAND卡的读写控制时序
以下所有内容皆是基于SPI模式下,读者注意区分!
1.命令与响应时序
SD NAND卡传输过程中有SCLK和DATA。下图的DATAIN指的是DI,DATAOUT指的是DO。当主机发送指令时,SD NAND卡接收到命令后,产生响应给主机。

主机发送的命令可以让SD NAND卡中的数据读出,并且通过CRC校验数据的正确性(注意CRC是由SD NAND卡硬件设备生成的),此时只对某一块(block)读数据。

        主机发送的命令除了可以让数据读出,还可以让数据写入,而且每次的读/写都是按块进行的,即每次读写一般为512 字节。
2.SD NAND卡的读写命令
发送的命令格式为:48bit,通过CMD命令线发送。

每一个命令都有一个起始位("0")和停止位("1")
传输标志:用于区分传输方向,主机传输到 SD 卡时为1;SD 卡传输到主机时为0;
命令号:它固定占用 6bit,总共有2^6=64条命令
地址/参数:这 32bit 用于指定目标 SD 卡的地址
CRC7 校验:长度为 7bit 的校验位用于验证命令传输内容正确性
3.SPI模式下常用的命令信息


4.响应命令类型
可参阅《Part1_Physical_Layer_Simplified_Specification_Ver6.00》一文。
实验目标
PC 机使用串口助手通过串口 RS232 协议向 SD NAND卡(基于SPI模式)指定扇区写入 512 字节数据,随后读出写入数据回传到 PC 机,验证数据正确性。
硬件资源
SD NAND卡的型号为深圳市雷龙发展有限公司的CSNP64GCR01-BOW,容量为64Gb


读写测试时直接将芯片焊接到测试板后,将测试板插到TF卡座部分,即可读写测试。


模块设计
这里主要介绍一下SD NAND卡的读写控制模块:
首先上电后,需要对SD NAND 卡进行初始化,初始化模块输出一个初始化完成信号,用于指示SD NAND卡的读和写模块的进行。

代码实现
module  data_rw_ctrl
(input   wire            sys_clk     ,   //输入工作时钟,频率50MHzinput   wire            sys_rst_n   ,   //输入复位信号,低电平有效input   wire            init_end    ,   //SD卡初始化完成信号input   wire            rx_flag     ,   //写fifo写入数据标志信号input   wire    [7:0]   rx_data     ,   //写fifo写入数据input   wire            wr_req      ,   //sd卡数据写请求input   wire            wr_busy     ,   //sd卡写数据忙信号output  wire            wr_en       ,   //sd卡数据写使能信号output  wire    [31:0]  wr_addr     ,   //sd卡写数据扇区地址output  wire    [15:0]  wr_data     ,   //sd卡写数据input   wire            rd_data_en  ,   //sd卡读出数据标志信号input   wire    [15:0]  rd_data     ,   //sd卡读出数据input   wire            rd_busy     ,   //sd卡读数据忙信号output  reg             rd_en       ,   //sd卡数据读使能信号output  wire    [31:0]  rd_addr     ,   //sd卡读数据扇区地址output  reg             tx_flag     ,   //读fifo读出数据标志信号output  wire    [7:0]   tx_data         //读fifo读出数据
);//parameter define
parameter   DATA_NUM    =   12'd256     ;   //读写数据个数
parameter   SECTOR_ADDR =   32'd1000    ;   //读写数据扇区地址
parameter   CNT_WAIT_MAX=   16'd60000   ;   //读fifo输出数据时间间隔计数最大值//wire  define
wire    [11:0]  wr_fifo_data_num    ;   //写fifo内数据个数
wire            wr_busy_fall        ;   //sd卡写数据忙信号下降沿
wire            rd_busy_fall        ;   //sd卡读数据忙信号下降沿
//wire            rd_fifo_rd_en       ;   //读fifo读使能信号//reg   define
reg             wr_busy_dly         ;   //sd卡写数据忙信号打一拍
reg             rd_busy_dly         ;   //sd卡读数据忙信号打一拍
reg             send_data_en        ;   //串口发送数据使能信号
reg     [15:0]  cnt_wait            ;   //读fifo输出数据时间间隔计数
reg     [11:0]  send_data_num       ;   //串口发送数据字节数计数
reg             rd_fifo_rd_en       ;//wr_en:sd卡数据写使能信号
assign  wr_en = ((wr_fifo_data_num == (DATA_NUM)) && (init_end == 1'b1))? 1'b1 : 1'b0;//wr_addr:sd卡写数据扇区地址
assign  wr_addr = SECTOR_ADDR;//wr_busy_dly:sd卡写数据忙信号打一拍
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)wr_busy_dly <=  1'b0;elsewr_busy_dly <=  wr_busy;//wr_busy_fall:sd卡写数据忙信号下降沿
assign  wr_busy_fall = ((wr_busy == 1'b0) && (wr_busy_dly == 1'b1))? 1'b1 : 1'b0;//rd_en:sd卡数据读使能信号
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rd_en   <=  1'b0;else    if(wr_busy_fall == 1'b1)rd_en   <=  1'b1;elserd_en   <=  1'b0;//rd_addr:sd卡读数据扇区地址
assign  rd_addr = SECTOR_ADDR;//rd_busy_dly:sd卡读数据忙信号打一拍
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rd_busy_dly <=  1'b0;elserd_busy_dly <=  rd_busy;//rd_busy_fall:sd卡读数据忙信号下降沿
assign  rd_busy_fall = ((rd_busy == 1'b0) && (rd_busy_dly == 1'b1))? 1'b1 : 1'b0;//send_data_en:串口发送数据使能信号
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)send_data_en    <=  1'b0;else    if((send_data_num == (DATA_NUM * 2) - 1'b1)&& (cnt_wait == CNT_WAIT_MAX - 1'b1))send_data_en    <=  1'b0;else    if(rd_busy_fall == 1'b1)send_data_en    <=  1'b1;elsesend_data_en    <=  send_data_en;//cnt_wait:读fifo输出数据时间间隔计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_wait    <=  16'd0;else    if(send_data_en == 1'b1)if(cnt_wait == CNT_WAIT_MAX)cnt_wait    <=  16'd0;elsecnt_wait    <=  cnt_wait + 1'b1;elsecnt_wait    <=  16'd0;//send_data_num:串口发送数据字节数计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)send_data_num   <=  12'd0;else    if(send_data_en == 1'b1)if(cnt_wait == CNT_WAIT_MAX)send_data_num   <=  send_data_num + 1'b1;elsesend_data_num   <=  send_data_num;elsesend_data_num   <=  12'd0;//rd_fifo_rd_en:读fifo读使能信号
//assign  rd_fifo_rd_en = (cnt_wait == CNT_WAIT_MAX) ? 1'b1 : 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rd_fifo_rd_en   <=  1'b0;else    if(cnt_wait == (CNT_WAIT_MAX - 1'b1))rd_fifo_rd_en   <=  1'b1;elserd_fifo_rd_en   <=  1'b0;//tx_flag:读fifo读出数据标志信号
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)tx_flag <=  1'b0;elsetx_flag <=  rd_fifo_rd_en;fifo_wr_data   fifo_wr_data_inst
(.wrclk      (sys_clk            ),  //数据写时钟.wrreq      (rx_flag            ),  //数据写使能.data       (rx_data            ),  //写入数据.rdclk      (sys_clk            ),  //数据读时钟.rdreq      (wr_req             ),  //数据读使能.q          (wr_data            ),  //读出数据.rdusedw    (wr_fifo_data_num   )   //fifo内剩余数据个数
);fifo_rd_data    fifo_rd_data_inst
(.wrclk      (sys_clk        ),  //数据写时钟.wrreq      (rd_data_en     ),  //数据写使能.data       (rd_data        ),  //写入数据.rdclk      (sys_clk        ),  //数据读时钟.rdreq      (rd_fifo_rd_en  ),  //数据读使能.q          (tx_data        )   //读出数据
);endmodule代码部分篇幅过长,详细源代码可参考本人主页资源部分自行学习
实验结果
PC机通过串口调试助手往SD NAND卡写入数据后,FPGA再将SD NAND卡中的数据读出通过UART发送模块发送给PC机后,可以发现写入和读出的数据位数都是512byte,而且输入数据和读出数据完全正确。

小结
这里感谢深圳市雷龙发展有限公司对本实验的支持。该公司专注于NAND Flash设计研发13年。创始人均为步步高/华为技术背景出身。是一家存储元器件代理分销商。 主营:CS创世 SD NAND(又叫贴片式 TF卡/贴片式SD卡)读写速度可达24/13 MB/S,韩国ATO Solution(SLC NAND、SPI NAND、MCP)eMMC、小容量TF卡存储芯片。