基于 FPGA 使用 SPI 协议控制 FLASH(M25P16) 进行数据读写操作

目录

  • 一、M25P16
  • 二、源码
    • 2.1 顶层模块
    • 2.2 按键模块
    • 2.3 SPI 模块
    • 2.4 数码管模块
  • 三、仿真模块
  • 四、管脚配置
  • 五、验证结果

本文内容:基于 SPI 协议控制开发板上的 FLASH 进行数据读写操作

一、M25P16

  • 查看开发板原理图,可以知道 FLASH 使用的是 M25P16 芯片,存储总容量 16 Mbit,采用串行方式传输数据
    在这里插入图片描述
  • 找一篇 M25P16 的手册,参照手册上面进行编程
    在这里插入图片描述
  • 芯片对应的管脚,其中 W(写保护)、HOLD(保持)用不着,可以忽略掉,其它的管脚后面会讲
    在这里插入图片描述
  • 根据 CPOL 和 CPHA 的取值不同,共有四种 SPI 传输模式,这里用的模式 3,也就是时钟 C 空闲状态高电平,在时钟 C 的上升沿对 D / Q 进行数据采样
    在这里插入图片描述
  • 存储容量,共 32 个扇区,每个扇区 256 页,每页 256 个字节
    在这里插入图片描述
  • 这里只需要用到 WREN、READ、PP、SE 指令即可,进行基本的读写操作,其它指令可以自行拓展
    在这里插入图片描述
  • 写指令,片选 S 拉低,表示数据传输阶段,拉高表示空闲状态,C 表示系统时钟,D 表示主机发送数据,FLASH 接收数据,Q 表示 FLASH 发送数据,主机接收数据
  • 写指令只需要发送一个 8’h06 给 FLASH 即可,FLASH 会在时钟的上升沿进行采样
    在这里插入图片描述
  • SE 指令(扇区擦除),发送一个 8’hd8 然后跟上 3byte 的地址,指定一个扇区进行擦除,即使指定了页地址或者字地址,它只认扇区地址,因为扇区擦除嘛,擦除的是一个扇区,也就是将一个扇区的数据变为 8’hff
  • 注意!!发送 SE 指令之前,必须先发送一个 WREN 指令,然后片选 S 拉高至少 100ns ,然后再拉低片选 S ,再发送 SE 指令,SE 与 3 byte 地址之间不需要拉低 100ns,发送完后,S 拉高,需要给 FLASH 1s 左右的时间让它进行扇区擦除
    在这里插入图片描述
  • PP 指令,发送一个 byte 的指令,然后发送 3byte 的字节,再跟上一个 byte 的数据,就把 S 拉高,这就往这个地址里面写入了一个 byte 的数据
  • 注意!!发送 PP 指令之前,必须先发送一个 WREN 指令,然后片选 S 拉高至少 100ns ,然后再拉低片选 S ,再发送 PP 指令,PP 与 3 byte 地址之间不需要拉低 100ns,发送完后,S 拉高,同时需要给 FLASH 1.4ms 左右的时间让它写数据
    在这里插入图片描述
  • 读指令,发送一个读指令,然后跟上一个 3byte 的地址,然后等待 Q 传输 1byte 的数据即可,再把 S 片选拉高即可,如果不拉高,它 Q 就会一直发数据
    在这里插入图片描述
  • 其它指令就不讲了,没用到,如果不理解的话,多看图,看着看着就明白了

二、源码

2.1 顶层模块

SPI_top.v

