基于 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
- 按左边按键,表示进行擦除操作
按中间按键,表示进行写数据操作
按右边按键,表示进行读数据操作
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
