基于FPGA的SPI读写M25P16 Flash芯片

本项目所用FPGA芯片型号为:EP4CE6F17C8
厂家:Altera
Flash芯片型号:M25P16
厂家:海力士

一、SPI协议简介

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串行扩展接口,是一种高速、全双工、同步通信总线,所以可以在同一时间发送和接收数据,SPI是一种事实标准因此并没有定义速度限制,本次项目所用的Flash芯片支持的最高速率达到了50Mb/s(页编程,扇区擦除等操作)。同时SPI协议支持一主多从,采用片选信号选择从机,具体的主从机制会在后文介绍。

SPI 设备间的数据传输之所以又被称为数据交换,是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”。在每个 Clock 周期内,SPI 设备都会发送并接收一个 bit 大小的数据(不管主设备好还是从设备),相当于该设备有一个 bit 大小的数据被交换了。一个 Slave 设备要想能够接收到 Master 发过来的控制信号,必须在此之前能够被 Master 设备进行访问 (Access)。所以,Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。 在数据传输的过程中,每次接收到的数据必须在下一次数据传输之前被采样。如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致 SPI 物理模块最终失效。因此,在程序中一般都会在 SPI 传输完数据后,去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的(虽然发送后紧接着的读取是无意义的,但仍然需要从寄存器中读出来)。这一点后续会在代码的滤除无效数据以及发送指令后发送无效数据占用CS片选信号线中体现出来。

1.1 SPI引脚

SPI协议共有四根线,分别为:SCLK(时钟线)、MISO(主机输入,从机输出)、MOSI(主机输出、从机输入)、CS(片选信号)。
在这里插入图片描述

  • SCLK:主要的作用是 Master(主)设备往 Slave(从)设备传输时钟信号, 控制数据交换的时机以及速率
  • CS:用于 Master(主)设备片选 Slave (从)设备,使被选中的 Slave(从)设备能够被 Master(主)设备所访问
  • MISO:在 Master(主)上面也被称为 Rx-Channel,作为数据的入口,主要用于SPI 设备接收数据
  • MOSI:在 Master(主)上面也被称为 Tx-Channel,作为数据的出口,主要用于 SPI 设备发送数据

1.2 时钟极性和时钟相位

时钟极性(CPOL):
根据硬件制造商的命名规则不同,时钟极性通常写为CKP或CPOL。时钟极性和相位共同决定读取数据的方式,比如信号上升沿读取数据还是信号下降沿读取数据。

CPOL可以配置为1或0。这意味着你可以根据需要将时钟的默认状态(IDLE)设置为高或低。

  • CKP = 0:时钟空闲IDLE为低电平 0
  • CKP = 1:时钟空闲IDLE为高电平1

时钟相位(CPHA):
根据硬件制造商的不同,时钟相位通常写为CKE或CPHA。顾名思义,时钟相位/边沿,也就是采集数据时是在时钟信号的具体相位或者边沿。

  • CPHA = 0:在时钟信号SCK的第一个跳变沿采样
  • CPHA = 1:在时钟信号SCK的第二个跳变沿采样

从以上介绍我们可以看出,SPI一共有四种传输模式,具体如下:

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

  • CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。

  • CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。

  • CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。

  • CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

1.3 主从模式

SPI协议通过主机主动拉低拉低CS片选信号选择从机,也正因如此分出如下两种情况:

多根CS片选信号线:

通常,每个从机都需要一条单独的CS线。
如果要和特定的从机进行通讯,可以将相应的CS信号线拉低,并保持其他CS信号线的状态为高电平;如果同时将两个CSS信号线拉低,则可能会出现乱码,因为从机可能都试图在同一条MISO线上传输数据,最终导致接收数据乱码。

具体情况如下图(NSS即片选信号线CS):

在这里插入图片描述

菊花链(只有一根CS片选信号线):
在数字通信世界中,在设备信号(总线信号或中断信号)以串行的方式从一 个设备依次传到下一个设备,不断循环直到数据到达目标设备的方式被称为菊花链。

菊花链的最大缺点是因为是信号串行传输,所以一旦数据链路中的某设备发生故障的时候,它下面优先级较低的设备就不可能得到服务了。
另一方面,距离主机越远的从机,获得服务的优先级越低,所以需要安排好从机的优先级,并且设置总线检测器,如果某个从机超时,则对该从机进行短路,防止单个从机损坏造成整个链路崩溃的情况。

具体情况如下图:
在这里插入图片描述
在这里插入图片描述

  • SCK为时钟信号,8clks表示8个边沿信号
  • 其中D为数据,X为无效数据

参考资料:https://blog.csdn.net/u010632165/article/details/109460814

二、Flash(M25P16)

2.1 Flash简介

Flash 存储器(FLASH EEPROM)又称闪存,快闪。它是EEPROM的一种。它结合了ROM和RAM的长处。不仅具备电可擦除可编程(EEPROM)的特点,还不会断电丢失数据同时可以快速读取数据。Flash和EEPROM之间的主要区别在于擦除方式和工作原理。传统的EEPROM可以按字节或页进行擦除和编程,而Flash通常以扇区为单位进行擦除。此外,Flash的擦写寿命较短,需要注意控制擦写次数,而EEPROM没有这个限制。

