一.特性总结
- 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