基于FPGA的SPI读写M25P16 Flash芯片

文章目录

  • 一、SPI协议简介
    • 1.1 SPI引脚
    • 1.2 时钟极性和时钟相位
    • 1.3 主从模式
  • 二、Flash(M25P16)
    • 2.1 Flash简介
    • 2.2 M25P16芯片分析
    • 2.3 项目所用指令时序
      • 2.3.1 WREN(06h)
      • 2.3.2 RDID(9Fh)
      • 2.3.3 READ(03h)
      • 2.3.4 PP(02h)
      • 2.3.5 SE(D8h)
  • 三、状态机
  • 四、项目源码

本项目所用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~255input   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,//3sMAX_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)begincstate <= IDLE;end else begin cstate <= nstate;end 
end//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begincase(cstate)IDLE : beginif (idle2rdid) beginnstate = RDID;endelse if (idle2read) beginnstate = READ;endelse if (idle2wen) beginnstate = WEN;endelse beginnstate = cstate;endendRDID : beginif (rdid2hold) beginnstate = HOLD;endelse beginnstate = cstate;endendREAD : beginif (read2hold) beginnstate = HOLD;endelse beginnstate = cstate;endendWEN  : beginif (wen2wait) beginnstate = WAIT;endelse beginnstate = cstate;endendWAIT :  beginif (wait2se) beginnstate = SE;endelse if (wait2pp) beginnstate = PP;endelse beginnstate = cstate;endendSE   : beginif (se2tse) beginnstate = TSE;endelse beginnstate = cstate;endendTSE  : beginif (tse2wen) beginnstate = WEN;endelse beginnstate = cstate;endendPP   : beginif (pp2tpp) beginnstate = TPP;endelse beginnstate = cstate;endendTPP  : beginif (tpp2idle) beginnstate = IDLE;endelse beginnstate = cstate;endendHOLD : beginif (hold2idle) beginnstate = IDLE;endelse beginnstate = cstate;endenddefault : nstate = IDLE;endcase
endassign 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)begincnt_bit <= 3'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 3'd0;endelse 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)begincnt_byte <= 4'd0;end else if(add_cnt_byte)begin if(end_cnt_byte)begin cnt_byte <= 4'd0;endelse 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) beginif (!rst_n) beginnum <= 1;endelse if (cstate == RDID) beginnum <= 4;endelse if (cstate == READ) beginnum <= 4 + read_num;endelse if (cstate == WEN) beginnum <= 1;endelse if (cstate == SE) beginnum <= 4;endelse if (cstate == PP) beginnum <= 5;endelse beginnum <= 1;end
end//****************************************************************
//                      指令发送
//****************************************************************
always @(*) begincase (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;endcaseREAD : 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;endcaseWEN  : case (cnt_byte)0 : master_data_in = 8'h06;default: master_data_in = 8'hff;endcaseSE   : 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;endcasePP   : 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;endcaseendcase
end
assign  master_data_in_vld = add_cnt_byte;
//****************************************************************
//                  数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n) beginif (!rst_n) begindata_in_r <= 0;endelse if (data_in_vld) begindata_in_r <= data_in;endelse begindata_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)begincnt_out <= 'd0;end else if (hold2idle || tpp2idle) begincnt_out <= 'd0;endelse if(add_cnt_out)begin if(end_cnt_out)begin cnt_out <= 'd0;endelse 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 @(*) beginif (!rst_n) beginnum_out = 0;endelse if (cstate == RDID) beginnum_out = 1;endelse if (cstate == READ) beginnum_out = 4;end
end
//****************************************************************
//                      记录操作
//****************************************************************
reg       [1:0]      op_flag;
`define         OP_RDID 1
`define         OP_READ 2    
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginop_flag <= 0;endelse if (idle2rdid) beginop_flag <= `OP_RDID;endelse if (idle2read) beginop_flag <= `OP_READ;endelse if (hold2idle) beginop_flag <= 0;end
end
//****************************************************************
//                  记录数据
//****************************************************************
//读id使能
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginrdid_flag <= 0;endelse if (end_cnt_out && op_flag == `OP_RDID) beginrdid_flag <= 1;endelse if (hold2idle) beginrdid_flag <= 0;end
end
//读read使能
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginread_flag <= 0;endelse if (end_cnt_out && op_flag == `OP_READ) beginread_flag <= 1;endelse if (hold2idle) beginread_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) beginif (!rst_n) beginwen_flag <= 0;endelse if (tse2wen) beginwen_flag <= 1;endelse if (tpp2idle) beginwen_flag <= 0;end
end
//****************************************************************
//                      延时开始使能
//****************************************************************
reg            delay_flag;
always @(posedge clk or negedge rst_n) beginif (!rst_n) begindelay_flag <= 0;endelse if ((cstate == TSE) && done) begindelay_flag <= 1;endelse if ((cstate == TPP) && done) begindelay_flag <= 1;endelse if (end_cnt_tpp || end_cnt_tse) begindelay_flag <= 0;end
end//****************************************************************
//                      TSE延时
//****************************************************************
always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_tse <= 'd0;end else if(add_cnt_tse)begin if(end_cnt_tse)begin cnt_tse <= 'd0;endelse 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)begincnt_tpp <= 'd0;end else if(add_cnt_tpp)begin if(end_cnt_tpp)begin cnt_tpp <= 'd0;endelse 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;//100nslocalparam  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)begincstate <= IDLE;end else begin cstate <= nstate;end 
end//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begincase(cstate)IDLE      : beginif (idle2delay_one) beginnstate = DELAY_ONE;endelse beginnstate = cstate;endendDELAY_ONE : beginif (delay_one2data) beginnstate = DATA;endelse beginnstate = cstate;endendDATA      : beginif (data2delay_two) beginnstate = DELAY_TWO;endelse beginnstate = cstate;endendDELAY_TWO : beginif (delay_two2hold) beginnstate = HOLD;endelse beginnstate = cstate;endendHOLD      : beginif (hold2idle) beginnstate = IDLE;endelse beginnstate = cstate;endenddefault : 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)begincnt_div <= 'd0;end else if(add_cnt_div)begin if(end_cnt_div)begin cnt_div <= 'd0;endelse 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 @(*) beginif (!rst_n) beginsclk =  1;endelse if (cstate == DATA && (cnt_div < (DIV >> 1))) beginsclk =  0;endelse beginsclk =  1;end
end
//****************************************************************
//                  bit计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_bit <= 'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 'd0;endelse 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) beginif (!rst_n) beginmosi <= 1;endelse if (cstate == DATA && (cnt_div == 0)) beginmosi <= fifo_data[7-cnt_bit];endelse beginmosi   <= mosi;end
end
//****************************************************************
//                  csn产生
//****************************************************************
always @(*) beginif (!rst_n) begincs_n = 1;endelse if (cstate == IDLE || cstate == HOLD) begincs_n = 1;endelse begincs_n = 0;end
end
//****************************************************************
//                 保持延迟
//****************************************************************
always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_delay <= 'd0;end else if(add_cnt_delay)begin if(end_cnt_delay)begin cnt_delay <= 'd0;endelse 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) beginif (!rst_n) begindata_out <= 0;endelse if (cstate == DATA && (cnt_div == DIV >> 1)) begindata_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)begincstate <= IDLE;end else begin cstate <= nstate;end 
end//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begincase(cstate)IDLE  : beginif (idle2start) beginnstate = START;endelse beginnstate = cstate;end            endSTART : beginif (start2data) beginnstate = DATA;endelse beginnstate = cstate;end            endDATA  : beginif (data2check) beginnstate = CHECK;endelse if (data2stop) beginnstate = STOP;endelse beginnstate = cstate;end            endCHECK : beginif (check2stop) beginnstate = STOP;endelse beginnstate = cstate;endendSTOP  : beginif (stop2idle) beginnstate = IDLE;endelse beginnstate = cstate;end            enddefault : ;endcase
endassign 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 @(*) beginif (!rst_n) begintx <= 1;endelse 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
endassign ready = cstate == IDLE;//****************************************************************
//              数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n) beginif (!rst_n) begintx_data_r <= 8'd0;endelse if (tx_data_vld) begintx_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)begincnt_data <= 12'd0;end else if(add_cnt_data)begin if(end_cnt_data)begin cnt_data <= 12'd0;endelse 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)begincnt_num <= 3'd0;end else if(add_cnt_num)begin if(end_cnt_num)begin cnt_num <= 3'd0;endelse 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 @(*) begincase (cstate)IDLE :  num = 1;     START:  num = 1;DATA :  num = 8;CHECK:  num = 1;STOP :  num = 1;default: num = 1;endcase
endendmodule

