FPGA使用SPI控制FLASH


前言

通过控制FLASH芯片进一步熟悉SPI协议

一、FLASH与EEPROM芯片

Flash 存储器: Flash 存储器是一种非易失性存储器,它具有 RAM 和 ROM 的一些特点。与 ROM 类似,Flash 存储器的内容在断电时不会丢失,但与 RAM 类似,它可以通过编程来修改存储的内容。Flash 存储器通常用于嵌入式系统中存储程序代码、配置数据等。
EEPROM(电可擦除可编程只读存储器): EEPROM 是一种可编程的非易失性存储器,可以通过电擦除和编程来修改存储的内容。EEPROM 具有 RAM 的特点,可以随机读写,但也具有 ROM 的特点,存储的内容在断电时不会丢失。EEPROM 通常用于存储配置数据、参数设置等。
Flash 存储器和 EEPROM(Electrically Erasable Programmable Read-Only Memory)之间的主要区别在于它们的擦除和编程方法、速度、寿命和应用范围等方面。

  • 擦除和编程方法:
    Flash 存储器通常使用扇区擦除(Sector Erase)的方式来擦除数据,并使用编程器(Programmer)来进行编程。扇区擦除意味着需要一次性擦除一整个存储扇区,然后才能进行写入操作。EEPROM 可以通过逐字节擦除和编程来修改数据,因此擦除和编程更加灵活和精细。

  • 速度:由于 Flash 存储器通常采用扇区擦除的方式,擦除和编程操作比 EEPROM 慢。EEPROM 在擦除和编程时的速度相对较快,因为它可以逐字节地擦除和编程。

  • 寿命:Flash 存储器和 EEPROM 的擦除和编程操作都会导致存储器单元的磨损,从而影响存储器的寿命。由于 Flash 存储器通常需要一次性擦除整个扇区,因此其寿命可能受到擦除次数的限制。EEPROM 的擦除和编程操作更加精细,因此其寿命可能比 Flash 存储器更长。

  • 应用范围:Flash 存储器通常用于存储大容量的程序代码、固件更新等。EEPROM 通常用于存储配置数据、参数设置、小容量数据等。

  • 总的来说,Flash 存储器和 EEPROM 都是非易失性存储器,但它们在擦除和编程方法、速度、寿命和应用范围等方面存在一些差异,因此在选择存储器时需要根据具体的应用需求进行选择
    以下内容为补充的更加详细的参考,来自:https://zhuanlan.zhihu.com/p/27621418
    rom最初不能编程,出厂什么内容就永远什么内容,不灵活。后来出现了prom,可以自己写入一次,要是写错了,只能换一片,自认倒霉。人类文明不断进步,终于出现了可多次擦除写入的EPROM,每次擦除要把芯片拿到紫外线上照一下,想一下你往单片机上下了一个程序之后发现有个地方需要加一句话,为此你要把单片机放紫外灯下照半小时,然后才能再下一次,这么折腾一天也改不了几次。历史的车轮不断前进,伟大的EEPROM出现了,拯救了一大批程序员,终于可以随意的修改rom中的内容了。
    EEPROM的全称是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory。是相对于紫外擦除的rom来讲的。但是今天已经存在多种EEPROM的变种,变成了一类存储器的统称。
    狭义的EEPROM:
    这种rom的特点是可以随机访问和修改任何一个字节,可以往每个bit中写入0或者1。这是最传统的一种EEPROM,掉电后数据不丢失,可以保存100年,可以擦写100w次。具有较高的可靠性,但是电路复杂/成本也高。因此目前的EEPROM都是几十千字节到几百千字节的,绝少有超过512K的。
    flash:
    flash属于广义的EEPROM,因为它也是电擦除的rom。但是为了区别于一般的按字节为单位的擦写的EEPROM,我们都叫它flash。flash做的改进就是擦除时不再以字节为单位,而是以块为单位,一次简化了电路,数据密度更高,降低了成本。 上M字节的rom一般都是flash。
    Flash又分NAND Flash和NOR Flash,NOR型存储内容以编码为主,其功能多与运算相关;NAND型主要功能是存储资料,如数码相机中所用的记忆卡。

  • Nor Flash:主要用来执行片上程序
      优点:具有很好的读写性能和随机访问性能,因此它先得到广泛的应用;
      缺点:单片容量较小且写入速度较慢,决定了其应用范围较窄。

  • NAND Flash:主要用在大容量存储场合
      优点:优秀的读写性能、较大的存储容量和性价比,因此在大容量存储领域得到了广泛的应用;
      缺点:不具备随机访问性能。
    nor flash数据线和地址线分开,可以实现ram一样的随机寻址功能,可以读取任何一个字节。但是擦除仍要按块来擦。
    nand flash同样是按块擦除,但是数据线和地址线复用,不能利用地址线随机寻址。读取只能按页来读取。(nandflash按块来擦除,按页来读,norflash没有页)
    由于nandflash引脚上复用,因此读取速度比nor flash慢一点,但是擦除和写入速度比nor flash快很多。nand flash内部电路更简单,因此数据密度大,体积小,成本也低。因此大容量的flash都是nand型的。小容量的2~12M的flash多是nor型的。
    使用寿命上,nand flash的擦除次数是nor的数倍。而且nand flash可以标记坏块,从而使软件跳过坏块。nor flash 一旦损坏便无法再用。因为nor flash可以进行字节寻址,所以程序可以在nor flash中运行。嵌入式系统多用一个小容量的nor flash存储引导代码,用一个大容量的nand flash存放文件系统和内核。

