详解基于SPI协议实现Flash读写

详解基于SPI协议实现Flash读写

  • 一、SPI协议
    • 1、协议简介
    • 2、SPI信号线
    • 3、SPI数据收发
    • 4、SPI工作模式【四种】
    • 5、优缺点
  • 二、Flash闪存
    • 1、芯片【M25P16】简介
    • 2、相关时序
  • 三、项目概述
    • 1、功能需求
    • 2、流程分析
    • 3、系统架构
    • 4、模块状态设计
      • 1、SPI
      • 2、读取数据
      • 3、写数据
    • 5、源码
      • 1、SPI接口
      • 2、顶层
      • 3、flash控制
      • 4、flash_read
      • 5、flash_write
      • 6、数码管驱动
    • 6、功能验证
      • 1、仿真
      • 2、上板验证
  • 四、参考资料

一、SPI协议

1、协议简介

  • SPI(Serial Peripheral interface)是由摩托罗拉公司定义的一种串行外围设备接口, 是一种全双工、同步的通信总线,只需要四根信号线即可,节约引脚,同时有利于 PCB 的布局。正是出于这种简单易用的特性,现在越来越多的芯片集成了 SPI 通信协议,如 FLASH、AD 转换器等。
  • 一种高速的、全双工、同步的通信总线;
  • SPI分为主、从两种模式,一个SPI通讯系统需要包含一个【且只能是一个】主设备,一个或多个从设备。提供时钟的为主设备【master】,接收时钟的设备为从设备【slave】,SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

2、SPI信号线

SPI接口一般使用四条信号线通信:
【SDI:(数据输入);SDO:(数据输出);SCK:(时钟);CS:(片选)】

  • MISO主设备输入/从设备输出引脚;该引脚在从模式下发送数据,在主模式下接收数据。
  • MOSI主设备输出/从设备输入引脚;该引脚在主模式下发送数据,从模式下接收数据;
  • SCLK串行时钟信号,由主设备产生;
  • CS/SS从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定的从设备通讯,避免数据线上的冲突。

详解基于SPI协议实现Flash读写_第1张图片
详解基于SPI协议实现Flash读写_第2张图片

3、SPI数据收发

SPI主机和从机都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。

  • 首先拉低片选信号,表示与该设备进行通信;
  • 主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
    这里要注意,SCLK时钟信号可能是低电平有效,也可能是高电平有效,因为SPI有四种模式。
  • 主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
  • 从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

详解基于SPI协议实现Flash读写_第3张图片

SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。【收发同步】

4、SPI工作模式【四种】

原理图
详解基于SPI协议实现Flash读写_第4张图片

CPOL配置SPI总线的极性
CPHA配置SPI总线的相位

SPI总线极性【CPOL】
CPOL = 1:表示空闲时是高电平;发起通信后的第一个时钟沿为下降沿
详解基于SPI协议实现Flash读写_第5张图片
CPOL = 0:表示空闲时是低电平;发起通信后的第一个时钟沿为上升沿
详解基于SPI协议实现Flash读写_第6张图片

数据传输往往是从跳变沿开始的,也就表示开始传输数据的时候,是下降沿还是上升沿。

SPI总线相位【CPHA】
CPHA = 0:【表示从第一个跳变沿开始采样】
详解基于SPI协议实现Flash读写_第7张图片
CPHA = 1:【表示从第二个跳变沿开始采样】
详解基于SPI协议实现Flash读写_第8张图片
模式0 (CPOL=0; CPHA=0)

CPOL = 0:空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 0:数据在第1个跳变沿(上升沿)【前沿】采样

详解基于SPI协议实现Flash读写_第9张图片
模式1 (CPOL=0; CPHA=1)

CPOL = 0:空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 1:数据在第2个跳变沿(下降沿)采样

详解基于SPI协议实现Flash读写_第10张图片
模式2 (CPOL=1; CPHA=0)

CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 0:数据在第1个跳变沿(下降沿)采样

