当前位置: 首页 > article >正文

基于FPGA实现SD NAND FLASH的SPI协议读写

在这里插入图片描述

基于FPGA实现SD NAND FLASH的SPI协议读写

在此介绍的是使用FPGA实现SD NAND FLASH的读写操作,以雷龙发展提供的CS创世SD NAND FLASH样品为例,分别讲解电路连接、读写时序与仿真和实验结果。

目录

1 FLASH背景介绍

2 样品申请

3 电路结构与接口协议

3.1 SD NAND

3.2 SD NAND测试板

3.3 FPGA开发板

4 SD卡协议与时序流程

4.1 SD卡协议

4.2 SD卡2.0版本初始化步骤

4.3 SD卡的读步骤

4.4 SD卡的写步骤

5 模块代码

5.1 sd_card_top

5.2 sd_card_cmd

5.3 sd_card_sec_read_write

5.4 spi_master

5.5 其余代码

5.5.1 sd_card_test

5.5.2 ax_debounce

5.5.3 seg_decoder

5.5.4 seg_scan

6 实验结果

使用FPGA讲解SD NAND FLASH的文章网上也有很多比较详实的内容,本文的部分思路也是参考了其他博主的博客思路。

1 FLASH背景介绍

简介

Flash是近些年应用最广、速度最快的只读存储器,原理是从 EEPROM 基础上改进发展来的,特点是擦除和编程速度快,因此得名为闪速(或闪烁)存储器,简称闪存。

NOR Flash 和 NAND Flash 是现在市场上两种主要的闪存技术。Intel于1988年首先开发出 NOR Flash 技术,彻底改变了原先由 EPROM 和 EEPROM 一统天下的局面。紧接着,1989年,东芝公司发表了 NAND Flash 结构,后者的单元电路尺寸几乎只是 NOR 器件的一半,可以在给定的芯片尺寸内提供更高的容量,也就相应地降低了价格。

NOR Flash 的特点是以字节为单位随机存取。这样,应用程序可以直接在 Flash中执行,不必再把程序代码预先读到 RAM 中。NOR Flash的接口一般以SPI为主,与通常的扩展存储器一样,可以直接连接到处理器的外围总线上。

NAND Flash应该是目前最热门的存储芯片了。因为我们生活中经常使用的电子产品都会涉及到它。比如你买手机,肯定会考虑64GB,还是256GB?买笔记本是买256GB,还是512GB容量的硬盘呢?(目前电脑大部分采用了基于 NAND Flash 产品的固态硬盘)。

NAND Flash 主要分为SLC,MLC,TLC,3D TLC ,3DQLC等,随时科技的发展和大众的需求,单位面积内的存储容量越来越大。SLC是指单个存储单元中,能容纳1bit 代表2种状态,0或者1. MLC 则是指单个存储单元中,能容纳2bit,代表4种状态 ,00,01,10,11。 TLC 则是指单个存储单元中,能容纳3bit,代表8种状态,000,001,010,011,100,101,110,111。最开始整个存储单元是2D展开的,也就是平面的,随着需要在单位空间内容纳更多的信息,就开始类似盖楼房一样,在3D也就是立体的方面来发展了。

如果在产品中选择nor flash 还是NAND FLASH,更多的时候是从容量角度来考量,如果存储的内容大于128Mbit 就选择NAND Flash ,小于128Mbit就选择 Nor flash。Nor flash受限于自己的工艺,大于128Mbit的容量,价格就会比128MB SLC NANDFLASH的价格还要贵。

未来发展

当前,NAND flash正在从 2D 发展到 3D。对于 2D NAND flash,如果在同一区域实现更多的单元数量,形成更小的工作区和栅级,便能增大存储容量。直至 2010 年初,2D NAND flash中的扩展一直是这项技术的主要焦点所在;然而,由于内部结构的限制,且储存数据会随时间推移而丢失导致使用寿命缩短,2D的技术已无法再实现扩展。

因此,3D NAND flash逐渐取而代之,成为业界关注焦点,现在所有 NAND 制造商都在开发和制造 3D NAND flash产品。

在 3D NAND flash 的结构中,存储容量会随着三维叠层中堆叠层数的增加而变大,类似盖楼房,一层一层叠加上去。3D NAND flash 使用了堆叠多层氮氧化物的方法,形成一个被称为“塞子”的垂直深孔,在其中形成一个由氧化物-氮化物-氧化物制成的存储设备。通过这种方法,仅需少量工艺即可同时形成大量单元。在 3D NAND flash 中,电流通过位于圆柱单元中心的多晶硅通道,便能根据存储在氮化硅中的电荷类型实现存储编程和擦除信息。虽然2D NAND flash 技术发展的目标是实现形成较小的单元, 3D NAND flash 的核心技术却是实现更多层数的三维堆叠。

由于NAND FLASH在大容量应用中的便利性,因此作为今天介绍的主角~

什么是SD NAND呢(以下省略FLASH)?下面的内容是从雷龙发展官网的介绍中得到:

SD NAND俗称贴片式TF卡,尽管与TF卡名称类似,但是有较大的区别:

在这里插入图片描述

相比常见的TF卡,SD NAND是专门为内置存储进行设计,焊接在PCB板上以供工业级产品的应用。因此对品质稳定性、一致性、以及尺寸都有较高的要求。

2 样品申请

本文所使用的CS创世SD NAND是从深圳雷龙发展申请获得,可以在官网中最上面找到申请样品的入口:

在这里插入图片描述

