基于verilog驱动M25P16(FALSH)--------- (三)

一、设计规范

1、设计框图

为了实现对M25P16(FLASH)的相关配置,设计了上图的相关的设计思路框图。

key_seshake:实现按键的消抖模块,在本次的设计中,使用了3个按键,key_in[0]控制数据的写入;key_in[1]控制数据的读取;key_in[2]控制扇区擦除。

uart_rx:串口数据接收模块,接收上位机发送来的数据,然后将 接收到的数据在发送的fifo中进行缓存。

fifo_rx:将uart_rx接收到的数据进行缓存,为FLASH的数据读取做准备。

spi_ctrl:主要是实现对M25P16的驱动,使用状态机进行驱动,主要实现了状态的转移。

spi:spi_ctrl模块的子模块,主要实现使用SPI协议对相关状态下数据的写入与读取。

fifo_tx:将从M25P16中读取到的数据缓存到该fifo中,为串口进行数据的发送做准备。

uart_tx:串口数据发送模块,主要是将fifo_tx模块中的数据发送给上位机。

2、spi_ctrl状态机

二、设计输入

1、顶层模块信号列表

信号

I/O

BIT

功能

clk

input

1

系统时钟

rst_n

input

1

复位

miso

input

1

主机输入,从机输出

uart_rx

input

1

Rx端口

key_in

input

3

按键输入

cs_n

output

1

片选

mosi

output

1

主机输出,从机输入

sclk

output

1

spi时钟

uart_tx

output

1

Tx端口

2、key_shake模块信号列表

信号

I/O

BIT

功能

clk

input

1

时钟信号

rst_n

input

1

复位信号

key_in

input

3

按键输入信号

key_out

output

3

按键输出信号

3、uart_rx模块信号列表

信号

I/O

BIT

功能

clk

input

1

系统时钟信号

rst_n

input

1

系统复位信号

uart_rx

input

1

数据接收

full_rx

input

1

fifo_rx的满信号

wrreq_rx

output

1

向fifo_rx发送写请求

rx_data

output

8

写入fifo_rx中的数据

4、fifo_rx模块信号列表(IP核)

信号

I/O

BIT

功能

aclr

input

1

fifo复位

clock

input

1

fifo时钟

data

input

8

fifo缓存uart_rx接收到的数据

rdreq

input

1

spi_ctrl读请求

wrreq

input

1

uart_rx写请求

empty

output

1

spi_ctrl读数据空标志

full

output

1

uart_rx写数据满标志

q    

output

8

fifo输出给spi_ctrl,将数据存储入flash

usedw

output

8

5、spi_ctrl模块信号列表

信号

I/O

BIT

功能

clk

input

1

系统

rst_n

input

1

复位

key_in

input

3

控制读、写 00:停止工作 01:写 10:读

data_rx

input

8

需要写入的数据(串口接收的数据)

miso

input

1

主机接收,从机输出

full_tx

input

1

fifo_tx的满信号

empty_rx

input

1

fifo_rx的空信号

mosi

output

1

主机输出,从机接收

cs_n

output

1

片选

sclk

output

1

SPI 时钟

wrreq_tx

output

1

向fifo_tx发送写请求

rdreq_rx

output

1

向fifo_rx发送读请求

data_tx

output

8

读取到的数据(串口发送的数据)

6、spi模块信号列表

信号

I/O

BIT

功能

clk

input

1

系统

rst_n

input

1

复位

miso

input

1

主机接收,从机输出

data_wr

input

8

写入的数据或指令

req

input

1

读写请求

rden

input

1

读数据使能信号

cs_n

output

1

片选

mosi

output

1

主机输出,从机接收

sclk

output

1

SPI 时钟

done

output

1

读写完1字节

data_rd

output

8

读取到的数据

7、fifo_tx模块信号列表(IP核)

信号

I/O

BIT

功能

aclr

input

1

fifo复位

clock

input

1

fifo时钟

data

input

8

从flash中将数据读取出来缓存进fifo

rdreq

input

1

uart_tx读请求

wrreq

input

1

spi_ctrl写请求

empty

output

1

uart_tx读数据空标志

full

output

1

spi_ctrl写数据满标志

q

output

8

将fifo中的数据发送给uart_tx进行输出

usedw

output

8

8、uart_tx模块信号列表

信号

I/O

BIT

功能

clk

input

1

时钟信号

rst_n

input

1

复位信号

tx_data

input

8

需要发送的数据

empty_tx

input

1

fifo_tx的空信号

rdreq_tx

output

1

向fifo_tx发送读请求

uart_tx

output

1