详解基于SPI协议实现Flash读写_第11张图片
模式3 (CPOL=1; CPHA=1)

CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 1:数据在第2个跳变沿(上升沿)采样

详解基于SPI协议实现Flash读写_第12张图片
四种模式表:

模式 CPOL【极性】 CPHA【相位】 描述
0 0 0 空闲时为低电平,时钟前沿采样【上升沿或第一个跳变沿】
1 0 1 空闲时为低电平,时钟后沿采样【下降沿或第二个跳变沿】
2 1 0 空闲时为高电平,时钟前沿采样【下降沿或第一个跳变沿】
3 1 1 空闲时为高电平,时钟后沿采样【上升沿或第二个跳变沿】

5、优缺点

优点

  • 全双工串行通信;
  • 高速数据传输速率;
  • 简单的软件配置;
  • 极其灵活的数据传输,不限于8位,它可以是任意大小的字;
  • 非常简单的硬件结构,从站不需要唯一地址【与I2C不同】,从机使用主机时钟,不需要精密时钟振荡器/晶振【与uart不同】,不需要收发器【与CAN】不同。

缺点

  • 没有硬件从机应答信号【从机可能在不知情的情况下无处发送】;
  • 通常仅支持一个主设备;
  • 需要更多的引脚【与I2C不同】;
  • 没有定义硬件级别的错误检查协议;
  • 与RS-232和CAN总线相比,只能支持非常短的距离。

二、Flash闪存

  • FLASH是存储芯片的一种,通过特定的程序可以修改里面的数据。
  • FLASH在电子以及半导体领域内往往表示Flash Memory的意思,即平时所说的“闪存”,全名叫Flash EEPROM Memory。
  • FLASH存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM的优势),使数据不会因为断电而丢失。

1、芯片【M25P16】简介

逻辑图
详解基于SPI协议实现Flash读写_第13张图片
信号说明

信号名称 英文 描述
C Serial Clock 串行时钟
D Serial Data Input 串行数据输入
Q Serial Data Output 串行数据输出
S_N Chip Select 片选
W_N Write Protect 写保护
HOLD_N Hold 保持
VCC Supply Voltage 电源电压
VSS Ground 接地

信号作用:

  • 在串行时钟的下降沿,输入的数据data被串行地移出;
  • 输入数据可以是指令、地址或数据,在串行时钟的上升沿被锁存;
  • 串行时钟为接口提供时序,指令、地址或数据在时钟上升沿被锁存,在时钟下降沿之后,输出数据Q改变;
  • 片选信号S为高时,flash未被选中,数据输出端Q为高阻态,除非内部
  • 程序擦除或写状态寄存器时,其处于待机模式,片选信号S为低时,flash
    处于激活状态。
  • 保持信号用于在片选信号S为低时,取消flash与其他任何设备之间的通信
  • 写保护信号的主要目的是冻结受程序或删除指令保护的内存区域

SPI 总线上的总线主设备和存储设备
详解基于SPI协议实现Flash读写_第14张图片

写保护 (W) 和保持 (HOLD) 信号应被驱动为适当的高或低。

2、相关时序

读取标识(RDID)指令序列和数据输出序列
详解基于SPI协议实现Flash读写_第15张图片
写入禁用(WRDI)指令顺序
详解基于SPI协议实现Flash读写_第16张图片
写入启用(WREN)指令顺序
详解基于SPI协议实现Flash读写_第17张图片
读取状态寄存器(RDSR)指令序列和数据输出序列
详解基于SPI协议实现Flash读写_第18张图片
写入状态寄存器(WRSR)指令顺序
详解基于SPI协议实现Flash读写_第19张图片
读取数据字节(READ)指令序列和数据输出序列
详解基于SPI协议实现Flash读写_第20张图片
快速读指令序列和数据输出序列
详解基于SPI协议实现Flash读写_第21张图片
页编程指令
详解基于SPI协议实现Flash读写_第22张图片
扇区擦除指令【SE】
详解基于SPI协议实现Flash读写_第23张图片
批量擦除(BE)指令顺序
详解基于SPI协议实现Flash读写_第24张图片
深度掉电模式【DP】
详解基于SPI协议实现Flash读写_第25张图片
释放深度掉电模式【RES】
详解基于SPI协议实现Flash读写_第26张图片

