FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)

摘要

本论文使用Verilog HDL硬件描述语言,结合野火可以FPGA征途Pro开发板,实现了SPI通信协议的全擦除,扇区擦除,读数据,页写,连续写的驱动设计。在 Altera Cyclone Ⅳ 芯片上采用“自顶向下”的模块化设计思想及 Verilog HDL 硬件描述语言,设计并实现串行外设接口(SPI)。在 Quartus II 13.0 软件开发平台上编译、仿真后下载到 FPGA 芯片上,进行在线编程调试,实现了 SPI 总线通信功能。基于 FPGA 的系统设计调试维护方便、可靠性高,而且设计具有灵活性,可以方便地进行扩展和移植。
关键词:SPI;串口通信;FPGA;Verilog HDL

1 绪论

1.1研究背景

串行外设接口 (Serial peripheral interface, SPI) 是由Motorola 公司推出的一种同步串行外围设备接口。SPI总线是一种高速、同步、全双工的串行通信总线。SPI 总线接口只有四根外部接口线,结构简单、速度快、可靠性强。典型的 SPI 接口通信由四根信号线实现,分别为:
SCS:从片选信号,逻辑 “0” 为有效状态,表示被选中与主设进行数据传输。
SCLK:串行时钟线,由主设输出,从设按照此时钟进行数据的同步传输。
MOSI:数据传输线,由主设到从设。
MISO:数据传输线,由从设到主设。[1]

1.2 研究目的和意义

SPI 接口是一种全双工、三线通信的系统,是常用的工业标准同步串行接口,它允许主机处理器与各种外围设备之间的通信方式是串行通信。在 SPI 接口中,主/从机之间数据的传输需要 1 个时钟信号和 2 条数据线,所以 SPI 总线区分主机(Master)和从机(Slave)2 部分,结构框图如图 1 所示。
主机和从机之间 SPI 总线由 4 根线构成:①SCK。串行同步时钟信号,用来同步主机和从机的数据传输,
由主机控制输出,从机在 SCK 的边沿接收或发送数据。②MOSI。主机输出/从机输入线,主机在上升沿(或下
降沿)通过该信号线发送数据给从机,从机在下降沿(或上升沿)通过该信号线接收该数据。③MISO。主
机输入/从机输出线,从机在上升沿(或下降沿)通过该信号线发送数据给主机,主机在下降沿(或上升沿)
通过该信号线接收该数据。④SS。从机片选信号线,它同样是由主机控制输出。[2]

1.2.1 SPI通信协议物理层简介

FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第1张图片
(图1-2-1-1:一主一从SPI通讯设备连接图)
(Figure 1-2-1: One master and one slave SPI communication device connection diagram)
FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第2张图片
(图1-2-1:一主多从SPI通讯设备连接图)
(Figure 1-2-1-2: One master and multiple slave SPI communication device connection diagram)
SPI通信协议采用的是主从通信模式,通信双方有主从之分,根据从机的设备个数,SPI通信设备之间的连接方式可以分为一主一从和一主多从。

SPI通信协议包含1条时钟信号线、2条数据总线和1条片选信号线,时钟信号线为SCK、2条数据总线为MOSI和MISO,片选信号线为CS。它们的作用介绍如下:
1.SCK(Serial Clock) : 时钟信号线,用于同步通信数据。由通信主机产生,决定了通信的速率,不同的设备支持的最高时钟频率不同,两个设备通信时通信速率受限于低速设备。
2.MOSI(MasterOutput ,Slave Input) : 主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机从这条信号线读入主机发送的数据,数据方向由主机到从机。
3.MISO(Master Input , Slave Output) : 主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输入到主机,数据方向由从机到主机。
。CS(Chip Select) : 片选信号线,也称为CS_N。当有多个从设备与主机相连时,设备的其他数据信号线SCK、MOSI、MISO同时并联到相同的SPI总线上,即无论有多少从设备都使用这三条总线,每个从设备都有一条独立的CS_N信号线,本数据线独占主机的一个引脚,既有多少个从设备就有多少个片选信号线。I2C协议中通过设备地址来寻址,选中总线上的某个设备并与其进行通信;而SPI协议中没有设备地址,它使用CS_N信号线来寻址,当主机要选择从设备时,把该设备的CS_N信号线设置为低电平,该设备即被选中,即片选信号有效,接着主机开始与被选中的从设备进行SPI通信。所以SPI通信以CS_N信号线置为低电平为开始信号,以CS_N信号线拉高为结束信号。

1.2.2 SPI 协议层