雷龙发展有限公司创立于2008年,专注NAND Flash领域13年。创始人均为步步高/华为技术背景出身。如果有技术问题也可以和其公司人员进行沟通,相关的工作人员非常专业和热心。

3 电路结构与接口协议

3.1 SD NAND

本文所使用的产品是CSNP4GCR01-AMW,是雷龙的第二代产品,产品如下图所示:

在这里插入图片描述

数据手册可以在立创商城进行下载,其封装与连接的电路原理参考图如下图所示:

在这里插入图片描述

在这里插入图片描述

芯片共包含8个引脚,包括4根数据线(6、7、1、2);2根电源线(4、8);1根时钟线(3);1根命令控制线(5)

手册中提供了SD NAND的两种使用模式,分别为SD MODE 以及 SPI MODE。他们所对应的引脚定义,如下图所示:

在这里插入图片描述

对于两种模式的切换,官方给出了初始化的方式。下文在代码的时序部分也会涉及到相关内容。

在对SD卡数据读写速度要求不高的情况下,选用SPI通信模式可以说是一种最佳方案。因为在该模式下,同只需要通过四根线就是可以完成所有的数据交换,可以为我们节省出宝贵的FPGA I/O资源。下图给出了SPI一对一通信时,主设备与从设备之间的连接关系。

在这里插入图片描述

因此本文主要介绍SPI MODE下各个引脚的功能:

在这里插入图片描述

确定了通讯模式后,也就便于我们后文中,利用这种通讯模式按照SD卡的读写时序进行读写操作。

3.2 SD NAND测试板

单独的SD NAND不便于我们使用FPGA进行读写测试,好在官方提供了测试板,如下图所示:

在这里插入图片描述

有了它就可以轻松实现SD NAND与我们常见的FPGA开发板上的Micro SD插槽进行连接与测试了。

适用产品:LGA8,6x8mm 封装的SD NAND产品。

测试板尺寸:长度6.22厘米,宽度2.49厘米,接口长度2.53厘米。

使用方法:将芯片焊接至测试板上,可在原有的Micro SD卡座上直接调试和测试。

准备工具:热风枪,烙铁,锡膏,镊子。

焊接方式: 先用烙铁将芯片的8个PIN脚上锡,中间的一个PIN脚不需要上锡保持NC即可。再将接板板上,对应芯片的8个PIN上锡。

最后用镊子将芯片放到PCB上,热风枪温度调至350℃ 在芯片表面均匀加热即可焊接。

​ 其它事项:测试板上其它元器件无需理会,直接将芯片焊接在测试板上即可当SD卡一样调试。

焊接好后,可以将转接板插入到读卡器,再将读卡器连接到电脑上看看是否能正确识别到容量,通过这个方式来判断芯片是否已经焊接正常。

3.3 FPGA开发板

本文所使用的是黑金的AX301开发板,上面装有一个 Micro SD 卡座, FPGA 通过 SPI 数据总线访问 Micro SD 卡,SD 卡座和 FPGA 的硬件电路连接如下:

在这里插入图片描述

借由硬件电路的连接,FPGA可以直接与我们的SD NAND进行通信了。

至此,我们已经实现了SD NANDSPI通信方式方案的确定以及基于此的硬件电路连接,下一步就是根据SD卡的读写时序讲通信方式初始化为SPI模式,并按照SD卡协议进行读写操作。

4 SD卡协议与时序流程

4.1 SD卡协议

以下内容来自黑金的实验手册:

SD 卡的协议是一种简单的命令/响应的协议。全部命令由主机发起, SD 卡接收到命令后并返

回响应数据。根据命令的不同,返回的数据内容和长度也不同。 SD 卡命令是一个 6 字节组成的命

令包,其中第一个字节为命令号, 命令号高位 bit7 和 bit6 为固定的“01“,其它 6 个 bit 为具体

的命令号。第 2 个字节到第 5 个字节为命令参数。第 6 个字节为 7 个 bit 的 CRC 校验加 1 个 bit 的结束位。 如果在 SPI 模式的时候, CRC 校验位为可选。 如下图所示, Command 表示命令,通常使用十进制表示名称,例如 CMD17,这个时候 Command 就是十进制的 17。

对于详细的SD卡协议内容,可以参考传送门中的相关内容,给出了比较具体的解释。

在这里插入图片描述

SD 卡对每个命令会返回一个响应,每个命令有一定的响应格式。响应的格式跟给它的命令号

有关。在 SPI 模式中,有三种响应格式: R1, R2, R3。

在这里插入图片描述

在进行SD NAND的SPI模式读写操作时,主要使用到了以下几种SD卡命令,下面的表格进行简单介绍,这里可以找到完整版:

在这里插入图片描述

4.2 SD卡2.0版本初始化步骤

上电后延时至少 74clock,等待 SD 卡内部操作完成

片选 CS 低电平选中 SD 卡

发送 CMD0,需要返回 0x01,进入 Idle 状态

为了区别 SD 卡是 2.0 还是 1.0,或是 MMC 卡,这里根据协议向上兼容的,首先发送只有SD2.0 才有的命令 CMD8,如果 CMD8 返回无错误,则初步判断为 2.0 卡,进一步循环发送命令 CMD55+ACMD41,直到返回 0x00,确定 SD2.0 卡