三、项目概述

1、功能需求

  1. 使用按键模拟读写请求信号;
  2. 收到读写请求信号时,FPGA 向 M25P16 芯片写入单字节数据、或者从
    M25P16 芯片读出单字节数据、或者读出器件 ID;
  3. 把写入或者读出的数据显示在数码管,并指示当前是读数据操作、写数据
    操作。

2、流程分析

①读器件id操作;②读数据操作;③写数据操作。
写操作包含 4 种操作:读状态寄存器,写使能、扇区擦除,页编程;每个操作又包括了指令、地址或数据组合,并且在写使能、扇区擦除和页编程操作之后还需要延时。

  • 读ID操作 读ID 读ID指令 地址高字节 地址中字节 地址低字节
  • 读数据操作 读数据 读数据指令 地址高字节 地址中字节 地址低字节 接收数据
  • 写数据操作

读状态寄存器 读状态寄存器指令 接收数据
写使能 写使能指令 延时
扇区擦除 扇区擦除指令 地址高字节 地址中字节 地址低字节
延时
页编程 页编程指令 地址高字节 地址中字节 地址低字节发送数据 延时

3、系统架构

系统框图
详解基于SPI协议实现Flash读写_第27张图片

  • 按键消抖【key_debounce】:消除按键抖动,避免毛刺,为flash控制模块提供模拟读写信号的按键输入;
  • 数码管驱动【seg_driver】:显示flash读写数据以及读写状态;
  • spi接口【spi_interface】:产生符合spi协议的串行时钟(时序),将数据串行输出给flash或者从flash串行读取数据;
  • flash读【flash_read】:读取数据
  • flash写【flash_write】:向flash写数据;
  • flash控制【flash_ctrl】:控制flash读写,按键模拟读写请求。

RTL
详解基于SPI协议实现Flash读写_第28张图片
flash_crtl
详解基于SPI协议实现Flash读写_第29张图片

4、模块状态设计

1、SPI

时序图【见上SPI工作模式【3】】
详解基于SPI协议实现Flash读写_第30张图片
状态划分:

  • 初始状态【IDLE】
  • 准备状态【REDY】:用于数据采样缓冲,模式三为时钟上升沿采样数据;
  • 传输状态【TRAN】:时钟初始为高电平,下降沿开始数据传输。

状态跳转图
详解基于SPI协议实现Flash读写_第31张图片
状态定义

//spi采样发送数据状态参数定义【同步通信 模式三】
    localparam IDLE = 3'b001,//初始状态 高电平
               REDY = 3'b010,//采样缓冲准备状态
               TRAN = 3'b100;//传输状态

2、读取数据

时序图
详解基于SPI协议实现Flash读写_第32张图片
状态划分

  • 初始状态【IDLE】
  • 指令【RD_CMD】:发送读数据指令;
  • 地址【RD_ADDR】:读取地址
  • 数据【RD_DATA】:读取数据

状态转移图
详解基于SPI协议实现Flash读写_第33张图片
状态定义

localparam    IDLE    = 4'b0001,//初始状态 
              RD_CMD  = 4'b0010,//发读命令
              RD_ADDR = 4'b0100,//读地址
              RD_DATA = 4'b1000;//读数据

3、写数据

划分为主从状态机两个部分。主状态机进行操作【使能、扇区擦除、页编程】,从状态机给指令【指令、地址、数据】。这里就不补状态转移图了。
状态参数定义

