SPI协议(二):SPI_Flash(M25P16)擦除操作

       通过全擦除和扇区擦除可将固化到Flash芯片中的程序擦除。

一 SPI_Flash全擦除

       全擦除就是将Flash芯片所有的存储空间都进行擦除,使各个存储空间的内存数据都恢复到初始值,Flash全擦除有两种方式:

       (1)利用FPGA编译软件,通过Quartus软件中的“programmer”可将烧录到Flash中的*.jic文件擦除,如下图所示。

       (2)通过编写全擦除程序,将Flash芯片进行全擦除。

1.1实验内容

       编写全擦除程序,将Flash芯片事先烧录的程序擦除,使FPGA重新上电后,程序无法自启动。

       全擦除指令和时序如下图所示:

       由手册可知,全擦除指令是将Flash芯片中的所有存储单元置1,在写入全擦除指令之前,需要写入写使能(WREN)指令,将芯片置为写使能锁存(WEL)状态。拉低片选信号,写入全擦除指令,在指令写入过程中片选信号保持低电平,当指令被芯片所存后,将片选信号拉高;全擦除指令被锁存并执行后,需要等待一个完整的全擦除周期(tBE),才能完成Flash芯片的全擦除操作。

       写使能指令和时序如下图所示:

        写使能指令、全擦除指令以及其它操作指令写入Flash之前需要遵循芯片的串行输入时序,如下图所示:

       需要注意tSLCH、tCHSH、tSHSL这三个参数值:

       tSLCH:片选信号拉低到第一个有效数据写入,需要等待的时间,必须大于等于5ns。

       tCHSH:最后一个有效数据写入到片选信号拉高,需要等待的时间,必须大于等于5ns。

       tSHSL:片选信号拉高到再次拉低,需要等待的时间,必须大于等于100ns。

       因此,完整的全擦除操作时序图如下所示,为了方便起见,每个等待时间统一为32个时钟周期,即640ns。

1.2工程代码 

       全擦除工程主要包含顶层例化flash_be_top、按键消抖key_filter、擦除控制spi_flash_be三个子模块。

        spi_flash_be模块代码如下: 

`timescale 1ns/1ps
module spi_flash_be(
	input			clk,
	input			rst_n,
	input			key_in,
	
	output	reg		cs_n,
	output	reg		sck,
	output	reg		mosi
);

parameter	WR_EN_INST	=	8'h06;	//写使能指令
parameter	BE_INST		=	8'hc7;	//全擦除指令

parameter	IDLE			=	4'b0001;	//空闲状态
parameter	WR_EN			=	4'b0010;	//写使能状态
parameter	DELAY			=	4'b0100;	//片选拉高等待状态
parameter	BE				=	4'b1000; //全擦除状态

reg	[3:0]	curr_state;
reg	[3:0]	next_state;
reg	[4:0]	clk_cnt;			//系统时钟计数器
reg	[2:0]	byte_cnt;		//字节计数器
reg	[1:0]	sck_cnt;			//sck时钟计数器
reg	[2:0]	bit_cnt;			//数据位计数器


always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		curr_state	<=	IDLE;
	else
		curr_state	<=	next_state;
end

always @(*)
begin
	next_state	<=	IDLE;
	case(curr_state)
		
		IDLE:begin
			if(key_in)
				next_state	<=	WR_EN;
			else
				next_state	<=	IDLE;	
		end
		
		WR_EN:begin
			if(byte_cnt == 3'd1 && clk_cnt == 5'd31)
				next_state	<=	DELAY;
			else
				next_state	<=	WR_EN;		
		end
		
		DELAY:begin
			if(byte_cnt == 3'd2 && clk_cnt == 5'd31)
				next_state 	<=	BE;
			else
				next_state	<=	DELAY;
		end
		
		BE:begin
			if(byte_cnt == 3'd6 && clk_cnt == 5'd31)
				next_state	<=	IDLE;
			else
				next_state	<=	BE;	
		end
		
		default: next_state	<=	IDLE;
	
	endcase

end

//clk_Cnt:系统时钟计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		clk_cnt	<=	5'd0;
	else if(curr_state != IDLE)
		clk_cnt <=	clk_cnt + 1'b1;
	else
		clk_cnt <=	5'd0;
end

//byte_cnt:字节计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		byte_cnt	<=	3'd0;
	else if(byte_cnt == 3'd6 && clk_cnt == 5'd31)
		byte_cnt	<=	3'd0;
	else if(clk_cnt == 5'd31)
		byte_cnt	<=	byte_cnt + 1'b1;
	else
		byte_cnt	<=	byte_cnt;
end

//sck_cnt:sck时钟计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		sck_cnt	<=	2'd0;
	else if(curr_state == WR_EN && byte_cnt == 3'd1)
		sck_cnt	<=	sck_cnt + 1'b1;
	else if(curr_state == BE && byte_cnt == 3'd5)
		sck_cnt	<=	sck_cnt + 1'b1;
	else
		sck_cnt	<=	2'd0;
end

//bit_cnt:数据位计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		bit_cnt <= 3'd0;
	else if(sck_cnt == 2'd1)
		bit_cnt	<=	bit_cnt + 1'b1;
	else
		bit_cnt	<=	bit_cnt;
end

//cs_n:片选信号
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		cs_n	<=	1'b1;
	else if(key_in)
		cs_n	<=	1'b0;
	else if(byte_cnt == 3'd2 && clk_cnt == 5'd31)
		cs_n	<=	1'b1;
	else if(byte_cnt == 3'd3 && clk_cnt == 5'd31)
		cs_n	<=	1'b0;
	else if(byte_cnt == 3'd6 && clk_cnt == 5'd31)
		cs_n	<=	1'b1;
	else
		cs_n	<=	cs_n;
end

//sck:sck时钟生成
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		sck	<=	1'b0;
	else if(sck_cnt == 2'd1)
		sck	<=	1'b1;
	else if(sck_cnt == 2'd3)
		sck	<=	1'b0;
	else
		sck	<=	sck;
end

//mosi:
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		mosi	<=	1'b0;
	else if(byte_cnt == 3'd1 && clk_cnt == 5'd31)
		mosi	<=	1'b0;
	else if(byte_cnt == 3'd5 && clk_cnt == 5'd31)
		mosi	<=	1'b0;
	else if(byte_cnt == 3'd1 && sck_cnt == 2'd0)	//写入写使能指令
		mosi	<=	WR_EN_INST[7 - bit_cnt];
	else if(byte_cnt == 3'd5 && sck_cnt == 2'd0) //写入全擦除指令
		mosi	<=	BE_INST[7 - bit_cnt];
	else
		mosi	<= mosi;
end

endmodule

       顶层模块flash_be_top代码如下:

`timescale 1ns/1ps
module flash_be_top(
	input			sys_clk,
	input			rst_n,
	input			key_in,	
	
	output			cs_n,
	output			sck,
	output			mosi
);