如果 CMD8 返回错误则判断为 1.0 卡还是 MMC 卡,循环发送 CMD55+ACMD41,返回无错误,则为 SD1.0 卡,到此 SD1.0 卡初始成功,如果在一定的循环次数下,返回为错误,则进一步发送 CMD1 进行初始化,如果返回无错误,则确定为 MMC 卡,如果在一定的次数下,返回为错误,则不能识别该卡,初始化结束。 (通过 CMD16 可以改变 SD 卡一次性读写的长度)

CS 拉高

4.3 SD卡的读步骤

发送 CMD17(单块)或 CMD18(多块)读命令,返回 0X00

接收数据开始令牌 fe(或 fc) +正式数据 512Bytes + CRC 校验 2Bytes(默认正式传输的数据长度是 512Bytes)

4.4 SD卡的写步骤

发送 CMD24(单块)或 CMD25(多块)写命令,返回 0X00

发送数据开始令牌 fe(或 fc) +正式数据 512Bytes + CRC 校验 2Bytes

5 模块代码

本代码所实现的功能,是基于黑金AX301B,实现对SD NAND FLASH的数据写入与读取,并显示在开发板的数码管上。当按下开发板上的按键时,会自动将数据加一操作,并进行同步显示。

前文介绍的是SD NAND的协议以及初始化、读写操作的流程,下面介绍代码的组成部分,整个工程主要由以下部分模块构成:

sd_card_test(top模块)

ax_debounce:ax_debounce_m0(按键消抖模块)

sd_card_top:sd_card_top_m0(SD卡top模块)

sd_card_cmd:sd_card_cmd_m0(SD卡指令)

sd_card_sec_read_write:sd_card_sec_read_write_m0(SD卡读写)

spi_master:spi_master_m0(SPI一个字节读写)

seg_decoder:seg_decoder_m0(数码管控制)

seg_decoder:seg_decoder_m1(数码管控制)

seg_scan:seg_scan_m0(数码管控制)

下面主要介绍上述四个加粗的模块以及其功能

5.1 sd_card_top

本模块是SD card的top模块,用来实现不同子模块之间的连接。

//  Author: meisq                                                               //
//          msq@qq.com                                                          //
//          ALINX(shanghai) Technology Co.,Ltd                                  //
//          heijin                                                              //
//     WEB: http://www.alinx.cn/                                                //
//     BBS: http://www.heijin.org/                                              //
//                                                                              //
//
//                                                                              //
// Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
//                    All rights reserved                                       //
//                                                                              //
// This source file may be used and distributed without restriction provided    //
// that this copyright statement is not removed from the file and that any      //
// derivative work contains the original copyright notice and the associated    //
// disclaimer.                                                                  //
//                                                                              //
//
 
//==========================================================================
//  Revision History:
//  Date          By            Revision    Change Description
//--------------------------------------------------------------------------
//  2017/6/21    meisq         1.0         Original
//*************************************************************************/
module sd_card_top
#(
parameter  SPI_LOW_SPEED_DIV = 248,         // SD card low speed mode frequency division parameter,spi clk speed = clk speed /((SPI_LOW_SPEED_DIV + 2) * 2 )
parameter  SPI_HIGH_SPEED_DIV = 0           // SD card high speed mode frequency division parameter,spi clk speed = clk speed /((SPI_HIGH_SPEED_DIV + 2) * 2 )
)
(
input            clk,
input            rst,
output           SD_nCS,                    //SD card chip select (SPI mode)
output           SD_DCLK,                   //SD card clock
output           SD_MOSI,                   //SD card controller data output
input            SD_MISO,                   //SD card controller data input
output           sd_init_done,              //SD card initialization is complete
input            sd_sec_read,               //SD card sector read
input[31:0]      sd_sec_read_addr,          //SD card sector read address
output[7:0]      sd_sec_read_data,          //SD card sector read data
output           sd_sec_read_data_valid,    //SD card sector read data valid
output           sd_sec_read_end,           //SD card sector read end
input            sd_sec_write,              //SD card sector write
input[31:0]      sd_sec_write_addr,         //SD card sector write address
input[7:0]       sd_sec_write_data,         //SD card sector write data
output           sd_sec_write_data_req,     //SD card sector write data next clock is valid
output           sd_sec_write_end           //SD card sector write end
);
wire[15:0]           spi_clk_div;               //SPI module clock division parameter
wire                 cmd_req;                   //SD card command request
wire                 cmd_req_ack;               //SD card command request response
wire                 cmd_req_error;             //SD card command request error
wire[47:0]           cmd;                       //SD card command
wire[7:0]            cmd_r1;                    //SD card expect response
wire[15:0]           cmd_data_len;              //SD card command read data length
wire                 block_read_req;            //SD card sector data read request
wire                 block_read_valid;          //SD card sector data read data valid
wire[7:0]            block_read_data;           //SD card sector data read data
wire                 block_read_req_ack;        //SD card sector data read response
wire                 block_write_req;           //SD card sector data write request
wire[7:0]            block_write_data;          //SD card sector data write data next clock is valid
wire                 block_write_data_rd;       //SD card sector data write data
wire                 block_write_req_ack;       //SD card sector data write response
 