2.2 M25P16芯片分析

在这里插入图片描述
该芯片的特性总结如上图:

  • 16Mbit存储空间(32个扇区 每个扇区256页 每页256字节)
  • 支持SPI接口
  • 数据可保存20年

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

由上图可以看出该芯片仅支持SPI模式0或模式3,同时该芯片传输方式为MSB,高字节先发。

在这里插入图片描述

由上图可以看出:

  • 在发出页编程指令前需要先发送写使能(WREN)指令
  • 页编程只能将1重置为0,因此在页编程之前需要先发送一次扇区擦除指令,将存储数据全部置为1
  • 同时在后续编程中,在发出扇区擦除指令前同样需要发送一次写使能指令
  • 该芯片有一个状态寄存器,我们可以通过读取状态寄存器的WIP位获取芯片是否处于忙状态,WEL位获取芯片是否处于写锁存。通过读状态寄存器我们可以在后续页编程和扇区擦除时不必强行等待手册所要求的操作最大时间。为了简写代码,本人未用到读状态寄存器的命令,而是在SE和PP指令发送后强行等待了手册所规定的最大等待时间。状态寄存器格式如下图:在这里插入图片描述

芯片相关指令如下图:

在这里插入图片描述
可以看出 页编程(PP)、扇区擦除(SE)、读(READ)指令均需要三个字节的地址数据(扇区地址+页地址+数据地址)

一些重要时间:

在这里插入图片描述

  • 由上图可以看出M25P16芯片要求片选信号要求在数据到来之前提前拉低至少5ns,同时在数据传输结束后至少继续拉低100ns才能被拉高
    在这里插入图片描述
  • 页编程指令发送后等待5ms
  • 扇区擦除指令发送后等待3s

时钟频率选择
在这里插入图片描述
由上图可以看出,除了读数据指令时钟最大速度为20MHz外其余指令均能达到50MHz,为了便于代码编写,本次项目在开发板50MHz的基础上进行了四分频,12.5MHz的时钟能够满足所用的所有指令。

2.3 项目所用指令时序

2.3.1 WREN(06h)

在执行PP,SE,BE,RESR指令前需要执行写使能操作。片选信号拉低后,开始执行写使能指令,接着传输一字节指令。指令发送完后,片选信号置为高电平.
在这里插入图片描述

  • 发送1字节指令

2.3.2 RDID(9Fh)

读ID指令一共会返回三个字节的数据,第一个数据为厂商ID(如下图20h代表的是意法半导体的厂商ID),后两个数据可以理解为器件ID。
在这里插入图片描述

在这里插入图片描述

  • 发送1字节指令
  • 接收3字节数据

2.3.3 READ(03h)

在这里插入图片描述

  • 发送1字节的指令
  • 发送3字节的地址
  • 接收1字节的数据

2.3.4 PP(02h)

在这里插入图片描述

2.3.5 SE(D8h)

在这里插入图片描述

三、状态机

接口模块:
SPI接口模块实际就是切割SPI时序图拆解出不同的状态,再在不同的状态中实现SPI时序,以写使能时序图为例:
在这里插入图片描述
我们可以拆分出四个状态:CS片选信号在数据传输前需要提前拉低的一段时间DELAY1(最少tSHSH 5ns),发送指令状态DATA,指令发送结束后片选信号延迟一段时间DELAY2,以及手册要求的片选恢复tSHSL(100ns)恢复状态HOLD。

控制模块:
该模块思路可以天马行空,博主就不给出状态机了,可以参考博主后续源码。

四、项目源码

控制模块:

/**************************************功能介绍***********************************
Date	: 
Author	: majiko
Version	: 
Description: 
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module spi_control( 
    input 	wire				clk		,
    input 	wire				rst_n	,
    input   wire                read_req,
    input   wire                rdid_req,
    input   wire                wen_req ,
    input   wire                ready   ,
    input   wire    [23:0]      read_addr,  //需要读的数据地址
    input   wire    [7:0]       read_num,   //需要读出的数据0~255
    input   wire    [23:0]      wen_adder,//需要写入的数据地址
    input   wire    [7:0]       wen_num , //需要写入的数据数量
    input   wire    [7:0]       data_in,    //需要写入的数据
    input   wire                data_in_vld,
    output  wire    [7:0]       data_out,   //返回的数据
    output  wire                data_out_vld,

    //spi接口
    input   wire                miso    ,
    output  wire                cs_n    ,
    output  wire                sclk    ,
    output  wire                mosi        //主机输出给从机
);								 
//---------<参数定义>--------------------------------------------------------- 
localparam  IDLE   = 10'b0000000001,
            RDID   = 10'b0000000010,
            READ   = 10'b0000000100,
            WEN    = 10'b0000001000,
            WAIT   = 10'b0000010000,
            SE     = 10'b0000100000,
            TSE    = 10'b0001000000,
            PP     = 10'b0010000000,
            TPP    = 10'b0100000000,
            HOLD   = 10'b1000000000;
parameter   MAX_TXE = 28'd150_000_000,//3s
            MAX_TPP = 18'd150_000;//3ms

//---------<内部信号定义>-----------------------------------------------------
reg 	[9:0]	cstate     ;//现态
reg	    [9:0]	nstate     ;//次态
wire            idle2rdid;
wire            idle2read;
wire            idle2wen ;
wire            rdid2hold;
wire            read2hold;
wire            wen2wait;
wire            wait2se   ;
wire            se2tse   ;
wire            tse2wen  ;
wire            wait2pp   ;
wire            pp2tpp   ;
wire            tpp2idle ;
wire            hold2idle;

reg			[2:0]	cnt_bit	   	;
wire				add_cnt_bit	;
wire				end_cnt_bit	;

reg			[3:0]	cnt_byte	   	;
wire				add_cnt_byte	;
wire				end_cnt_byte	;
reg         [3:0]   num             ;

//子模块例化参数
reg         [7:0]   master_data_in  ;
wire                master_data_in_vld;
wire                done            ;
wire        [7:0]   master_data_out ;
wire                master_data_out_vld;
reg         [23:0]  id_data         ;
wire                master_ready    ;

//数据接收
reg         rdid_flag;
wire      [7:0]  read_data;
wire             read_data_vld;
reg              read_flag;
reg			[2:0]	cnt_read	   	;
wire				add_cnt_read	;
wire				end_cnt_read	;

//tse延时
reg			[27:0]	cnt_tse	   	;
wire				add_cnt_tse	;
wire				end_cnt_tse	;

//tpp延时
reg			[17:0]	cnt_tpp	   	;
wire				add_cnt_tpp	;
wire				end_cnt_tpp	;

//wen跳转使能
reg           wen_flag;

//数据寄存
reg             data_in_r;

//****************************************************************
//                  状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cstate <= IDLE;
    end 
    else begin 
        cstate <= nstate;
    end 
end

//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
    case(cstate)
        IDLE : begin
                    if (idle2rdid) begin
                        nstate = RDID;
                    end
                    else if (idle2read) begin
                        nstate = READ;
                    end
                    else if (idle2wen) begin
                        nstate = WEN;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        RDID : begin
                    if (rdid2hold) begin
                        nstate = HOLD;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        READ : begin
                    if (read2hold) begin
                        nstate = HOLD;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        WEN  : begin
                    if (wen2wait) begin
                        nstate = WAIT;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        WAIT :  begin
                    if (wait2se) begin
                        nstate = SE;
                    end
                    else if (wait2pp) begin
                        nstate = PP;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        SE   : begin
                    if (se2tse) begin
                        nstate = TSE;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        TSE  : begin
                    if (tse2wen) begin
                        nstate = WEN;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        PP   : begin
                    if (pp2tpp) begin
                        nstate = TPP;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        TPP  : begin
                    if (tpp2idle) begin
                        nstate = IDLE;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        HOLD : begin
                    if (hold2idle) begin
                        nstate = IDLE;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        default : nstate = IDLE;
    endcase
end


    assign idle2rdid = cstate == IDLE && rdid_req;
    assign idle2read = cstate == IDLE && read_req;
    assign idle2wen  = cstate == IDLE && wen_req ;
    assign rdid2hold = cstate == RDID && end_cnt_byte;
    assign read2hold = cstate == READ && end_cnt_byte;
    assign wen2wait  = cstate == WEN  && end_cnt_byte;
    assign wait2se   = cstate == WAIT  && done && ~wen_flag;
    assign se2tse    = cstate == SE   && end_cnt_byte;
    assign tse2wen   = cstate == TSE  && end_cnt_tse;
    assign wait2pp   = cstate == WAIT  && done && wen_flag;
    assign pp2tpp    = cstate == PP   && end_cnt_byte;
    assign tpp2idle  = cstate == TPP  && end_cnt_tpp;
    assign hold2idle = cstate == HOLD && done;

            
//第三段:描述输出,时序逻辑或组合逻辑皆可
//****************************************************************
//                  byte计数器
//**************************************************************** 


always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_bit <= 3'd0;
    end 
    else if(add_cnt_bit)begin 
        if(end_cnt_bit)begin 
            cnt_bit <= 3'd0;
        end
        else begin 
            cnt_bit <= cnt_bit + 1'b1;
        end 
    end
end 

assign add_cnt_bit = ((cstate == RDID)||(cstate == READ)||(cstate == WEN)||(cstate == SE)||(cstate == PP)) && master_ready;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 7;



always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_byte <= 4'd0;
    end 
    else if(add_cnt_byte)begin 
        if(end_cnt_byte)begin 
            cnt_byte <= 4'd0;
        end
        else begin 
            cnt_byte <= cnt_byte + 1'b1;
        end 
    end
end 

assign add_cnt_byte = end_cnt_bit;
assign end_cnt_byte = add_cnt_byte && cnt_byte == num-1;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        num <= 1;
    end
    else if (cstate == RDID) begin
        num <= 4;
    end
    else if (cstate == READ) begin
        num <= 4 + read_num;
    end
    else if (cstate == WEN) begin
        num <= 1;
    end
    else if (cstate == SE) begin
        num <= 4;
    end
    else if (cstate == PP) begin
        num <= 5;
    end
    else begin
        num <= 1;
    end
end

//****************************************************************
//                      指令发送
//****************************************************************
always @(*) begin
    case (cstate)
        RDID : case (cnt_byte)
                    0 : master_data_in = 8'h9f;
                    1 : master_data_in = 8'hff;
                    2 : master_data_in = 8'hff;
                    3 : master_data_in = 8'hff;
                    default: master_data_in = 8'hff;
               endcase
        READ : case (cnt_byte)
                    0 : master_data_in = 8'h03;
                    1 : master_data_in = read_addr[23:16];
                    2 : master_data_in = read_addr[15:8];
                    3 : master_data_in = read_addr[7:0];
                    default: master_data_in = 8'hff;
               endcase
        WEN  : case (cnt_byte)
                    0 : master_data_in = 8'h06;
                    default: master_data_in = 8'hff;
               endcase
        SE   : case (cnt_byte)
                    0 : master_data_in = 8'hd8;
                    1 : master_data_in = read_addr[23:16];
                    2 : master_data_in = read_addr[15:8];
                    3 : master_data_in = read_addr[7:0];
                    default: master_data_in = 8'hff;
               endcase
        PP   : case (cnt_byte)
                    0 : master_data_in = 8'h02;
                    1 : master_data_in = read_addr[23:16];
                    2 : master_data_in = read_addr[15:8];
                    3 : master_data_in = read_addr[7:0];
                    4 : master_data_in = 8'hF1;
                    default: master_data_in = 8'hff;
               endcase
    endcase
end
assign  master_data_in_vld = add_cnt_byte;
//****************************************************************
//                  数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_in_r <= 0;
    end
    else if (data_in_vld) begin
        data_in_r <= data_in;
    end
    else begin
        data_in_r <= data_in_r;
    end
end

//****************************************************************
//                  滤除无用数据
//****************************************************************
reg			[3:0]	cnt_out	   	;
wire				add_cnt_out	;
wire				end_cnt_out	;
reg         [3:0]   num_out     ;

always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_out <= 'd0;
    end 
    else if (hold2idle || tpp2idle) begin
        cnt_out <= 'd0;
    end
    else if(add_cnt_out)begin 
        if(end_cnt_out)begin 
            cnt_out <= 'd0;
        end
        else begin 
            cnt_out <= cnt_out + 1'b1;
        end 
    end
end 

assign add_cnt_out =  master_data_out_vld;
assign end_cnt_out = add_cnt_out && cnt_out == num_out - 1;

always @(*) begin
    if (!rst_n) begin
        num_out = 0;
    end
    else if (cstate == RDID) begin
        num_out = 1;
    end
    else if (cstate == READ) begin
        num_out = 4;
    end
end
//****************************************************************
//                      记录操作
//****************************************************************
reg       [1:0]      op_flag;
`define         OP_RDID 1
`define         OP_READ 2    
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        op_flag <= 0;
    end
    else if (idle2rdid) begin
        op_flag <= `OP_RDID;
    end
    else if (idle2read) begin
        op_flag <= `OP_READ;
    end
    else if (hold2idle) begin
        op_flag <= 0;
    end
end
//****************************************************************
//                  记录数据
//****************************************************************
//读id使能
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rdid_flag <= 0;
    end
    else if (end_cnt_out && op_flag == `OP_RDID) begin
        rdid_flag <= 1;
    end
    else if (hold2idle) begin
        rdid_flag <= 0;
    end
end
//读read使能
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        read_flag <= 0;
    end
    else if (end_cnt_out && op_flag == `OP_READ) begin
        read_flag <= 1;
    end
    else if (hold2idle) begin
        read_flag <= 0;
    end
end
assign data_out = master_data_out;
assign data_out_vld = (rdid_flag || read_flag) && master_data_out_vld;
//****************************************************************
//                      WEN跳转使能
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        wen_flag <= 0;
    end
    else if (tse2wen) begin
        wen_flag <= 1;
    end
    else if (tpp2idle) begin
        wen_flag <= 0;
    end
end
//****************************************************************
//                      延时开始使能
//****************************************************************
reg            delay_flag;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        delay_flag <= 0;
    end
    else if ((cstate == TSE) && done) begin
        delay_flag <= 1;
    end
    else if ((cstate == TPP) && done) begin
        delay_flag <= 1;
    end
    else if (end_cnt_tpp || end_cnt_tse) begin
        delay_flag <= 0;
    end
end

//****************************************************************
//                      TSE延时
//****************************************************************
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_tse <= 'd0;
    end 
    else if(add_cnt_tse)begin 
        if(end_cnt_tse)begin 
            cnt_tse <= 'd0;
        end
        else begin 
            cnt_tse <= cnt_tse + 1'b1;
        end 
    end
end 

assign add_cnt_tse = (cstate == TSE) && delay_flag;
assign end_cnt_tse = add_cnt_tse && cnt_tse == MAX_TXE - 1;

//****************************************************************
//                          TPP延时
//****************************************************************
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_tpp <= 'd0;
    end 
    else if(add_cnt_tpp)begin 
        if(end_cnt_tpp)begin 
            cnt_tpp <= 'd0;
        end
        else begin 
            cnt_tpp <= cnt_tpp + 1'b1;
        end 
    end
end 

assign add_cnt_tpp = (cstate == TPP) && delay_flag;
assign end_cnt_tpp = add_cnt_tpp && cnt_tpp == MAX_TPP - 1;

//****************************************************************
//                      子模块例化
//****************************************************************
spi_master spi_master_inst( 
    /*input 	wire		*/		.clk		    (clk),
    /*input 	wire		*/		.rst_n	        (rst_n),
    /*input   wire    [7:0] */      .data_in        (master_data_in),
    /*input   wire          */      .data_in_vld    (master_data_in_vld),   
    /*input   wire          */      .miso           (miso),
    /*output  wire          */      .master_ready   (master_ready),
    /*output  reg     [7:0] */      .data_out       (master_data_out),
    /*output  wire          */      .data_out_vld   (master_data_out_vld),  
    /*output  wire          */      .done           (done),
    /*output  reg           */      .cs_n           (cs_n),
    /*output  reg           */      .sclk           (sclk),
    /*output  reg           */      .mosi           (mosi)//主机输出给从机
);