//状态机状态参数定义 【Master 主 Slave 从】
  localparam M_IDLE  = 6'b000_001,//初始状态
             M_WREN0 = 6'b000_010,//扇区擦除之前使能
             M_WRSE  = 6'b000_100,//扇区擦除
             M_WAIT  = 6'b001_000,//等待延时
             M_WREN1 = 6'b010_000,//页编程之前使能
             M_WRPP  = 6'b100_000;//页编程

    localparam S_IDLE    = 4'b0001,//初始状态
               S_WR_CMD  = 4'b0010,//写命令
               S_WR_ADDR = 4'b0100,//写地址
               S_WR_DATA = 4'b1000;//写数据

状态转移

    wire                 m_idle2m_wren0     ;
    wire                 m_wren02m_wrse     ;
    wire                 m_wrse2m_wait      ;
    wire                 m_wait2m_wren1     ;
    wire                 m_wren12m_wrpp     ;
    wire                 m_wrpp2m_idle      ;

    wire                 s_idle2s_wr_cmd    ;
    wire                 s_wr_cmd2s_idle    ;
    wire                 s_wr_cmd2s_wr_addr ;
    wire                 s_wr_addr2s_idle   ;
    wire                 s_wr_addr2s_wr_data;
    wire                 s_wr_data2s_idle   ;

5、源码

1、SPI接口

产生串行时钟sclk,收发数据

 //计数器设计 cnt_bit cnt_sclk
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          cnt_sclk <= 0;
        end
        else if(add_cnt_sclk)begin
          if(end_cnt_sclk)begin
          cnt_sclk <= 0;
          end
          else begin
          cnt_sclk <= cnt_sclk + 1;
          end
        end
    end
    assign add_cnt_sclk = state_c == TRAN;//传输状态 开始计时
    assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == SCLK_CYCLE-1;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          cnt_bit <= 0;
        end
        else if(add_cnt_bit)begin
          if(end_cnt_bit)begin
          cnt_bit <= 0;
          end
          else begin
          cnt_bit <= cnt_bit + 1;
          end
        end
    end
    assign add_cnt_bit = end_cnt_sclk;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == 8-1;

    //sclk_s
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          sclk_s <= 1'b1;//模式三 初始状态为高电平
        end
        else if(add_cnt_sclk && cnt_sclk == SCLK_NEDGE-1)begin
          sclk_s <= 1'b0;//串行时钟下降沿
        end
        else if(add_cnt_sclk && cnt_sclk == SCLK_PEDGE-1)begin
          sclk_s <= 1'b1;//串行时钟上升沿
        end
    end
    assign sclk = sclk_s;

    //采样数据 pick_data
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          pick_data <= 0;
        end
        else if(cnt_sclk == SCLK_PEDGE)begin
          pick_data[7-cnt_bit]<= miso;//采样数据寄存输入
        end
    end
    assign dout = pick_data;

    //数据发送 send_data
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          send_data <= 0;
        end
        else if(state_c == REDY)begin
          send_data <= din;//发送输入数据 等到串行时钟上升沿进行采样输出
        end
    end
    assign mosi = send_data[7-cnt_bit];//MSB 高位在前 循环左移

    //cs_n 片选
    assign cs_n = !req;

    //done 数据传输完成标志
    assign done = tran2idle;

2、顶层

