【FPGA】SPI读写flash

这篇具有很好参考价值的文章主要介绍了【FPGA】SPI读写flash。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、spi协议

1.1spi简介

SPI是同步全双工通信,通信原理以主从方式工作,通常有一个主设备和一个或多个从设备,需要4根线连接:MISO(主设备数据输入)、MOSI(主设备输出)、SCLK(时钟)、CS(片选)。通常拉低对应从机的片选来收发数据。

MISO:主设备输入,从设备输出

MOSI:主设备输出,从设备输入

SCLK:时钟信号,由主设备产生

CS:从设备使能信号,由主设备控制

1.2时钟极性CPOL和时钟相位CPHA

时钟极性和时钟相位共同决定了读取数据的方式。

时钟极性CPOL = 0 : 同步时钟SCLK在空闲时为低电平

时钟极性CPOL = 1 : 同步时钟SCLK在空闲时为高电平;

时钟相位CPHA = 0 : 在同步时钟信号SCLK的第一个跳变沿采样

时钟相位CPHA = 1 : 在同步时钟信号SCLK的第二个跳变沿采样

我使用的FLASH只适用模式0(CPOL=0,CPHA=0)和模式3(CPOL=1,CPHA=1)。

【FPGA】SPI读写flash

1.3整体传输过程

  • 主机先将cs片选信号拉低,这样保证开始传输数据

  • 当接收端检测到时钟的第二个边沿信号时,它将立即读取数据线上的信号,这样就得到了1bit的数据。(前沿输出,后沿采样

  • 主机发送给从机时,主机产生相应的时钟信号(SCLK),然后数据一位一位地从M0SI信号线上发送给从机(先发高位,再发低位

  • 主机接收从机数据时,从机需要将数据送回主机,则主机将继续生成预定数量分频的时钟信号,并且从机通过MISO信号线发送数据给主机(先发高位,再发低位)

2、flash手册

Flash有16Mbits的存储空间,一共有32个扇区,每个扇区256页,每页256字节,可以单扇区擦除或者整块擦除。

SPI总线兼容串行接口,所以可以用spi协议来读写flash。

2.1flash信号

信号逻辑图:

【FPGA】SPI读写flash

指令表

【FPGA】SPI读写flash

RDID

8'h9F

读ID

RDSR

8'h05

读寄存器

READ

8'h03

读数据

PP

8'h02

页擦除

SE

8'hD8

扇区擦除

2.2读写时序

写使能时序

写使能WREN:1字节的指令

(WREN)指令设置写使能锁存器(WEL)位。必须在每个页编程(PP)、扇区擦除(SE)、全擦除(BE)和写入状态寄存器(WRSR)指令之前设置写使能锁存器(WEL)位拉高

【FPGA】SPI读写flash

读ID时序

读ID:1字节的指令、3字节的ID数据

读取标识(RDID)指令允许读取8位的制造商标识,然后是两个字节的设备标识,也就是3字节的数据。其中设备标识由设备制造商指定,第一个字节的内存类型,以第二个字节表示设备的内存容量。

【FPGA】SPI读写flash

读状态寄存器

读状态寄存器RDSR:发送1字节的指令、接收2字节的数据

【FPGA】SPI读写flash

状态寄存器可以在任何时候被读取,即使在一个程序、擦除或写状态寄存器周期正在进行中。

【FPGA】SPI读写flash

WIP位:“正在写入”(WIP)位指内存是忙于写入状态寄存器、编程还是擦除周期。当设置为1时,这样的循环正在进行,当重置为0时,表示擦除完成。

WEL位 :写使能锁存器(WEL)位表示内部写使能锁存器的状态。当设置为0时,不能进行写状态寄存器、页编程或擦除指令,相当于重新进行写使能让WEL拉高。

读数据时序

一字节的读取数据字节(READ)指令,3字节地址(24bits),1字节的数据。

在每个字节数据读取后,地址自动增加到下一个更高的地址。因此,可以用单个的读取数据字节(READ)指令来读取整个内存。

【FPGA】SPI读写flash

页编程时序

在页编程之前,必须先执行了写使能(WREN)指令。在执行(WREN)指令,设备将设置写使能锁存器(WEL)拉高。如果写使能锁存位为0,则必须再执行写使能指令,没有置位(一般都会置位),为1时可以进行页编程。

当页编程正在进行时,可以读取状态寄存器以检查正在写入(WIP)位的值。在页编程期间,正在写入(WIP)位为1,页编程完成时为0。

一字节指令、3字节地址、一字节数据

【FPGA】SPI读写flash

扇区擦除时序

扇区擦除(SE)指令之前,必须先执行了写使能(WREN)指令。在写使能(WREN)指令执行后,写使能锁存器(WEL)位拉高

当扇区擦除周期正在进行时,可以读取状态寄存器以检查正在写入(WIP)位的值。在扇区擦除周期中,正在进行的写入(WIP)位为1,在擦除完成时为0。

一字节指令、三字节地址

【FPGA】SPI读写flash

2.3页编程和扇区擦除的时间

【FPGA】SPI读写flash
【FPGA】SPI读写flash

tpp

1.4-5ms

页编程的时间

tSE

1-3s

扇区擦除的时间

tSHSL

100ns

一次操作结束到下一次开始的时间

3、项目设计

3.1flash写模块

主状态机:

【FPGA】SPI读写flash

从状态机:

【FPGA】SPI读写flash
module flash_write(

    input               clk         ,
    input               rst_n       ,
    
    //key
    input               write       ,
    input   [ 7:0]      wr_data     ,//写入的数据
    input   [23:0]      wr_addr     ,//flash写地址

    //spi_master
    output              trans_req   ,
    output  [7:0]       tx_dout     ,
    input   [7:0]       rx_din      ,
    input               trans_done  ,

    //output
    output  [47:0]      dout        ,
    output  [5:0]       dout_mask   ,
    output              dout_vld    
);

//参数定义
    //主状态机状态参数
    localparam  M_IDLE = 5'b0_0001,
                M_WREN = 5'b0_0010,//主机发写使能序列
                M_SE   = 5'b0_0100,//主机发扇区擦除序列
                M_RDSR = 5'b0_1000,//主机发读状态寄存器序列
                M_PP   = 5'b1_0000;//主机发页编程序列
    //从状态机状态参数
    localparam  S_IDLE = 5'b0_0001,
                S_CMD  = 5'b0_0010,//发送命令
                S_ADDR = 5'b0_0100,//发送地址
                S_DATA = 5'b0_1000,//发送数据、接收数据
                S_DELA = 5'b1_0000;//延时 拉高片选信号

    localparam  CMD_WREN = 8'h06,//写使能命令
                CMD_SE   = 8'hD8,//擦除命令
                CMD_RDSR = 8'h05,//读状态寄存器命令
                CMD_PP   = 8'h02;//页编程命令

    parameter   TIME_DELAY = 16,//发完WREN、SE、PP序列 拉高片选
                TIME_SE    = 15_000,//150_000_000,//扇区擦除3s
                TIME_PP    = 2500,//25_000,//页编程5ms
                TIME_RDSR  = 2000;//读状态寄存器 最大2000次

//信号定义

    reg         [4:0]       m_state_c       ;
    reg         [4:0]       m_state_n       ;
    reg         [4:0]       s_state_c       ;
    reg         [4:0]       s_state_n       ;

    reg         [3:0]       cnt0            ;
    wire                    add_cnt0        ;
    wire                    end_cnt0        ;
    reg         [3:0]       xx              ;

    reg         [31:0]      cnt1            ;
    wire                    add_cnt1        ;
    wire                    end_cnt1        ;
    reg            [31:0]      yy                ;

    reg                     rdsr_wip        ;
    reg                     rdsr_wel        ;
    reg         [2:0]       flag            ;
    
    reg         [7:0]       tx_data         ;
    reg                     tx_req          ;
    
    reg         [47:0]      data            ;
    reg         [5:0]       data_mask       ;
    reg                     data_vld        ;

    wire                    m_idle2m_wren   ; 
    wire                    m_wren2m_se     ; 
    wire                    m_se2m_rdsr     ; 
    wire                    m_rdsr2m_wren   ; 
    wire                    m_rdsr2m_pp     ; 
    wire                    m_wren2m_pp     ; 
    wire                    m_rdsr2m_idle   ; 
    wire                    m_pp2m_rdsr     ; 

    wire                    s_idle2s_cmd    ; 
    wire                    s_cmd2s_addr    ; 
    wire                    s_cmd2s_data    ; 
    wire                    s_cmd2s_dela    ; 
    wire                    s_addr2s_data   ; 
    wire                    s_addr2s_dela   ; 
    wire                    s_data2s_dela   ; 
    wire                    s_dela2s_idle   ; 

//状态机设计
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            m_state_c <= M_IDLE ;
        end
        else begin
            m_state_c <= m_state_n;
       end
    end
    
    always @(*) begin 
        case(m_state_c)  
            M_IDLE :begin
                if(m_idle2m_wren)
                    m_state_n = M_WREN ;
                else 
                    m_state_n = m_state_c ;
            end
            M_WREN :begin
                if(m_wren2m_se)
                    m_state_n = M_SE ;
                else if(m_wren2m_pp)
                    m_state_n = M_PP ;
                else 
                    m_state_n = m_state_c ;
            end
            M_SE :begin
                if(m_se2m_rdsr)
                    m_state_n = M_RDSR ;
                else 
                    m_state_n = m_state_c ;
            end
            M_RDSR :begin
                if(m_rdsr2m_wren)
                    m_state_n = M_WREN ;
                else if(m_rdsr2m_pp)
                    m_state_n = M_PP ;
                else if(m_rdsr2m_idle)
                    m_state_n = M_IDLE ;
                else 
                    m_state_n = m_state_c ;
            end
            M_PP :begin
                if(m_pp2m_rdsr)
                    m_state_n = M_RDSR ;
                else 
                    m_state_n = m_state_c ;
            end
            default : m_state_n = M_IDLE ;
        endcase
    end
    
    assign m_idle2m_wren = m_state_c==M_IDLE && (write);
    assign m_wren2m_se   = m_state_c==M_WREN && (s_dela2s_idle && flag[0]);
    assign m_se2m_rdsr   = m_state_c==M_SE   && (s_dela2s_idle);
    assign m_rdsr2m_wren = m_state_c==M_RDSR && (s_dela2s_idle && ~rdsr_wel && ~rdsr_wip && flag[1]);
    assign m_rdsr2m_pp   = m_state_c==M_RDSR && (s_dela2s_idle && rdsr_wel && ~rdsr_wip && flag[1]);
    assign m_wren2m_pp   = m_state_c==M_WREN && (s_dela2s_idle && flag[1]);
    assign m_rdsr2m_idle = m_state_c==M_RDSR && (s_dela2s_idle && flag[2]);
    assign m_pp2m_rdsr   = m_state_c==M_PP   && (s_dela2s_idle);
    
 //从状态机设计
        
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            s_state_c <= S_IDLE ;
        end
        else begin
            s_state_c <= s_state_n;
       end
    end
    
    always @(*) begin 
        case(s_state_c)  
            S_IDLE :begin
                if(s_idle2s_cmd)
                    s_state_n = S_CMD ;
                else 
                    s_state_n = s_state_c ;
            end
            S_CMD :begin
                if(s_cmd2s_addr)
                    s_state_n = S_ADDR ;
                else if(s_cmd2s_data)
                    s_state_n = S_DATA ;
                else if(s_cmd2s_dela)
                    s_state_n = S_DELA ;
                else 
                    s_state_n = s_state_c ;
            end
            S_ADDR :begin
                if(s_addr2s_data)
                    s_state_n = S_DATA ;
                else if(s_addr2s_dela)
                    s_state_n = S_DELA ;
                else 
                    s_state_n = s_state_c ;
            end
            S_DATA :begin
                if(s_data2s_dela)
                    s_state_n = S_DELA ;
                else 
                    s_state_n = s_state_c ;
            end
            S_DELA :begin
                if(s_dela2s_idle)
                    s_state_n = S_IDLE ;
                else 
                    s_state_n = s_state_c ;
            end
            default : s_state_n = S_IDLE ;
        endcase
    end
    
    assign s_idle2s_cmd  = s_state_c==S_IDLE && (m_state_c != M_IDLE);
    assign s_cmd2s_addr  = s_state_c==S_CMD  && (trans_done && (m_state_c == M_SE || m_state_c == M_PP));
    assign s_cmd2s_data  = s_state_c==S_CMD  && (trans_done && m_state_c == M_RDSR);
    assign s_cmd2s_dela  = s_state_c==S_CMD  && (trans_done && m_state_c == M_WREN);
    assign s_addr2s_data = s_state_c==S_ADDR && (end_cnt0 && (m_state_c == M_RDSR || m_state_c == M_PP));
    assign s_addr2s_dela = s_state_c==S_ADDR && (end_cnt0 && m_state_c == M_SE);
    assign s_data2s_dela = s_state_c==S_DATA && (end_cnt0 && m_state_c == M_PP || m_state_c == M_RDSR && end_cnt1);
    assign s_dela2s_idle = s_state_c==S_DELA && (end_cnt1);
    
    //计数器设计
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt0 <= 0; 
        end
        else if(add_cnt0) begin
            if(end_cnt0)
                cnt0 <= 0; 
            else
                cnt0 <= cnt0+1 ;
       end
    end
    assign add_cnt0 = (m_state_c != M_RDSR && trans_done);
    assign end_cnt0 = add_cnt0 && cnt0 == (xx)-1 ;
    
    always  @(*)begin
        if(s_state_c == S_CMD)  //发命令1字节
            xx = 1;
        else if(s_state_c == S_ADDR)    //发地址3字节
            xx = 3;
        else /*if(s_state_c == S_DATA)*/ //发数据1字节
            xx = 4;
    end
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt1 <= 0; 
        end
        else if(add_cnt1) begin
            if(end_cnt1)
                cnt1 <= 0; 
            else
                cnt1 <= cnt1+1 ;
       end
    end
    assign add_cnt1 = (s_state_c == S_DELA || m_state_c == M_RDSR && s_state_c == S_DATA && trans_done);
    assign end_cnt1 = add_cnt1  && (cnt1 == (yy)-1 || trans_done && ~rdsr_wip);
    
    always  @(*)begin
        if((m_state_c == M_WREN || m_state_c == M_RDSR) && s_state_c == S_DELA)  //发完写使能延时
            yy = TIME_DELAY;
        else if(m_state_c == M_SE)//发完擦除延时
            yy = TIME_SE;
        else if(m_state_c == M_PP)//发完页编程延时
            yy = TIME_PP;
        else if(m_state_c == M_RDSR && s_state_c == S_DATA)//读状态寄存器值 yy 次
            yy = TIME_RDSR;
         else 
            yy = TIME_DELAY;
    end

//rdsr_wel rdsr_wip
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rdsr_wel <= 1'b0;
            rdsr_wip <= 1'b0;
        end
        else if(m_state_c == M_RDSR && s_state_c == S_DATA && trans_done)begin
            rdsr_wel <= rx_din[1];
            rdsr_wip <= rx_din[0];
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 3'b000;
        end
        else if(m_idle2m_wren)begin
            flag <= 3'b001;
        end
        else if(m_se2m_rdsr)begin
            flag <= 3'b010;
        end
        else if(m_pp2m_rdsr)begin
            flag <= 3'b100;
        end
    end

//输出    

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_data <= 0;
        end
        else if(m_state_c == M_WREN)begin
            tx_data <= CMD_WREN;
        end
        else if(m_state_c == M_SE)begin
            case(s_state_c)
                S_CMD :tx_data <= CMD_SE;
                S_ADDR:tx_data <= wr_addr[23-cnt0*8 -:8];
                default:tx_data <= 0;
            endcase 
        end
        else if(m_state_c == M_RDSR)begin   //在读状态寄存器时,可以发一次命令,也可以连续发命令
            tx_data <= CMD_RDSR;
        end 
        else if(m_state_c == M_PP)begin 
            case(s_state_c)
                S_CMD :tx_data <= CMD_PP;
                S_ADDR:tx_data <= wr_addr[23-cnt0*8 -:8];
                S_DATA:tx_data <= wr_data + cnt0;
                default:tx_data <= 0;
            endcase 
        end 
    end

//tx_req  cs_n
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_req <= 1'b0;
        end
        else if(s_idle2s_cmd)begin
            tx_req <= 1'b1;
        end
        else if(s_cmd2s_dela | s_addr2s_dela | s_data2s_dela)begin
            tx_req <= 1'b0;
        end
    end
//data    data_mask
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data <= 0;
            data_mask <= 0;
        end 
        else if(m_rdsr2m_idle & ~rdsr_wip)begin //PP completed
            data <= {"P","P",16'd0,4'd0,wr_data[7:4],4'd0,wr_data[3:0]};
            data_mask <= 6'b001100;
        end
        else if(m_rdsr2m_idle & rdsr_wip)begin //PP failed
            data <= {"P","P",8'd0,"ERR"};
            data_mask <= 6'b001000;
        end  
    end

//data_vld   ,
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data_vld <= 0;
        end 
        else begin 
            data_vld <= m_rdsr2m_idle;
        end 
    end

//输出
    assign tx_dout = tx_data;
    assign trans_req = tx_req;
    assign dout_vld = data_vld;
    assign dout_mask = data_mask;
    assign dout = data;

endmodule

3.2 flash读模块

【FPGA】SPI读写flash
module flash_read(

    input               clk         ,
    input               rst_n       ,
    
    //key
    input               rd_id       ,
    input               rd_data     ,
    
    input   [23:0]      rd_addr     ,//flash读地址

    //spi_master
    output              trans_req   ,
    output  [7:0]       tx_dout     ,
    input   [7:0]       rx_din      ,
    input               trans_done  ,

    //output
    output  [47:0]      dout        ,
    output  [5:0]       dout_mask   ,
    output              dout_vld    
);

/*********  工程注释        ****************

M25P16 Flash读控制器,实现读数据和读存储器的ID。

**********  注释结束    ****************/

//状态机参数定义
    localparam  IDLE = 4'b0001,
                RDID = 4'b0010,//读器件ID
                RDDA = 4'b0100,//读数据字节
                DONE = 4'b1000;
//Flash命令参数定义
    localparam  CMD_RDID = 8'h9F,
                CMD_RDDA = 8'h03;

//信号定义

    reg     [3:0]       state_c     ;
    reg     [3:0]       state_n     ;

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

    reg     [7:0]       tx_data     ;
    reg                 tx_req      ;
    
    reg                 flag        ;//读数据、读ID标志

    wire                idle2rdid   ; 
    wire                rdid2done   ; 
    wire                idle2rdda   ; 
    wire                rdda2done   ; 
    wire                done2idle   ; 
    
    reg     [31:0]      rx_data    /* synthesis keep */;//串并转换寄存器
    reg     [47:0]      data        ;
    reg     [5:0]       data_mask   ;
    reg                 data_vld    ;

//状态机
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            state_c <= IDLE ;
        end
        else begin
            state_c <= state_n;
       end
    end
    
    always @(*) begin 
        case(state_c)  
            IDLE :begin
                if(idle2rdid)
                    state_n = RDID ;
                else if(idle2rdda)
                    state_n = RDDA ;
                else 
                    state_n = state_c ;
            end
            RDID :begin
                if(rdid2done)
                    state_n = DONE ;
                else 
                    state_n = state_c ;
            end
            RDDA :begin
                if(rdda2done)
                    state_n = DONE ;
                else 
                    state_n = state_c ;
            end
            DONE :begin
                if(done2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            default : state_n = IDLE ;
        endcase
    end
    
    assign idle2rdid = state_c==IDLE && (rd_id  );
    assign rdid2done = state_c==RDID && (end_cnt_byte);
    assign idle2rdda = state_c==IDLE && (rd_data);
    assign rdda2done = state_c==RDDA && (end_cnt_byte);
    assign done2idle = state_c==DONE && (1'b1);
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_byte <= 0; 
        end
        else if(add_cnt_byte) begin
            if(end_cnt_byte)
                cnt_byte <= 0; 
            else
                cnt_byte <= cnt_byte+1 ;
       end
    end
    assign add_cnt_byte = (state_c != IDLE && trans_done);
    assign end_cnt_byte = add_cnt_byte  && cnt_byte == (byte_num)-1 ;
        
    always  @(*)begin
        if(state_c == RDID)
            byte_num = 4;
        else  
            byte_num = 8;   //至少5B CMD + 3 ADDR + X DATA
    end

//输出
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_req <= 1'b0;
        end
        else if(idle2rdid | idle2rdda)begin
            tx_req <= 1'b1;
        end
        else if(rdid2done | rdda2done)begin
            tx_req <= 1'b0;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_data <= 0;
        end
        else if(state_c == RDID)begin
            case(cnt_byte)
                0:tx_data <= CMD_RDID;  
                default:tx_data <= 0; 
            endcase 
        end
        else if(state_c == RDDA)begin
            case(cnt_byte)
                0:tx_data <= CMD_RDDA; 
                1:tx_data <= rd_addr[23:16];
                2:tx_data <= rd_addr[15:8];
                3:tx_data <= rd_addr[7:0];
                default:tx_data <= 0; 
            endcase 
        end
    end

    //rx_data
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= 0;
        end
        else if(state_c == RDID && trans_done)begin
            rx_data <= {rx_data[23:0],rx_din};
        end
        else if(state_c == RDDA && trans_done)begin
            rx_data <= {rx_data[23:0],rx_din};
        end
    end

    //data  
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            data <= 0;
        end
        else if(state_c == DONE && ~flag)begin   //读ID
            data <= {4'd0,rx_data[23:20],4'd0,rx_data[19:16],   //2 0
                     4'd0,rx_data[15:12],4'd0,rx_data[11:8],    //2 0
                     4'd0,rx_data[7:4],4'd0,rx_data[3:0]};      //1 5 
        end 
        else if(state_c == DONE && flag)begin //读数据
            data <= {"R","D",16'd0,4'd0,rx_data[7:4],4'd0,rx_data[3:0]};
        end 
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            data_mask <= 0;
        end
        else if(state_c == DONE && ~flag)begin
            data_mask <= 6'b00_0000;
        end
        else if(state_c == DONE && flag)begin
            data_mask <= 6'b00_1100;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            data_vld <= 0;
        end
        else begin
            data_vld <= state_c == DONE; 
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 1'b0;
        end
        else if(idle2rdda)begin
            flag <= 1'b1;
        end
        else if(idle2rdid)begin
            flag <= 1'b0;
        end
    end

//输出
    assign tx_dout =tx_data;
    assign trans_req = tx_req;

    assign dout = data;
    assign dout_mask = data_mask;
    assign dout_vld = data_vld;

endmodule 

3.3 spi_master接口模块

module spi_master(
    input           clk     ,
    input           rst_n   ,

    input           req     ,
    input   [7:0]   din     ,
    output  [7:0]   dout    ,
    output          done    ,

    output          cs_n    ,
    output          mosi    ,
    input           miso    ,
    output          sclk        
      
);

//参数定义
    localparam  SCLK_PERIOD = 16,   
                SCLK_FALL   = 4 ,
                SCLK_RISE   = 12;
//信号定义
    reg [ 3:0]  cnt_sclk        ;
    wire        add_cnt_sclk    ;
    wire        end_cnt_sclk    ; 

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

    reg         spi_sclk        ;
    reg         spi_mosi        ;
    reg [7:0]   rx_data         ;

//计数器
 
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_sclk <= 0; 
        end
        else if(add_cnt_sclk) begin
            if(end_cnt_sclk)
                cnt_sclk <= 0; 
            else
                cnt_sclk <= cnt_sclk+1 ;
       end
    end
    assign add_cnt_sclk = (req);
    assign end_cnt_sclk = add_cnt_sclk  && cnt_sclk == (SCLK_PERIOD)-1 ;
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_bit <= 0; 
        end
        else if(add_cnt_bit) begin
            if(end_cnt_bit)
                cnt_bit <= 0; 
            else
                cnt_bit <= cnt_bit+1 ;
       end
    end
    assign add_cnt_bit = (end_cnt_sclk);
    assign end_cnt_bit = add_cnt_bit  && cnt_bit == (8)-1 ;

//spi_sclk 模式3     
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            spi_sclk <= 1'b1;
        end
        else if(cnt_sclk == SCLK_FALL-1)begin
            spi_sclk <= 1'b0;
        end
        else if(cnt_sclk == SCLK_RISE-1)begin
            spi_sclk <= 1'b1;
        end
    end
//发送数据
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            spi_mosi <= 1'b0;
        end
        else if(cnt_sclk == SCLK_FALL-1)begin
            spi_mosi <= din[7-cnt_bit];//并串转换
        end
    end
//接收数据
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= 0;
        end
        else if(cnt_sclk == SCLK_RISE-1)begin
            rx_data[7-cnt_bit] <= miso;//串并转换
        end
    end

//输出
    assign sclk = spi_sclk;
    assign mosi = spi_mosi;
    assign cs_n = ~req;
    assign done = end_cnt_bit;
    assign dout = rx_data;

endmodule 

3.4 整体设计框图

使用写操作指令向M25P16中连续写入256个以内的任意数据,然后按下读操作按键, 执行flash读操作读取写入M25P16的数据

按键消抖模块

  • 按键消抖模块输出三个按键信号,相当于一个读ID使能、一个读数据使能和一个写使能信号;

flash_write模块

  • 当按键消抖发送写请求信号后,flash_write模块开始写入,每当写使能,扇区擦除,读状态寄存器和页编程要发送命令时,写模块发送trans_req给spi_master模块,spi_master模块生成SCLK准备接收写模块的数据,写模块将写使能的命令,扇区擦除的命令和地址,读状态寄存器的命令,页编程的命令、地址和数据分别发送tx_dout[7:0]给spi_master模块;将写入的1字节的页编程数据发送给seg_driver模块进行数码管显示;

  • spi_master模块将接收到的(tx_dout[7:0])进行并串转换发送给从机(M25P16);

  • M25P16发送发送串行数据miso给spi_master模块;spi_master模块经过串并转换发送给flash_read模块;

flash_read模块

  • 当 按键消抖模块发送读id使能信号后,flash_read模块发送读请求(trans_req)和1字节的读ID命令(tx_dout)给spi_master模块;3字节的ID数据发送给数码管驱动seg_driver模块;

  • 当按键消抖模块发送读数据使能信号后,flash_read模块发读请求(trans_req)和1字节读数据命令+3字节读地址(tx_dout)给spi_master模块;最后1字节的读数据发送给seg_driver模块;

spi_master模块

spi_master模块接收到flash_crtl模块发送的trans_req时,生成SCLK时钟,在SCLK第一个跳变沿(SCLK_FALL-1)时,接收flash_crtl模块发送的tx_dout[7:0]进行并串转换发送mosi给从机(M25P16);

在SCLK第二个跳变沿(SCLK_RISE-1)时,接收从机(M25P16)的miso信号进行串并转换发送rx_din[7:0]给flash_ctrl模块文章来源地址https://www.toymoban.com/news/detail-448059.html

【FPGA】SPI读写flash

到了这里,关于【FPGA】SPI读写flash的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

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

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

    本论文使用Verilog HDL硬件描述语言,结合野火可以FPGA征途Pro开发板,实现了SPI通信协议的全擦除,扇区擦除,读数据,页写,连续写的驱动设计。在 Altera Cyclone Ⅳ 芯片上采用“自顶向下”的模块化设计思想及 Verilog HDL 硬件描述语言,设计并实现串行外设接口(SPI)。在 Qu

    2024年02月12日
    浏览(6)
  • 实验(三):SPI应用:读写串行FLASH 实验

    实验(三):SPI应用:读写串行FLASH 实验

    实验目的: 1. 学习对SPI的使用; 2. 掌握KEIL5的仿真与调试。 任务: 1. 根据要求编写程序,并写出原理性注释; 2. 将检查程序运行的结果,分析一下是否正确; 3. 完成所建工程的验证调试。 以一种使用SPI 通讯的串行FLASH 存储芯片的读写实验为大家讲解STM32 的SPI 使用方法。

    2024年01月21日
    浏览(13)
  • AXI Quad SPI读写Flash做远程升级

    AXI Quad SPI读写Flash做远程升级

    未经允许,本文禁止转载 目录 简介 AXI Quad SPI IP设置 寄存器说明 AXI Quad SPI支持的通用命令 读flash id 读flash 数据 擦除扇区 写flash 数据 注意事项         本文简要介绍xilinx 7系的AXI quad spi IP核的使用,主要用于读写boot用的flash(n25q128为例)做在线升级用。本文会略去很多细节,

    2024年02月03日
    浏览(87)
  • FPGA使用SPI控制FLASH

    FPGA使用SPI控制FLASH

    通过控制FLASH芯片进一步熟悉SPI协议 Flash 存储器 : Flash 存储器是一种非易失性存储器,它具有 RAM 和 ROM 的一些特点。与 ROM 类似,Flash 存储器的内容在断电时不会丢失,但与 RAM 类似,它可以通过编程来修改存储的内容。Flash 存储器通常用于嵌入式系统中存储程序代码、配置

    2024年03月19日
    浏览(6)
  • 【STM32】SPI初步使用 读写FLASH W25Q64

    【STM32】SPI初步使用 读写FLASH W25Q64

    (1) SS( Slave Select):从设备选择信号线,常称为片选信号线,每个从设备都有独立的这一条 NSS 信号线,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI通讯。所以 SPI通讯以 NSS 线置低电

    2024年02月10日
    浏览(11)
  • 【FPGA】SPI-FLASH-M25P16手册解读

    【FPGA】SPI-FLASH-M25P16手册解读

    M25P16概述: M25P16是一款带有先进写保护机制和高速SPI总线访问的串行Flash存储器。M25P16特点如下: 存储结构:16M Bit(2M Byte)的存储空间,一共32个扇区(sector),每个扇区256页,每页256字节,每个字节的的存储地址由扇区地址(8bit)+页地址(8bit)+字节地址(8bit)构成。 SP

    2024年02月04日
    浏览(7)
  • STM32单片机初学8-SPI flash(W25Q128)数据读写

    STM32单片机初学8-SPI flash(W25Q128)数据读写

            当使用单片机进行项目开发,涉及大量数据需要储存时(例如使用了屏幕作为显示设备,常常需要存储图片、动画等数据),单靠单片机内部的Flash往往是不够用的。         如STM32F103系列,内部Flash最多只能达到512KByte,假设要储存240*240分辨率、64K彩色图片,

    2024年02月03日
    浏览(10)
  • FPGA解析串口指令控制spi flash完成连续写、读、擦除数据

    FPGA解析串口指令控制spi flash完成连续写、读、擦除数据

    最近在收拾抽屉时找到一个某宝的spi flash模块,如下图所示,我就想用能不能串口来读写flash,大致过程就是,串口向fpga发送一条指令,fpga解析出指令控制flah,这个指令协议目前就是: 55 + AA + CMD + LEN_h + LEN_m + LEN_l + DATA CMD:01 写;02 读;03 擦除(片擦除); LEN_h/m/l:三个字

    2024年02月03日
    浏览(8)
  • 【STM32学习】——SPI通信协议&SPI时序&W25Q64存储芯片&软件SPI读写

    【STM32学习】——SPI通信协议&SPI时序&W25Q64存储芯片&软件SPI读写

    目录 前言 一、SPI通信协议 1.概述​ 2.硬件电路  3.移位示意图 二、SPI时序 1.时序基本单元 2.完整时序波形 三、W25Q64存储芯片 1.芯片简介  2.硬件电路引脚定义  3.芯片框图 4.Flash操作注意事项 四、软件SPI读写W25Q64 五、SPI通信外设 总结 声明:学习笔记来自江科大自化协B站教

    2024年02月09日
    浏览(7)
  • STM32开发_利用SPI协议读写SD卡、介绍SD卡SPI时序

    STM32开发_利用SPI协议读写SD卡、介绍SD卡SPI时序

    目录 一、​  SD卡引脚接口功能介绍 1.1 SD卡引脚接口图 1.2 SPI方式驱动SD卡介绍 1.3 开发板接口定义 二、MMC卡、SD卡介绍 2.1 SD卡和MMC两者间区别 2.2 SD卡版本说明 2.3 SD卡常用的指令表 三、向SD卡发送命令的步骤介绍(SendSDCardCmd) 3.1 取消选中SD卡(SDCardCancelCS) 3.2 选中SD卡(SDCardSele

    2024年02月16日
    浏览(8)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包