/*========================================*\filename        : SPI_top.vdescription     : SPI顶层模块up file         : reversion       : v1.0 : 2022-8-25 19:09:45author          : 张某某
\*========================================*/module SPI_top (input                               clk             ,input                               rst_n           ,input           [ 2:0]              key_in          ,input                               MISO            ,output                              SCK             ,output                              CS              ,output                              MOSI            ,output          [ 5:0]              SEL             ,output          [ 7:0]              DIG             
);// Parameter definition// Signal definitionwire            [ 7:0]              data            ;wire            [ 7:0]              press           ;// Module callskey_filter              U_key_filter(/*input                 */  .clk        (clk    ),/*input                 */  .rst_n      (rst_n  ),/*input           [ 2:0]*/  .key_in     (key_in ),/*output  reg     [ 2:0]*/  .press      (press  ));spi_m25p16              U_spi_m25p16(/*input                 */  .clk        (clk    ),/*input                 */  .rst_n      (rst_n  ),/*input           [ 2:0]*/  .press      (press  ),/*input                 */  .MISO       (MISO   ),/*output  reg           */  .SCK        (SCK    ),/*output  reg           */  .CS         (CS     ),/*output  reg           */  .MOSI       (MOSI   ),/*output  reg     [ 7:0]*/  .data_out   (data   ));display                 U_display(/*input                 */  .clk        (clk    ), // 50MHz/*input                 */  .rst_n      (rst_n  ), // 复位信号/*input           [ 7:0]*/  .data       (data   ),/*output  reg     [ 5:0]*/  .SEL        (SEL    ), // SEL信号/*output  reg     [ 7:0]*/  .DIG        (DIG    )  // DIG信号);// Logic descriptionendmodule

2.2 按键模块

key_filter.v

/*========================================*\filename        : key_filter.vdescription     : 按键消抖模块up file         : reversion       : v1.0 : 2022-8-25 19:09:59author          : 张某某
\*========================================*/module key_filter #(parameter MS_20 = 20'd1000_000)(input                               clk         ,input                               rst_n       ,input           [ 2:0]              key_in      ,output  reg     [ 2:0]              press
);
// 全局变量定义// 信号定义reg             [ 2:0]              key_0       ; // 按键信号当前时钟周期电平reg             [ 2:0]              key_1       ; // 按键信号下一个时钟周期电平wire            [ 2:0]              key_nedge   ; // 下降沿使能信号reg                                 add_flag    ; // 计数使能信号reg             [19:0]              delay_cnt   ; // 延时计数器// 模块功能//打拍器always @(posedge clk or negedge rst_n) beginif (!rst_n) beginkey_0 <= 'b1;key_1 <= 'b1;endelse beginkey_0 <= key_in;key_1 <= key_0;endend// 检测下降沿assign key_nedge = ~key_0 & key_1;// 计数使能信号always @(posedge clk or negedge rst_n) beginif (!rst_n) beginadd_flag <= 'b0;endelse if (key_nedge) beginadd_flag <= 'b1;endelse if (delay_cnt >= MS_20 - 1) beginadd_flag <= 'b0;endelse beginadd_flag <= add_flag;endend// 计数20msalways @(posedge clk or negedge rst_n) beginif (!rst_n) begindelay_cnt <= 20'd0;endelse if (add_flag) beginif (delay_cnt >= MS_20 - 1) begindelay_cnt <= 20'd0;endelse begindelay_cnt <= delay_cnt + 1;endendelse begindelay_cnt <= 20'd0;endend// 输出always @(posedge clk or negedge rst_n) beginif (!rst_n) beginpress <= 'd0;endelse if (delay_cnt >= MS_20 - 1) beginpress <= ~key_in;endelse beginpress <= 'd0;endendendmodule

2.3 SPI 模块

spi_m25p16.v

