verilog Spi_Flash手册分析以及代码编写思路(M25P16)

一.特性总结

  • FLASH型号:M25P16,SPI总线兼容串行接口
  • Memory: 16Mbit,32个sector,每个扇区512kbit,每个扇区256页,每一页256byte.断电不会被擦除
  • 最高时钟频率:50Mhz
  • 传输顺序:从最高位开始传
  • spi:全双工串行通信

二.引脚介绍

在这里插入图片描述
在这里插入图片描述
** 四个用到的I/O **

©SCLK:spi时钟

(D)MOSI:主机输出从机接收

(Q)MISO:主机接收从机输出

CS# :设备选择(片选信号低电平有效 )

三. SPI模式(支持的时钟极性和时钟相位模式)

​ MODEL0:CPOL=0, CPHA=0(sclk起始电平为低电平,数据采样在第一个边沿,数据变化在第二个边沿)

​ MODEL2:CPOL=1, CPHA=1(sclk起始电平为高电平,数据采样在第二个边沿,数据变化在第一个边沿)

CPOL:决定起始电平,CPHA:决定采样边沿>

在这里插入图片描述

四. 操作功能

在这里插入图片描述

1.页编程:

(1)WREN 写指令完成

(2)Page_Program序列完成页写命令+3个地址+data

2.扇区擦除和块擦除

在Page_Program之前需要擦除块或者扇区的每一个bit为1

3.在写,编程和擦除时的轮询

WIP位的设置确保写,编程和擦除的完成

4.don’t care

5.Status Register
在这里插入图片描述

6.WIP bit.(写进程保护位):表示是否是在写状态寄存器,编程,擦除周期里

7.WEL bit.(写使能锁定):表示内部状态是否在写使能锁定状态

8.因为原理图将Write Protect (W) signal.拉高了,所以SPWD bit和BP2,BP1,BP0就不需要care了

五. 操作指南

根据自己需要,选择命令

在这里插入图片描述

根据M25P16手册可以得到以下信息

1.WREN(写使能)

在这里插入图片描述

2.RDSR(读寄存状态寄存器):

为了保证WREN或者SE是否完成可以通过读寄存状态器的最后两位(WEL,WIP)来判断

(1).WEL为1则WRSR,PP,SE,BE正在进行中,为0则没有进行这些操作

(2)WIP为1则内部写使能锁存,为0则写和擦除都可以操作

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

3.SE(扇区擦除):擦除前需要写使能,擦除后需要等待3s

在这里插入图片描述

4.PP(页编程):页写前需要写使能

(1.)发送命令+扇区地址+页地址+字地址

(2)发送数据

在这里插入图片描述

5. READ(读)

(1.)发送命令+扇区地址+页地址+字地址

(2).读取数据

在这里插入图片描述

六. AC特性表

在这里插入图片描述

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

1.tSHSL:一个命令结束后cs取消选择,跳转下一个命令开始cs被选择,中间至少需要100ns(保持cs高电平100ns).

2.tPP:页编写至少需要1.4ms后才能进行下一个命令操作

3.tSE:页擦除至少需要1s将该页擦除为1,通常等待擦除3s

七. 代码,整体框架,状态转移图

1.整体框架图

在这里插入图片描述

2.spi控制模块状态转移图

在这里插入图片描述

3.代码

(1).spi_interface(接口模块)

spi模式选择 CPOL=1,CPHA=1(起始电平高.第二个边沿采样)

module spi_interface #(parameter CPOL = 1,CPHA = 1)(
    input               clk           ,
    input               rst_n         ,

    input               req           ,//读写请求
    input    [7:0]      data_write    ,//写入的数据
    output   [7:0]      data_read     ,//读出的数据 
    output              done          ,//一个字节写完

    output    reg       sclk          ,//flash时钟
    output    reg       cs_n          ,//flash片选信号
    output    reg       mosi          ,//主机输出数据,从机接收数据
    input               miso           //主机接收数据,从机输出数据  
);
/* 参数定义 */
localparam              SCLK            = 4,        //给flash12.5M时钟
                        SCLK_HIGH       = SCLK/4*3, //spi时钟拉高
                        SCLK_LOW        = SCLK/4,   //spi时钟拉低
                        SCLK_HALF       = 2;        //

// 信号定义
reg    [7:0]    data_read_r;

reg             sclk_start_flag;//从机时钟开始标志
reg    [2:0]    sclk_cnt;
wire            add_sclk_cnt;
wire            end_sclk_cnt;

reg    [3:0]    bit_cnt;
wire            add_bit_cnt;
wire            end_bit_cnt;


