1、spi协议
1.1spi简介
SPI是同步全双工通信,通信原理以主从方式工作,通常有一个主设备和一个或多个从设备,需要4根线连接:MISO(主设备数据输入)、MOSI(主设备输出)、SCLK(时钟)、CS(片选)。通常拉低对应从机的片选来收发数据。
MISO:主设备输入,从设备输出
MOSI:主设备输出,从设备输入
SCLK:时钟信号,由主设备产生
CS:从设备使能信号,由主设备控制
1.2时钟极性CPOL和时钟相位CPHA
时钟极性和时钟相位共同决定了读取数据的方式。
时钟极性CPOL = 0 : 同步时钟SCLK在空闲时为低电平
时钟极性CPOL = 1 : 同步时钟SCLK在空闲时为高电平;
时钟相位CPHA = 0 : 在同步时钟信号SCLK的第一个跳变沿采样
时钟相位CPHA = 1 : 在同步时钟信号SCLK的第二个跳变沿采样。
我使用的FLASH只适用模式0(CPOL=0,CPHA=0)和模式3(CPOL=1,CPHA=1)。

1.3整体传输过程
主机先将cs片选信号拉低,这样保证开始传输数据
当接收端检测到时钟的第二个边沿信号时,它将立即读取数据线上的信号,这样就得到了1bit的数据。(前沿输出,后沿采样)
主机发送给从机时,主机产生相应的时钟信号(SCLK),然后数据一位一位地从M0SI信号线上发送给从机(先发高位,再发低位)
主机接收从机数据时,从机需要将数据送回主机,则主机将继续生成预定数量分频的时钟信号,并且从机通过MISO信号线发送数据给主机(先发高位,再发低位)
2、flash手册
Flash有16Mbits的存储空间,一共有32个扇区,每个扇区256页,每页256字节,可以单扇区擦除或者整块擦除。
SPI总线兼容串行接口,所以可以用spi协议来读写flash。
2.1flash信号
信号逻辑图:

指令表

RDID |
8'h9F |
读ID |
RDSR |
8'h05 |
读寄存器 |
READ |
8'h03 |
读数据 |
PP |
8'h02 |
页擦除 |
SE |
8'hD8 |
扇区擦除 |
2.2读写时序
写使能时序
写使能WREN:1字节的指令
(WREN)指令设置写使能锁存器(WEL)位。必须在每个页编程(PP)、扇区擦除(SE)、全擦除(BE)和写入状态寄存器(WRSR)指令之前设置写使能锁存器(WEL)位拉高。

读ID时序
读ID:1字节的指令、3字节的ID数据
读取标识(RDID)指令允许读取8位的制造商标识,然后是两个字节的设备标识,也就是3字节的数据。其中设备标识由设备制造商指定,第一个字节的内存类型,以第二个字节表示设备的内存容量。

读状态寄存器
读状态寄存器RDSR:发送1字节的指令、接收2字节的数据

状态寄存器可以在任何时候被读取,即使在一个程序、擦除或写状态寄存器周期正在进行中。

WIP位:“正在写入”(WIP)位指内存是忙于写入状态寄存器、编程还是擦除周期。当设置为1时,这样的循环正在进行,当重置为0时,表示擦除完成。
WEL位 :写使能锁存器(WEL)位表示内部写使能锁存器的状态。当设置为0时,不能进行写状态寄存器、页编程或擦除指令,相当于重新进行写使能让WEL拉高。
读数据时序
一字节的读取数据字节(READ)指令,3字节地址(24bits),1字节的数据。
在每个字节数据读取后,地址自动增加到下一个更高的地址。因此,可以用单个的读取数据字节(READ)指令来读取整个内存。

页编程时序
在页编程之前,必须先执行了写使能(WREN)指令。在执行(WREN)指令,设备将设置写使能锁存器(WEL)拉高。如果写使能锁存位为0,则必须再执行写使能指令,没有置位(一般都会置位),为1时可以进行页编程。
当页编程正在进行时,可以读取状态寄存器以检查正在写入(WIP)位的值。在页编程期间,正在写入(WIP)位为1,页编程完成时为0。
一字节指令、3字节地址、一字节数据

