一.M25P16flash芯片介绍
本设计使用了M25P16flash芯片,它拥有16Mbit的空间。M25P16flash芯片有32个扇区,每个扇区有256页,每页有256个位空间。32*256*256=2097152=16M。因此它的地址有24位。它的各扇区地址如下表。
二.扇区擦除原理
扇区擦除(SE)指令可以按照扇区擦除 Flash。和块擦除不同的是,扇区擦除是要指定扇区地址,扇区擦除前也需要发送写使能指令。时序图如下:
同全擦除一样,首先得发送写允许指令,然后发送扇区擦除指令。和全擦除不同的是它的扇区擦除指令后面得加一个24位的地址。例如我们现在要把第一扇区擦除,地址的高八位就得是01h,另外24位可以为0000h-ffffh中的任意值。
三.代码
此代码没有像野火那样使用状态机,具体如下:
// -----------------------------------------------------------------------------
// Create : 2023-12-21
// Note : spi flash扇区擦除实验
// 实验开发板:黑金ax301,flash芯片:M25P16
// 需要用到的命令:
// WREN(Write Enable): 06h
// SE(Sector Erase): D8h
// 扇区擦除前要先写入WREN,然后cs拉高需要至少100ns才能开始下一次传输,本代码将擦除第0扇区
// Revise : 2023-12-2
// -----------------------------------------------------------------------------
module spi_se (
clk,
reset,
spi_miso,
spi_mosi,
spi_cs,
spi_sck
);
input clk;
input reset;
input spi_miso;
output spi_mosi;
output spi_sck;
output spi_cs;
reg [11:0] cnt;
//设置扇区擦除程序运行一次的周期位4095*20ns,当cnt为12'hfff时,cnt将保持不变。
always @(posedge clk, negedge reset) begin
if(!reset)
cnt <= 0;
else if(cnt < 12'hfff)
cnt <= cnt + 12'd1;
end
//设置指令的产生和指令标准位脉冲的产生,cnt为2000时,指令为写允许指令“00000110”,产生一个时钟周期的指令标准为脉冲,
//cnt为2050时,指令为扇区擦除指令“d8001213h”,产生一个时钟周期的指令标准为脉冲。
wire data_flag_1 = cnt == 12'd2000;
wire data_flag_2 = cnt == 12'd2050;
wire [31:0]data = (cnt == 12'd2000) ? 32'h00000006 : ((cnt == 12'd2050) ? 32'hd8001213 : 8'd0);
spi_driver spi_driver(
.clk(clk),
.reset(reset),
.data(data),
.spi_miso(spi_miso),
.data_flag_1(data_flag_1),
.data_flag_2(data_flag_2),
.spi_cs(spi_cs),
.spi_sck(spi_sck),
.spi_mosi(spi_mosi)
);
endmodule
// -----------------------------------------------------------------------------
// Create : 2023-11-31
// Revise : 2023-12-2
// Note : spi驱动,模式0,时钟默认低电平,上升沿数据有效,主机模式,目前只有单字节发送功能
// 该flash快速命令最高支持50M时钟,常规命令最高支持20M时钟,所以SPI时钟使用主时钟四分频12.5M
// -----------------------------------------------------------------------------
module spi_driver (
clk,
reset,
data,
spi_miso,
data_flag_1,//写允许指令标准位
data_flag_2,//扇区擦除指令标准位
spi_cs,
spi_sck,
spi_mosi
);
input clk;
input reset;
input[31:0]data; //指令数据
input data_flag_1; //写允许指令标准位
input data_flag_2; //扇区擦除指令标志位
input spi_miso;
output reg spi_cs;
output wire spi_sck;
output reg spi_mosi;
reg[1:0]cnt ; //用于四分频
reg[4:0]cnt_data; //用于计数当前发送的数据位
reg[31:0]data_r;
reg[1:0]data_flag_r;//01表示发写允许指令,10表示发扇区擦除指令
//当写允许指令来的时候,data_flag_r为2‘b01,扇区擦除指令来的时候为2'b10
always @(posedge clk or negedge reset) begin
if(!reset)
data_flag_r <= 0;
else if(data_flag_1)
data_flag_r <= 2'b01;
else if(data_flag_2)
data_flag_r <= 2'b10;
end
//data_r,由于写允许指令只有8位,因此把写允许指令给data_r的低8位,剩下的24位补0
always @(posedge clk or negedge reset) begin
if(!reset)
data_r <= 0;
else if(data_flag_1)
data_r <= {24'h00,data[7:0]};
else if(data_flag_2)
data_r <= data;
end
//cnt
always @(posedge clk or negedge reset) begin
if(!reset)
cnt <= 0;
else if(!spi_cs)
cnt <= cnt+1'b1;
end
//产生12.5mhz的sck
assign spi_sck = cnt[1];
//8位数据的计数
always @(posedge clk or negedge reset) begin
if(!reset)
cnt_data <= 0;
else if(cnt == 2'b11)
if(data_flag_r == 2'b01 && cnt_data == 5'd7)
cnt_data <= 0;
else
cnt_data <= cnt_data+1'b1;
end
//片选信号,低电平有效
always @(posedge clk or negedge reset) begin
if(!reset)
spi_cs <= 1;
else if(data_flag_1 || data_flag_2)
spi_cs <= 0;
else if(data_flag_r == 2'b01 && cnt_data == 5'b00111 && cnt == 2'b11)
spi_cs <= 1;
else if(data_flag_r == 2'b10 && cnt_data == 5'b11111 && cnt == 2'b11)
spi_cs <= 1;
end
//spi协议规定先发高位,因此将cnt_data取反。
always @(*) begin
if(!reset)
spi_mosi <= 0;
else if(data_flag_r == 2'b01)
spi_mosi <= spi_cs? 0 : data_r[~cnt_data[2:0]];//此时数据有效位为7:0位
else if(data_flag_r == 2'b10)
spi_mosi <= spi_cs? 0 : data_r[~cnt_data];
end
endmodule
`timescale 1ns/1ns
module spi_tb;
reg clk;
reg reset;
reg spi_miso;
wire spi_cs;
wire spi_sck;
wire spi_mosi;
spi_se spi_se(
.clk(clk),
.reset(reset),
.spi_miso(spi_miso),
.spi_cs(spi_cs),
.spi_sck(spi_sck),
.spi_mosi(spi_mosi)
);
initial clk=0;
always#10 clk=!clk;
initial begin
reset=0;
spi_miso=0;
#201;
reset=1;
#100000
$stop;
end
endmodule
四.仿真波形