wire	key_flag;

spi_flash_be u_spi_flash_be(
	.clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_flag),
	
	.cs_n(cs_n),
	.sck(sck),
	.mosi(mosi)
);

key_filter u_key_filter(
	.clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_in),		//按键输入
	
	.key_flag(key_flag)		//检测到按键按下标志信号
);

endmodule 

       仿真代码flsh_be_top_tb如下:

`timescale 1ns/1ps
module flsh_be_top_tb();

reg	    sys_clk;
reg	    rst_n;
reg	    key_in;
	
wire	cs_n;
wire	sck;
wire	mosi;

initial 
begin
	sys_clk	=	1'b1;
	rst_n		=	1'b0;
	key_in	=	1'b1;
	#200
	rst_n		=	1'b1;
	key_in	=	1'b0;
	#21_000_000
	key_in	=	1'b1;
end

always #10 sys_clk	<=	~sys_clk;

defparam memory.mem_access.initfile = "initmemory.txt";

flash_be_top u_flash_be_top(
	.sys_clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_in),
	
	.cs_n(cs_n),
	.sck(sck),
	.mosi(mosi)
);

m25p16  memory
(
	.c          (sck  ),  //输入串行时钟,频率12.5Mhz,1bit
	.data_in    (mosi ),  //输入串行指令或数据,1bit
	.s          (cs_n ),  //输入片选信号,1bit
	.w          (1'b1 ),  //输入写保护信号,低有效,1bit
	.hold       (1'b1 ),  //输入hold信号,低有效,1bit
	
	.data_out   (     )   //输出串行数据
);

endmodule 

        仿真结果如下:

       仿真结果显示,当byte_cnt 为3’d1时完成写使能指令8’h06的写入,当字节计数器byte_cnt为3’d5时完成全擦除指令的写入。之后等待40s后,仿真结果提示全擦除操作完成。

1.3 板级验证

        首先将流水灯程序烧录到Flash芯片中,断电后重新上电检测Flash芯片中的流水灯程序可以自启动,然后将全擦除程序下载到FPGA开发板中,之后断电后重新上电,发现流水灯程序无法自启动,可以说明全擦除程序板级验证成功。

二 SPI_flash扇区擦除

       M25P16 Flash芯片总存储容量为16Mbit,分为32个扇区,每个扇区容量为512Kbit,扇区地址如下图所示:

2.1实验内容 

       通过扇区擦除工程将实现烧录到Flash芯片中的流水灯程序所在的某个扇区进行擦除,使流水灯程序上电之后无法自启动,本次选择擦除第0个扇区,扇区地址为24’h00_05_20。   

        扇区擦除指令和时序如下所示:

        从数据手册可以看出,扇区擦除指令是将Flash芯片中的被选中扇区的所有存储单元置为1,在Flash芯片写入扇区擦除指令之前,需要先写入写使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态。然后拉低片选信号,写入扇区擦除指令、扇区地址、页地址和字节地址,在写入过程中,片选信号一直保持低电平状态,当指令和地址被芯片锁存后,将片选信号拉高。扇区擦除指令、地址被锁存并执行后,需要等待一个完整的扇区擦除周期(tSE),才能完成Flash芯片的扇区擦除操作。

       完整的扇区擦除操作时序如下图所示,为了方便起见,每个等待时间统一为32个时钟周期,即640ns。

2.2工程代码

      扇区擦除工程主要包括顶层例化flash_se_top、按键消抖key_filter、扇区擦除控制spi_flash_se三个子模块。

       扇区擦除控制spi_flash_se模块如下:

`timescale 1ns/1ps
module spi_flash_se
#(
	parameter	SE_ADDR		=	8'h00,	//扇区地址
	parameter	PP_ADDR		=	8'h05,	//页地址
	parameter	BYTE_ADDR	=	8'h20	//字节地址
)
(
	input	    clk,
	input	    rst_n,
	input		key_in,
	
	output	reg	cs_n,
	output	reg	sck,
	output	reg	mosi
);