module spi_flash #(parameter KEY_W = 2)(

    input                       clk     ,//系统时钟 50Mhz
    input                       rst_n   ,//系统复位 低电平有效
    input         [KEY_W-1:0]   key_in  ,//按键输入 模拟写请求
    //SPI接口
    output                      mosi    ,//主入从出
    output                      sclk    ,//串行时钟
    output                      cs_n    ,//片选
    input                       miso    ,//主出从入
    //数码管
    output        [1:0]         sel     ,//数码管位选
    output        [7:0]         dig      //数码管段选
);

    //中间信号定义
    wire         [KEY_W-1:0]       key_out       ;//按键检测输出
    wire         [7:0]             disp_data     ;
    wire                           disp_data_vld ;
    wire         [7:0]             trans_data    ;
    wire         [7:0]             rd_data       ;
    wire                           req           ;
    wire                           done          ;

    //模块例化
    //按键消抖
    key_debounce    #(.KEY_W(2)) u_key_debounce
    (
        .clk                  ( clk          ),
        .rst_n                ( rst_n        ),
        .key_in               ( key_in       ),
        .key_out              ( key_out      )
    );

    //数码管驱动
    seg_driver  u_seg_driver
    (
        .clk                  ( clk             ),
        .rst_n                ( rst_n           ),
        .din                  ( disp_data       ),
        .din_vld              ( disp_data_vld   ),
        .sel                  ( sel             ),
        .dig                  ( dig             )
    );

    //flash 控制模块
    flash_ctrl u_flash_ctrl
    (
         .clk                (  clk             ),
         .rst_n              (  rst_n           ),
         .key                (  key_out         ),
         .done               (  done            ),
         .din                (  rd_data         ),
         .dout               (  trans_data      ),
         .req                (  req             ),
         .disp_data          (  disp_data       ),
         .disp_data_vld      (  disp_data_vld   )
    );

    //spi接口
    spi_interface  u_spi_interface
    (
        .clk                 ( clk             ),
        .rst_n               ( rst_n           ),
        .din                 ( trans_data      ),
        .req                 ( req             ),
        .done                ( done            ),
        .dout                ( rd_data         ),
        .miso                ( miso            ),
        .cs_n                ( cs_n            ),
        .mosi                ( mosi            ),
        .sclk                ( sclk            )
    );

endmodule

3、flash控制

//flash控制模块 控制flash读写
//例化读写模块 控制读写
module flash_ctrl(

    input                         clk           ,//系统时钟
    input                         rst_n         ,//复位
    //按键 
    input          [1:0]          key           ,//按键 控制flash读写
    //spi_interface
    input                         done          ,//传输完成标志
    input           [7:0]         din           ,//data in
    output          [7:0]         dout          ,//数据输出
    output                        req           ,//输出请求信号
    //seg数码管显示数据
    output          [7:0]         disp_data     ,//数码管显示数据
    output                        disp_data_vld  //显示数据有效
);

    //中间信号定义
    wire          [7:0]         rd_dout  ;
    wire                        rd_done  ;
    wire          [7:0]         wr_dout  ;
    wire                        wr_done  ;
    wire                        rd_req   ;
    wire                        wr_req   ;
    wire          [7:0]         wr_din   ;

    //模块例化
    //flash读模块 flash_read
    flash_read      u_flash_read
    (
        .clk                ( clk             ),
        .rst_n              ( rst_n           ),
        .din                ( din             ),
        .done               ( rd_done         ),
        .rd_flag            ( key[0]          ),
        .dout               ( rd_dout         ),
        .disp_data          ( disp_data       ),
        .disp_data_vld      ( disp_data_vld   ),
        .rd_req             ( rd_req          )
    );

    //flash写模块 flash_write
    flash_write    u_flash_write
    (
        .clk                ( clk              ),
        .rst_n              ( rst_n            ),
        .done               ( wr_done          ),
        .wr_flag            ( key[1]           ),
        .dout               ( wr_dout          ),
        .wr_req             ( wr_req           )
    );
    assign req = rd_req | wr_req;
    assign dout = rd_req?rd_dout:wr_dout;
    assign wr_done = done;
	assign rd_done = done;

endmodule