endmodule

接口模块:

/**************************************功能介绍***********************************
Date	: 
Author	: majiko
Version	: 
Description: 
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module spi_master( 
    input 	wire				clk		,
    input 	wire				rst_n	,
    input   wire    [7:0]       data_in ,
    input   wire                data_in_vld,
    input   wire                miso    ,
    output  wire                master_ready,
    output  reg     [7:0]       data_out,
    output  wire                data_out_vld,
    output  wire                done    ,
    output  reg                 cs_n    ,
    output  reg                 sclk    ,
    output  reg                 mosi        //主机输出给从机
);								 
//---------<参数定义>--------------------------------------------------------- 
parameter   DIV         = 4;//时钟分频倍数
parameter   MAX100      = 5;//100ns

localparam  IDLE        = 5'b00001,//
            DELAY_ONE   = 5'b00010,//
            DATA        = 5'b00100,//
            DELAY_TWO   = 5'b01000,//
            HOLD        = 5'B10000;
//---------<内部信号定义>-----------------------------------------------------
//fifo数据缓存
wire                fifo_empty;
wire                fifo_full;
wire        [7:0]   fifo_data;
wire                fifo_rd_req;
//时钟分频
reg			[1:0]	cnt_div	   	;
wire				add_cnt_div	;
wire				end_cnt_div	;
//状态机参数定义
reg 	    [4:0]	cstate     ;//现态
reg	        [4:0]	nstate     ;//次态
wire                idle2delay_one;
wire                delay_one2data;
wire                data2delay_two;
wire                delay_two2hold;
wire                hold2idle     ;
//bit计数器参数
reg			[2:0]	cnt_bit	   	;
wire				add_cnt_bit	;
wire				end_cnt_bit	;

//延时计数器参数
reg			[2:0]	cnt_delay	   	;
wire				add_cnt_delay	;
wire				end_cnt_delay	;


//****************************************************************
//                      寄存数据缓存
//****************************************************************
fifo_rx	fifo_rx_inst (
	.aclr       ( ~rst_n         ),
	.clock      ( clk           ),
	.data       ( data_in       ),
    .q          ( fifo_data     ),
	.rdreq      ( fifo_rd_req   ),
	.wrreq      ( data_in_vld   ),
	.empty      ( fifo_empty    ),
	.full       ( fifo_full     )

	);
// assign  fifo_rd_req = !fifo_empty && data2delay_two;//因为采用了浅显模式,如果使用delay_one2data会导致第一个数据丢失
//提前一点读取新数据,让fifo的empty信号早点更新
//(根据仿真调整的,仿真体现出来的结果是,fifo的empty信号晚了一个时钟周期,导致在DATA状态多待了一个时钟周期1
//从而导致SCLK信号有毛刺,以及div计数器未归0)
assign  fifo_rd_req = !fifo_empty && (cnt_bit == 7) &&(cnt_div == DIV - 2) ;//为了使fifo连续读取数据
//****************************************************************
//                      状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cstate <= IDLE;
    end 
    else begin 
        cstate <= nstate;
    end 
end

//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
    case(cstate)
        IDLE      : begin
                        if (idle2delay_one) begin
                            nstate = DELAY_ONE;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        DELAY_ONE : begin
                        if (delay_one2data) begin
                            nstate = DATA;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        DATA      : begin
                        if (data2delay_two) begin
                            nstate = DELAY_TWO;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        DELAY_TWO : begin
                        if (delay_two2hold) begin
                            nstate = HOLD;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        HOLD      : begin
                        if (hold2idle) begin
                            nstate = IDLE;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        default : nstate = IDLE;
    endcase
end
assign  idle2delay_one = cstate == IDLE      &&  !fifo_empty;
assign  delay_one2data = cstate == DELAY_ONE &&  1;
assign  data2delay_two = cstate == DATA      &&  fifo_empty;
assign  delay_two2hold = cstate == DELAY_TWO &&  1;
assign  hold2idle      = cstate == HOLD      &&  end_cnt_delay;
            
//第三段:描述输出,时序逻辑或组合逻辑皆可
//****************************************************************
//                  时钟分频
//****************************************************************
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_div <= 'd0;
    end 
    else if(add_cnt_div)begin 
        if(end_cnt_div)begin 
            cnt_div <= 'd0;
        end
        else begin 
            cnt_div <= cnt_div + 1'b1;
        end 
    end
end 

assign add_cnt_div = cstate == DATA;
assign end_cnt_div = add_cnt_div && cnt_div == DIV - 1;
//产生时钟sclk
always @(*) begin
    if (!rst_n) begin
        sclk =  1;
    end
    else if (cstate == DATA && (cnt_div < (DIV >> 1))) begin
        sclk =  0;
    end
    else begin
        sclk =  1;
    end
end
//****************************************************************
//                  bit计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_bit <= 'd0;
    end 
    else if(add_cnt_bit)begin 
        if(end_cnt_bit)begin 
            cnt_bit <= 'd0;
        end
        else begin 
            cnt_bit <= cnt_bit + 1'b1;
        end 
    end
end 

assign add_cnt_bit = end_cnt_div;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 7;
//****************************************************************
//                  mosi数据传输
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        mosi <= 1;
    end
    else if (cstate == DATA && (cnt_div == 0)) begin
        mosi <= fifo_data[7-cnt_bit];
    end
    else begin
      mosi   <= mosi;
    end
end
//****************************************************************
//                  csn产生
//****************************************************************
always @(*) begin
    if (!rst_n) begin
        cs_n = 1;
    end
    else if (cstate == IDLE || cstate == HOLD) begin
        cs_n = 1;
    end
    else begin
        cs_n = 0;
    end
end
//****************************************************************
//                 保持延迟
//****************************************************************
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_delay <= 'd0;
    end 
    else if(add_cnt_delay)begin 
        if(end_cnt_delay)begin 
            cnt_delay <= 'd0;
        end
        else begin 
            cnt_delay <= cnt_delay + 1'b1;
        end 
    end
end 

assign add_cnt_delay = cstate == HOLD;
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX100 - 1;
//****************************************************************
//                          数据接收 MISO
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_out <= 0;
    end
    else if (cstate == DATA && (cnt_div == DIV >> 1)) begin
        data_out <= {data_out[6:0],miso};
    end
end
assign data_out_vld = end_cnt_bit;
assign done         = hold2idle;
assign master_ready = ~fifo_full;

endmodule

串口输出模块:

/**************************************功能介绍***********************************
Date	: 
Author	: majiko
Version	: 
Description: 串口发送模块
*********************************************************************************/