二、FLASH(W25Q64JV)常见操作

参考FPGA奇哥系列网课

2.1、FLASH常见操作

在这里插入图片描述

2.2、W25Q64JV芯片存储大小

在这里插入图片描述

2.3、状态寄存器:

BUSY:忙信号,写、擦除操作后自动置0,
WEL:写使能信号,写数据、擦除、写状态寄存器时需要置1.
一般只用这两,有其他需求需要看芯片手册。在这里插入图片描述

2.4、时序图

该芯片存储较大,因此不支持页擦除,最小为扇区擦除
在这里插入图片描述
写使能:SPI采用模式0或3
在这里插入图片描述
写失能:
在这里插入图片描述
读状态寄存器:0h05则返回第一段状态寄存器数值(7-0bit),0h35为第二段,0h15为第三段,第三段时QSPI才有的。
在这里插入图片描述
读数据: 读数据时候先传输指令03h,然后地址(24bit分别对应8位扇区地址、8位页地址和8位字节地址),然后FLASH返回读数据
在这里插入图片描述
页编程: 写数据之前需要先执行擦除,至少写一个byte数据,最大可写246byte,此时字节地址应该为0,若不是从0开始,当写到当前页最后一个地址时,不会跳到下一页,会返回到当前页0地址开始写起。
在这里插入图片描述
扇区擦除 给扇区擦除指令和地址即可,地址当中的页地址、字节地址没关系,只看扇区地址(本人没有验证过,但看到过这样一句话,有兴趣可以自己验证一下,验证好记得戳一戳我hhh)
在这里插入图片描述
块擦除(32KB块)
在这里插入图片描述
块擦除(64KB块)
在这里插入图片描述
全片擦除:
在这里插入图片描述
读厂商ID
在这里插入图片描述

三、程序设计框图

完整代码参考GiTHub:https://github.com/shun6-6/FLASH_spi
在这里插入图片描述

3.1、FLASH_drive模块

FLASH_drive模块与用户模块和FLASH芯片连接
用户模块输入读写操作指令,以及相应的读写数据地址和长度,FLASH_drive模块结果处理后通过SPI将数据写入FLASH或从中读取数据。
以下为接口代码:

module Flash_drive#(
    parameter                      P_DATA_WIDTH  = 8  ,
    parameter                      P_SPI_CPOL    = 0  ,
    parameter                      P_SPI_CPHL    = 0  ,
    parameter                      P_READ_DWIDTH = 8  ,
    parameter                      P_OP_LEN      = 32  
)(
    input       i_clk                                       ,
    input       i_rst                                       ,
    /*--------user接口--------*/ 
    input  [1 :0]                   i_operation_type        ,
    input  [23:0]                   i_operation_addr        ,
    input  [8 :0]                   i_operation_byte_num    ,
    input                           i_operation_valid       ,
    output                          o_operation_ready       ,
    input  [P_DATA_WIDTH - 1 : 0]   i_write_data            ,
    input                           i_write_sop             ,
    input                           i_write_eop             ,
    input                           i_write_valid           ,
    output [P_DATA_WIDTH - 1 : 0]   o_read_data             ,
    output                          o_read_sop              ,
    output                          o_read_eop              ,
    output                          o_read_valid            ,   
    /*--------spi接口--------*/
    output                          o_spi_cs                ,
    output                          o_spi_clk               ,
    output                          o_spi_mosi              ,
    input                           i_spi_miso              
);