/*========================================*\filename        : spi_m25p16.vdescription     : 基于SPI控制M25P16up file         : SPI_top.vreversion       : v1.0 : 2022-8-25 19:08:52author          : 张某某
\*========================================*/module spi_m25p16 #(parameter S_3 = 28'd50_000_000)(input                               clk                     ,input                               rst_n                   ,input           [ 2:0]              press                   ,input                               MISO                    ,output  reg                         SCK                     ,output  reg                         CS                      ,output  reg                         MOSI                    ,output  reg     [ 7:0]              data_out
);// Parameter definitionparameter       ORDER_WREN  =       8'h06                   ,ORDER_SE    =       8'hd8                   ,ORDER_PP    =       8'h02                   ,ORDER_READ  =       8'h03                   ;parameter       IDLE        =       8'b0000_0001            ,WREN        =       8'b0000_0010            ,WAIT        =       8'b0000_0100            ,SEPP        =       8'b0000_1000            ,RDEN        =       8'b0001_0000            ,ADDRESS     =       8'b0010_0000            ,DATA        =       8'b0100_0000            ,FINAL       =       8'b1000_0000            ;// Signal definitionreg             [31:0]              state_ascll             ;reg             [ 7:0]              state_c                 ; // 现态reg             [ 7:0]              state_n                 ; // 次态wire                                idle2wren               ;wire                                idle2rden               ;wire                                wren2wait               ;wire                                wait2sepp               ;wire                                sepp2address            ;wire                                rden2address            ;wire                                address2final           ;wire                                address2data            ;wire                                data2final              ;wire                                final2idle              ;reg                                 flag_SE                 ;reg                                 flag_PP                 ;reg                                 flag_RD                 ;reg             [ 3:0]              cnt_SCK_fre             ; // SCK频率计数器wire                                add_cnt_SCK_fre         ;wire                                end_cnt_SCK_fre         ;reg             [ 2:0]              press_0                 ; // press时序逻辑化reg             [ 7:0]              send_data               ; // 发送的1byte数据reg             [ 7:0]              receive_data            ; // 接收的1byte数据reg             [ 2:0]              cnt_bit                 ; // bit计数器wire                                add_cnt_bit             ;wire                                end_cnt_bit             ;reg             [ 1:0]              cnt_byte                ; // byte计数器wire                                add_cnt_byte            ;wire                                end_cnt_byte            ;reg             [27:0]              cnt_3s                  ; // 计数三秒wire                                add_cnt_3s              ;wire                                end_cnt_3s              ;// Logic description// 仿真时显示state_c对应的字符串always @(*) begincase (state_c)IDLE    : state_ascll = 32'h49444C45;WREN    : state_ascll = 32'h5752454E;WAIT    : state_ascll = 32'h57414954;SEPP    : state_ascll = 32'h53455050;RDEN    : state_ascll = 32'h5244454E;ADDRESS : state_ascll = 32'h41444452;DATA    : state_ascll = 32'h44415441;FINAL   : state_ascll = 32'h46494E41;default : state_ascll = 32'h49444C45;endcaseend// 第一段 状态转移always @(posedge clk or negedge rst_n) beginif (!rst_n) beginstate_c <= IDLE;endelse beginstate_c <= state_n;endend// 第二段 状态转移规律always @(*) begincase (state_c)IDLE : beginif (idle2wren) state_n = WREN;else if (idle2rden) state_n = RDEN;else state_n = state_c;endRDEN : beginif (rden2address) state_n = ADDRESS;else state_n = state_c;endWREN : beginif (wren2wait) state_n = WAIT;else state_n = state_c;endWAIT : beginif (wait2sepp) state_n = SEPP;else state_n = state_c;endSEPP : beginif (sepp2address) state_n = ADDRESS;else state_n = state_c;endADDRESS : beginif (address2final) state_n = FINAL;else if (address2data) state_n = DATA;else state_n = state_c;endDATA : beginif (data2final) state_n = FINAL;else state_n = state_c;endFINAL : beginif (final2idle) state_n = IDLE;else state_n = state_c;enddefault : state_n = state_c;endcaseendassign idle2wren     = state_c == IDLE && (flag_SE || flag_PP) && cnt_SCK_fre == 'd6;assign idle2rden     = state_c == IDLE && flag_RD && cnt_SCK_fre == 'd6;assign wren2wait     = state_c == WREN && end_cnt_bit;assign wait2sepp     = state_c == WAIT && cnt_SCK_fre == 'd6;assign sepp2address  = state_c == SEPP && end_cnt_bit;assign address2final = state_c == ADDRESS && flag_SE && end_cnt_byte;assign address2data  = state_c == ADDRESS && (flag_PP || flag_RD) && end_cnt_byte;assign rden2address  = state_c == RDEN && end_cnt_bit;assign data2final    = state_c == DATA && end_cnt_bit;assign final2idle    = state_c == FINAL && end_cnt_3s;// 第三段 描述输出// SCK时钟信号频率计数器always @(posedge clk or negedge rst_n) beginif (!rst_n) begincnt_SCK_fre <= 4'd0;endelse if (add_cnt_SCK_fre) beginif (end_cnt_SCK_fre) begincnt_SCK_fre <= 4'd0;endelse begincnt_SCK_fre <= cnt_SCK_fre + 4'd1;endendelse begincnt_SCK_fre <= 4'd0;endendassign add_cnt_SCK_fre = 1'b1;assign end_cnt_SCK_fre = add_cnt_SCK_fre && cnt_SCK_fre >= 4'd9;// SCK时钟信号生成always @(posedge clk or negedge rst_n) beginif (!rst_n) beginSCK <= 1'd0;endelse begincase (cnt_SCK_fre)4'd9 : SCK <= 1'd0;4'd4 : SCK <= 1'd1;default: SCK <= SCK;endcaseendend// 对press时序逻辑化always @(posedge clk or negedge rst_n) beginif (!rst_n) beginpress_0 <= 3'd0;endelse beginpress_0 <= press;endend// 按键控制使能信号always @(posedge clk or negedge rst_n) beginif (!rst_n) beginflag_SE <= 1'b0;flag_PP <= 1'b0;flag_RD <= 1'b0;endelse if (final2idle) beginflag_SE <= 1'b0;flag_PP <= 1'b0;flag_RD <= 1'b0;endelse begincase (press_0)3'b100 : flag_SE <= 1'b1;3'b010 : flag_PP <= 1'b1;3'b001 : flag_RD <= 1'b1;default: beginflag_SE <= flag_SE;flag_PP <= flag_PP;flag_RD <= flag_RD;endendcaseendend// CS片选信号always @(posedge clk or negedge rst_n) beginif (!rst_n) beginCS <= 1'd1;endelse begincase (state_c)WREN, SEPP, ADDRESS, DATA, RDEN : CS <= 1'd0; default: CS <= 1'd1;endcaseendend// 待发送的1byte数据always @(posedge clk or negedge rst_n) beginif (!rst_n) beginsend_data <= 8'd0;endelse begincase (state_c)WREN : send_data <= ORDER_WREN;SEPP :beginif (flag_SE) send_data <= ORDER_SE;else if (flag_PP) send_data <= ORDER_PP;else send_data <= 8'd0;endADDRESS :begincase (cnt_byte)2'd0 : send_data <= 8'h01;2'd1 : send_data <= 8'h02;2'd2 : send_data <= 8'h03;default: send_data <= 8'h00;endcaseendDATA : send_data <= flag_RD ? 8'h00 : 8'h23;RDEN : send_data <= ORDER_READ;default: send_data <= 8'd0;endcaseendend// bit计数器always @(posedge clk or negedge rst_n) beginif (!rst_n) begincnt_bit <= 'd0;endelse if (add_cnt_bit) beginif (end_cnt_bit) begincnt_bit <= 'd0;endelse begincnt_bit <= cnt_bit + 'd1;endendelse begincnt_bit <= cnt_bit;endendassign add_cnt_bit = (state_c == WREN || state_c >= SEPP && state_c <= DATA) && cnt_SCK_fre == 4'd6;assign end_cnt_bit = add_cnt_bit && cnt_bit >= 3'd7;// byte计数器always @(posedge clk or negedge rst_n) beginif (!rst_n) begincnt_byte <= 2'd0;endelse if (add_cnt_byte) beginif (end_cnt_byte) begincnt_byte <= 2'd0;endelse begincnt_byte <= cnt_byte + 2'd1;endendelse begincnt_byte <= cnt_byte;endendassign add_cnt_byte = state_c == ADDRESS && end_cnt_bit;assign end_cnt_byte = add_cnt_byte && cnt_byte >= 2'd2;// MOSI输出always @(posedge clk or negedge rst_n) beginif (!rst_n) beginMOSI <= 1'd0;endelse if (~CS && cnt_SCK_fre == 4'd1) beginMOSI <= send_data[7 - cnt_bit];endelse beginMOSI <= MOSI;endend// 计数3秒always @(posedge clk or negedge rst_n) beginif (!rst_n) begincnt_3s <= 'd0;endelse if (add_cnt_3s) beginif (end_cnt_3s) begincnt_3s <= 'd0;endelse begincnt_3s <= cnt_3s + 'd1;endendelse begincnt_3s <= 'd0;endendassign add_cnt_3s = state_c == FINAL;assign end_cnt_3s = add_cnt_3s && cnt_3s >= S_3 - 28'd1;// 接收1byte数据always @(posedge clk or negedge rst_n) beginif (!rst_n) beginreceive_data <= 8'd0;endelse if (flag_RD && state_c == DATA) beginreceive_data[7 - cnt_bit] <= MISO;endelse beginreceive_data <= 8'd0;endend// 将接收的1byte数据送给数码管显示always @(posedge clk or negedge rst_n) beginif (!rst_n) begindata_out <= 8'd0;endelse if (data2final && flag_RD) begindata_out <= receive_data;endelse begindata_out <= data_out;endendendmodule