//sclk_start_flag
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        sclk_start_flag <= 0;
    end 
    else if(req)begin 
        sclk_start_flag <= 1'b1;
    end 
    else begin 
        sclk_start_flag <= 1'b0; 
    end 
end

//SCLK计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        sclk_cnt <= 0;
    end 
   else if(sclk_start_flag == 0)begin
       sclk_cnt <= 0; 
    end
    else if(add_sclk_cnt)begin 
            if(end_sclk_cnt)begin 
                sclk_cnt <= 0;
            end
            else begin 
                sclk_cnt <= sclk_cnt + 1;
            end 
    end
   else  begin
       sclk_cnt <= sclk_cnt;
    end
end 

assign add_sclk_cnt = sclk_start_flag;
assign end_sclk_cnt = add_sclk_cnt && sclk_cnt == SCLK -1;

//SCLK
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        sclk <= 1;
    end
    else if (req ==0) begin
        sclk <= 1'b1;
    end 
    else if(sclk_cnt == SCLK_HIGH - 1 && add_sclk_cnt )begin 
        sclk <= 1'b1;
    end 
    else if(sclk_cnt == SCLK_LOW - 1 && add_sclk_cnt)begin 
        sclk <= 1'b0;
    end 

end

//CS_n#
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cs_n <= 1'b1;
    end 
    else if(req)begin 
        cs_n <= 1'b0;
    end 
    else if(~req)begin 
        cs_n <= 1'b1;
    end 
end

//bit_cnt计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        bit_cnt <= 0;
    end 
    else if(add_bit_cnt)begin 
            if(end_bit_cnt)begin 
                bit_cnt <= 0;
            end
            else begin 
                bit_cnt <= bit_cnt + 1;
            end 
    end
   else  begin
       bit_cnt <= bit_cnt;
    end
end 

assign add_bit_cnt = end_sclk_cnt;
assign end_bit_cnt = add_bit_cnt && bit_cnt == 8-1;

//写入flash数据,data_write,mosi
always @(negedge sclk or negedge rst_n)begin 
    if(!rst_n)begin
        mosi <= 0;
    end 
    else begin 
        mosi <=data_write[7-bit_cnt];
    end 
end

//读取flash数据,data_read,miso
always @(posedge sclk or negedge rst_n)begin 
    if(!rst_n)begin
        data_read_r <= 0;
    end 
    else begin 
        data_read_r[7-bit_cnt] <= miso;
    end 
end

assign data_read = data_read_r;
assign done = end_bit_cnt;
endmodule //spi_interface

(2).spi_control(spi控制模块)

module spi_control (
    input               clk           ,
    input               rst_n         ,
    input       [1:0]   key_down      ,//按键控制
    input       [7:0]   data_in       ,//输入的数据
    input               done          ,//读写完一个字节
    input       [7:0]   data_read     ,//读取的数据

    output  reg         req           ,//读写请求
    output  reg [7:0]   data_write    ,//写入的数据
    output  reg [7:0]   data_out       //输出的数据

);
/* 参数定义 */
localparam        IDLE   = 8'b0000_0001,   //状态                     
                  READ   = 8'b0000_0010,                    
                  WREN1  = 8'b0000_0100,                    
                  SE     = 8'b0000_1000,                    
                  WAIT   = 8'b0001_0000,                    
                  WREN2  = 8'b0010_0000,                    
                  PP     = 8'b0100_0000,                    
                  DONE   = 8'b1000_0000;                                  


localparam        READ_ADDR_SECTOR  = 8'b0000_0000,//地址
                  READ_ADDR_PAGE    = 8'b0000_0000,
                  READ_ADDR_WORD    = 8'b0000_0000,
                  WRITE_ADDR_SECTOR = 8'b0000_0000,
                  WRITE_ADDR_PAGE   = 8'b0000_0000,
                  WRITE_ADDR_WORD   = 8'b0000_0000;

localparam        READ_CMD          = 8'b0000_0011,//指令
                  READ_ID_CMD       = 8'b1001_1111,
                  WREN_CMD          = 8'b0000_0110,
                  SE_CMD            = 8'b1101_1000,
                  PP_CMD            = 8'b0000_0010;


/* 信号定义 */

reg  [7:0]      data_out_r  ;

reg             r_req;
reg             w_req;

reg  [4:0]      byte_cnt;//字节计数器
wire            add_byte_cnt;
wire            end_byte_cnt;
reg  [4:0]      byte_sel;

reg  [27:0]     delay_cnt;//延时计数器
wire            add_delay_cnt;
wire            end_delay_cnt;
reg             delay_flag;

reg  [7:0]      state_c ;//状态
reg  [7:0]      state_n ;