localparam	WR_EN_INST	=	8'h06;	//写使能指令
localparam	SE_INST		=	8'hd8;	//扇区擦除指令


localparam	IDLE			=	4'b0001;
localparam	WR_EN			=	4'b0010;
localparam	DELAY			=	4'b0100;
localparam	SE				=	4'b0000;

reg	[3:0]	curr_state;
reg	[3:0]	next_state;
reg	[4:0]	clk_cnt;
reg	[3:0]	byte_cnt;
reg	[2:0]	bit_cnt;
reg	[1:0]	sck_cnt;

always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		curr_state	<=	IDLE;
	else
		curr_state	<=	next_state;
end

always @(*)
begin
	next_state	<=	IDLE;
	case(curr_state)
		
		IDLE:begin
			if(key_in)
				next_state	<=	WR_EN;
			else
				next_state	<=	IDLE;
		end
		
		WR_EN:begin
			if(byte_cnt == 4'd2 && clk_cnt == 5'd31)
				next_state	<=	DELAY;
			else
				next_state	<=	WR_EN;		
		end
		
		DELAY:begin
			if(byte_cnt == 4'd3 && clk_cnt == 5'd31)
				next_state	<=	SE;
			else
				next_state	<=	DELAY;		
		end
		
		SE:begin
			if(byte_cnt == 4'd9 && clk_cnt == 5'd31)
				next_state	<=	IDLE;
			else
				next_state	<=	SE;		
		end
		
		default: next_state <= IDLE;
	endcase
end

//clk_cnt:系统时钟计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		clk_cnt	<=	5'd0;
	else if(curr_state != IDLE)
		clk_cnt	<=	clk_cnt + 1'b1;
	else
		clk_cnt	<=	5'd0;
end

