基于FPGA的SD NAND读写测试(图文并茂+源代码+详细注释)

发布于:2024-11-29 ⋅ 阅读:(13) ⋅ 点赞:(0)

        本实验所使用的源代码已同步至个人主页的资源处,可供读者自行学习......

什么是SD NAND?

1.SD NAND 卡介绍

        SD NAND 卡是一种基于 NAND 闪存技术的存储设备,其外观和接口类似于标准的 SD 卡。它将 NAND 闪存芯片和必要的控制电路集成在一个小型的封装内,以 SD 卡的形式提供数据存储功能。

2.SD NAND 卡与其他SD卡的对比

119c024e608146fcbf06ef8c0297cdb6.png

        与原始的 NAND(闪存)相比,该产品有许多优势,它有具有以下功能

        嵌入式坏块管理功能:这意味着它可以自己检测和标记坏块,在数据存储和读取过程中自动避开这些坏块。例如,当写入数据时,产品会先检查目标块是否为坏块,如果是,就会选择其他正常的块来存储数据,从而提高了数据存储的可靠性和效率。

        更强的嵌入式纠错码(ECC)功能:ECC 是一种用于检测和纠正数据传输或存储过程中错误的技术。在 NAND 闪存中,数据可能会因为各种因素(如电气干扰、闪存单元老化等)而出现错误。原始的 NAND 闪存可能只有基本的 ECC 功能或者需要依赖外部的 ECC 机制。该产品具有更强的嵌入式 ECC 功能,能够更有效地检测和纠正更多的数据错误。例如,它可以纠正多位数据错误,确保从闪存中读取的数据的准确性,降低数据出错导致系统故障或数据丢失的风险。

        即使是异常断电,它仍然能保持你的数据安全。

3.SD NAND卡的物理结构如下:

        SD 卡主要由以下几个部分组成,存储单元作为存储数据的核心部件,通过存储单元接口与卡控制单元实现数据传输。电源检测单元能够确保 SD 卡工作在适宜的电压范围内,一旦出现掉电或上电的情况,它会促使控制单元和存储单元接口进行复位操作。卡及接口控制单元负责掌控 SD 卡的运行状态,其中包含6个寄存器。接口驱动器则对 SD 卡引脚的输入输出进行控制。

c07887a1ca0940328a08c2b8bb1e6a00.png

 4.引脚分配图如下:

a5555337f0d44857b8709bface8d88d8.png

        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 片选信号线不可共用外,其他信号均可共用。

6719b1c3440c4b83b2dc668fe6ca53aa.png

5.SD卡的寄存器

bb2f9534e3bc442ead23fa357109135a.png

                              对于SD卡寄存器的详细介绍,读者可参考以下文章                                     《Part1_Physical_Layer_Simplified_Specification_Ver7.10》  

6.SD NAND卡的读写控制时序

       以下所有内容皆是基于SPI模式下,读者注意区分!

       1.命令与响应时序

        SD NAND卡传输过程中有SCLK和DATA。下图的DATAIN指的是DI,DATAOUT指的是DO。当主机发送指令时,SD NAND卡接收到命令后,产生响应给主机。

e073b7624af34fc9972ea25c11960583.png

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

a47ecc477ea04eaea17d21f9ac067f83.png

        主机发送的命令除了可以让数据读出,还可以让数据写入,而且每次的读/写都是按块进行的,即每次读写一般为512 字节。8aef4c34165c4980a7ac1759332e57b6.png

        2.SD NAND卡的读写命令

        发送的命令格式为:48bit,通过CMD命令线发送。

50f534dba9b24d4b969f23132428c4e9.png

        每一个命令都有一个起始位("0")和停止位("1")

        传输标志:用于区分传输方向,主机传输到 SD 卡时为1;SD 卡传输到主机时为0;

        命令号:它固定占用 6bit,总共有2^6=64条命令

        地址/参数:这 32bit 用于指定目标 SD 卡的地址

        CRC7 校验:长度为 7bit 的校验位用于验证命令传输内容正确性

        3.SPI模式下常用的命令信息

4504752eb3bb493a9ee3d3256a1e20c0.png

d401f3b142dd41c2b0394164c232f4e7.png

        4.响应命令类型

        可参阅《Part1_Physical_Layer_Simplified_Specification_Ver6.00》一文。

实验目标

       PC 机使用串口助手通过串口 RS232 协议向 SD  NAND卡(基于SPI模式)指定扇区写入 512 字节数据,随后读出写入数据回传到 PC 机,验证数据正确性。 

硬件资源 

        SD NAND卡的型号为深圳市雷龙发展有限公司的CSNP64GCR01-BOW,容量为64Gb

87c4ffd9c23442eca417cd79d584ba23.png

        将SD NAND 芯片焊接至测试板,测试板可直接 插入TF卡座或读卡器进行测试及调试,预留 的第二组焊盘可用于飞线。

76b71ad8ae34480ca2307f9d1a4b351d.png

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

e92e242b55074600a0beadae97856450.png

952f8eac6e4045c4bb1bd21260e444dd.jpeg

模块设计

        这里主要介绍一下SD NAND卡的读写控制模块:

        首先上电后,需要对SD NAND 卡进行初始化,初始化模块输出一个初始化完成信号,用于指示SD NAND卡的读和写模块的进行。

856611843fca4a998b9c9c194cfc8576.png

代码实现

module  data_rw_ctrl
(
    input   wire            sys_clk     ,   //输入工作时钟,频率50MHz
    input   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;
    else
        wr_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;
    else
        rd_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;
    else
        rd_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;
    else
        send_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;
        else
            cnt_wait    <=  cnt_wait + 1'b1;
    else
        cnt_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;
        else
            send_data_num   <=  send_data_num;
    else
        send_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;
    else
        rd_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;
    else
        tx_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,而且输入数据和读出数据完全正确。

8e692de7e4c0486e8a53665fa6fd4800.png

小结

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