wire    idle_read  ;//状态转换条件
wire    idle_wren1 ;
wire    read_done  ;
wire    wren1_se   ;
wire    se_wait    ;
wire    wait_wren2 ;
wire    wren2_pp   ;
wire    pp_done    ;
wire    done_idle  ;


//状态转换
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        state_c <= IDLE;
    end 
    else begin 
        state_c <= state_n;
    end 
end

//状态转换条件
assign    idle_read  = state_c == IDLE  && r_req;  
assign    idle_wren1 = state_c == IDLE  && w_req;
assign    read_done  = state_c == READ  && end_byte_cnt; 
assign    wren1_se   = state_c == WREN1 && end_delay_cnt;   
assign    se_wait    = state_c == SE    && end_byte_cnt;
assign    wait_wren2 = state_c == WAIT  && end_delay_cnt;
assign    wren2_pp   = state_c == WREN2 && end_delay_cnt;   
assign    pp_done    = state_c == PP    && end_byte_cnt;
assign    done_idle  = state_c == DONE  && 1'b1;


//状态转换规律
always @(*) begin
    case(state_c)
        IDLE  : 
                if (idle_read) begin
                    state_n = READ;            
                end 
                else if (idle_wren1) begin
                    state_n = WREN1;            
                end 
                else begin
                    state_n = state_c;
                end
        READ  : 
                if (read_done) begin
                    state_n = DONE;            
                end 
                else begin
                    state_n = state_c;
                end
        WREN1 : 
                if (wren1_se) begin
                    state_n = SE;            
                end 
                else begin
                    state_n = state_c;
                end
        SE    : 
                if (se_wait) begin
                    state_n = WAIT;            
                end 
                else begin
                    state_n = state_c;
                end
        WAIT  : 
                if (wait_wren2) begin
                    state_n = WREN2;            
                end 
                else begin
                    state_n = state_c;
                end
        WREN2 : 
                if (wren2_pp) begin
                    state_n = PP;            
                end 
                else begin
                    state_n = state_c;
                end
        PP    : 
                if (pp_done) begin
                    state_n = DONE;            
                end 
                else begin
                    state_n = state_c;
                end
        DONE  : 
                if (done_idle) begin
                    state_n = IDLE;            
                end 
                else begin
                    state_n = state_c;
                end
        default : state_n <= state_c ;
    endcase
end

//r_req,w_req
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        r_req <= 0;
        w_req <= 0;
    end 
    else if(key_down[0])begin 
        r_req <= 1;      
    end 
    else if(key_down[1])begin 
        w_req <= 1;       
    end 
    else begin 
        r_req <= 0;
        w_req <= 0;        
    end 
end


//字节计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        byte_cnt <= 0;
    end 
    else if(add_byte_cnt)begin 
            if(end_byte_cnt)begin 
                byte_cnt <= 0;
            end
            else begin 
                byte_cnt <= byte_cnt + 1;
            end 
    end
   else  begin
       byte_cnt <= byte_cnt;
    end
end 

assign add_byte_cnt = (state_c == READ || state_c == WREN1 || state_c == SE || state_c == WREN2 || state_c == PP) && done;
assign end_byte_cnt = add_byte_cnt && byte_cnt == byte_sel-1;

//byte_sel,可以自己选择读写字节的数目
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        byte_sel <= 0;
    end 
    else if(state_n == READ)begin 
        byte_sel <= 5;
    end 
    else if(state_n == WREN1)begin 
        byte_sel <= 1;
    end 
    else if(state_n == SE)begin 
        byte_sel <= 4;
    end 
    else if(state_n == WREN2)begin 
        byte_sel <= 1;
    end 
    else if(state_n == PP)begin 
        byte_sel <= 10;
    end 
    else begin 
        byte_sel <= 1;
    end 
end


//延时delay_flag
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        delay_flag <= 0;
    end 
    else if(wren1_se || se_wait || wren2_pp || wait_wren2)begin 
        delay_flag <= 1'b0;
    end 
    else if(( state_c == WREN1 || state_c == SE || state_c == WREN2 ) && end_byte_cnt)begin 
        delay_flag <= 1'b1;
    end 
    else if(state_c == WAIT)begin 
        delay_flag <= 1'b1;
    end 

end

//延时计数器3s或者100ns
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        delay_cnt <= 0;
    end 
    else if(add_delay_cnt)begin 
            if(end_delay_cnt)begin 
                delay_cnt <= 0;
            end
            else begin 
                delay_cnt <= delay_cnt + 1;
            end 
    end
   else  begin
       delay_cnt <= delay_cnt;
    end
end 

assign add_delay_cnt = delay_flag ;
assign end_delay_cnt = add_delay_cnt && delay_cnt == ((state_c == WAIT)?150_000_000-1:5-1);


