1、实验目标
使用页写或连续写操作向Flash芯片写入数据,再使用数据读操作读取之前写入数据,将读取的数据使用串口传回PC机,使用串口助手传回数据并与之前写入数据比较,判断正误。
注意:在向Flash芯片写入数据之前,先要对芯片执行全擦除操作。
2、操作时序
2.1 读操作时序
结合数据手册来详细说明一下SPI-Flash芯片数据读操作的相关内容。数据读操作,操作指令为8’b0000_0011(03h),具体见图
要执行数据读指令,首先拉低片选信号选中Flash芯片,随后写入数据读(READ)指令,紧跟指令写入3字节的数据读取首地址,指令和地址会在串行时钟上升沿被芯片锁存。随后存储地址对应存储单元中的数据在串行时钟下降沿通过串行数据总线输出。
数据读取首地址可以为芯片中的任何一个有效地址,使用数据读(READ)指令可以对芯片内数据连续读取,当首地址数据读取完成,会自动对首地址的下一个地址进行数据读取。若最高位地址内数据读取完成,会自动跳转到芯片首地址继续进行数据读取,只有再次拉高片选信号,才能停止数据读操作,否者会对芯片执行无线循环读操作 。
数据读操作的详细介绍及时序图,具体见图
数据读操作指令写入之前无需先写入写使能指令,且执行数据读操作过程中,片选信号拉低后和拉高前无需做规定时间等待,上图中的时序图就是完整的数据读操作时序。
2.2 写操作时序
写操作分为页写操作和
2.2.1 页写操作时序
实验目标:
使用页写指令,向Flash中写入N字节数据,N为整数,且大于0小于等于256。在本本实验中我们向Flash芯片中写入0-99,共100字节数据,数据初始地址为24’h00_04_25。
注意:在向Flash芯片写入数据之前,先要对芯片执行全擦除操作。
页写(Page Program)操作,简称PP,操作指令为8’b0000_0010(02h),具体见图:
由数据手册中页写操作可知,页写指令是根据写入数据将存储单元中的“1”置为“0”,实现数据的写入。在写入页写指令之前,需要先写入写使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态;随后要拉低片选信号,写入页写指令、扇区地址、页地址、字节地址,紧跟地址写入要存储在Flash的字节数据, 在指令、地址以及数据写入过程中,片选信号始终保持低电平,待指令、地址、数据被芯片锁存后,将片选信号拉高;片选信号拉高后,等待一个完整的页写周期(tPP),才能完成Flash芯片的页写操作。
Flash芯片中一页最多可以存储256字节数据,这也表示页写操作一次最多向Flash芯片写入256字节数据。页写指令写入后,随即写入3字节数据写入首地址,首地址为扇区地址、页地址、字节地址组成,扇区地址与页地址是确定数据写入Flash的特定扇区的特定页,字节地址位再该页数据写入的字 节首地址。
当数据写入的字节首地址为该页的首地址,及字节首地址为8’b0000_0000,数据写入个数为0-256字节,数据可以被正确写入Flash芯片;
当数据写入的字节首地址不是该页的首地址,及字节首地址不是8’b0000_0000,数据写入个数为0-256字节,若数据写入个数少于字节首地址地址到末地址之间的存储单元个数,数据可以被正确写入Flash芯片;若数据写入个数多于字节首地址地址到末地址之间的存储单元个数,等于字节首地址地址到末地址之间的存储单元个数的数据可以被正确写入Flash芯片,超出的那部分数据,会以8’b0000_0000为字节首地址顺序写入本页,覆盖改地址之前存储的数据。
3、实验设计
3.1 读实验设计
整个工程也分为3个模块,按键消抖模块(key_filter)、数据读模块(flash_read_ctrl)、串口数据发送模块(uart_tx)和包含各模块实例化的顶层模块(spi_flash_read),模块框图,具体见图
模块名称 | 功能描述 |
---|---|
spi_flsah_read | 数据读工程顶层模块 |
key_filter | 按键消抖模块 |
flash_read _ctrl | 数据读模块 |
uart_tx | 串口数据发送模块 |
`timescale 1ns/1ns
module flash_read_ctrl(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire key , //按键输入信号
input wire miso , //读出flash数据
output reg sck , //串行时钟
output reg cs_n , //片选信号
output reg mosi , //主输出从输入数据
output reg tx_flag , //输出数据标志信号
output wire [7:0] tx_data //输出数据
);
//parameter define
parameter IDLE = 3'b001 , //初始状态
READ = 3'b010 , //数据读状态
SEND = 3'b100 ; //数据发送状态
parameter READ_INST = 8'b0000_0011; //读指令
parameter NUM_DATA = 16'd100 ; //读出数据个数
parameter SECTOR_ADDR = 8'b0000_0000, //扇区地址
PAGE_ADDR = 8'b0000_0100, //页地址
BYTE_ADDR = 8'b0010_0101; //字节地址
parameter CNT_WAIT_MAX= 16'd6_00_00 ;
//wire define
wire [7:0] fifo_data_num ; //fifo内数据个数
//reg define
reg [4:0] cnt_clk ; //系统时钟计数器
reg [2:0] state ; //状态机状态
reg [15:0] cnt_byte ; //字节计数器
reg [1:0] cnt_sck ; //串行时钟计数器
reg [2:0] cnt_bit ; //比特计数器
reg miso_flag ; //miso提取标志信号
reg [7:0] data ; //拼接数据
reg po_flag_reg ; //输出数据标志信号
reg po_flag ; //输出数据
reg [7:0] po_data ; //输出数据
reg fifo_read_valid ; //fifo读有效信号
reg [15:0] cnt_wait ; //等待计数器
reg fifo_read_en ; //fifo读使能
reg [7:0] read_data_num ; //读出fifo数据个数
//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 5'd0;
else if(state == READ)
cnt_clk <= cnt_clk + 1'b1;
//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_byte <= 16'd0;
else if((cnt_clk == 5'd31) && (cnt_byte == NUM_DATA + 16'd3))
cnt_byte <= 16'd0;
else if(cnt_clk == 5'd31)
cnt_byte <= cnt_byte + 1'b1;
//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sck <= 2'd0;
else if(state == READ)
cnt_sck <= cnt_sck + 1'b1;
//cs_n:片选信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cs_n <= 1'b1;
else if(key == 1'b1)
cs_n <= 1'b0;
else if((cnt_byte == NUM_DATA + 16'd3) && (cnt_clk == 5'd31) && (state == READ))
cs_n <= 1'b1;
//sck:输出串行时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sck <= 1'b0;
else if(cnt_sck == 2'd0)
sck <= 1'b0;
else if(cnt_sck == 2'd2)
sck <= 1'b1;
//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 3'd0;
else if(cnt_sck == 2'd2)
cnt_bit <= cnt_bit + 1'b1;
//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE: if(key == 1'b1)
state <= READ;
READ: if((cnt_byte == NUM_DATA + 16'd3) && (cnt_clk == 5'd31))
state <= SEND;
SEND: if((read_data_num == NUM_DATA)
&& ((cnt_wait == (CNT_WAIT_MAX - 1'b1))))
state <= IDLE;
default: state <= IDLE;
endcase
//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
mosi <= 1'b0;
else if((state == READ) && (cnt_byte>= 16'd4))
mosi <= 1'b0;
else if((state == READ) && (cnt_byte == 16'd0) && (cnt_sck == 2'd0))
mosi <= READ_INST[7 - cnt_bit]; //读指令
else if((state == READ) && (cnt_byte == 16'd1) && (cnt_sck == 2'd0))
mosi <= SECTOR_ADDR[7 - cnt_bit]; //扇区地址
else if((state == READ) && (cnt_byte == 16'd2) && (cnt_sck == 2'd0))
mosi <= PAGE_ADDR[7 - cnt_bit]; //页地址
else if((state == READ) && (cnt_byte == 16'd3) && (cnt_sck == 2'd0))
mosi <= BYTE_ADDR[7 - cnt_bit]; //字节地址
//miso_flag:miso提取标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
miso_flag <= 1'b0;
else if((cnt_byte >= 16'd4) && (cnt_sck == 2'd1))
miso_flag <= 1'b1;
else
miso_flag <= 1'b0;
//data:拼接数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data <= 8'd0;
else if(miso_flag == 1'b1)
data <= {data[6:0],miso};
//po_flag_reg:输出数据标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag_reg <= 1'b0;
else if((cnt_bit == 3'd7) && (miso_flag == 1'b1))
po_flag_reg <= 1'b1;
else
po_flag_reg <= 1'b0;
//po_flag:输出数据标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= po_flag_reg;
//po_data:输出数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'd0;
else if(po_flag_reg == 1'b1)
po_data <= data;
else
po_data <= po_data;
//fifo_read_valid:fifo读有效信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
fifo_read_valid <= 1'b0;
else if((read_data_num == NUM_DATA)
&& ((cnt_wait == (CNT_WAIT_MAX - 1'b1))))
fifo_read_valid <= 1'b0;
else if(fifo_data_num == NUM_DATA)
fifo_read_valid <= 1'b1;
//cnt_wait:两数据读取时间间隔
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 16'd0;
else if(fifo_read_valid == 1'b0)
cnt_wait <= 16'd0;
else if(cnt_wait == (CNT_WAIT_MAX - 1'b1))
cnt_wait <= 16'd0;
else if(fifo_read_valid == 1'b1)
cnt_wait <= cnt_wait + 1'b1;
//fifo_read_en:fifo读使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
fifo_read_en <= 1'b0;
else if((cnt_wait == (CNT_WAIT_MAX - 1'b1))
&& (read_data_num < NUM_DATA))
fifo_read_en <= 1'b1;
else
fifo_read_en <= 1'b0;
//read_data_num:自fifo中读出数据个数计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
read_data_num <= 8'd0;
else if(fifo_read_valid == 1'b0)
read_data_num <= 8'd0;
else if(fifo_read_en == 1'b1)
read_data_num <= read_data_num + 1'b1;
//tx_flag
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tx_flag <= 1'b0;
else
tx_flag <= fifo_read_en;
//-------------fifo_data_inst--------------
fifo_data fifo_data_inst(
.clock (sys_clk ), //时钟信号
.data (po_data ), //写数据,8bit
.wrreq (po_flag ), //写请求
.rdreq (fifo_read_en ), //读请求
.q (tx_data ), //数据读出,8bit
.usedw (fifo_data_num) //fifo内数据个数
);
endmodule
经过仿真后,设计正确,由于波形图时域很长,便不负图了,将实验工程分享出来供参考。
3.2 页写实验设计
模块名称 | 功能描述 |
---|---|
spi_flsah_pp | 页写工程顶层模块 |
key_filter | 按键消抖模块 |
flash_pp_ctrl | 页写模块 |
状态机状态跳转流程如下:系统上电后,状态机状态变量state一直处于初始状态(IDLE);当传入的扇区擦除触发信号key有效时,表示实验工程开始执行对Flash芯片的扇区擦除操作,状态机跳转到写使能状态(WR_EN),同时片选信号拉低,选中要进行扇区擦除操作的Flash芯片;状态跳转到写使能状态且片 选信号拉低后,要进行tSLCH≥5ns的等待时间,等待时间过后对主输出从输入信号写入写使能指令,指令写入完成后需要进行tCHSH≥5ns的等待时间,等待时间过后拉高片选信号,取消对Flash芯片的选择,同时状态机跳转到两指令间等待状态(DELAY);在此状态等待时间tSHSL≥ 100ns后,状态机跳转到页写状态(PP),同时片选信号拉低,选中已写入写使能指令的Flash芯片;状态机跳转到扇区擦除状态且片选信号拉低后,要进行tSLCH≥5ns的等待时间,等待时间过后对主输出从输入信号写入页写指令、3字节的数据写入首地址地址和待写入数据,数据写入完成后需要进行tCHSH≥5ns的等待时间,等待时间过后拉高片选信号,取消对Flash芯片的选择,同时状态机跳回初始状态(IDLE),一次完整的页写操作完成。
(写的内容持续更新。。。。)