SPI通信协议一共有4种通信模式:模式0、模式1、模式2、模式3.这四种模式分由时钟极性(CPLD,Clock Polarity)和时钟相位(CPHA,ClockPhase)来定义,其中CPOL参数规定了空闲状态(CS_N为高电平,设备未被选中时SCK时钟信号的电平状态,CPHA规定了数据采样是在SCK时钟奇数边沿还是偶数边沿。

1.2.3 设计SPI总线接口要求

设计的 SPI 总线接口完成工作有:①将主机收到的 16 位并行数据转换为串行数据,并发送给从机;
②接收来自从机的串行数据,将其转换为并行数据,通过并行端口输出;③输出从机所需要的输入信号、
时钟信号 SCK 和片选信号 SS
FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第3张图片
FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第4张图片

1.3 国内外研究现状(SPI接口的发展)

SPI 接口最早是由美国的Motorola 公司所定义的,它的中文名称叫做串行外设接口,用于 Motorola 公司自己研发的产品之中。SPI 接口可以作为通信的桥梁,连接 CPU 等控制设备和外围设备。SPI 接口能够以串行、同步、高速的特点来传输通信数据,具有简单易用、节省面积等优点,因此具有越来越广泛的用途,并且逐渐应用到其他场景,比如 SD 卡、液晶显示屏、射频通信卡等。传统的 SPI 接口可以连接控制设备和外围设备进行全双工通信,随着通信场景的需求,会引入设计半双工、单工等通信方式,会根据功能设计更多的寄存器进行控制。在规模一般的芯片设计中,可能仅简单设计 SPI 接口、满足基本通信需求即可,但是在芯片性能不断发展的今天,在设计 SPI 接口时需要考虑到 SPI 能否与其他各 IP 进行高效的通信与交互,能否在芯片 IP 复杂化的同时可以尽量复用之前的设计,能否适应芯片设计中新的功能点和需求点。SPI 接口设计好后,会进行功能点的验证。如果以传统的 Verilog 语言验证SPI,需要编写大量激励文件,以众多定向验证覆盖到功能点。如果以单纯的System Verilog 语言验证SPI,可以实现随机化的激励输入,达到高效的验证,但是验证平台环境不容易得到复用,在项目迭代中具有一定的麻烦。所以需要寻求既可以高效验证 SPI 功能点,又可以高效搭建实现验证平台环境的方式。[3]

1.4 研究内容

1.5 研究方法及技术路线

2 设计SPI-FLASH全擦除实验模块

2.1 设计SPI总线接口要求

2.2 设计SPI接口模块结构

FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第5张图片

2.3 SPI接口的子模块设计

2.3.1 通讯模块

2.3.2 控制模块

FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第6张图片

2.3.3 FIFO模块

2.3.4 数据收发模块

2.4 设计过程波形图绘制及各信号产生原因解析

FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第7张图片
FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第8张图片
FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第9张图片

FPGA实现基于SPI协议的Flash驱动控制(全擦除、页擦除、读数据、页写、连续写—地址写)_第10张图片

2.5 设计的仿真、综合与实现

module  key_filter
#(
    parameter   CNT_20MS_MAX    =   20'd999_999
)
(
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire            key_in      ,
    
    output  reg             key_flag
);
reg [19:0]  cnt_20ms;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'd0;
    else    if(key_in == 1'b1)
        cnt_20ms <= 20'd0;
    else    if(cnt_20ms == CNT_20MS_MAX && key_in == 1'b0)
        cnt_20ms <= CNT_20MS_MAX;
    else    if(key_in == 1'b0)
        cnt_20ms <= cnt_20ms + 20'd1;
    else
        cnt_20ms <= cnt_20ms;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else    if(cnt_20ms == CNT_20MS_MAX - 20'd1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

endmodule

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

module  flash_be_ctrl
(
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire            key_flag    ,
    
    output  reg             cs_n        ,
    output  reg             sck         ,
    output  reg             mosi
);

parameter   CNT_CLK_MAX     =   5'd31;
parameter   CNT_BYTE_MAX    =   3'd6;
parameter   CNT_SCK_MAX     =   2'd3;
parameter   CNT_BIT_MAX     =   3'd7;

parameter   IDLE    =   4'b0001,
            WREN    =   4'b0010,
            DELAY   =   4'b0100,
            BE      =   4'b1000;
            
parameter   WREN_IN =   8'b0000_0110;
parameter   BE_IN   =   8'b1100_0111;

reg     [3:0]   state;
reg     [4:0]   cnt_clk;
reg     [2:0]   cnt_byte;
reg     [1:0]   cnt_sck;
reg     [2:0]   cnt_bit;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state <= IDLE; 
    else    
        case(state)
            IDLE:
                if(key_flag == 1'b1)
                    state <= WREN;
                else
                    state <= IDLE;
            WREN:
                if(cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd2)
                    state <= DELAY;
                else
                    state <=WREN;
            DELAY:
                if(cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd3)
                    state <= BE;
                else
                    state <=DELAY;
            BE  :
                if(cnt_clk == CNT_CLK_MAX && cnt_byte == CNT_BYTE_MAX)
                    state <= IDLE;
                else
                    state <=BE;
            default:state <= IDLE;
        endcase
            
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk <= 5'd0;
    else    
        case(state)
            IDLE :cnt_clk <= 5'd0;
            WREN :cnt_clk <= cnt_clk + 5'd1;//溢出清零
            DELAY:cnt_clk <= cnt_clk + 5'd1;//溢出清零
            BE   :cnt_clk <= cnt_clk + 5'd1;//溢出清零
            default:cnt_clk <= cnt_clk;
        endcase

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_byte <= 3'd0;
    else    if(cnt_clk == CNT_CLK_MAX && cnt_byte == CNT_BYTE_MAX)
        cnt_byte <= 3'd0;
    else    if(cnt_clk == CNT_CLK_MAX)
        cnt_byte <= cnt_byte + 3'd1;
    else
        cnt_byte <= cnt_byte;
        
always@(posedge sys_clk or negedge sys_rst_n)//不同
    if(sys_rst_n == 1'b0)
        cnt_sck <= 2'd0;
    else    if((cnt_byte == 3'd1 || cnt_byte == 3'd5) && (cnt_sck == CNT_SCK_MAX))
        cnt_sck <= 2'd0;
    else    if(cnt_byte == 3'd1 || cnt_byte == 3'd5)
        cnt_sck <= cnt_sck + 2'd1;
    else
        cnt_sck <= 2'd0;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <= 3'd0;
    else    if(cnt_sck == 2'd2 && cnt_bit == CNT_BIT_MAX)
        cnt_bit <= 3'd0;
    else    if(cnt_sck == 2'd2)
        cnt_bit <= cnt_bit + 3'd1;
    else
        cnt_bit <= cnt_bit;
        
/* always@(*)  
    case(state)
        IDLE :cs_n <= 1'b1;
        WREN :cs_n <= 1'b0;
        DELAY:cs_n <= 1'b1;
        BE   :cs_n <= 1'b0;
        default:cs_n <= 1'b1;
    endcase */
    
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cs_n <= 1'b1;
    else    if(key_flag == 1'b1)
        cs_n <= 1'b0;
    else    if(state == WREN && cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd2)
        cs_n <= 1'b1;
    else    if(state == DELAY && cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd3)
        cs_n <= 1'b0;
    else    if(state == BE    && cnt_clk == CNT_CLK_MAX && cnt_byte == 3'd6)
        cs_n <= 1'b1;
    else
        cs_n <= cs_n;

always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sck <= 1'b0;
    else    if(cnt_sck == 2'd0)
        sck <= 1'b0;
    else    if(cnt_sck == 2'd2)
        sck <= 1'b1;
    else    
        sck <= sck;
        
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        mosi <= 1'b0;
    else    if(state == WREN && cnt_byte == 3'd2)
        mosi <= 1'b0;
    else    if(state == BE && cnt_byte == 3'd6)
        mosi <= 1'b0;
    else    if(state == WREN && cnt_byte == 3'd1 && cnt_sck == 2'd0)
        mosi <= WREN_IN[7 - cnt_bit];
    else    if(state == BE && cnt_byte == 3'd5 && cnt_sck == 2'd0)
        mosi <= BE_IN[7 - cnt_bit];
    else
        mosi <= mosi;
        
endmodule
module  spi_flash_be
(
    input   wire            sys_clk     ,
    input   wire            sys_rst_n   ,
    input   wire            key_in      ,
    
    output  wire            cs_n        ,
    output  wire            sck         ,
    output  wire            mosi        
);

wire            key_flag;

key_filter
#(
    .CNT_20MS_MAX(20'd999_999)
)
key_filter_inst
(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .key_in      (key_in   ),

    .key_flag    (key_flag )
);

flash_be_ctrl   flash_be_ctrl_inst
(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .key_flag    (key_flag ),

    .cs_n        (cs_n     ),
    .sck         (sck      ),
    .mosi        (mosi     )
);

endmodule
`timescale  1ns/1ns 
module  tb_flash_be_ctrl();

reg             sys_clk     ;
reg             sys_rst_n   ;
reg             key_flag    ;
                            
wire            cs_n        ;
wire            sck         ;
wire            mosi        ;

initial
    begin
        sys_clk <= 1'b1;
        sys_rst_n <= 1'b0;
        key_flag <= 1'b0;
        #20
        sys_rst_n <= 1'b1;
        #200
        key_flag <= 1'b1;
        #20
        key_flag <= 1'b0;
    end
    
always  #10 sys_clk <= ~sys_clk;

defparam memory.mem_access.initfile = "initmemory.txt";

flash_be_ctrl   flash_be_ctrl_inst
(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .key_flag    (key_flag ),

    .cs_n        (cs_n     ),
    .sck         (sck      ),
    .mosi        (mosi     )
);

m25p16 memory 
(
    .c          (sck  ), 
    .data_in    (mosi ), 
    .s          (cs_n ), 
    .w          (1'b1 ), 
    .hold       (1'b1 ), 
    .data_out   (     )
); 

endmodule

2.5 模块小结

参考文献

[1]蒋国庆,顾军.基于FPGA的LPC总线转多路SPI总线设计[J].电子质量,2022(10):39-45.
[2]杨梓鹤,彭秋雨,李湛艺,程晓迪.SPI接口仿真设计与实现[J].科技与创新,2022(19):121-123+126.DOI:10.15913/j.cnki.kjycx.2022.19.038.
[3]王大为. 基于UVM的SPI接口IP核的设计与验证[D].北方工业学,2022.DOI:10.26926/d.cnki.gbfgu.2022.000608.

你可能感兴趣的:(fpga开发)