2.4 数码管模块

display.v

/*========================================*\filename        : display.vdescription     : 滚动显示输入的数据up file         : reversion       : v1.0 : 2022-7-27 18:49:34author          : 张某某
\*========================================*/module display #(parameter  MS_1= 16'd50000)(input                                   clk                 ,   // 50MHzinput                                   rst_n               ,   // 复位信号input               [ 7:0]              data                ,output      reg     [ 5:0]              SEL                 ,   // SEL信号output      reg     [ 7:0]              DIG                     // DIG信号
);// 信号定义reg                 [15:0]              cnt_flicker         ;   // 计数1mswire                                    SEL_change          ;   // cnt_flicker计满使能信号reg                                     show_wei            ; reg                 [ 3:0]              tmp_data            ;   // 当前DIG的值// 逻辑描述// 闪烁频率计数器always @(posedge clk or negedge rst_n) beginif (!rst_n) begincnt_flicker <= 'd0;endelse if (SEL_change) begincnt_flicker <= 'd0;endelse begincnt_flicker <= cnt_flicker + 'd1; endendassign SEL_change = cnt_flicker >= MS_1 - 'd1 ? 1'b1 : 1'b0;// SEL信号输出always @(posedge clk or negedge rst_n) beginif (!rst_n) beginSEL <= 6'b011_111;endelse if (SEL_change) beginSEL <= {SEL[4], SEL[5], SEL[3:0]};endelse beginSEL <= SEL;endend// tmp_data当前SEL位选所对应的DIG十进制值always @(*) begincase (SEL)6'b011_111 : tmp_data = data[3:0];6'b101_111 : tmp_data = data[7:4];default: tmp_data = 'd0;endcaseend// DIG输出各数字对应的二进制always @(*) begincase (tmp_data)4'd0 : DIG = 8'b1100_0000;4'd1 : DIG = 8'b1111_1001;4'd2 : DIG = 8'b1010_0100;4'd3 : DIG = 8'b1011_0000;4'd4 : DIG = 8'b1001_1001;4'd5 : DIG = 8'b1001_0010;4'd6 : DIG = 8'b1000_0010;4'd7 : DIG = 8'b1111_1000;4'd8 : DIG = 8'b1000_0000;4'd9 : DIG = 8'b1001_0000;4'd10: DIG = 8'b1000_1000;4'd11: DIG = 8'b1000_0011;4'd12: DIG = 8'b1100_0110;4'd13: DIG = 8'b1010_0001;4'd14: DIG = 8'b1000_0110;4'd15: DIG = 8'b1000_1110;endcaseendendmodule