wire                 nCS_ctrl;                  //SPI module chip select control
wire                 spi_wr_req;                //SPI module data sending request
wire                 spi_wr_ack;                //SPI module data request response
wire[7:0]            spi_data_in;               //SPI module send data
wire[7:0]            spi_data_out;              //SPI module data returned
wire[15:0]           clk_div;
sd_card_sec_read_write
#(
.SPI_LOW_SPEED_DIV(SPI_LOW_SPEED_DIV),
.SPI_HIGH_SPEED_DIV(SPI_HIGH_SPEED_DIV)
)
sd_card_sec_read_write_m0(
.clk                            (clk                    ),
.rst                            (rst                    ),
.sd_init_done                   (sd_init_done           ),
.sd_sec_read                    (sd_sec_read            ),
.sd_sec_read_addr               (sd_sec_read_addr       ),
.sd_sec_read_data               (sd_sec_read_data       ),
.sd_sec_read_data_valid         (sd_sec_read_data_valid ),
.sd_sec_read_end                (sd_sec_read_end        ),
.sd_sec_write                   (sd_sec_write           ),
.sd_sec_write_addr              (sd_sec_write_addr      ),
.sd_sec_write_data              (sd_sec_write_data      ),
.sd_sec_write_data_req          (sd_sec_write_data_req  ),
.sd_sec_write_end               (sd_sec_write_end       ),
.spi_clk_div                    (spi_clk_div            ),
.cmd_req                        (cmd_req                ),
.cmd_req_ack                    (cmd_req_ack            ),
.cmd_req_error                  (cmd_req_error          ),
.cmd                            (cmd                    ),
.cmd_r1                         (cmd_r1                 ),
.cmd_data_len                   (cmd_data_len           ),
.block_read_req                 (block_read_req         ),
.block_read_valid               (block_read_valid       ),
.block_read_data                (block_read_data        ),
.block_read_req_ack             (block_read_req_ack     ),
.block_write_req                (block_write_req        ),
.block_write_data               (block_write_data       ),
.block_write_data_rd            (block_write_data_rd    ),
.block_write_req_ack            (block_write_req_ack    )
);
 
sd_card_cmd sd_card_cmd_m0(
.sys_clk                        (clk                    ),
.rst                            (rst                    ),
.spi_clk_div                    (spi_clk_div            ),
.cmd_req                        (cmd_req                ),
.cmd_req_ack                    (cmd_req_ack            ),
.cmd_req_error                  (cmd_req_error          ),
.cmd                            (cmd                    ),
.cmd_r1                         (cmd_r1                 ),
.cmd_data_len                   (cmd_data_len           ),
.block_read_req                 (block_read_req         ),
.block_read_req_ack             (block_read_req_ack     ),
.block_read_data                (block_read_data        ),
.block_read_valid               (block_read_valid       ),
.block_write_req                (block_write_req        ),
.block_write_data               (block_write_data       ),
.block_write_data_rd            (block_write_data_rd    ),
.block_write_req_ack            (block_write_req_ack    ),
.nCS_ctrl                       (nCS_ctrl               ),
.clk_div                        (clk_div                ),
.spi_wr_req                     (spi_wr_req             ),
.spi_wr_ack                     (spi_wr_ack             ),
.spi_data_in                    (spi_data_in            ),
.spi_data_out                   (spi_data_out           )
 
);
 