扇区擦除时序
扇区擦除(SE)指令之前,必须先执行了写使能(WREN)指令。在写使能(WREN)指令执行后,写使能锁存器(WEL)位拉高。
当扇区擦除周期正在进行时,可以读取状态寄存器以检查正在写入(WIP)位的值。在扇区擦除周期中,正在进行的写入(WIP)位为1,在擦除完成时为0。
一字节指令、三字节地址

2.3页编程和扇区擦除的时间


tpp |
1.4-5ms |
页编程的时间 |
tSE |
1-3s |
扇区擦除的时间 |
tSHSL |
100ns |
一次操作结束到下一次开始的时间 |
3、项目设计
3.1flash写模块
主状态机:

从状态机:

module flash_write(
input clk ,
input rst_n ,
//key
input write ,
input [ 7:0] wr_data ,//写入的数据
input [23:0] wr_addr ,//flash写地址
//spi_master
output trans_req ,
output [7:0] tx_dout ,
input [7:0] rx_din ,
input trans_done ,
//output
output [47:0] dout ,
output [5:0] dout_mask ,
output dout_vld
);
//参数定义
//主状态机状态参数
localparam M_IDLE = 5'b0_0001,
M_WREN = 5'b0_0010,//主机发写使能序列
M_SE = 5'b0_0100,//主机发扇区擦除序列
M_RDSR = 5'b0_1000,//主机发读状态寄存器序列
M_PP = 5'b1_0000;//主机发页编程序列
//从状态机状态参数
localparam S_IDLE = 5'b0_0001,
S_CMD = 5'b0_0010,//发送命令
S_ADDR = 5'b0_0100,//发送地址
S_DATA = 5'b0_1000,//发送数据、接收数据
S_DELA = 5'b1_0000;//延时 拉高片选信号
localparam CMD_WREN = 8'h06,//写使能命令
CMD_SE = 8'hD8,//擦除命令
CMD_RDSR = 8'h05,//读状态寄存器命令
CMD_PP = 8'h02;//页编程命令
parameter TIME_DELAY = 16,//发完WREN、SE、PP序列 拉高片选
TIME_SE = 15_000,//150_000_000,//扇区擦除3s
TIME_PP = 2500,//25_000,//页编程5ms
TIME_RDSR = 2000;//读状态寄存器 最大2000次
//信号定义
reg [4:0] m_state_c ;
reg [4:0] m_state_n ;
reg [4:0] s_state_c ;
reg [4:0] s_state_n ;
reg [3:0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;
reg [3:0] xx ;
reg [31:0] cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;
reg [31:0] yy ;
reg rdsr_wip ;
reg rdsr_wel ;
reg [2:0] flag ;
reg [7:0] tx_data ;
reg tx_req ;
reg [47:0] data ;
reg [5:0] data_mask ;
reg data_vld ;
wire m_idle2m_wren ;
wire m_wren2m_se ;
wire m_se2m_rdsr ;
wire m_rdsr2m_wren ;
wire m_rdsr2m_pp ;
wire m_wren2m_pp ;
wire m_rdsr2m_idle ;
wire m_pp2m_rdsr ;
wire s_idle2s_cmd ;
wire s_cmd2s_addr ;
wire s_cmd2s_data ;
wire s_cmd2s_dela ;
wire s_addr2s_data ;
wire s_addr2s_dela ;
wire s_data2s_dela ;
wire s_dela2s_idle ;
//状态机设计
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
m_state_c <= M_IDLE ;
end
else begin
m_state_c <= m_state_n;
end
end
always @(*) begin
case(m_state_c)
M_IDLE :begin
if(m_idle2m_wren)
m_state_n = M_WREN ;
else
m_state_n = m_state_c ;
end
M_WREN :begin
if(m_wren2m_se)
m_state_n = M_SE ;
else if(m_wren2m_pp)
m_state_n = M_PP ;
else
m_state_n = m_state_c ;
end
M_SE :begin
if(m_se2m_rdsr)
m_state_n = M_RDSR ;
else
m_state_n = m_state_c ;
end
M_RDSR :begin
if(m_rdsr2m_wren)
m_state_n = M_WREN ;
else if(m_rdsr2m_pp)
m_state_n = M_PP ;
else if(m_rdsr2m_idle)
m_state_n = M_IDLE ;
else
m_state_n = m_state_c ;
end
M_PP :begin
if(m_pp2m_rdsr)
m_state_n = M_RDSR ;
else
m_state_n = m_state_c ;
end
default : m_state_n = M_IDLE ;
endcase
end
assign m_idle2m_wren = m_state_c==M_IDLE && (write);
assign m_wren2m_se = m_state_c==M_WREN && (s_dela2s_idle && flag[0]);
assign m_se2m_rdsr = m_state_c==M_SE && (s_dela2s_idle);
assign m_rdsr2m_wren = m_state_c==M_RDSR && (s_dela2s_idle && ~rdsr_wel && ~rdsr_wip && flag[1]);
assign m_rdsr2m_pp = m_state_c==M_RDSR && (s_dela2s_idle && rdsr_wel && ~rdsr_wip && flag[1]);
assign m_wren2m_pp = m_state_c==M_WREN && (s_dela2s_idle && flag[1]);
assign m_rdsr2m_idle = m_state_c==M_RDSR && (s_dela2s_idle && flag[2]);
assign m_pp2m_rdsr = m_state_c==M_PP && (s_dela2s_idle);
//从状态机设计
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
s_state_c <= S_IDLE ;
end
else begin
s_state_c <= s_state_n;
end
end
always @(*) begin
case(s_state_c)
S_IDLE :begin
if(s_idle2s_cmd)
s_state_n = S_CMD ;
else
s_state_n = s_state_c ;
end
S_CMD :begin
if(s_cmd2s_addr)
s_state_n = S_ADDR ;
else if(s_cmd2s_data)
s_state_n = S_DATA ;
else if(s_cmd2s_dela)
s_state_n = S_DELA ;
else
s_state_n = s_state_c ;
end
S_ADDR :begin
if(s_addr2s_data)
s_state_n = S_DATA ;
else if(s_addr2s_dela)
s_state_n = S_DELA ;
else
s_state_n = s_state_c ;
end
S_DATA :begin
if(s_data2s_dela)
s_state_n = S_DELA ;
else
s_state_n = s_state_c ;
end
S_DELA :begin
if(s_dela2s_idle)
s_state_n = S_IDLE ;
else
s_state_n = s_state_c ;
end
default : s_state_n = S_IDLE ;
endcase
end
assign s_idle2s_cmd = s_state_c==S_IDLE && (m_state_c != M_IDLE);
assign s_cmd2s_addr = s_state_c==S_CMD && (trans_done && (m_state_c == M_SE || m_state_c == M_PP));
assign s_cmd2s_data = s_state_c==S_CMD && (trans_done && m_state_c == M_RDSR);
assign s_cmd2s_dela = s_state_c==S_CMD && (trans_done && m_state_c == M_WREN);
assign s_addr2s_data = s_state_c==S_ADDR && (end_cnt0 && (m_state_c == M_RDSR || m_state_c == M_PP));
assign s_addr2s_dela = s_state_c==S_ADDR && (end_cnt0 && m_state_c == M_SE);
assign s_data2s_dela = s_state_c==S_DATA && (end_cnt0 && m_state_c == M_PP || m_state_c == M_RDSR && end_cnt1);
assign s_dela2s_idle = s_state_c==S_DELA && (end_cnt1);
//计数器设计
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt0 <= 0;
end
else if(add_cnt0) begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0+1 ;
end
end
assign add_cnt0 = (m_state_c != M_RDSR && trans_done);
assign end_cnt0 = add_cnt0 && cnt0 == (xx)-1 ;
always @(*)begin
if(s_state_c == S_CMD) //发命令1字节
xx = 1;
else if(s_state_c == S_ADDR) //发地址3字节
xx = 3;
else /*if(s_state_c == S_DATA)*/ //发数据1字节
xx = 4;
end
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt1 <= 0;
end
else if(add_cnt1) begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1+1 ;
end
end
assign add_cnt1 = (s_state_c == S_DELA || m_state_c == M_RDSR && s_state_c == S_DATA && trans_done);
assign end_cnt1 = add_cnt1 && (cnt1 == (yy)-1 || trans_done && ~rdsr_wip);
always @(*)begin
if((m_state_c == M_WREN || m_state_c == M_RDSR) && s_state_c == S_DELA) //发完写使能延时
yy = TIME_DELAY;
else if(m_state_c == M_SE)//发完擦除延时
yy = TIME_SE;
else if(m_state_c == M_PP)//发完页编程延时
yy = TIME_PP;
else if(m_state_c == M_RDSR && s_state_c == S_DATA)//读状态寄存器值 yy 次
yy = TIME_RDSR;
else
yy = TIME_DELAY;
end
//rdsr_wel rdsr_wip
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdsr_wel <= 1'b0;
rdsr_wip <= 1'b0;
end
else if(m_state_c == M_RDSR && s_state_c == S_DATA && trans_done)begin
rdsr_wel <= rx_din[1];
rdsr_wip <= rx_din[0];
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 3'b000;
end
else if(m_idle2m_wren)begin
flag <= 3'b001;
end
else if(m_se2m_rdsr)begin
flag <= 3'b010;
end
else if(m_pp2m_rdsr)begin
flag <= 3'b100;
end
end
//输出
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
tx_data <= 0;
end
else if(m_state_c == M_WREN)begin
tx_data <= CMD_WREN;
end
else if(m_state_c == M_SE)begin
case(s_state_c)
S_CMD :tx_data <= CMD_SE;
S_ADDR:tx_data <= wr_addr[23-cnt0*8 -:8];
default:tx_data <= 0;
endcase
end
else if(m_state_c == M_RDSR)begin //在读状态寄存器时,可以发一次命令,也可以连续发命令
tx_data <= CMD_RDSR;
end
else if(m_state_c == M_PP)begin
case(s_state_c)
S_CMD :tx_data <= CMD_PP;
S_ADDR:tx_data <= wr_addr[23-cnt0*8 -:8];
S_DATA:tx_data <= wr_data + cnt0;
default:tx_data <= 0;
endcase
end
end
//tx_req cs_n
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
tx_req <= 1'b0;
end
else if(s_idle2s_cmd)begin
tx_req <= 1'b1;
end
else if(s_cmd2s_dela | s_addr2s_dela | s_data2s_dela)begin
tx_req <= 1'b0;
end
end
//data data_mask
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data <= 0;
data_mask <= 0;
end
else if(m_rdsr2m_idle & ~rdsr_wip)begin //PP completed
data <= {"P","P",16'd0,4'd0,wr_data[7:4],4'd0,wr_data[3:0]};
data_mask <= 6'b001100;
end
else if(m_rdsr2m_idle & rdsr_wip)begin //PP failed
data <= {"P","P",8'd0,"ERR"};
data_mask <= 6'b001000;
end
end
//data_vld ,
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_vld <= 0;
end
else begin
data_vld <= m_rdsr2m_idle;
end
end
//输出
assign tx_dout = tx_data;
assign trans_req = tx_req;
assign dout_vld = data_vld;
assign dout_mask = data_mask;
assign dout = data;
endmodule
3.2 flash读模块

module flash_read(
input clk ,
input rst_n ,
//key
input rd_id ,
input rd_data ,
input [23:0] rd_addr ,//flash读地址
//spi_master
output trans_req ,
output [7:0] tx_dout ,
input [7:0] rx_din ,
input trans_done ,
//output
output [47:0] dout ,
output [5:0] dout_mask ,
output dout_vld
);
/********* 工程注释 ****************
M25P16 Flash读控制器,实现读数据和读存储器的ID。
********** 注释结束 ****************/
//状态机参数定义
localparam IDLE = 4'b0001,
RDID = 4'b0010,//读器件ID
RDDA = 4'b0100,//读数据字节
DONE = 4'b1000;
//Flash命令参数定义
localparam CMD_RDID = 8'h9F,
CMD_RDDA = 8'h03;
//信号定义
reg [3:0] state_c ;
reg [3:0] state_n ;
reg [3:0] cnt_byte ;
wire add_cnt_byte;
wire end_cnt_byte;
reg [3:0] byte_num ;
reg [7:0] tx_data ;
reg tx_req ;
reg flag ;//读数据、读ID标志
wire idle2rdid ;
wire rdid2done ;
wire idle2rdda ;
wire rdda2done ;
wire done2idle ;
reg [31:0] rx_data /* synthesis keep */;//串并转换寄存器
reg [47:0] data ;
reg [5:0] data_mask ;
reg data_vld ;
//状态机
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end
always @(*) begin
case(state_c)
IDLE :begin
if(idle2rdid)
state_n = RDID ;
else if(idle2rdda)
state_n = RDDA ;
else
state_n = state_c ;
end
RDID :begin
if(rdid2done)
state_n = DONE ;
else
state_n = state_c ;
end
RDDA :begin
if(rdda2done)
state_n = DONE ;
else
state_n = state_c ;
end
DONE :begin
if(done2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end
assign idle2rdid = state_c==IDLE && (rd_id );
assign rdid2done = state_c==RDID && (end_cnt_byte);
assign idle2rdda = state_c==IDLE && (rd_data);
assign rdda2done = state_c==RDDA && (end_cnt_byte);
assign done2idle = state_c==DONE && (1'b1);
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_byte <= 0;
end
else if(add_cnt_byte) begin
if(end_cnt_byte)
cnt_byte <= 0;
else
cnt_byte <= cnt_byte+1 ;
end
end
assign add_cnt_byte = (state_c != IDLE && trans_done);
assign end_cnt_byte = add_cnt_byte && cnt_byte == (byte_num)-1 ;
always @(*)begin
if(state_c == RDID)
byte_num = 4;
else
byte_num = 8; //至少5B CMD + 3 ADDR + X DATA
end
//输出
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
tx_req <= 1'b0;
end
else if(idle2rdid | idle2rdda)begin
tx_req <= 1'b1;
end
else if(rdid2done | rdda2done)begin
tx_req <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
tx_data <= 0;
end
else if(state_c == RDID)begin
case(cnt_byte)
0:tx_data <= CMD_RDID;
default:tx_data <= 0;
endcase
end
else if(state_c == RDDA)begin
case(cnt_byte)
0:tx_data <= CMD_RDDA;
1:tx_data <= rd_addr[23:16];
2:tx_data <= rd_addr[15:8];
3:tx_data <= rd_addr[7:0];
default:tx_data <= 0;
endcase
end
end
//rx_data
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_data <= 0;
end
else if(state_c == RDID && trans_done)begin
rx_data <= {rx_data[23:0],rx_din};
end
else if(state_c == RDDA && trans_done)begin
rx_data <= {rx_data[23:0],rx_din};
end
end
//data
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data <= 0;
end
else if(state_c == DONE && ~flag)begin //读ID
data <= {4'd0,rx_data[23:20],4'd0,rx_data[19:16], //2 0
4'd0,rx_data[15:12],4'd0,rx_data[11:8], //2 0
4'd0,rx_data[7:4],4'd0,rx_data[3:0]}; //1 5
end
else if(state_c == DONE && flag)begin //读数据
data <= {"R","D",16'd0,4'd0,rx_data[7:4],4'd0,rx_data[3:0]};
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data_mask <= 0;
end
else if(state_c == DONE && ~flag)begin
data_mask <= 6'b00_0000;
end
else if(state_c == DONE && flag)begin
data_mask <= 6'b00_1100;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data_vld <= 0;
end
else begin
data_vld <= state_c == DONE;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 1'b0;
end
else if(idle2rdda)begin
flag <= 1'b1;
end
else if(idle2rdid)begin
flag <= 1'b0;
end
end
//输出
assign tx_dout =tx_data;
assign trans_req = tx_req;
assign dout = data;
assign dout_mask = data_mask;
assign dout_vld = data_vld;
endmodule
3.3 spi_master接口模块
module spi_master(
input clk ,
input rst_n ,
input req ,
input [7:0] din ,
output [7:0] dout ,
output done ,
output cs_n ,
output mosi ,
input miso ,
output sclk
);
//参数定义
localparam SCLK_PERIOD = 16,
SCLK_FALL = 4 ,
SCLK_RISE = 12;
//信号定义
reg [ 3:0] cnt_sclk ;
wire add_cnt_sclk ;
wire end_cnt_sclk ;
reg [ 3:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg spi_sclk ;
reg spi_mosi ;
reg [7:0] rx_data ;
//计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_sclk <= 0;
end
else if(add_cnt_sclk) begin
if(end_cnt_sclk)
cnt_sclk <= 0;
else
cnt_sclk <= cnt_sclk+1 ;
end
end
assign add_cnt_sclk = (req);
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == (SCLK_PERIOD)-1 ;
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_bit <= 0;
end
else if(add_cnt_bit) begin
if(end_cnt_bit)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit+1 ;
end
end
assign add_cnt_bit = (end_cnt_sclk);
assign end_cnt_bit = add_cnt_bit && cnt_bit == (8)-1 ;
//spi_sclk 模式3
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
spi_sclk <= 1'b1;
end
else if(cnt_sclk == SCLK_FALL-1)begin
spi_sclk <= 1'b0;
end
else if(cnt_sclk == SCLK_RISE-1)begin
spi_sclk <= 1'b1;
end
end
//发送数据
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
spi_mosi <= 1'b0;
end
else if(cnt_sclk == SCLK_FALL-1)begin
spi_mosi <= din[7-cnt_bit];//并串转换
end
end
//接收数据
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_data <= 0;
end
else if(cnt_sclk == SCLK_RISE-1)begin
rx_data[7-cnt_bit] <= miso;//串并转换
end
end
//输出
assign sclk = spi_sclk;
assign mosi = spi_mosi;
assign cs_n = ~req;
assign done = end_cnt_bit;
assign dout = rx_data;
endmodule
3.4 整体设计框图
使用写操作指令向M25P16中连续写入256个以内的任意数据,然后按下读操作按键, 执行flash读操作读取写入M25P16的数据
按键消抖模块
按键消抖模块输出三个按键信号,相当于一个读ID使能、一个读数据使能和一个写使能信号;
flash_write模块
当按键消抖发送写请求信号后,flash_write模块开始写入,每当写使能,扇区擦除,读状态寄存器和页编程要发送命令时,写模块发送trans_req给spi_master模块,spi_master模块生成SCLK准备接收写模块的数据,写模块将写使能的命令,扇区擦除的命令和地址,读状态寄存器的命令,页编程的命令、地址和数据分别发送tx_dout[7:0]给spi_master模块;将写入的1字节的页编程数据发送给seg_driver模块进行数码管显示;
spi_master模块将接收到的(tx_dout[7:0])进行并串转换发送给从机(M25P16);
M25P16发送发送串行数据miso给spi_master模块;spi_master模块经过串并转换发送给flash_read模块;
flash_read模块
当 按键消抖模块发送读id使能信号后,flash_read模块发送读请求(trans_req)和1字节的读ID命令(tx_dout)给spi_master模块;3字节的ID数据发送给数码管驱动seg_driver模块;
当按键消抖模块发送读数据使能信号后,flash_read模块发读请求(trans_req)和1字节读数据命令+3字节读地址(tx_dout)给spi_master模块;最后1字节的读数据发送给seg_driver模块;
spi_master模块
spi_master模块接收到flash_crtl模块发送的trans_req时,生成SCLK时钟,在SCLK第一个跳变沿(SCLK_FALL-1)时,接收flash_crtl模块发送的tx_dout[7:0]进行并串转换发送mosi给从机(M25P16);文章来源:https://www.toymoban.com/news/detail-448059.html
在SCLK第二个跳变沿(SCLK_RISE-1)时,接收从机(M25P16)的miso信号进行串并转换发送rx_din[7:0]给flash_ctrl模块文章来源地址https://www.toymoban.com/news/detail-448059.html

到了这里,关于【FPGA】SPI读写flash的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!