4、flash_read

 //计数器 cnt_byte
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         cnt_byte <= 0;
       end
       else if(add_cnt_byte)begin
         if(end_cnt_byte)begin
         cnt_byte <= 0;
         end
         else begin
         cnt_byte <= cnt_byte + 1;
         end
       end
   end
   assign add_cnt_byte = (state_c != IDLE) && (done);//非初始状态且字节传输完成 开始计数
   assign end_cnt_byte = add_cnt_byte && cnt_byte == X-1;

   //字节寄存 组合逻辑
   always @(*) begin
       if(state_c == RD_CMD)begin
         X = 1;
       end
       else if(state_c == RD_ADDR)begin
         X = 3;
       end
       else if(state_c == RD_DATA)begin
         X = 1;
       end
       else begin
         X = 0;
       end
   end

   //读请求寄存 rd_req_r
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         rd_req_r <= 1'b0;
       end
       else if(idle2rd_cmd)begin//开始发送数据命令 拉高请求信号
         rd_req_r <= 1'b1;
       end
       else if(rd_data2idle)begin//读取数据完成 拉低请求信号
         rd_req_r <= 1'b0;
       end
   end
   assign rd_req = rd_req_r;

   //dout_r
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         dout_r <= 0;
       end
       else if(idle2rd_cmd)begin
         dout_r <= 8'h03;//发送读数据命令
       end
       else if(rd_cmd2rd_addr)begin
         dout_r <= 0;
       end
   end
   assign dout = dout_r;

   //din_r
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         din_r <= 0;
       end
       else if(rd_data2idle)begin
         din_r <= din;
       end
   end

   //disp_data disp_data_vld
   assign disp_data = din;
   assign disp_data_vld = rd_data2idle;

5、flash_write

//计数器设计 cnt_wait cnt_100ns cnt_key cnt_byte
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          cnt_key <= 0;
        end
        else if(add_cnt_key)begin
          if(end_cnt_key)begin
          cnt_key <= 0;
          end
          else begin
          cnt_key <= cnt_key + 1;
          end
        end
    end
    assign add_cnt_key = wr_flag;
    assign end_cnt_key = add_cnt_key && cnt_key == 256-1;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
        cnt_wait <= 0;
        end
        else if(add_cnt_wait)begin
         if(end_cnt_wait)begin
         cnt_wait <= 0;
         end
         else begin
         cnt_wait <= cnt_wait + 1;
         end
        end
    end
    assign add_cnt_wait = (m_state_c == M_WAIT);//等待状态下 开始计数
    assign end_cnt_wait = add_cnt_wait && cnt_wait == TIME_3S-1;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          cnt_100ns <= 0;
        end
        else if(add_100ns)begin
          if(end_100ns)begin
            cnt_100ns <= 0;
            end
            else begin
            cnt_100ns <= cnt_100ns + 1;
          end
        end
    end
    assign add_100ns = delay_flag;
    assign end_100ns = add_100ns && cnt_100ns == 5-1;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          cnt_byte <= 0;
        end
        else if(add_cnt_byte)begin
          if(end_cnt_byte)begin
          cnt_byte <= 0;
          end
          else begin
          cnt_byte <= cnt_byte + 1;
          end
        end
    end
    assign add_cnt_byte = (s_state_c != S_IDLE) && done;//处于发送指令状态且发送完成
    assign end_cnt_byte = add_cnt_byte && cnt_byte == XX-1;

    //字节数 XX
    always @(*) begin
        if(s_state_c == S_WR_CMD)begin
          XX = 1;
        end
        else if(s_state_c == S_WR_ADDR)begin
          XX = 3;
        end
        else if(s_state_c == S_WR_DATA)begin
          XX = 1;
        end
        else begin
          XX = 0;
        end
    end

reg   wr_flag_r;
     always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            wr_flag_r <= 1'b0;
        end
        else begin
            wr_flag_r <= wr_flag;
        end
    end

    //取消片选延时 delay_flag 
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          delay_flag <= 1'b1;
        end
        else if(s_wr_cmd2s_idle | s_wr_addr2s_idle | s_wr_data2s_idle)begin
          delay_flag <= 1'b1;
        end
        else if(s_idle2s_wr_cmd)begin
          delay_flag <= 1'b0;
        end
    end
    
    //wr_req_r
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          wr_req_r <= 1'b0;
        end
        else if(s_idle2s_wr_cmd)begin
          wr_req_r <= 1'b1;
        end
        else if(s_wr_cmd2s_idle | s_wr_addr2s_idle | s_wr_data2s_idle)begin
          wr_req_r <= 1'b0;
        end
    end
    assign wr_req = wr_req_r;

    //dout_r
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          dout_r <= 0;
        end
        else if((m_state_c == M_WREN0) | (m_state_c == M_WREN1))begin//发送使能命令
          dout_r <= 8'h06;
        end
        else if(m_state_c == M_WRSE)begin
          if(s_state_c == S_WR_CMD)begin//擦除
          dout_r <= 8'hd8;
          end
          else if(s_state_c == S_WR_ADDR)begin
          dout_r <= 0;
        end
    end
    else if(m_state_c == M_WRPP)begin
      if(s_state_c == S_WR_CMD)begin//页编程
      dout_r <= 8'h02;
      end
      else if(s_state_c == S_WR_ADDR)begin
      dout_r <= 0;
      end
      else if(s_state_c == S_WR_DATA)begin
        dout_r <= cnt_key;
      end
      end
    end
    assign dout = dout_r;