spi_master spi_master_m0(
.sys_clk                        (clk                    ),
.rst                            (rst                    ),
.nCS                            (SD_nCS                 ),
.DCLK                           (SD_DCLK                ),
.MOSI                           (SD_MOSI                ),
.MISO                           (SD_MISO                ),
.clk_div                        (clk_div                ),
.CPOL                           (1'b1                   ),
.CPHA                           (1'b1                   ),
.nCS_ctrl                       (nCS_ctrl               ),
.wr_req                         (spi_wr_req             ),
.wr_ack                         (spi_wr_ack             ),
.data_in                        (spi_data_in            ),
.data_out                       (spi_data_out           )
);

5.2 sd_card_cmd

sd_card_cmd 模块主要实验 sd 卡基本命令操作,还有上电初始化的 88 个周期的时钟,数据

块的读写,状态机如下:

在这里插入图片描述

代码如下:

//
//                                                                              //
//                                                                              //
//  Author: meisq                                                               //
//          msq@qq.com                                                          //
//          ALINX(shanghai) Technology Co.,Ltd                                  //
//          heijin                                                              //
//     WEB: http://www.alinx.cn/                                                //
//     BBS: http://www.heijin.org/                                              //
//                                                                              //
//
//                                                                              //
// Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
//                    All rights reserved                                       //
//                                                                              //
// This source file may be used and distributed without restriction provided    //
// that this copyright statement is not removed from the file and that any      //
// derivative work contains the original copyright notice and the associated    //
// disclaimer.                                                                  //
//                                                                              //
//
 
//==========================================================================
//  Revision History:
//  Date          By            Revision    Change Description
//--------------------------------------------------------------------------
//  2017/6/21     meisq         1.0         Original
//*************************************************************************/
module sd_card_cmd(
input                       sys_clk,
input                       rst,
input[15:0]                 spi_clk_div,                  //SPI module clock division parameter
input                       cmd_req,                      //SD card command request
output                      cmd_req_ack,                  //SD card command request response
output reg                  cmd_req_error,                //SD card command request error
input[47:0]                 cmd,                          //SD card command
input[7:0]                  cmd_r1,                       //SD card expect response
input[15:0]                 cmd_data_len,                 //SD card command read data length
input                       block_read_req,               //SD card sector data read request
output reg                  block_read_valid,             //SD card sector data read data valid
output reg[7:0]             block_read_data,              //SD card sector data read data
output                      block_read_req_ack,           //SD card sector data read response
input                       block_write_req,              //SD card sector data write request
input[7:0]                  block_write_data,             //SD card sector data write data next clock is valid
output                      block_write_data_rd,          //SD card sector data write data
output                      block_write_req_ack,          //SD card sector data write response
output                      nCS_ctrl,                     //SPI module chip select control
output reg[15:0]            clk_div,
output reg                  spi_wr_req,                   //SPI module data sending request
input                       spi_wr_ack,                   //SPI module data request response
output[7:0]                 spi_data_in,                  //SPI module send data
input[7:0]                  spi_data_out                  //SPI module data returned
);
parameter S_IDLE         = 0;
parameter S_WAIT         = 1;
parameter S_INIT         = 2;
parameter S_CMD_PRE      = 3;
parameter S_CMD          = 4;
parameter S_CMD_DATA     = 5;
parameter S_READ_WAIT    = 6;
parameter S_READ         = 7;
parameter S_READ_ACK     = 8;
parameter S_WRITE_TOKEN  = 9;
parameter S_WRITE_DATA_0 = 10;
parameter S_WRITE_DATA_1 = 11;
parameter S_WRITE_CRC    = 12;
parameter S_WRITE_ACK    = 13;
parameter S_ERR          = 14;
parameter S_END          = 15;
 
reg[3:0]                      state;
reg                           CS_reg;
reg[15:0]                     byte_cnt;
reg[7:0]                      send_data;
wire[7:0]                     data_recv;
reg[9:0]                      wr_data_cnt;
 
assign cmd_req_ack = (state == S_END);
assign block_read_req_ack = (state == S_READ_ACK);
assign block_write_req_ack= (state == S_WRITE_ACK);
assign block_write_data_rd = (state == S_WRITE_DATA_0);
assign spi_data_in = send_data;
assign data_recv = spi_data_out;
assign nCS_ctrl = CS_reg;
always@(posedge sys_clk or posedge rst)
begin
if(rst == 1'b1)
begin
CS_reg <= 1'b1;
spi_wr_req <= 1'b0;
byte_cnt <= 16'd0;
clk_div <= 16'd0;
send_data <= 8'hff;
state <= S_IDLE;
cmd_req_error <= 1'b0;
wr_data_cnt <= 10'd0;
end
else
case(state)
S_IDLE:
begin
state <= S_INIT;
clk_div <= spi_clk_div;
CS_reg <= 1'b1;
end
S_INIT:
begin
//send 11 bytes on power(at least 74 SPI clocks)
if(spi_wr_ack == 1'b1)
begin
if(byte_cnt >= 16'd10)
begin
byte_cnt <= 16'd0;
spi_wr_req <= 1'b0;
state <= S_WAIT;
end
begin
byte_cnt <= byte_cnt + 16'd1;
end
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_WAIT:
begin
cmd_req_error <= 1'b0;
wr_data_cnt <= 10'd0;
//wait for  instruction
if(cmd_req == 1'b1)
state <= S_CMD_PRE;
else if(block_read_req == 1'b1)
state <= S_READ_WAIT;
else if(block_write_req == 1'b1)
state <= S_WRITE_TOKEN;
clk_div <= spi_clk_div;
end
S_CMD_PRE:
begin
//before sending a command, send an byte 'ff',provide some clocks
if(spi_wr_ack == 1'b1)
begin
state <= S_CMD;
spi_wr_req <= 1'b0;
byte_cnt <= 16'd0;
end
else
begin
spi_wr_req <= 1'b1;
CS_reg <= 1'b1;
send_data <= 8'hff;
end
end
S_CMD:
begin
if(spi_wr_ack == 1'b1)
begin
if((byte_cnt == 16'hffff) || (data_recv != cmd_r1 && data_recv[7] == 1'b0))
begin
state <= S_ERR;
spi_wr_req <= 1'b0;
end
else if(data_recv == cmd_r1)
begin
spi_wr_req <= 1'b0;
if(cmd_data_len != 16'd0)
begin
state <= S_CMD_DATA;
byte_cnt <= 16'd0;
end
else
state <= S_END;
end
else
byte_cnt <=  byte_cnt + 16'd1;
end
else
begin
spi_wr_req <= 1'b1;
CS_reg <= 1'b0;
if(byte_cnt == 16'd0)
send_data <= (cmd[47:40] | 8'h40);
else if(byte_cnt == 16'd1)
send_data <= cmd[39:32];
else if(byte_cnt == 16'd2)
send_data <= cmd[31:24];
else if(byte_cnt == 16'd3)
send_data <= cmd[23:16];
else if(byte_cnt == 16'd4)
send_data <= cmd[15:8];
else if(byte_cnt == 16'd5)
send_data <= cmd[7:0];
else
send_data <= 8'hff;
end
end
S_CMD_DATA:
begin
if(spi_wr_ack == 1'b1)
begin
if(byte_cnt == cmd_data_len - 16'd1)
begin
state <= S_END;
spi_wr_req <= 1'b0;
byte_cnt <= 16'd0;
end
else
begin
byte_cnt <= byte_cnt + 16'd1;
end
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_READ_WAIT:
begin
if(spi_wr_ack == 1'b1 && data_recv == 8'hfe)
begin
spi_wr_req <= 1'b0;
state <= S_READ;
byte_cnt <= 16'd0;
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_READ:
begin
if(spi_wr_ack == 1'b1)
begin
if(byte_cnt == 16'd513)
begin
state <= S_READ_ACK;
spi_wr_req <= 1'b0;
byte_cnt <= 16'd0;
end
else
begin
byte_cnt <= byte_cnt + 16'd1;
end
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_WRITE_TOKEN:
if(spi_wr_ack == 1'b1)
begin
state <= S_WRITE_DATA_0;
spi_wr_req <= 1'b0;
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hfe;
end
S_WRITE_DATA_0:
begin
state <= S_WRITE_DATA_1;
wr_data_cnt <= wr_data_cnt + 10'd1;
end
S_WRITE_DATA_1:
begin
if(spi_wr_ack == 1'b1 && wr_data_cnt == 10'd512)
begin
state <= S_WRITE_CRC;
spi_wr_req <= 1'b0;
end
else if(spi_wr_ack == 1'b1)
begin
state <= S_WRITE_DATA_0;
spi_wr_req <= 1'b0;
end
else
begin
spi_wr_req <= 1'b1;
send_data <= block_write_data;
end
end
S_WRITE_CRC:
begin
if(spi_wr_ack == 1'b1)
begin
if(byte_cnt == 16'd2)
begin
state <= S_WRITE_ACK;
spi_wr_req <= 1'b0;
byte_cnt <= 16'd0;
end
else
begin
byte_cnt <= byte_cnt + 16'd1;
end
end
else
begin
spi_wr_req <= 1'b1;
send_data <= 8'hff;
end
end
S_ERR:
begin
state <= S_END;
cmd_req_error <= 1'b1;
end
S_READ_ACK,S_WRITE_ACK,S_END:
begin
state <= S_WAIT;
end
default:
state <= S_IDLE;
endcase
end
always@(posedge sys_clk or posedge rst)
begin
if(rst == 1'b1)
block_read_valid <= 1'b0;
else if(state == S_READ && byte_cnt < 16'd512)
block_read_valid <= spi_wr_ack;
else
block_read_valid <= 1'b0;
end
 
always@(posedge sys_clk or posedge rst)
begin
if(rst == 1'b1)
block_read_data <= 8'd0;
else if(state == S_READ && spi_wr_ack == 1'b1)
block_read_data <= data_recv;
end

5.3 sd_card_sec_read_write

sd_card_sec_read_write 模块继续完成 SD 卡初始化,然后等待扇区读写指令,并完成扇区的

读写操作。 下图为模块的状态机转换图,首先发送 CMD0 命令,然后发送 CMD8 命令,再发送

CMD55,接着发送 ACMD41,如果应答正常, sd 卡初始化完成,等待扇区的读写。

在这里插入图片描述

代码如下:

//
//                                                                              //
//                                                                              //
//  Author: meisq                                                               //
//          msq@qq.com                                                          //
//          ALINX(shanghai) Technology Co.,Ltd                                  //
//          heijin                                                              //
//     WEB: http://www.alinx.cn/                                                //
//     BBS: http://www.heijin.org/                                              //
//                                                                              //
//
//                                                                              //
// Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
//                    All rights reserved                                       //
//                                                                              //
// This source file may be used and distributed without restriction provided    //
// that this copyright statement is not removed from the file and that any      //
// derivative work contains the original copyright notice and the associated    //
// disclaimer.                                                                  //
//                                                                              //
//

//===============================================================================
//  Revision History:
//  Date          By            Revision    Change Description
//-------------------------------------------------------------------------------
//  2017/6/21     meisq         1.0         Original
//*******************************************************************************/
module sd_card_sec_read_write
#(
parameter  SPI_LOW_SPEED_DIV = 248,         // spi clk speed = clk speed /((SPI_LOW_SPEED_DIV + 2) * 2 )
parameter  SPI_HIGH_SPEED_DIV = 0           // spi clk speed = clk speed /((SPI_HIGH_SPEED_DIV + 2) * 2 )
)
(
input            clk,
input            rst,
output reg       sd_init_done,
input            sd_sec_read,
input[31:0]      sd_sec_read_addr,
output[7:0]      sd_sec_read_data,
output           sd_sec_read_data_valid,
output           sd_sec_read_end,
input            sd_sec_write,
input[31:0]      sd_sec_write_addr,
input[7:0]       sd_sec_write_data,
output           sd_sec_write_data_req,
output           sd_sec_write_end,

output reg[15:0] spi_clk_div,
output reg       cmd_req,
input            cmd_req_ack,
input            cmd_req_error,
output reg[47:0] cmd,
output reg[7:0]  cmd_r1,
output reg[15:0] cmd_data_len,
output reg       block_read_req,
input            block_read_valid,
input[7:0]       block_read_data,
input            block_read_req_ack,
output reg       block_write_req,
output[7:0]      block_write_data,
input            block_write_data_rd,
input            block_write_req_ack
);
reg[7:0] read_data;
reg[31:0] timer;

localparam S_IDLE               = 0;
localparam S_CMD0               = 1;
localparam S_CMD8               = 2;
localparam S_CMD55              = 3;
localparam S_CMD41              = 4;
localparam S_CMD17              = 5;
localparam S_READ               = 6;
localparam S_CMD24              = 7;
localparam S_WRITE              = 8;
localparam S_ERR                = 14;
localparam S_WRITE_END          = 15;
localparam S_READ_END           = 16;
localparam S_WAIT_READ_WRITE    = 17;
localparam S_CMD16              = 18;

reg[4:0]                       state;
reg[31:0]                      sec_addr;
assign sd_sec_read_data_valid = (state == S_READ) && block_read_valid;
assign sd_sec_read_data = block_read_data;
assign sd_sec_read_end = (state == S_READ_END);
assign sd_sec_write_data_req = (state == S_WRITE) && block_write_data_rd;
assign block_write_data = sd_sec_write_data;
assign sd_sec_write_end = (state == S_WRITE_END);

always@(posedge clk or posedge rst)
begin
if(rst == 1'b1)
begin
state <= S_IDLE;
cmd_req <= 1'b0;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'd0;
cmd <= 48'd0;
spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
block_write_req <= 1'b0;
block_read_req <= 1'b0;
sec_addr <= 32'd0;
sd_init_done <= 1'b0;
end
else
case(state)
S_IDLE:
begin
state <= S_CMD0;
sd_init_done <= 1'b0;
spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
end
S_CMD0:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD8;
cmd_req <= 1'b0;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'h01;
cmd <= {8'd0,8'h00,8'h00,8'h00,8'h00,8'h95};
end
end
S_CMD8:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD55;
cmd_req <= 1'b0;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd4;
cmd_r1 <= 8'h01;
cmd <= {8'd8,8'h00,8'h00,8'h01,8'haa,8'h87};
end
end
S_CMD55:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD41;
cmd_req <= 1'b0;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'h01;
cmd <= {8'd55,8'h00,8'h00,8'h00,8'h00,8'hff};
end
end
S_CMD41:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD16;
cmd_req <= 1'b0;
sd_init_done <= 1'b1;
spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
end
else if(cmd_req_ack)
begin
state <= S_CMD55;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'h00;
cmd <= {8'd41,8'h40,8'h00,8'h00,8'h00,8'hff};
end
end
S_CMD16:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_WAIT_READ_WRITE;
cmd_req <= 1'b0;
sd_init_done <= 1'b1;
spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
end
else if(cmd_req_ack)
begin
state <= S_CMD55;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'h00;
cmd <= {8'd16,32'd512,8'hff};
end
end 
S_WAIT_READ_WRITE:
begin
if(sd_sec_write ==  1'b1)
begin
state <= S_CMD24;
sec_addr <= sd_sec_write_addr;
end
else if(sd_sec_read == 1'b1)
begin
state <= S_CMD17;
sec_addr <= sd_sec_read_addr;
end
spi_clk_div <= 16'd0;
end
S_CMD24:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_WRITE;
cmd_req <= 1'b0;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'h00;
cmd <= {8'd24,sec_addr,8'hff};

end
end
S_WRITE:
begin
if(block_write_req_ack == 1'b1)
begin
block_write_req <= 1'b0;
state <= S_WRITE_END;
end
else
block_write_req <= 1'b1;
end
S_CMD17:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_READ;
cmd_req <= 1'b0;
end
else
begin
cmd_req <= 1'b1;
cmd_data_len <= 16'd0;
cmd_r1 <= 8'h00;
cmd <= {8'd17,sec_addr,8'hff};
end
end
S_READ:
begin
if(block_read_req_ack)
begin
state <= S_READ_END;
block_read_req <= 1'b0;
end
else
begin
block_read_req <= 1'b1;
end
end
S_WRITE_END:
begin
state <= S_WAIT_READ_WRITE;
end
S_READ_END:
begin
state <= S_WAIT_READ_WRITE;
end
default:
state <= S_IDLE;
endcase
end
endmodule

5.4 spi_master

这一模块用来完成SPI一个字节的读写。

spi master 状态机设计, 主要完成一个字节 spi 数据的读写,由于是全双工的,写一个字节的

同时也读一个字节。 首先空闲状态“IDLE”接收到写请求后进入“DCLK_IDLE”状态,这个状态为

spi 时钟沿变化保持一定的时间,用来控制 spi 时钟的周期,然后进入 spi 时钟沿的变化状态,一

个字节上升沿和下降沿一共 16 个数据沿。 在最后一个数据沿进入“LAST_HALF_CYCLE”状态,为

让最后一个沿也保持一定的时间,再进入应答状态,完成一次写请求。spi_master 模块中模拟了一个 spi 时钟,在状态机进入到‘DCLK_EDGE’时进行翻转。状态机图示如下:

在这里插入图片描述

代码如下:

//  Author: meisq                                                               //
//          msq@qq.com                                                          //
//          ALINX(shanghai) Technology Co.,Ltd                                  //
//          heijin                                                              //
//     WEB: http://www.alinx.cn/                                                //
//     BBS: http://www.heijin.org/                                              //
//                                                                              //
//
//                                                                              //
// Copyright (c) 2017,ALINX(shanghai) Technology Co.,Ltd                        //
//                    All rights reserved                                       //
//                                                                              //
// This source file may be used and distributed without restriction provided    //
// that this copyright statement is not removed from the file and that any      //
// derivative work contains the original copyright notice and the associated    //
// disclaimer.                                                                  //
//                                                                              //
//

//==========================================================================
//  Revision History:
//  Date          By            Revision    Change Description
//--------------------------------------------------------------------------
//  2017/6/19     meisq         1.0         Original
//*************************************************************************/
module spi_master
(
input                       sys_clk,
input                       rst,
output                      nCS,       //chip select (SPI mode)
output                      DCLK,      //spi clock
output                      MOSI,      //spi data output
input                       MISO,      //spi input
input                       CPOL,
input                       CPHA,
input                       nCS_ctrl,
input[15:0]                 clk_div,
input                       wr_req,
output                      wr_ack,
input[7:0]                  data_in,
output[7:0]                 data_out
);
localparam                   IDLE            = 0;
localparam                   DCLK_EDGE       = 1;
localparam                   DCLK_IDLE       = 2;
localparam                   ACK             = 3;
localparam                   LAST_HALF_CYCLE = 4;
localparam                   ACK_WAIT        = 5;
reg                          DCLK_reg;
reg[7:0]                     MOSI_shift;
reg[7:0]                     MISO_shift;
reg[2:0]                     state;
reg[2:0]                     next_state;
reg [15:0]                   clk_cnt;
reg[4:0]                     clk_edge_cnt;
assign MOSI = MOSI_shift[7];
assign DCLK = DCLK_reg;
assign data_out = MISO_shift;
assign wr_ack = (state == ACK);
assign nCS = nCS_ctrl;
always@(posedge sys_clk or posedge rst)
begin
if(rst)
state <= IDLE;
else
state <= next_state;
end

always@(*)
begin
case(state)
IDLE:
if(wr_req == 1'b1)
next_state <= DCLK_IDLE;
else
next_state <= IDLE;
DCLK_IDLE:
//half a SPI clock cycle produces a clock edge
if(clk_cnt == clk_div)
next_state <= DCLK_EDGE;
else
next_state <= DCLK_IDLE;
DCLK_EDGE:
//a SPI byte with a total of 16 clock edges
if(clk_edge_cnt == 5'd15)
next_state <= LAST_HALF_CYCLE;
else
next_state <= DCLK_IDLE;
//this is the last data edge
LAST_HALF_CYCLE:
if(clk_cnt == clk_div)
next_state <= ACK;
else
next_state <= LAST_HALF_CYCLE;
//send one byte complete
ACK:
next_state <= ACK_WAIT;
//wait for one clock cycle, to ensure that the cancel request signal
ACK_WAIT:
next_state <= IDLE;
default:
next_state <= IDLE;
endcase
end

always@(posedge sys_clk or posedge rst)
begin
if(rst)
DCLK_reg <= 1'b0;
else if(state == IDLE)
DCLK_reg <= CPOL;
else if(state == DCLK_EDGE)
DCLK_reg <= ~DCLK_reg;//SPI clock edge
end
//SPI clock wait counter
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_cnt <= 16'd0;
else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
clk_cnt <= clk_cnt + 16'd1;
else
clk_cnt <= 16'd0;
end
//SPI clock edge counter
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_edge_cnt <= 5'd0;
else if(state == DCLK_EDGE)
clk_edge_cnt <= clk_edge_cnt + 5'd1;
else if(state == IDLE)
clk_edge_cnt <= 5'd0;
end
//SPI data output
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MOSI_shift <= 8'd0;
else if(state == IDLE && wr_req)
MOSI_shift <= data_in;
else if(state == DCLK_EDGE)
if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b1)
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
else if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] == 1'b0))
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
end
//SPI data input
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MISO_shift <= 8'd0;
else if(state == IDLE && wr_req)
MISO_shift <= 8'h00;
else if(state == DCLK_EDGE)
if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b0)
MISO_shift <= {MISO_shift[6:0],MISO};
else if(CPHA == 1'b1 && (clk_edge_cnt[0] == 1'b1))
MISO_shift <= {MISO_shift[6:0],MISO};
end
endmodule

6 实验结果

下载实验程序后,可以看到数码管显示一个数字,这个数字是存储在 sd 卡中第一扇区的第一个数据,数据是随机的,这个时候按键 KEY1 按下,数字加一,并写入了 sd 卡,再次下载程序,可以看到直接显示更新后的数据。

在这里插入图片描述

在这里插入图片描述

人之所以能,是相信能


http://www.kler.cn/a/288675.html

相关文章:

  • 免费,WPS Office教育考试专用版
  • [代码随想录Day10打卡] 理论基础 232.用栈实现队列 225. 用队列实现栈 20. 有效的括号 1047. 删除字符串中的所有相邻重复项
  • 计算机毕业设计Python+Neo4j知识图谱医疗问答系统 大模型 机器学习 深度学习 人工智能 大数据毕业设计 Python爬虫 Python毕业设计
  • Prometheus面试内容整理-Prometheus 的架构和工作原理
  • FFmpeg 4.3 音视频-多路H265监控录放C++开发十三:将AVFrame转换成AVPacket。视频编码原理.编码相关api
  • 红帽认证和华为认证哪个好?看完这4点你就明白了
  • 棋类游戏定制开发:步骤详解
  • ET6框架(七)Excel配置工具
  • 【数论 状态机dp】2572. 无平方子集计数
  • c++懒汉式单例模式(Singleton)多种实现方式及最优比较
  • laravel8快速开发简单博客系统(二)
  • HarmonyOS NEXT实战:“相机分段式拍照”性能提升实践
  • 深度学习100问11:什么是one-hot编码
  • Anaconda安装和环境配置教程(深度学习准备)
  • 用SQL语句 对时间进行周期计算week(date,mode)
  • SAP B1 三大基本表单标准功能介绍-物料主数据(下)
  • ClickHouse实时探索与实践 京东云
  • 使用LLaMA-Factory快速训练自己的专用大模型
  • 空间计量 | 似不相关回归SUR
  • k8s的Service和持久化存储
  • B端系统门门清之:CRM-客户管理系统,客户是一切的源头。
  • 动态规划---分割等和子集
  • 8.30-使用docker容器部署考试项目+使用Dockerfile部署java项目
  • 视频:Python深度学习量化交易策略、股价预测:LSTM、GRU深度门控循环神经网络|附代码数据...
  • (十五)SpringCloudAlibaba-Sentinel持久化到Nacos
  • python图像处理基础(skimage、PIL、OpenCV)