3.2、FLASH_ctrl模块

FLASH_ctrl模块将用户操作指令以及数据进行处理,并产生相应指令驱动SPI_drive模块。
对于写数据过程:用户模块会将写指令、写地址以及写数据发送至FLASH_ctrl模块,FLASH_ctrl模块将数据存至本地FIFO,并发送写使能指令和写数据指令,并且根据SPI_drive模块的i_user_write_req信号把FIFO当中存入的用户写数据一个个取出,这是因为用户输入的的数据都是并行的8bit数据,而SPI是穿行传输的,所以需要SPI_drive串行传输完一个byte之后,向FLASH_ctrl模块发起下一个byte请求,也就是i_user_write_req信号。注:在写同一个地址的时候,一定要先擦除,再写,这是因为FLASH芯片只能把1变为0,不可以把0变为1,需要重新擦除,将芯片变为全1,才可以继续写。
对于读数据过程:用户模块输入读指令、读地址以及要读取的数据长度信息告知FLASH_ctrl模块,FLASH_ctrl模块会产生相应的读指令以控制SPI_drive模块读FLASH,并且将读到的数据先存入FIFO,最后完整的传递给用户模块。
FLASH_ctrl模块接口代码:

module Flash_ctrl#(
    parameter                       P_DATA_WIDTH  = 8       ,//数据位宽
    parameter                       P_SPI_CPOL    = 0       ,//spi时钟极性:0/1表示空闲时钟电平为0/1
    parameter                       P_SPI_CPHL    = 0       ,//spi时钟相位:0/1表示数据采集沿为时钟第1/2跳变沿
    parameter                       P_READ_DWIDTH = 8       ,//读数据位宽
    parameter                       P_OP_LEN      = 32       //操作数据长度
)(      
    input                           i_clk                   ,
    input                           i_rst                   ,
    /*--------用户接口--------*/        
    input  [1 :0]                   i_operation_type        ,//操作类型 1:read 2:write
    input  [23:0]                   i_operation_addr        ,//操作地址
    input  [8 :0]                   i_operation_byte_num    ,//max write 256 byte
    input                           i_operation_valid       ,//操作有效信号
    output                          o_operation_ready       ,//操作准备信号

    input  [P_DATA_WIDTH - 1 : 0]   i_write_data            ,//写数据
    input                           i_write_sop             ,//写数据-开始信号
    input                           i_write_eop             ,//写数据-结束信号
    input                           i_write_valid           ,//写数据有效

    output [P_DATA_WIDTH - 1 : 0]   o_read_data             ,//读数据
    output                          o_read_sop              ,//读数据-开始信号
    output                          o_read_eop              ,//读数据-结束信号
    output                          o_read_valid            ,//读数据有效
    /*--------驱动接口--------*/            
    output [P_OP_LEN - 1 : 0]       o_user_op_data          ,//操作数据(数据8+地址24)
    output [1:0]                    o_user_op_type          ,//操作类型(读写数据,读写指令)
    output [15:0]                   o_user_op_len           ,//操作数据长度(读写数据8+24,指令8)
    output [15:0]                   o_user_clk_len          ,//时钟周期,读写数据时为8+24+8*字节数
    output                          o_user_op_valid         ,//用户数据有效信号
    input                           i_user_op_ready         ,//驱动准备信号

    output [P_DATA_WIDTH - 1 : 0]   o_user_write_data       ,//写数据
    input                           i_user_write_req        ,//写数据请求

    input  [P_READ_DWIDTH - 1 : 0]  i_user_read_data        ,//读数据
    input                           i_user_read_valid        //读数据有效
    );

FLASH_ctrl模块状态机描述代码:

//FSM 
localparam  P_ST_IDLE      = 11'b00000000001,//空闲状态,握手成功后进入运行状态
            P_ST_RUN       = 11'b00000000010,//运行状态,如果是读指令则进入读数据状态,否则为擦除或者写数据指令,都需要先进入写使能状态
            P_ST_W_EN      = 11'b00000000100,//写使能状态,若为写数据指令则进入写指令状态,否则进入擦除状态
            P_ST_W_INS     = 11'b00000001000,//写数据指令状态
            P_ST_W_DATA    = 11'b00000010000,//写数据状态
            P_ST_R_INS     = 11'b00000100000,//读数据指令状态
            P_ST_R_DATA    = 11'b00001000000,//读数据状态
            P_ST_CLEAR     = 11'b00010000000,//擦除状态
            P_ST_BUSY      = 11'b00100000000,//读忙状态寄存器
            P_ST_BUSY_CHK  = 11'b01000000000,//检查返回的忙状态寄存器状态,若为忙则进入P_ST_BUSY_WAIT状态,不忙则说明读数据结束,返回空闲状态
            P_ST_BUSY_WAIT = 11'b10000000000;//读忙等待状态,计数256后再次返回P_ST_BUSY读忙状态