按键消抖模块:

/**************************************功能介绍***********************************
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)begincnt_delay <= 20'd0;end else if(add_cnt_delay)begin if(end_cnt_delay)begin cnt_delay <= 20'd0;endelse 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) beginif (!rst_n) beginkey_in_r0 <= {N{1'b1}};key_in_r1 <= {N{1'b1}};key_in_r2 <= {N{1'b1}};endelse beginkey_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) beginif (!rst_n) beginflag <= 0;endelse if (nedge) beginflag <= 1'b1;endelse if (end_cnt_delay) beginflag <= 0;endelse beginflag <= flag;end
end   //****************************************************************
//              赋值输出
//****************************************************************
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginkey_out <= {N{1'b0}};endelse if (end_cnt_delay) beginkey_out <= ~key_in_r1;endelse beginkey_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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/139700.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lite-UNet: 轻量且高效的细胞定位模型,轻量化的U-Net

论文&#xff1a;Lite-UNet: A Lightweight and Efficent Network for Cell Localization Paper Link&#xff1a;Lite-UNet: A Lightweight and Efficent Network for Cell Localization Code&#xff1a; https://github.com/Boli-trainee/Lite-UNet 核心思想&#xff1a;魔…

C++ —— Tinyxml2在Vs2017下相关使用2(较文1更复杂,附源码)

相关链接 C —— Tinyxml2在Vs2017下相关使用1&#xff08;附源码&#xff09; tinyxml2简介 TinyXML2是一个简单&#xff0c;小巧&#xff0c;高效&#xff0c;CXML解析器&#xff0c;可以很容易地集成到其他程序中。TinyXML-2解析一个XML文档&#xff0c;并从中构建一个 可以…

开源CasaOS云软件发现关键漏洞

近日&#xff0c;开源 CasaOS 个人云软件中发现的两个严重的安全漏洞。该漏洞一旦被攻击者成功利用&#xff0c;就可实现任意代码执行并接管易受攻击的系统。 这两个漏洞被追踪为CVE-2023-37265和CVE-2023-37266&#xff0c;CVSS评分均为9.8分。 发现这些漏洞的Sonar安全研究…

京东手机销售数据:2023年9月京东手机行业TOP10品牌排行榜

鲸参谋监测的京东平台9月份手机市场销售数据已出炉&#xff01; 9月份&#xff0c;手机市场销售整体呈现下滑。鲸参谋数据显示&#xff0c;今年9月&#xff0c;京东平台手机销量为300万&#xff0c;环比下降约20%&#xff0c;同比下降约18%&#xff1b;销售额为92亿&#xff0c…

分享一下怎么开发一个陪诊小程序

开发一个陪诊小程序需要综合考虑许多方面&#xff0c;包括但不限于市场需求、用户体验、技术实现和运营策略。以下是一篇以开发陪诊小程序为主题的文章。 一、背景介绍 随着社会的发展和人口老龄化的加剧&#xff0c;越来越多的老年人、病患和孕妇需要就医&#xff0c;而由于各…

SpringBoot前后端分离jar包nginx配置https访问

背景&#xff1a;做微信支付回调需要用到https域名&#xff0c;服务器是linux系统&#xff0c;用nginx做反向代理 准备&#xff1a;阿里云、腾讯云或者自己生成的SSL证书&#xff0c;java是8086端口 一&#xff1a;安装nginx&#xff0c;以前博客有记录 二&#xff1a;安装SS…

微信隐秘功能:如何巧妙隐藏好友和消息的方法教程

在日常使用微信的过程中&#xff0c;难免会有些私密内容或特定的联系人和群聊希望能够暂时隐藏起来&#xff0c;以保护个人隐私。幸运的是&#xff0c;微信提供了一些内置功能以及额外的工具&#xff0c;能够帮助我们实现这一目的。下面就来详细介绍一种简单易行的方法&#xf…

Java——List接口

1.Java单列集合类&#xff08;Collection&#xff09;概述 Java中的集合类就像一个容器&#xff0c;专门用来存储Java类的对象。 数组可以用来保存对个对象&#xff0c;但有时无法事先确定需要保存对象的个数&#xff0c;此时数组便不再使用&#xff0c;因为数组的长度不可变…

Aria2NG连接aria2-pro提示认证失败的处理办法

本文档适用于已经安装了aria2-pro和AriaNg的小伙伴~ 第一次登录管理端会提示”认证失败“ 这是因为aria设置了密码&#xff0c;需要在设置中配置上密码即可 配置完密码重新加载就可以正常使用啦 下载速度明显比以前快了很多 下载参考文档 Docker安装下载神器aria2并使用过程记…

数据结构-----红黑树(全)

目录 前言 一、什么是红黑树&#xff1f; 二、为什么需要红黑树&#xff1f;&#xff08;与AVL树对比&#xff09; 三、红黑树的特性 四、红黑树的储存结构 五、节点旋转操作 左旋&#xff08;Left Rotation&#xff09; 右旋&#xff08;Right Rotation&#xff09; 六、…

【Linux-常用命令-基础命令-解压rar文件-unrar-x-命令-笔记】

【Linux-常用命令-基础命令-解压rar文件-unrar-x-命令-笔记】 1、前言2、操作3、自己的操作 1、前言 最近&#xff0c;在使用Linux的时&#xff0c;使用相关基础命令是&#xff0c;总是容易忘记&#xff0c;上网一搜&#xff0c;大部分都写的比较繁琐&#xff0c;解压不同文件…

单链表经典OJ题:反转链表

题目&#xff1a; 给你单链表的头节点 head &#xff0c;i请你反转链表&#xff0c;并返回反转后的链表。 图例&#xff1a; 分析&#xff1a; 根据链表的特征&#xff0c;反转链表的本质便是改变节点内部的指针方向。 将原先指向下一个节点的指针进行修改&#xff0c;将其的…