本实验所使用的源代码已同步至个人主页的资源处,可供读者自行学习......
什么是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 , //输入工作时钟,频率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,而且输入数据和读出数据完全正确。
小结
这里感谢深圳市雷龙发展有限公司对本实验的支持。该公司专注于NAND Flash设计研发13年。创始人均为步步高/华为技术背景出身。是一家存储元器件代理分销商。 主营:CS创世 SD NAND(又叫贴片式 TF卡/贴片式SD卡)读写速度可达24/13 MB/S,韩国ATO Solution(SLC NAND、SPI NAND、MCP)eMMC、小容量TF卡存储芯片。