数据输出

  1. 三、代码片段

  2. 1、M23P16顶层模块

  3. module M25P16 #(parameter KEY_W = 3)(   //按键位宽
        input                  clk         ,//系统时钟
        input                  rst_n       ,//复位
        input                  miso        ,//主机输入,从机输出
        input                  uart_rx     ,//Rx端口
        input  [KEY_W - 1:0]   key_in      ,//按键
    
        output                 cs_n        ,//片选
        output                 mosi        ,//主机输出,从机输入
        output                 sclk        ,//spi时钟
        output                 uart_tx      //Tx端口
    );
    
    wire    [KEY_W - 1:0]   key     ;
    wire    [7:0]           rx_data ;
    wire    [7:0]           data_rx ;
    wire                    wrreq_rx;
    wire                    full_rx ;
    wire                    empty_rx;
    wire    [7:0]           tx_data ;
    wire    [7:0]           data_tx ;
    wire                    full_tx ;
    wire                    empty_tx;
    
    key_deshake #(.KEY_W(KEY_W))key_deshake_inst(
        /* input                             */.clk     (clk     ),//时钟信号
        /* input                             */.rst_n   (rst_n   ),//复位信号
        /* input           [KEY_W - 1:0]     */.key_in  (key_in  ),//按键输入信号
        /* output  reg     [KEY_W - 1:0]     */.key_out (key     ) //按键输出信号
    );
    
    uart_rx uart_rx_inst(
        /* input                */.clk     (clk     ),//系统时钟信号
        /* input                */.rst_n   (rst_n   ),//系统复位信号
        /* input                */.uart_rx (uart_rx ),//数据接收
        /* input                */.full_rx (full_rx ),//fifo_rx的满信号
        /* output reg           */.wrreq_rx(wrreq_rx),//向fifo_rx发送写请求
        /* output     [7:0]     */.rx_data (rx_data ) //数据串行接并行出给fifo_rx
    );
    
    fifo_rx fifo_rx_inst (
    	.aclr   (!rst_n     ),//fifo复位
    	.clock  (clk        ),//fifo时钟
    	.data   (rx_data    ),//fifo缓存uart_rx接收到的数据
    	.rdreq  (rdreq_rx   ),//spi_ctrl读请求
    	.wrreq  (wrreq_rx   ),//uart_rx写请求
    	.empty  (empty_rx   ),//spi_ctrl读数据空标志
    	.full   (full_rx    ),//uart_rx写数据满标志
    	.q      (data_rx    ),//fifo输出给spi_ctrl,将数据存储入flash
    	.usedw  (           )
    );
    
    spi_ctrl spi_ctrl_inst(
        /* input                */.clk     (clk     ),//系统    
        /* input                */.rst_n   (rst_n   ),//复位
        /* input       [01:0]   */.key_in  (key     ),//控制读、写 00:停止工作 01:写 10:读
        /* input       [07:0]   */.data_rx (data_rx ),//需要写入的数据(串口接收的数据)
        /* input                */.miso    (miso    ),//主机接收,从机输出
        /* input                */.empty_rx(empty_rx),//fifo_rx的空信号
        /* input                */.full_tx (full_tx ),//fifo_tx的满信号   
        /* output               */.mosi    (mosi    ),//主机输出,从机接收
        /* output               */.cs_n    (cs_n    ),//片选
        /* output               */.sclk    (sclk    ),//SPI 时钟
        /* output  reg [07:0]   */.data_tx (data_tx ),//读取到的数据(串口发送的数据)
        /* output               */.rdreq_rx(rdreq_rx),//向fifo_rx发送读请求
        /* output               */.wrreq_tx(wrreq_tx) //向fifo_tx发送写请求
    );
    
    fifo_tx fifo_tx_inst (
    	.aclr   (!rst_n     ),//fifo复位
    	.clock  (clk        ),//fifo时钟
    	.data   (data_tx    ),//从flash中将数据读取出来缓存进fifo
    	.rdreq  (rdreq_tx   ),//uart_tx读请求
    	.wrreq  (wrreq_tx   ),//spi_ctrl写请求
    	.empty  (empty_tx   ),//uart_tx读数据空标志
    	.full   (full_tx    ),//spi_ctrl写数据满标志
    	.q      (tx_data    ),//将fifo中的数据发送给uart_tx进行输出
    	.usedw  (           )
    );
    
    uart_tx uart_tx_inst(
        /* input                */.clk     (clk     ),//时钟信号
        /* input                */.rst_n   (rst_n   ),//复位信号
        /* input       [07:0]   */.tx_data (tx_data ),//需要发送的数据
        /* input                */.empty_tx(empty_tx),//fifo_tx的空信号
        /* output  reg          */.rdreq_tx(rdreq_tx),//向fifo_tx发送读请求   
        /* output  reg          */.uart_tx (uart_tx ) //数据输出
    );
    
    endmodule

    2、key_shake模块

  4. //状态机实现按键消抖																		
    module key_deshake #(parameter KEY_W = 3)//按键位宽
    (
        input                            clk     ,//时钟信号
        input                            rst_n   ,//复位信号
        input           [KEY_W - 1:0]    key_in  ,//按键输入信号
        output  reg     [KEY_W - 1:0]    key_out  //按键输出信号
    );
    
    
    parameter TIME_20ms = 1_000_000; //20ms延时常量声明
    
    parameter IDLE = 4'b0001,   //初始状态 
              DOWN = 4'b0010,   //检测到按键按下
              HOLD = 4'b0100,   //按键按下稳定的状态
              UP   = 4'b1000;   //按键弹起的状态
    
    reg [19:0]          cnt;
    wire                add_cnt;
    wire                end_cnt;
    
    reg [KEY_W - 1:0]   key_in_ff0; //同步打拍
    reg [KEY_W - 1:0]   key_in_ff1;
    reg [KEY_W - 1:0]   key_in_ff2;
    
    wire        posdge; //上升沿
    wire        negdge; //下降沿
    
    reg [3:0]   state_c  ;	//现态
    reg [3:0]   state_n  ;	//次态
    wire        idle2down;	//IDLE跳转到DOWN的转移条件
    wire        down2hold;	//DOWN跳转到HOLD的转移条件
    wire        hold2up  ;	//HOLD跳转到UP的转移条件
    wire        up2idle  ;	//UP跳转到IDLE的转移条件
    
    //同步 打拍 检测上升沿、下降沿
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            key_in_ff0 <= {KEY_W{1'b1}};
            key_in_ff1 <= {KEY_W{1'b1}};
            key_in_ff2 <= {KEY_W{1'b1}};
        end
        else begin
            key_in_ff0 <= key_in; 
            key_in_ff1 <= key_in_ff0;
            key_in_ff2 <= key_in_ff1;
        end
    end
    assign posdge = (&key_in_ff1) && ~(&key_in_ff2);//检测按键上升沿
    assign negdge = ~(&key_in_ff1) && (&key_in_ff2);//检测按键下降沿
    
    //消抖20ms计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt <= 20'b0;
        end
        else if(add_cnt) begin
            if(end_cnt) begin
                cnt <= 20'b0;
            end
            else begin
                cnt <= cnt + 20'b1;
            end
        end
        else
            cnt <= 20'b0;	//只有在DOWN和UP状态进行计数,其他状态清零
    end
    assign  add_cnt = state_c == DOWN || state_c == UP;      //计数器使能信号
    assign  end_cnt = add_cnt && cnt == TIME_20ms - 1; 
    
    //状态机描述按键消抖
    //同步时序描述状态转移
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            state_c <= IDLE;
        end
        else begin
            state_c <= state_n;
        end
    end
    
    //组合逻辑描述状态转移的条件
    always @(*) begin
        case(state_c)
        IDLE:begin
            if(idle2down) begin	//当满足条件时进行状态转移
                state_n = DOWN;
            end
            else begin
                state_n = state_c;
            end
        end
        DOWN:begin
            if(down2hold) begin
                state_n = HOLD;
            end
            else begin
                state_n = state_c;
            end
        end
        HOLD:begin
            if(hold2up) begin
                state_n = UP;
            end
            else begin
                state_n = state_c;
            end
        end
        UP  :begin
            if(up2idle) begin
                state_n = IDLE;
            end
            else begin
                state_n = state_c;
            end
        end
        endcase
    end
    
    //状态转移的条件
    assign  idle2down = state_c == IDLE && negdge	; //在初始状态IDLE检测到下降沿时跳转到DOWN
    assign  down2hold = state_c == DOWN && end_cnt	; //在DOWN下20ms计数器开始计数,计数到最大值时跳转到HOLD
    assign  hold2up   = state_c == HOLD && posdge	; //在HOLD下检测到上升沿时跳转到UP
    assign  up2idle   = state_c == UP   && end_cnt	; //在UP下20ms计数器开始计数,计数到最大值时跳转到IDLE
    
    //时序逻辑描述输出
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            key_out <= {KEY_W{1'b0}};
        end
        else if(hold2up) begin		//在DOWN跳转到HOLD或者在HOLD跳转到UP时进行输出,输出对输入进行取反
            key_out <= ~key_in_ff2;
        end
        else begin
            key_out <= {KEY_W{1'b0}};
        end
    end
    
    endmodule		

    3、uart_rx模块

  5. module uart_rx(
        input               clk     ,   //系统时钟信号
        input               rst_n   ,   //系统复位信号
        input               uart_rx ,   //数据接收
        input               full_rx ,   //fifo_rx的满信号
    
        output              wrreq_rx,   //向fifo_rx发送写请求
        output     [7:0]    rx_data     //数据串行接并行出
    
    );
    
    /**********************************相关常量定义***********************************
    *********************************************************************************/
    parameter   COUNT = 50_000_000; //系统时钟频率 周期20ns
    parameter   BPS   = 115200;     //波特率
    parameter   CNT   = COUNT / BPS;      
    parameter   BIT   = 10  ; //接收1帧的bit数
    
    parameter   IDLE    =   4'b0001,
                START   =   4'b0010,
                DATA    =   4'b0100,
                DONE    =   4'b1000;
    
    /**********************************相关变量定义***********************************
    *********************************************************************************/
    reg [3:0]   state_c;
    reg [3:0]   state_n;
    
    reg [15:0]  cnt_clk;
    wire        add_cnt_clk;
    wire        end_cnt_clk;
    
    reg [03:0]  cnt_bit;
    wire        add_cnt_bit;
    wire        end_cnt_bit;
    
    wire        rx_start;    //rx数据接收开始信号
    reg         rx_done ;      
    reg         data_reg;    //寄存uart_rx接收的数据
    reg [7:0]   data_out_reg;
    reg [7:0]   data    ;
    
    reg         uart_rx_reg0;//同步打拍
    reg         uart_rx_reg1;
    reg         uart_rx_reg2;
    
    reg         check       ;//判断接收到的数据是否正确
    
    /******************************向fifo_rx发送写请求********************************
    *********************************************************************************/
    assign  wrreq_rx = full_rx ? 'd0 : rx_done;
    
    /****************************对发送的数据进行同步打拍******************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            uart_rx_reg0 <= 1'b1;           
            uart_rx_reg1 <= 1'b1;           
            uart_rx_reg2 <= 1'b1;               
        end
        else begin
            uart_rx_reg0 <= uart_rx;
            uart_rx_reg1 <= uart_rx_reg0;
            uart_rx_reg2 <= uart_rx_reg1;
        end
    end
    assign rx_start = (~uart_rx_reg1 && uart_rx_reg2) && cnt_bit == 0; //检测到下降沿
    
    /**********************************相关计数器设计*********************************
    *********************************************************************************/
    //发送1bit所需要的周期数cnt_clk
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt_clk <= 0;
        end
        else if(state_c == IDLE) begin
            cnt_clk <= 0;
        end 
        else if(add_cnt_clk)begin 
            if(end_cnt_clk)begin 
                cnt_clk <= 0;
            end
            else begin 
                cnt_clk <= cnt_clk + 1;
            end 
        end
    end 
    assign add_cnt_clk = state_c == DATA;
    assign end_cnt_clk = add_cnt_clk && cnt_clk == CNT - 1;
    
    //发送一字节所需要的bit数cnt_bit
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt_bit <= 0;
        end
        else if(state_c == IDLE) 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_clk;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == BIT - 1;
    
    /**********************************rx_done信号设计********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            rx_done <= 'd0;
        end 
        else if(state_c == DATA && end_cnt_bit) begin 
            rx_done <= 'd1;
        end 
        else begin 
            rx_done <= 'd0;
        end 
    end
    
    /*********************************uart_rx状态机设计*******************************
    *********************************************************************************/
    //状态机第一段
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
    
    //第二段
    always @(*) begin
        case(state_c)
            IDLE:   begin
                        if(rx_start) begin
                            state_n = START;
                        end
                        else begin
                            state_n = IDLE;
                        end
                    end
            START:  begin
                        state_n = DATA;
                    end
            DATA:   begin   
                        if(end_cnt_bit) begin
                            state_n = DONE;
                        end
                        else if(check) begin
                            state_n = IDLE;
                        end
                        else begin
                            state_n = state_c;
                        end
                    end
            DONE:   begin
                        state_n = IDLE;
                    end
            default:state_n = IDLE;
        endcase 
    end
    
    //第三段
    //对接收到的数据进行寄存
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            data_reg <= 'd0;
        end 
        else if(state_c == DATA) begin 
            data_reg <= uart_rx_reg2;
        end 
        else begin 
            data_reg <= 'd0;
        end 
    end
    
    /*********************************检测数据是否是误接*******************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            check <= 'd0;
        end 
        else if(state_c == DATA && cnt_clk == CNT / 2 && cnt_bit == 0) begin 
            check <= data_reg;
        end 
        else if(state_c == DATA && cnt_clk == CNT / 2 && cnt_bit == 9)begin 
            check <= !data_reg;
        end
        else begin
            check <= 'd0;
        end 
    end
    
    /********************************接收到的数据寄存发送******************************
    *********************************************************************************/
    //对接收到的数据进行发送的寄存器 逐位寄存
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data_out_reg <= 8'h0;
        end 
        else if(state_c == DATA && cnt_clk == CNT / 2 && cnt_bit > 0 && cnt_bit < 9)begin  //只有在数据位才接收数据
            data_out_reg <= {data_reg,data_out_reg[7:1]};                                  //并且在每位数据传输时间的一般进行接收
        end 
        else if(state_c == DONE || state_c == IDLE) begin  //当接收数据完成时,对接收寄存器进行清零操作
            data_out_reg <= 8'h0;
        end
        else begin 
            data_out_reg <= data_out_reg;
        end 
    end
    
    //对接收到的数据进行发送 
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data <= 8'h0;
        end 
        else if(state_c == DATA && end_cnt_bit)begin //当bit计数器计数到最大值时,将接收到完整的数据赋值给数据发送端  
            data <= data_out_reg;
        end 
        else begin 
            data <= data;
        end 
    end
    
    assign rx_data = data;
    
    endmodule

    4、spi_ctrl模块

  6. module spi_ctrl(
        input               clk         ,//系统
        input               rst_n       ,//复位
        input       [02:0]  key_in      ,//控制读、写 00:停止工作 01:写 10:读
        input       [07:0]  data_rx     ,//需要写入的数据(串口接收的数据)
        input               miso        ,//主机接收,从机输出
        input               full_tx     ,//fifo_tx的满信号
        input               empty_rx    ,//fifo_rx的空信号
    
        output              mosi        ,//主机输出,从机接收
        output              cs_n        ,//片选
        output              sclk        ,//SPI 时钟
        output  reg         wrreq_tx    ,//向fifo_tx发送写请求
        output              rdreq_rx    ,//向fifo_rx发送读请求
        output  reg [07:0]  data_tx      //读取到的数据(串口发送的数据)
    );
    
    parameter   WAIT_WR =   100         ,   //两个指令间等待100ns  
                WAIT_PP =   250_000     ,   //页编程所需时间5ms
                WAIT_SE =   150_000_000 ;   //扇区擦除所需时间3s 
    
    parameter   BYTE_CNT_MAX =  'd260;
    
    parameter   IDLE    =   'd1,
                WREN    =   'd2,
                WAIT    =   'd3,
                SEPP    =   'd4,
                READ    =   'd5,
                DONE    =   'd6;
    
    reg [03:0]  state_c     ;
    reg [03:0]  state_n     ;
    
    reg [11:0]  BYTE_CNT    ;//字节数
    reg [11:0]  cnt_byte    ;//字节计数器
    wire        add_cnt_byte;
    wire        end_cnt_byte;
    
    reg [27:0]  WAIT_CNT    ;//等待时间
    reg [27:0]  cnt_wait    ;//等待计数器
    wire        add_cnt_wait;
    wire        end_cnt_wait;
    
    reg         sepp_flag   ;//0:执行D8h扇区擦除 1:执行02h页面写入
    reg [01:0]  wr_rd       ;//读写判断
    reg         req         ;//读写请求 相当于片选
    wire        done        ;//读写完一个字节
    reg         wr_rd_done  ;//读、写数据结束
    wire[07:0]  data_rd     ;//读取到的数据
    reg [07:0]  data_wr     ;//数据写入
    reg         rden        ;//读数据使能信号
    reg         wrreq_tx_reg;
    
    reg [07:0]  wr_addr     ;
    
    spi spi_inst(
        /* input                */.clk         (clk       ),//系统
        /* input                */.rst_n       (rst_n     ),//复位
        /* input                */.miso        (miso      ),//主机接收,从机输出
        /* input       [07:0]   */.data_wr     (data_wr   ),//写入的数据或指令
        /* input                */.req         (req       ),//读写请求
        /* input                */.rden        (rden      ),//读数据使能信号
        /* output               */.cs_n        (cs_n      ),//片选
        /* output               */.mosi        (mosi      ),//主机输出,从机接收
        /* output               */.sclk        (sclk      ),//SPI 时钟
        /* output               */.done        (done      ),//读写完1字节
        /* output      [07:0]   */.data_rd     (data_rd   ) //读取到的数据
    );
    
    /********************************fifo_rx/tx的读写请求*****************************
    *********************************************************************************/
    assign rdreq_rx = empty_rx ? 'd0 : state_c == SEPP && sepp_flag == 'd1 && cnt_byte >= 'd4 && done;
    // assign wrreq_tx = full_tx  ? 'd0 : state_c == READ && cnt_byte >= 'd4 && done; 
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            wrreq_tx_reg <= 'd0;
        end 
        else if(!full_tx && state_c == READ && cnt_byte >= 'd4 && done) begin 
            wrreq_tx_reg <= 'd1;
        end 
        else begin 
            wrreq_tx_reg <= 'd0;
        end 
    end
    
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            wrreq_tx <= 'd0;
        end 
        else begin 
            wrreq_tx <= wrreq_tx_reg;
        end 
    end
    /*************************************字节计数器**********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n) begin
            cnt_byte <= 'd0;
        end 
        else if(state_c == SEPP && sepp_flag == 'd1 && empty_rx || wr_addr == 'd255 && rdreq_rx) begin
            cnt_byte <= 'd0;
        end
        else if(add_cnt_byte) begin 
            if(end_cnt_byte) begin 
                cnt_byte <= 'd0;
            end
            else begin 
                cnt_byte <= cnt_byte + 'd1;
            end 
        end
    end 
    assign add_cnt_byte = done && (state_c == WREN || state_c == SEPP || state_c == READ);
    assign end_cnt_byte = add_cnt_byte && cnt_byte == BYTE_CNT - 'd1;
    
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            wr_addr <= 'd0;
        end
        else if(wr_addr == 'd255 && rdreq_rx) begin
            wr_addr <= 'd0;
        end
        else if(rdreq_rx) begin 
            wr_addr <= wr_addr + 'd1;
        end 
        else begin 
            wr_addr <= wr_addr;
        end 
    end
    
    always @(*) begin 
        if(!rst_n) begin
            BYTE_CNT <= 'd0;
        end 
        else begin 
            case(state_c)
                WREN   :BYTE_CNT = 'd1;
                SEPP   :if(sepp_flag)
                            BYTE_CNT = BYTE_CNT_MAX; //1字节指令 + 3字节地址 + 最多256的写入数据
                        else
                            BYTE_CNT = 'd4;          //1字节指令 + 3字节地址
                READ   :BYTE_CNT =  BYTE_CNT_MAX;    //1字节指令 + 3字节知道 + 最多256的读取数据
                default:BYTE_CNT = 'd0;
            endcase
        end 
    end
    
    /***********************************延迟等待计数器*********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n) begin
            cnt_wait <= 'd0;
        end 
        else if(add_cnt_wait) begin 
            if(end_cnt_wait) begin 
                cnt_wait <= 'd0;
            end
            else begin 
                cnt_wait <= cnt_wait + 'd1;
            end 
        end
    end 
    assign add_cnt_wait = state_c == WAIT;
    assign end_cnt_wait = add_cnt_wait && cnt_wait == WAIT_CNT - 1;
    
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            WAIT_CNT <= 'd0;
        end
        else begin
            case(state_c)
                WREN   :WAIT_CNT <= WAIT_WR;
                SEPP   :if(sepp_flag)
                            WAIT_CNT <= WAIT_PP;
                        else
                            WAIT_CNT <= WAIT_SE;         
                default:WAIT_CNT <= WAIT_CNT;
            endcase 
        end
    end
    
    /**********************************读/写判断及请求********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            wr_rd <= 2'b0;
        end 
        else if(key_in == 3'b001) begin 
            wr_rd <= 2'b01;
        end 
        else if(key_in == 3'b010) begin 
            wr_rd <= 2'b10;
        end 
        else if(wr_rd_done) begin
            wr_rd <= 2'b00;
        end
        else begin
            wr_rd <= wr_rd;
        end
    end
    
    always @(*) begin 
        if(!rst_n) begin
            req <= 'd1;
        end 
        else if(state_c == WREN || state_c == SEPP || state_c == READ) begin //读、写状态
            req <= 'd0;
        end 
        else if(state_c == IDLE || state_c == DONE || state_c == WAIT) begin //初始、等待、接收状态
            req <= 'd1;
        end
        else begin
            req <= req;
        end
    end
    
    /***********************************读/写结束信号*********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            wr_rd_done <= 'd0;
        end 
        else if((state_c == SEPP && sepp_flag == 'd1 && (end_cnt_byte || empty_rx)) || (state_c == READ && end_cnt_byte)) begin 
            wr_rd_done <= 'd1;
        end 
        else begin 
            wr_rd_done <= 'd0;
        end 
    end
    
    /*************************************SE/PP选择***********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            sepp_flag <= 'd0;
        end 
        else if(state_c == WAIT && WAIT_CNT == WAIT_SE && end_cnt_wait) begin //擦除结束,sepp_flag转为pp模式
            sepp_flag <= 'd1;
        end 
        else if(key_in == 3'b100/* state_c == WAIT && WAIT_CNT == WAIT_PP && end_cnt_wait */) begin //写数据结束,sepp_flag转化为SE模式
            sepp_flag <= 'd0;
        end 
        else begin
            sepp_flag <= sepp_flag;
        end
    end
    
    /***************************************状态机************************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
    
    always @(*) begin 
        case(state_c)
            IDLE   :begin
                    if(wr_rd == 2'b01)
                        state_n = WREN;
                    else if(wr_rd == 2'b10)
                        state_n = READ;
                    else
                        state_n = IDLE;
            end
            WREN   :begin
                    if(done)
                        state_n = WAIT;
                    else
                        state_n = WREN;
            end
            WAIT   :begin
                    if(end_cnt_wait && WAIT_CNT == WAIT_WR)
                        state_n = SEPP;
                    else if(end_cnt_wait && (WAIT_CNT == WAIT_PP || WAIT_SE))
                        state_n = DONE;
                    else
                        state_n = WAIT;
            end 
            SEPP   :begin
                    if(sepp_flag == 'd0 && end_cnt_byte && BYTE_CNT == 'd4) //扇区擦除
                        state_n = WAIT;
                    else if(sepp_flag == 'd1 && BYTE_CNT == BYTE_CNT_MAX && (end_cnt_byte || empty_rx)) //数据写入
                        state_n = WAIT;
                    else
                        state_n = SEPP;
            end
            READ   :begin
                    if(end_cnt_byte && BYTE_CNT ==  BYTE_CNT_MAX)
                        state_n = DONE;
                    else
                        state_n = READ;
            end
            DONE   :begin
                    state_n = IDLE;
            end
            default:state_n = IDLE;
        endcase 
    end
    
    /***********************************写入接口的数据********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            data_wr <= 'h0;
        end 
        else if(state_c == WREN) begin 
            data_wr <= 8'h06;
        end 
        else if(state_c == SEPP && sepp_flag == 'd0)begin //扇区擦除
            case(cnt_byte) 
                0:data_wr <= 8'hD8;
                1:data_wr <= 8'h00;
                2:data_wr <= 8'h00;
                3:data_wr <= 8'h00;
            endcase
        end 
        else if(state_c == SEPP && sepp_flag == 'd1) begin //写数据
            case(cnt_byte)
                0:data_wr <= 8'h02;
                1:data_wr <= 8'h00;
                2:data_wr <= 8'h00;
                3:data_wr <= wr_addr;
                default:data_wr <= data_rx;
            endcase
        end
        else if(state_c == READ) begin
            case(cnt_byte)
                0:data_wr <= 8'h03;
                1:data_wr <= 8'h00;
                2:data_wr <= 8'h00;
                3:data_wr <= 8'h00;
                default:data_wr <= 8'h0;
            endcase
        end
        else begin
            data_wr <= 'h0;
        end
    end
    
    /***********************************读取接口的数据********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            rden    <= 'd0;
        end 
        else if(state_c == READ) begin 
            case(cnt_byte)
                0,1,2,3:begin rden <= 'd0;end
                default:begin rden <= 'd1;end
            endcase
        end 
        else begin 
            rden <= 'h0;
        end 
    end
    
    always @(*) begin 
        if(!rst_n) begin
            data_tx <= 'd0;
        end 
        else if(wrreq_tx) begin 
            data_tx <= data_rd;        
        end 
        else begin 
            data_tx <= 'd0;
        end 
    end
    
    endmodule

    5、spi模块

  7. module spi (
        input               clk         ,//系统
        input               rst_n       ,//复位
        input               miso        ,//主机接收,从机输出
        input       [07:0]  data_wr     ,//写入的数据或指令
        input               req         ,//读写请求,低电平有效
        input               rden        ,//读数据使能信号
    
        output  reg         cs_n        ,//片选
        output  reg         mosi        ,//主机输出,从机接收
        output  reg         sclk        ,//SPI 时钟
        output  reg         done        ,//读写完1字节
        output  reg [07:0]  data_rd      //读取到的数据
    );
    
    reg [01:0]  cnt_sclk;
    reg [03:0]  cnt_bit ;
    reg [07:0]  data_rd_reg;
    
    /**************************************sclk时钟***********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            cnt_sclk <= 'd0;
        end 
        else if(req) begin
            cnt_sclk <= 'd0;
        end
        else if(cnt_sclk == 4 - 1) begin 
            cnt_sclk <= 'd0;
        end 
        else if(!req)begin 
            cnt_sclk <= cnt_sclk + 'd1;
        end 
        else begin
            cnt_sclk <= cnt_sclk;
        end
    end
    
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            sclk <= 'd1;
        end
        else if(cs_n) begin
            sclk <= 'd1;
        end 
        else if(cnt_sclk == 1) begin 
            sclk <= 'd0;
        end 
        else if(cnt_sclk == 3)begin 
            sclk <= 'd1;
        end 
        else begin
            sclk <= sclk;
        end
    end
    
    /**************************************cs_n片选***********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            cs_n <= 'd0;
        end 
        else begin 
            cs_n <= req;
        end 
    end
    
    /***********************************数据--位计数器********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            cnt_bit <= 'd0;
        end 
        else if(cnt_bit == 8 - 1 && cnt_sclk == 'd3) begin 
            cnt_bit <= 'd0;
        end 
        else if(cnt_sclk == 'd3)begin 
            cnt_bit <= cnt_bit + 'd1;
        end 
        else begin
            cnt_bit <= cnt_bit;
        end
    end
    
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            done <= 'd0;
        end 
        else if(cnt_bit == 8 - 1 && cnt_sclk == 'd2) begin 
            done <= 'd1;
        end 
        else begin 
            done <= 'd0;
        end 
    end
    
    /**************************************mosi信号***********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            mosi <= 'd0;
        end 
        else if(cnt_sclk == 'd1) begin 
            mosi <= data_wr[7 - cnt_bit];
        end 
        else begin 
            mosi <= mosi;
        end 
    end
    
    /**************************************data_rd***********************************
    *********************************************************************************/
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            data_rd_reg <= 'd0;
        end 
        else if(rden && cnt_sclk == 'd3) begin 
            data_rd_reg[7 - cnt_bit] <= miso;
        end 
        else begin 
            data_rd_reg <= data_rd_reg;
        end 
    end
    
    always @(*) begin 
        if(!rst_n) begin
            data_rd <= 'd0;
        end 
        else if(cnt_bit == 'd0) begin 
            data_rd <= data_rd_reg;
        end 
        else begin 
            data_rd <= 'd0;
        end 
    end
    
    endmodule
    

    6、uart_tx模块

  8. //UART发送端  无校验位 结束为1位
    module uart_tx(
        input           clk     ,//时钟信号
        input           rst_n   ,//复位信号
        input  [07:0]   tx_data ,//需要发送的数据
        input           empty_tx,//fifo_tx的空信号
    
        output          rdreq_tx,//向fifo_tx发送读请求 
        output  reg     uart_tx  //数据输出
    );
    
    /**********************************相关常量定义***********************************
    *********************************************************************************/
    parameter   COUNT = 50_000_000; //系统时钟频率 周期20ns
    parameter   BPS   = 115200;     //波特率
    parameter   CNT   = COUNT / BPS;//传输1bit所需计数次数 COUNT / BPS; 
    parameter   BIT   = 10  ;       //传输一个字节(1位起始位,8位数据位,1位结束为) 从低位开始发
    parameter   IDLE  = 4'b0001,
                START = 4'b0010,
                DATA  = 4'b0100,
                DONE  = 4'b1000;
    
    /**********************************相关信号定义***********************************
    *********************************************************************************/
    reg [15:0]  cnt_clk;
    wire        add_cnt_clk;
    wire        end_cnt_clk;
    
    reg [3:0]   cnt_bit;
    wire        add_cnt_bit;
    wire        end_cnt_bit;
    
    reg [3:0]   state_c;
    reg [3:0]   state_n;
    
    reg [7:0]   tx_data_reg ; 
    reg [7:0]   data    ; 
    wire        tx_start;
    wire        tx_done ;
    
    /*******************************向fifo_tx发送读请求********************************
    *********************************************************************************/
    assign rdreq_tx = ~empty_tx && tx_done;
    assign tx_start = tx_done ? ~empty_tx && tx_done : ~empty_tx;
    
    /*****************************对要输出的数据进行寄存*******************************
    *********************************************************************************/
    //tx_data_reg 对要输出的数据进行打拍
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            tx_data_reg <= 'd0;
        end 
        else if(tx_start) begin 
            tx_data_reg <= tx_data;
        end 
        else begin 
            tx_data_reg <= 'd0;
        end 
    end
    
    //data 对打拍后的数据进行寄存,为后续数据的输出做准备
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            data <= 'd0;
        end
        else if(state_c == DONE) begin
            data <= 'd0;
        end 
        else if(state_c == START)begin 
            data <= tx_data_reg;
        end 
        else begin
            data <= data;
        end
    end
    
    /**********************************相关计数器设计*********************************
    *********************************************************************************/
    //发送1bit所需要的周期数cnt_clk
    always @(posedge clk  or negedge rst_n) begin
        if(!rst_n) begin
            cnt_clk <= 13'b0;
        end
        else if(add_cnt_clk) begin
            if(end_cnt_clk) begin
                cnt_clk <= 13'b0;
            end
            else begin
                cnt_clk <= cnt_clk + 13'b1;
            end
        end
        else begin
            cnt_clk <= 13'b0;
        end
    end
    assign add_cnt_clk = state_c == DATA;
    assign end_cnt_clk = add_cnt_clk && cnt_clk == CNT - 1;
    
    //发送一字节所需要的bit数cnt_bit
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_bit <= 4'b0;
        end
        else if(add_cnt_bit) begin
            if(end_cnt_bit) begin
                cnt_bit <= 4'b0;
            end
            else begin
                cnt_bit <= cnt_bit + 4'b1;
            end
        end
        else begin
            cnt_bit <= cnt_bit;
        end
    end
    assign add_cnt_bit = end_cnt_clk;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == BIT - 1;
    
    //tx_done
    assign tx_done = end_cnt_bit;
    
    /**********************************Tx状态机设计***********************************
    *********************************************************************************/
    //状态机第一段
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            state_c <= IDLE;
        end 
        else begin 
            state_c <= state_n;
        end 
    end
    
    //第二段 状态转移
    always @(*) begin 
        case(state_c)
            IDLE:   begin
                        if(tx_start) begin
                            state_n = START;
                        end
                        else begin
                            state_n =IDLE;
                        end
                    end 
            START:  begin
                        state_n = DATA;
                    end
            DATA :  begin
                        if(end_cnt_bit) begin
                            state_n = DONE;
                        end
                        else begin
                            state_n = state_c;
                        end
                    end
            DONE :  begin
                        state_n = IDLE;
                    end
            default:state_n <= IDLE;
        endcase
    end
    
    //第三段 uart_tx输出
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n) begin
            uart_tx <= 'd1;
        end 
        else if(state_c == DATA) begin 
            case(cnt_bit) 
                0:uart_tx <= 1'b0          ;
                1:uart_tx <= data[0];
                2:uart_tx <= data[1];
                3:uart_tx <= data[2];
                4:uart_tx <= data[3];
                5:uart_tx <= data[4];
                6:uart_tx <= data[5];
                7:uart_tx <= data[6];
                8:uart_tx <= data[7];
                9:uart_tx <= 1'b1          ;
                default:uart_tx <= 1'b1;
            endcase
        end 
        else begin 
            uart_tx <= 1'b1;
        end 
    end
    
    endmodule

  9. 四、仿真波形

五、上板测试

在串口调试助手发送“成功驱动M25P16!(含回车)”,然后按下key_in[0],等待3s先进行扇区擦除,然后才进行数据的写入。

再按下key_in[1],读取FLASH中的数据。

再按下key_in[0],写入数据“再次写入数据成功!(含回车)”,再按下key_in[1]读取FLASH中的数据。

按下key_in[2],进行扇区擦除,在按下key_in[1]读取数据全为FF,说明擦除成功。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值