//---------<模块及端口声名>------------------------------------------------------
module uart_tx#
(
    parameter   BPS = 115200,
    parameter   CLK_FRE = 50_000_000,
    parameter   CHECK_BIT = "NONE"//NONE 不校验 DDO奇数 EVEN偶数

)
( 
    input 	wire				        clk		,
    input 	wire				        rst_n	,
    input   wire                        tx_data_vld,//数据发送标志
    input   wire    [7:0]               tx_data  ,//发送数据
    output  reg                         tx       , 
    output  wire                        ready       //准备好发送
);								 
//---------<参数定义>--------------------------------------------------------- 
//状态机参数定义
localparam  IDLE   = 5'b00001,//空闲状态
            START  = 5'b00010,//发送开始位
            DATA   = 5'b00100,//发送8bit数据
            CHECK  = 5'b01000,//发送校验位
            STOP   = 5'b10000;//发送结束位

parameter   MAX_1bit = CLK_FRE/BPS;//传输1bit数据所需时间

//---------<内部信号定义>-----------------------------------------------------
//状态机参数定义
reg 	[4:0]	cstate     ;//现态
reg	    [4:0]	nstate     ;//次态    

wire    idle2start;
wire    start2data;
wire    data2check ;
wire    data2stop;
wire    check2stop;
wire    stop2idle ;

