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

一、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从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定的从设备通讯,避免数据线上的冲突。

在这里插入图片描述
在这里插入图片描述

3、SPI数据收发

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

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

在这里插入图片描述

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

4、SPI工作模式【四种】

原理图
在这里插入图片描述

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

SPI总线极性【CPOL】
CPOL = 1:表示空闲时是高电平;发起通信后的第一个时钟沿为下降沿
在这里插入图片描述
CPOL = 0:表示空闲时是低电平;发起通信后的第一个时钟沿为上升沿
在这里插入图片描述

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

SPI总线相位【CPHA】
CPHA = 0:【表示从第一个跳变沿开始采样】
在这里插入图片描述
CPHA = 1:【表示从第二个跳变沿开始采样】
在这里插入图片描述
模式0 (CPOL=0; CPHA=0)

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

在这里插入图片描述
模式1 (CPOL=0; CPHA=1)

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

在这里插入图片描述
模式2 (CPOL=1; CPHA=0)

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

在这里插入图片描述
模式3 (CPOL=1; CPHA=1)

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

在这里插入图片描述
四种模式表:

模式 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】简介

逻辑图
在这里插入图片描述
信号说明

信号名称 英文 描述
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 总线上的总线主设备和存储设备
在这里插入图片描述

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

2、相关时序

读取标识(RDID)指令序列和数据输出序列
在这里插入图片描述
写入禁用(WRDI)指令顺序
在这里插入图片描述
写入启用(WREN)指令顺序
在这里插入图片描述
读取状态寄存器(RDSR)指令序列和数据输出序列
在这里插入图片描述
写入状态寄存器(WRSR)指令顺序
在这里插入图片描述
读取数据字节(READ)指令序列和数据输出序列
在这里插入图片描述
快速读指令序列和数据输出序列
在这里插入图片描述
页编程指令
在这里插入图片描述
扇区擦除指令【SE】
在这里插入图片描述
批量擦除(BE)指令顺序
在这里插入图片描述
深度掉电模式【DP】
在这里插入图片描述
释放深度掉电模式【RES】
在这里插入图片描述

三、项目概述

1、功能需求

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

2、流程分析

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

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

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

3、系统架构

系统框图
在这里插入图片描述

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

RTL
在这里插入图片描述
flash_crtl
在这里插入图片描述

4、模块状态设计

1、SPI

时序图【见上SPI工作模式【3】】
在这里插入图片描述
状态划分:

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

状态跳转图
在这里插入图片描述
状态定义

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

2、读取数据

时序图
在这里插入图片描述
状态划分

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

状态转移图
在这里插入图片描述
状态定义

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
在这里插入图片描述
flash_write
在这里插入图片描述
spi_interface
在这里插入图片描述

2、上板验证

初始
在这里插入图片描述
扇区擦除
在这里插入图片描述
写入数据
在这里插入图片描述

四、参考资料

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

猜你喜欢

转载自blog.csdn.net/QWERTYzxw/article/details/121642204