//data_wite传入给接口模块,data_read,data_out
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        data_write <= 0;
    end 
    else if(state_c == READ)begin 
        case(byte_cnt) 
            0:data_write = READ_CMD;
            1:data_write = READ_ADDR_SECTOR;
            2:data_write = READ_ADDR_PAGE  ;
            3:data_write = READ_ADDR_WORD  ;
            4:data_out   = data_read       ;
        default:data_out = data_read;
        endcase
    end 
    else if(state_c == WREN1)begin 
        case(byte_cnt) 
            0:data_write = WREN_CMD;
        default:data_write = 8'b0000_0000;
        endcase
    end
    else if(state_c == SE)begin 
        case(byte_cnt) 
            0:data_write = SE_CMD;
            1:data_write = WRITE_ADDR_SECTOR;
            2:data_write = WRITE_ADDR_PAGE  ;
            3:data_write = WRITE_ADDR_WORD  ;
        default:data_write = 8'b0000_0000;
        endcase
    end  
    else if(state_c == WREN2)begin 
        case(byte_cnt) 
            0:data_write = WREN_CMD;
        default:data_write = 8'b0000_0000;
        endcase
    end     
    else if(state_c == PP)begin 
        case(byte_cnt) 
            0:data_write = PP_CMD;
            1:data_write = WRITE_ADDR_SECTOR;
            2:data_write = WRITE_ADDR_PAGE  ;
            3:data_write = WRITE_ADDR_WORD  ;
            4:data_write = data_in          ;
        default:data_write = 8'b0000_0000;
        endcase
    end 
    else begin 
        
    end 
end

//req传入接口模块
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        req <= 0;
    end 
        else if(end_byte_cnt || delay_flag)begin 
        req <= 1'b0;
    end 
    else if(( state_c == WREN1 || state_c == SE || state_c == WREN2 || state_c == READ || state_c == PP))begin 
        req <= 1;
    end 
end
endmodule //spi_control

(3).top(顶层模块)

module top (
    input           clk     ,
    input           rst_n   ,
    input    [1:0]  key_in  ,

    output  [7:0]  seg_dig  ,
    output  [5:0]  seg_sel  ,   

    output          sclk    , 
    output          cs_n    ,
    output          mosi    ,
    input           miso           

);
wire [7:0]    data_read;
wire [7:0]    data_write;
wire [7:0]    data_out;
wire          req;
wire          done;
wire  [1:0]   key_down;   
spi_control u_spi_control(
    /* input              */  .clk        (clk       )    ,
    /* input              */  .rst_n      (rst_n     )    ,
    /* input       [1:0]  */  .key_down   (key_down  )    ,//按键控制
    /* input       [7:0]  */  .data_in    (8'b0110_0110   )    ,//输入的数据
    /* input              */  .done       (done      )    ,//读写完一个字节
    /* input       [7:0]  */  .data_read  (data_read )    ,//读取的数据

    /* output             */  .req        (req       )    ,//读写请求
    /* output  reg [7:0]  */  .data_write (data_write)    ,//写入的数据
    /* output  reg [7:0]  */  .data_out   (data_out  )     //输出的数据

);

spi_interface u_spi_interface (
    /* input           */     .clk         (clk       )    ,
    /* input           */     .rst_n       (rst_n     )    ,

    /* input           */     .req         (req       )    ,//读写请求
    /* input    [7:0]  */     .data_write  (data_write)    ,//写入的数据
    /* output   [7:0]  */     .data_read   (data_read )    ,//读出的数据 
    /* output          */     .done        (done      )    ,//一个字节写完

    /* output          */     .sclk        (sclk      )    ,//flash时钟
    /* output          */     .cs_n        (cs_n      )    ,//flash片选信号
    /* output          */     .mosi        (mosi      )    ,//主机输出数据,从机接收数据
    /* input           */     .miso        (miso      )     //主机接收数据,从机输出数据  
);

seg u_seg(
   /*  input        */  .clk      (clk    ) ,
   /*  input        */  .rst_n    (rst_n  ) ,
   /*  input  [23:0 */  .data_in  ({4'b0,4'b0,4'b0,4'b0,data_out[7:4],data_out[3:0]}) ,//输入的数据

   /*  output [7:0] */  .seg_dig  (seg_dig) ,//数码管段选 + 小数点
   /*  output [5:0] */  .seg_sel  (seg_sel)  //数码管位选
);

key_debounce u_key_debounce (
/* 	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_down)	 //检测到按下,输出一个周期的高脉冲,其他时刻为0
);
endmodule //top

版权声明:本文为worse_or_better原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。