wire             temp;//奇偶校验位

//计数器参数定义
reg         [3:0]   num             ;
reg			[8:0]	cnt_start	   	;
wire				add_cnt_start	;
wire				end_cnt_start	;

reg			[11:0]	cnt_data	   	;
wire				add_cnt_data	;
wire				end_cnt_data	;

reg			[2:0]	cnt_num	   	;
wire				add_cnt_num	;
wire				end_cnt_num	;

reg			[8:0]	cnt_stop	   	;
wire				add_cnt_stop	;
wire				end_cnt_stop	;

reg         [7:0]   tx_data_r       ;

//****************************************************************
//                      发送模块状态机
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cstate <= IDLE;
    end 
    else begin 
        cstate <= nstate;
    end 
end

//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
    case(cstate)
        IDLE  : begin
                    if (idle2start) begin
                        nstate = START;
                    end
                    else begin
                        nstate = cstate;
                    end            
                end
        START : begin
                    if (start2data) begin
                        nstate = DATA;
                    end
                    else begin
                        nstate = cstate;
                    end            
                end
        DATA  : begin
                    if (data2check) begin
                        nstate = CHECK;
                    end
                    else if (data2stop) begin
                        nstate = STOP;
                    end
                    else begin
                        nstate = cstate;
                    end            
                end
        CHECK : begin
                    if (check2stop) begin
                        nstate = STOP;
                    end
                    else begin
                        nstate = cstate;
                    end
                end
        STOP  : begin
                    if (stop2idle) begin
                        nstate = IDLE;
                    end
                    else begin
                        nstate = cstate;
                    end            
                end
        default : ;
    endcase