三、仿真模块

tb_spi_m25p16.v

/*========================================*\filename        : description     : up file         : reversion       : v1.0 : author          : 张某某
\*========================================*/`timescale 1ns/1nsmodule tb_spi_m25p16;// Parameter definitionparameter       CYC_CLK             = 20            ;// Drive signalreg                                 tb_clk          ;reg                                 tb_rst_n        ;reg            [ 2:0]               tb_press        ;reg                                 tb_MISO         ;// Observation signalwire                                tb_SCK          ;wire                                tb_CS           ;wire                                tb_MOSI         ;// Module callsspi_m25p16    #(.S_3(150))          U_spi_m25p16(/*input                 */  .clk    (tb_clk     ),/*input                 */  .rst_n  (tb_rst_n   ),/*input           [ 1:0]*/  .press  (tb_press   ),/*input                 */  .MISO   (tb_MISO    ),/*output  reg           */  .SCK    (tb_SCK     ),/*output  reg           */  .CS     (tb_CS      ),/*output  reg           */  .MOSI   (tb_MOSI    ));// System initializationinitial begintb_clk = 1'b1;tb_rst_n = 1'b0;#(CYC_CLK) tb_rst_n = 1'b1;endalways #(CYC_CLK >> 1) tb_clk = ~tb_clk;initial begintb_press = 3'b000;tb_MISO  = 1'b0;#(30 * CYC_CLK);// 擦除操作tb_press = 3'b100;#(CYC_CLK) tb_press = 3'b000;#(16000);// 页面编程tb_press = 3'b010;#(CYC_CLK) tb_press = 3'b000;#(20000);// 读数据tb_press = 3'b001;#(CYC_CLK) tb_press = 3'b000;#(12000);$stop;endendmodule

四、管脚配置

set_location_assignment PIN_E1  -to clk
set_location_assignment PIN_E15 -to rst_n
set_location_assignment PIN_E16 -to key_in[0]
set_location_assignment PIN_M16 -to key_in[1]
set_location_assignment PIN_M15 -to key_in[2]
set_location_assignment PIN_H2  -to MISO  
set_location_assignment PIN_H1  -to SCK   
set_location_assignment PIN_D2  -to CS    
set_location_assignment PIN_C1  -to MOSI  
set_location_assignment PIN_A4  -to SEL[0]
set_location_assignment PIN_B4  -to SEL[1] 
set_location_assignment PIN_A3  -to SEL[2]
set_location_assignment PIN_B3  -to SEL[3]
set_location_assignment PIN_A2  -to SEL[4]
set_location_assignment PIN_B1  -to SEL[5]
set_location_assignment PIN_B7  -to DIG[0]
set_location_assignment PIN_A8  -to DIG[1]
set_location_assignment PIN_A6  -to DIG[2]
set_location_assignment PIN_B5  -to DIG[3]
set_location_assignment PIN_B6  -to DIG[4]
set_location_assignment PIN_A7  -to DIG[5]
set_location_assignment PIN_B8  -to DIG[6]
set_location_assignment PIN_A5  -to DIG[7]

五、验证结果

  • 仿真结果,第一部分是擦除操作,第二部分是PP写操作,第三部分是读操作
    在这里插入图片描述
  • 板上验证

    SPI控制FLASH

  • 按左边按键,表示进行擦除操作
    按中间按键,表示进行写数据操作
    按右边按键,表示进行读数据操作


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部