//byte_cnt:字节计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		byte_cnt	<=	4'd0;
	else if(byte_cnt == 4'd9 && clk_cnt == 5'd31)
		byte_cnt	<=	4'd0;
	else if(clk_cnt == 5'd31)
		byte_cnt	<=	byte_cnt + 1'b1;
	else
		byte_cnt	<=	byte_cnt;	
end

//sck_cnt:sck时钟计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		sck_cnt	<=	2'd0;
	else if(curr_state == WR_EN && byte_cnt == 4'd1)
		sck_cnt	<=	sck_cnt + 1'b1;
	else if(byte_cnt >= 4'd5 && byte_cnt <= 4'd8)
		sck_cnt	<=	sck_cnt + 1'b1;
	else
		sck_cnt	<=	2'd0;
end

//bit_cnt:数据位计数器
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		bit_cnt	<=	1'b0;
	else if(sck_cnt == 2'd1)
		bit_cnt	<=	bit_cnt + 1'b1;
	else
		bit_cnt	<=	bit_cnt;
end

//sck:sck时钟生成
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)	
		sck	<=	1'b0;
	else if(sck_cnt == 2'd1)
		sck	<=	1'b1;
	else if(sck_cnt == 2'd3)
		sck	<=	1'b0;
	else
		sck	<=	sck;
end

//cs_n:片选信号
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		cs_n	<=	1'b1;
	else if(key_in)
		cs_n	<=	1'b0;
	else if(curr_state == WR_EN && byte_cnt == 4'd2 && clk_cnt == 5'd31)
		cs_n	<=	1'b1;
	else if(curr_state == DELAY && byte_cnt == 4'd3 && clk_cnt == 5'd31)
		cs_n	<=	1'b0;
	else if(curr_state == SE && byte_cnt == 4'd9 && clk_cnt == 5'd31)
		cs_n	<=	1'b1;
	else 
		cs_n	<=	cs_n;
end

//mosi:数据输出
always @(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		mosi	<=	1'b0;
	else if(byte_cnt == 4'd1 && clk_cnt == 5'd31)
		mosi	<=	1'b0;
	else if(byte_cnt == 4'd8 && clk_cnt == 5'd31)
		mosi	<=	1'b0;
	else if(byte_cnt == 4'd1 && sck_cnt == 2'd0)
		mosi	<=	WR_EN_INST[7 - bit_cnt];
	else if(byte_cnt == 4'd5 && sck_cnt == 2'd0)
		mosi	<=	SE_INST[7 - bit_cnt];
	else if(byte_cnt == 4'd6 && sck_cnt == 2'd0)
		mosi	<=	SE_ADDR[7 - bit_cnt];
	else if(byte_cnt == 4'd7 && sck_cnt == 2'd0)
		mosi	<=	PP_ADDR[7 - bit_cnt];
	else if(byte_cnt == 4'd8 && sck_cnt == 2'd0)
		mosi	<=	BYTE_ADDR[7 - bit_cnt];
	else 
		mosi	<=	mosi;
end

endmodule 

        顶层例化flash_se_top模块如下:

`timescale 1ns/1ps
module flash_se_top(
	input			sys_clk,
	input			rst_n,
	input			key_in,
	
	output			cs_n,
	output			sck,
	output			mosi
);

parameter	SE_ADDR		=	8'h00;	//扇区擦除地址
parameter	PP_ADDR		=	8'h05;	//页地址
parameter	BYTE_ADDR	=	8'h20;	//字节地址

wire		key_flag;

//spi_flash_se模块例化
spi_flash_se
#(
	.SE_ADDR	(SE_ADDR),
	.PP_ADDR	(PP_ADDR),
	.BYTE_ADDR	(BYTE_ADDR)
)
u_spi_flash_se(
	.clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_flag),
	
	.cs_n(cs_n),
	.sck(sck),
	.mosi(mosi)
);

//key_filter 模块例化
key_filter u_key_filter(
	.clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_in),		//按键输入
	
	.key_flag(key_flag)		//检测到按键按下标志信号
);

endmodule 

仿真代码flash_se_top_tb如下:

`timescale 1ns/1ps
module flash_se_top_tb();

reg	    sys_clk;
reg	    rst_n;
reg	    key_in;
	
wire	cs_n;
wire	sck;
wire	mosi;

initial 
begin
	sys_clk	=	1'b1;
	rst_n	=	1'b0;
	key_in	=	1'b1;
	#200
	rst_n	=	1'b1;
	key_in	=	1'b0;
	#21_000_000
	key_in	=	1'b1;
end

always #10 sys_clk	<=	~sys_clk;

defparam memory.mem_access.initfile = "initmemory.txt";

flash_se_top u_flash_se_top(
	.sys_clk(sys_clk),
	.rst_n(rst_n),
	.key_in(key_in),
	
	.cs_n(cs_n),
	.sck(sck),
	.mosi(mosi)
);

m25p16  memory
(
	.c          (sck   ),  //输入串行时钟,频率12.5Mhz,1bit
	.data_in    (mosi  ),  //输入串行指令或数据,1bit
	.s          (cs_n  ),  //输入片选信号,1bit
	.w          (1'b1  ),  //输入写保护信号,低有效,1bit
	.hold       (1'b1  ),  //输入hold信号,低有效,1bit
	
	.data_out   (      )   //输出串行数据
);

endmodule 

仿真结果如下:

       仿真结果显示,在byte_cnt为4’d1时,完成写使能指令写入,当byte_cnt为4’d5时,完成扇区擦除指令写入,随后发送需要擦除的扇区地址、页地址和字节地址三个字节数据,经过3s后,仿真结果提示扇区擦除操作完成。

2.3 板级验证

       将流水灯程固化到Flash芯片中,断电后上电验证流水灯程序可以自启动。然后下载扇区擦除工程到Cyclone IV 开发板中,按下按键,执行扇区擦除操作。最后,重新断电后上电,发现流水灯程序无法自启动,证明扇区擦除程序板级验证成功。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值