FLASH_ctrl模块状态机跳转代码:

always @(*)begin
    case (r_st_cur)
        P_ST_IDLE       : r_st_nxt = w_user_op_active   ? P_ST_RUN      : P_ST_IDLE  ;
        P_ST_RUN        : r_st_nxt = ri_operation_type == P_READ_TYPE ? P_ST_R_INS : P_ST_W_EN;
        P_ST_W_EN       : r_st_nxt = w_spi_op_active    ? 
                                     ri_operation_type == P_WRITE_TYPE ? P_ST_W_INS : P_ST_CLEAR
                                    : P_ST_W_EN  ;
        P_ST_W_INS      : r_st_nxt = w_spi_op_active    ? P_ST_W_DATA   : P_ST_W_INS ;
        P_ST_W_DATA     : r_st_nxt = i_user_op_ready    ? P_ST_BUSY     : P_ST_W_DATA;
        P_ST_R_INS      : r_st_nxt = w_spi_op_active    ? P_ST_R_DATA   : P_ST_R_INS ;
        P_ST_R_DATA     : r_st_nxt = i_user_op_ready    ? P_ST_BUSY     : P_ST_R_DATA;
        P_ST_CLEAR      : r_st_nxt = w_spi_op_active    ? P_ST_BUSY     : P_ST_CLEAR ;
        P_ST_BUSY       : r_st_nxt = w_spi_op_active    ? P_ST_BUSY_CHK : P_ST_BUSY  ;      
        P_ST_BUSY_CHK   : r_st_nxt = ri_user_read_valid ? 
                                     ri_user_read_data[0] ? P_ST_BUSY_WAIT : P_ST_IDLE
                                     : P_ST_BUSY_CHK;  
        P_ST_BUSY_WAIT  : r_st_nxt = r_st_cnt == 255    ? P_ST_BUSY     : P_ST_BUSY_WAIT;
        default         : r_st_nxt = P_ST_IDLE;      
    endcase
end

3.3、spi_drive模块

该模块则是按照SPI协议和FLASH的相关操作指令,将输入的指令以及数据发送给FLASH。
spi_drive模块接口代码:

module spi_drive#(
    parameter                      P_DATA_WIDTH  = 8  ,
    parameter                      P_SPI_CPOL    = 0  ,
    parameter                      P_SPI_CPHL    = 0  ,
    parameter                      P_READ_DWIDTH = 8  ,
    parameter                      P_OP_LEN      = 32  //操作数据长度
)( 
    input                          i_clk              ,
    input                          i_rst              ,
                               
    output                         o_spi_cs           ,//spi片选信号
    output                         o_spi_clk          ,//spi时钟线
    output                         o_spi_mosi         ,//spi主机输出
    input                          i_spi_miso         ,//spi主机输入
 
    input  [P_OP_LEN - 1 : 0]      i_user_op_data     ,//操作数据(数据8+地址24)
    input  [1:0]                   i_user_op_type     ,//操作类型(读写数据,读写指令)
    input  [15:0]                  i_user_op_len      ,//操作数据长度(读写数据8+24,指令8)
    input  [15:0]                  i_user_clk_len     ,//时钟周期,读写数据时为8+24+8*字节数
    input                          i_user_op_valid    ,//用户数据有效信号
    output                         o_user_op_ready    ,//主机准备信号
 
    input  [P_DATA_WIDTH - 1 : 0]  i_user_write_data  ,//写数据
    output                         o_user_write_req   ,//写数据请求
 
    output [P_READ_DWIDTH - 1 : 0] o_user_read_data   ,//读数据
    output                         o_user_read_valid   //读数据有效

    );

SPI协议的实现过程:
该FLASH芯片支持的SPI是模式0和3,波形图当中是模式0。
run运行信号在片选信号cs拉低时同步拉高,同时开启一个1bit计数器r_spi_cnt,该计数器波形与spi_clk一致,因此以此计数器可以表示当前时钟的上升沿或者下降沿,模式0是在时钟的下降沿改变数据,上升沿采样数据。
在这里插入图片描述

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顺子学不会FPGA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值