end

assign idle2start = cstate == IDLE  && tx_data_vld ; //接收到vld信号开始发送数据
assign start2data = cstate == START && end_cnt_num;//计时发送1bit后跳转
assign data2check = cstate == DATA  && end_cnt_num && CHECK_BIT != "NONE";//计时发送8bit数据后,需要校验位
assign data2stop  = cstate == DATA  && end_cnt_num && CHECK_BIT == "NONE";//不需要校验位
assign check2stop = cstate == CHECK && end_cnt_num;//发送1bit校验位
assign stop2idle  = cstate == STOP  && end_cnt_num;//计时发送1bit数据后跳转



//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(*) begin
    if (!rst_n) begin
        tx <= 1;
    end
    else case (cstate)
        IDLE : tx = 1;//拉高
        START: tx = 0;//拉低发送开始位
        DATA: tx = tx_data_r[cnt_num];//发送数据
        CHECK: tx = temp;//奇偶校验位
        STOP : tx = 1;//拉高发送结束位
        default: tx = 1;
    endcase
end

assign ready = cstate == IDLE;

//****************************************************************
//              数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        tx_data_r <= 8'd0;
    end
    else if (tx_data_vld) begin
        tx_data_r <= tx_data;
    end
end
//****************************************************************
//                  奇偶校验位
//****************************************************************

assign    temp = (CHECK_BIT == "DDO")? ~^tx_data_r:^tx_data_r;         
//****************************************************************
//                          1bit数据
//**************************************************************** 



always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_data <= 12'd0;
    end 
    else if(add_cnt_data)begin 
        if(end_cnt_data)begin 
            cnt_data <= 12'd0;
        end
        else begin 
            cnt_data <= cnt_data + 1'b1;
        end 
    end
end 

assign add_cnt_data = cstate != IDLE;
assign end_cnt_data = add_cnt_data && cnt_data == MAX_1bit-1;
//****************************************************************
//                          选择数据位计数器
//****************************************************************


always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_num <= 3'd0;
    end 
    else if(add_cnt_num)begin 
        if(end_cnt_num)begin 
            cnt_num <= 3'd0;
        end
        else begin 
            cnt_num <= cnt_num + 1'b1;
        end 
    end
end 

assign add_cnt_num = end_cnt_data;
assign end_cnt_num = add_cnt_num && cnt_num == num - 1;


//****************************************************************
//                  数据控制
//****************************************************************
always @(*) begin
    case (cstate)
        IDLE :  num = 1;     
        START:  num = 1;
        DATA :  num = 8;
        CHECK:  num = 1;
        STOP :  num = 1;
        default: num = 1;
    endcase
end


endmodule

按键消抖模块:

/**************************************功能介绍***********************************
Date	: 
Author	: majiko
Version	: 
Description: 按键消抖的参数化模块(高电平有效)
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module key_debounce 
#(parameter N = 4)
( 
    input 	wire				clk		,
    input 	wire				rst_n	,
    input   wire    [N-1:0]     key_in  ,//N个按键输入
    output  reg     [N-1:0]     key_out  //N个按键输出

);								 
//---------<参数定义>--------------------------------------------------------- 
parameter   MAX_20MS = 20'd999_999;

//---------<内部信号定义>-----------------------------------------------------
//延时计数器参数
reg			[19:0]	cnt_delay	   	;
wire				add_cnt_delay	;
wire				end_cnt_delay	;

//边沿检测参数
reg     [N-1:0] key_in_r0;
reg     [N-1:0] key_in_r1;
reg     [N-1:0] key_in_r2;

wire            nedge   ;
wire            podge   ;

reg         flag;//flag驱动使能

//****************************************************************
//                     计数器延迟20ms
//****************************************************************    
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_delay <= 20'd0;
    end 
    else if(add_cnt_delay)begin 
        if(end_cnt_delay)begin 
            cnt_delay <= 20'd0;
        end
        else begin 
            cnt_delay <= cnt_delay + 1'b1;
        end 
    end
end 

assign add_cnt_delay = flag;
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX_20MS;

//****************************************************************
//                  下降沿检测
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_in_r0 <= {N{1'b1}};
        key_in_r1 <= {N{1'b1}};
        key_in_r2 <= {N{1'b1}};
    end
    else begin
        key_in_r0 <= key_in;
        key_in_r1 <= key_in_r0;
        key_in_r2 <= key_in_r1;
    end
end 
assign nedge = |(~key_in_r1&key_in_r2);
assign podge = |(key_in_r1&~key_in_r2);

//****************************************************************
//              flag使能
//****************************************************************

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        flag <= 0;
    end
    else if (nedge) begin
        flag <= 1'b1;
    end
    else if (end_cnt_delay) begin
        flag <= 0;
    end
    else begin
        flag <= flag;
    end
end   

//****************************************************************
//              赋值输出
//****************************************************************
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_out <= {N{1'b0}};
    end
    else if (end_cnt_delay) begin
        key_out <= ~key_in_r1;
    end
    else begin
        key_out <= {N{1'b0}};
    end
end
endmodule

顶层模块:

/**************************************功能介绍***********************************
Date	: 
Author	: majiko
Version	: 
Description: 
*********************************************************************************/
    
//---------<模块及端口声名>------------------------------------------------------
module top( 
    input 	wire				clk		,
    input 	wire				rst_n	,
    input   wire    [2:0]       key_in  ,

    input   wire                miso    ,
    output  wire                cs_n    ,
    output  wire                sclk    ,
    output  wire                mosi    ,
    output  wire                tx      

);								 
//---------<参数定义>--------------------------------------------------------- 
wire    [2:0]   key_debounce;
wire    [7:0]   data_out    ;
wire            data_out_vld;
wire    [7:0]   tx_data     ;
wire            tx_data_vld ;
wire            ready       ;
wire            fifo_empty  ;
wire            fifo_full   ;
//---------<内部信号定义>-----------------------------------------------------
key_debounce 
#(.N(3))
key_debounce_inst
( 
    /*input 	wire			*/	.clk		(clk),
    /*input 	wire			*/	.rst_n	    (rst_n),
    /*input   wire    [N-1:0]   */  .key_in     (key_in),//N个按键输入
    /*output  reg     [N-1:0]   */  .key_out    (key_debounce)//N个按键输出

);

spi_control spi_control_inst( 
    /*input 	wire		*/		.clk		    (clk),
    /*input 	wire		*/		.rst_n	        (rst_n),
    /*input   wire          */      .read_req       (key_debounce[1]),
    /*input   wire          */      .rdid_req       (key_debounce[0]),
    /*input   wire          */      .wen_req        (key_debounce[2]),
    /*input   wire          */      .ready          (),
    /*input   wire    [23:0]*/      .read_addr      (24'h000000),  //需要读的数据地址
    /*input   wire    [7:0] */      .read_num       (3),   //需要读出的数据0~255
    /*input   wire    [7:0] */      .data_in        (8'h11),
    /*input   wire          */      .data_in_vld    (1),
    /*output  wire    [7:0] */      .data_out       (data_out),   //返回的数据
    /*output  wire          */      .data_out_vld   (data_out_vld),

    /*//spi接口*/
    /*input   wire          */      .miso           (miso),
    /*output  wire          */      .cs_n           (cs_n),
    /*output  wire          */      .sclk           (sclk),
    /*output  wire          */      .mosi           (mosi)    //主机输出给从机
);

uart_tx#
(
    .BPS         (115200)     ,
    .CLK_FRE     (50_000_000) ,
    .CHECK_BIT   ("NONE")     //NONE 不校验 DDO奇数 EVEN偶数

)uart_tx_inst
( 
    /*input   wire			*/	  .clk		      (clk),
    /*input   wire			*/	  .rst_n	      (rst_n),
    /*input   wire          */    .tx_data_vld    (tx_data_vld),
    /*input   wire    [7:0] */    .tx_data        (tx_data),
    /*output  reg           */    .tx             (tx), 
    /*output  wire          */    .ready          (ready)
);

fifo_tx	fifo_tx_inst (
	.aclr       ( ~rst_n ),
	.clock      ( clk ),
	.data       ( data_out ),
	.wrreq      ( data_out_vld ),
    .q          ( tx_data ),
    .rdreq      ( tx_data_vld ),
	.empty      ( fifo_empty ),
	.full       ( fifo_full )
	);
assign  tx_data_vld = ready && ~fifo_empty;
    
    
    
endmodule
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值