6、数码管驱动

//数码管驱动
`include "number.v"
module seg_driver(

    input                  clk     ,//系统时钟
    input                  rst_n   ,//复位
    input          [7:0]   din     ,//data in
    input                  din_vld ,
    output      reg[1:0]   sel     ,//数码管位选
    output      reg[7:0]   dig      //数码管段选
);

   //数码管刷新参数定义
   parameter TIME_REFRE = 25_000;

   //信号定义
   reg         [19:0]        cnt_refre        ;//数码管刷新计数器
   wire                      add_cnt_refre    ;
   wire                      end_cnt_refre    ;

   reg         [3:0]         data             ;//数码管位选数字显示
   reg         [7:0]         disp_num         ;//显示数字

   //计数器设计 cnt_refre
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         cnt_refre <= 0;
       end
       else if(add_cnt_refre)begin
         if(end_cnt_refre)begin
         cnt_refre <= 0;
         end
         else begin
         cnt_refre <= cnt_refre + 1;
         end
       end
   end
   assign add_cnt_refre = 1'b1;
   assign end_cnt_refre = add_cnt_refre && cnt_refre == TIME_REFRE-1;

   //disp_num
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         disp_num <= 0;
       end
       else if(din_vld)begin
         disp_num <= din;
       end
   end

   //sel
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         sel <= 2'b10;
       end
       else if(end_cnt_refre)begin
         sel <= ~sel;
       end
   end

   //data
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         data <= 0;
       end
       else begin
         case(sel)
         2'b01:data <= disp_num[3:0];
         2'b10:data <= disp_num[7:4];
         default:data <= disp_num[3:0];
         endcase
       end
   end

   //dig
   always @(posedge clk or negedge rst_n) begin
       if(!rst_n)begin
         dig <= 8'hff;
       end
        else begin
        case(data)
            4'h0:dig <= `ZERO    ;
            4'h1:dig <= `ONE     ;
            4'h2:dig <= `TWO     ;
            4'h3:dig <= `THREE   ;
            4'h4:dig <= `FOUR    ;
            4'h5:dig <= `FIVE    ;
            4'h6:dig <= `SIX     ;
            4'h7:dig <= `SEVEN   ;
            4'h8:dig <= `EIGHT   ;
            4'h9:dig <= `NINE    ;
            4'ha:dig <= `A       ;
            4'hb:dig <= `B       ;
            4'hc:dig <= `C       ;
            4'hd:dig <= `D       ;
            4'he:dig <= `E       ;
            4'hf:dig <= `F       ;
            default: dig <= 8'hff;
        endcase
        end
   end

endmodule

6、功能验证

1、仿真

flash_read
详解基于SPI协议实现Flash读写_第34张图片
flash_write
详解基于SPI协议实现Flash读写_第35张图片
spi_interface
详解基于SPI协议实现Flash读写_第36张图片

2、上板验证

初始
详解基于SPI协议实现Flash读写_第37张图片
扇区擦除
详解基于SPI协议实现Flash读写_第38张图片
写入数据
详解基于SPI协议实现Flash读写_第39张图片

四、参考资料

SPI总线工作模式
SPI总线传输的4种模式

你可能感兴趣的:(FPGA,spi,verilog,flash)