强烈建议先看完下面两篇再看这一篇:
FPGA——SPI总线详解(概念)_居安士的博客-CSDN博客_fpga芯片
FPGA——SPI总线控制flash(1)(含代码)_居安士的博客-CSDN博客
在(1)中详细介绍了写使能,读状态,擦除三种操作,接下来介绍如何使用SPI总线对flash进行读写
目录
页写指令允许在先前擦除(FFh)的存储器位置编程一个字节到 256个字节(一页)的数据。 在器件接受页写指令(状态寄存器位 WEL=1)之前,必须执行写使能指令。 该指令通过将 /CS 引脚驱动为低电平,然后将指令代码“02h”后跟一个 24 位地址 (A23-A0) 和至少一个数据字节移入 DI 引脚来启动。 当数据被发送到设备时,/CS 引脚必须在指令的整个长度内保持低电平。

页写模块框图如下:

先向fifo写入数据,fifo写完了之后,再从fifo里面读数据(start_write),启动页写操作
首先发送02H的指令,后面是24位数据首地址,后面至多可以跟256个字节(256*8位)的数据
ps:页写需要时间,需要设置定时器,保证页写时间结束之后(3ms)停止页写

输入输出信号:
| 名称 | 输入输出 | 位宽 | 解释 |
| start_write | input | 1 | 启动页写 |
| brust_length | input | 9(256个字节) | 突发长度:写入数据的长度 |
| write_addr | input | 24 | 页写数据24位首地址 |
| clk_wr | input | 1 | fifo写入时钟 |
| wr_en | input | 1 | fifo写使能信号 |
| din | input | 8 | fifo输入数据 |
| wr_fifo_empty | output | 1 | fifo空 |
| wr_fifo_full | ouput | 1 | fifo满 |
| spi_cs | output | 1 | spi片选线 |
| spi_clk | output | 1 | spi时钟线 |
| spi_dout | output | 1 | spi输出 |
| mux_write | output | 1 | 写总线请求 |
| mux_write_done | input | 1 | 写总线应答 |
| write_en | output | 1 | 启动计时器 |
| time_done | input | 1 | 计时器完成 |
| write_done | output | 1 | 页写操作完成 |
flash页写流程图如下:
页写过程:
0(初始状态):把页写突发长度和页写地址寄存
1(指令解析):把02h给指令寄存器
2(请求总线):请求打开总线
3(发送指令1):把指令最高位给spi_dout,移完8位把地址最高位给spi_dout
4(发送指令2):移位8位指令
5(发送地址1):把地址最高位给spi_dout,移完24位把数据最高位给spi_dout
6(发送地址2):移位24位地址
7(发送数据1):把数据最高位给spi_dout,移完8位把cnt_byte加一;
如果cnt_byte小于突发长度-1,启动fifo读使能
否则,关闭读使能,把下一个字节的数据给spi_dout
8(发送数据2):移位8位数据
9(计数发送数据):判断cnt_byte是否等于突发长度
10(停止):请求总线停止
11(等待总线应答2):等待总线同意停止
12(等待页写时间到):3ms的延时
13(页写完成)
页写代码如下:
- module write_top(
- input clk ,
- input clk_wr ,//fifo时钟
- input reset ,
- input start_write ,//启动页写
- //fifo部分驱动接口
- input [8:0]brust_length,//突发长度
- input [23:0]write_addr ,//初始地址
- input wr_fifo_en ,//写使能
- input [7:0]din ,//写数据
- output wr_fifo_empty ,//fifo空
- output wr_fifo_full ,//fifo满
- //总线接口
- output reg spi_cs ,//spi片选
- output reg spi_clk ,//spi时钟
- output reg spi_dout ,//spi输出
- output reg mux_write ,//请求总线
- input mux_write_done ,//总线应答
- output reg write_done ,//页写完成
- //计时器
- input time_done ,//计时完成
- output reg write_en //启动计时
-
- );
-
-
- //例化页写延时
- erase_time inst_erase_time(
- .clk (clk),
- .reset (reset),
- .write_en (write_en),//页写计时
- .erase_en (),//擦除计时
- .erase_cmd(),//擦除模式选择
- .time_done(time_done)
-
- );
-
- //例化FIFO
-
- reg rd_en ;//fifo读使能
- wire[7:0] dout ;//fifo输出
-
- FIFO_wr inst_FIFO_wr (
- .rst(reset), // input wire rst
- .wr_clk(clk_wr), // input wire wr_clk
- .rd_clk(clk), // input wire rd_clk
- .din(din), // input wire [7 : 0] din
- .wr_en(wr_fifo_en), // input wire wr_en
- .rd_en(rd_en), // input wire rd_en
- .dout(dout), // output wire [7 : 0] dout
- .full(wr_fifo_full), // output wire full
- .empty(wr_fifo_empty), // output wire empty
- .wr_rst_busy(wr_rst_busy), // output wire wr_rst_busy
- .rd_rst_busy(rd_rst_busy) // output wire rd_rst_busy
- );
-
-
-
-
- reg [7:0] vaild_cmd ;//指令寄存器
- reg [23:0]write_addr_reg ;//地址寄存器
- reg [7:0] write_data ;//数据寄存器
- reg [8:0] brust_length_reg;//突发长度寄存器
-
- reg [4:0] cnt_bit ;//位计数器
- reg [8:0] cnt_byte ;//字节计数器
-
- reg [3:0]state;//定义状态机
-
- always@(posedge clk)begin
- if(reset)begin
- spi_cs <=1'd1;
- spi_clk <=1'd0;
- spi_dout <=1'd1;
- write_done <=1'd0;
- write_en <=1'd0;
- vaild_cmd <=8'd0;
- write_addr_reg <=24'd0;
- write_data <=8'd0;
- brust_length_reg<=9'd0;
- cnt_bit <=5'd0;
- cnt_byte <=9'd0;
- mux_write <=1'd0;
- state<=4'd0;
-
- end
- else begin
- case(state)
- 4'd0:begin
- if(start_write)begin
- state<=4'd1;
- write_addr_reg<=write_addr;
- brust_length_reg<=brust_length;
- end
- else begin
- state<=4'd0;
- end
- end
- 4'd1:begin
- vaild_cmd<=8'h02;
- state<=4'd2;
- end
- 4'd2:begin
- mux_write<=1'd1;
- if(mux_write_done)begin
- state<=4'd3;
- write_en<=1'd1;//启动延时计时
- end
- else begin
- state<=4'd2;
- end
- end
- 4'd3:begin
- if(cnt_bit==5'd8)begin
- cnt_bit<=5'd0;
- spi_cs<=1'd0;
- spi_clk<=1'd0;
- spi_dout<=write_addr_reg[23];//提前准备好地址
- state<= 4'd6;
- end
- else begin
- cnt_bit<=cnt_bit+5'd1;
- spi_cs<=1'd0;
- spi_clk<=1'd0;
- spi_dout<=vaild_cmd[7];
- state<= 4'd4;
- end
- end
- 4'd4:begin
- state<= 4'd3;
- spi_cs<=1'd0;
- spi_clk<=1'd1;
- vaild_cmd<={vaild_cmd[6:0],vaild_cmd[7]};
- end
- 4'd5:begin
- if(cnt_bit==5'd23)begin
- cnt_bit<=5'd0;
- spi_cs<=1'd0;
- spi_clk<=1'd0;
- spi_dout<=write_data[7];//提前准备好数据
- state<= 4'd7;
- end
- else begin
- cnt_bit<=cnt_bit+5'd1;
- spi_cs<=1'd0;
- spi_clk<=1'd0;
- spi_dout<=write_addr_reg[23];
- state<= 4'd6;
- end
- end
- 4'd6:begin
- state<= 4'd5;
- spi_cs<=1'd0;
- spi_clk<=1'd1;
- write_addr_reg<={write_addr_reg[22:0],write_addr_reg[23]};
- end
- 4'd7:begin
- if(cnt_bit==5'd7)begin
- cnt_bit<=5'd0;
- cnt_byte<=cnt_byte+9'd1;//字节计数器++
- spi_cs<=1'd0;
- spi_clk<=1'd0;
- spi_dout<=dout[7];//提前准备fifo输出字节最高位
- state<=4'd9 ;
- if(cnt_byte
1)begin//是否达到突发长度 - rd_en<=1'd1;
- write_data<=dout;//更新fifo输出的字节
- end
- else begin
- rd_en<=1'd0;
- end
- end
- else begin
- cnt_bit<=cnt_bit+5'd1;
- spi_cs<=1'd0;
- spi_clk<=1'd0;
- spi_dout<=write_data[7];
- state<=4'd8;
- end
- end
- 4'd8:begin
- spi_cs<=1'd0;
- spi_clk<=1'd1;
- write_data<={write_data[6:0],write_data[7]};
- state<=4'd7;
- end
- 4'd9:begin
- if(cnt_byte==brust_length_reg)begin
- cnt_bit<=5'd0;
- cnt_byte<=9'd0;
- spi_cs<=1'd1;
- spi_clk<=1'd0;
- spi_dout<=1'd1;
- rd_en<=1'd0;
- state<=4'd10;
- end
- else begin
- state<=4'd7;
- spi_cs<=1'd0;
- spi_clk<=1'd1;//clk连续
- write_data<={write_data[6:0],write_data[7]};//最高位已经发送,因此需要移位
- end
- end
- 4'd10:begin
- mux_write<=1'd0;
- state<=4'd11;
- end
- 4'd11:begin
- if(mux_write_done)begin
- state<=4'd12;
- write_en<=1'd0;
- end
- else begin
- state<=4'd11;
- end
- end
- 4'd12:begin
- if(time_done)begin//延时结束
- state<=4'd13;
- end
- else begin
- state<=4'd12;
- end
- end
- 4'd13:begin
- write_done<=1'd1;
- state<=4'd0;
- end
- endcase
- end
- end
-
-
- endmodule
关于FIFO的操作方法可以看这篇(1条消息) FPGA存储器(FIFO+RAM+ROM)存储实战_居安士的博客-CSDN博客_fpga的存储器资源
TB文件里面完成fifo的写,仿真代码如下:
- module TB_write_top(
-
- );
-
- reg clk ;
- reg reset ;
- reg start_write ;//启动页写
- reg [8:0]brust_length;//突发长度
- reg [23:0]write_addr ;//初始地址
- //fifo部分驱动接口
- reg clk_wr ;//fifo时钟
- reg wr_fifo_en ;//写使能
- reg [7:0]din ;//写数据
- wire wr_fifo_empty ;//fifo空
- wire wr_fifo_full ;//fifo满
- //总线接口
- wire spi_cs ;//spi片选
- wire spi_clk ;//spi时钟
- wire spi_dout ;//spi输出
- wire mux_write ;//请求总线
- reg mux_write_done ;//总线应答
- wire write_done ;//页写完成
- //计时器
- wire time_done ;//计时完成
- wire write_en ;//启动计时
-
-
-
- write_top inst_write_top(
- .clk (clk) ,
- .reset (reset) ,
- .start_write (start_write) ,//启动页写
- //fifo部分驱动接口
- .brust_length (brust_length) ,//突发长度
- .write_addr (write_addr) ,//初始地址
- .clk_wr (clk_wr) ,//fifo时钟
- .wr_fifo_en (wr_fifo_en) ,//写使能
- .din (din) ,//写数据
- .wr_fifo_empty (wr_fifo_empty) ,//fifo空
- .wr_fifo_full (wr_fifo_full) ,//fifo满
- //总线接口
- .spi_cs (spi_cs) ,//spi片选
- .spi_clk (spi_clk) ,//spi时钟
- .spi_dout (spi_dout) ,//spi输出
- .mux_write (mux_write) ,//请求总线
- .mux_write_done (mux_write_done) ,//总线应答
- .write_done (write_done) ,//页写完成
- //计时器
- .time_done (time_done) ,//计时完成
- .write_en (write_en) //启动计时
- );
-
- initial begin
- clk=0;
- clk_wr=0;
- reset=1;
- start_write <= 1'b0;
- brust_length<= 9'd0;
- write_addr <= 24'd0;
-
- #1000;
- reset=0; //复位信号
-
- #20000;
- start_write <= 1'b1;
- brust_length<= 9'd255;
- write_addr <= 24'h010203;
- #40;
- start_write <= 1'b0;
- brust_length<= 9'd0;
- write_addr <= 24'd0;
- end
-
- always #20 clk=~clk;
- always #20 clk_wr=~clk_wr;
-
- //写FIFO
- reg [7:0]count_wait;//FIFO复位需要时间
- reg [8:0]count_wren;
- always @(posedge clk_wr)begin
- if(reset)begin
- count_wait<=8'd0;
- count_wren<=9'd0;
- wr_fifo_en<=1'b0;
- din<=8'd0;
- end
- else if(count_wait <= 8'd20) begin//fifo复位时间到了再进行下面程序
- count_wait<=count_wait+8'd1;
- end
- else if(count_wren>=9'd255)begin
- count_wren<=count_wren;
- wr_fifo_en<=1'b0; //fifo写使能=0
- din<=8'd0;//数据清零
- end
- else begin
- count_wren<=count_wren+9'b1;//计数器++
- wr_fifo_en<=1'b1; //fifo写使能=1
- din<=din+8'b1; //数据++
- end
- end
-
- reg [2:0] state;
- always @(posedge clk)begin
- if(reset)begin
- state <= 3'd0;
- mux_write_done<=1'b0;
- end
- else begin
- case (state)
- 3'd0:begin
- if(mux_write)begin
- state <= 3'd1;
- mux_write_done<=1'b1;
- end
- else begin
- state <= 3'd0;
- mux_write_done<=1'b0;
- end
- end
- 3'd1:begin
- if(!mux_write)begin
- state <= 3'd2;
- mux_write_done<=1'b1;
- end
- else begin
- state <= 3'd1;
- mux_write_done<=1'b0;
- end
- end
- 3'd2:begin
- mux_write_done<=1'b0;
- state <= 3'd0;
- end
- default :state <= 3'd0;
- endcase
- end
- end
仿真结果如下:

首先向FIFO的din写入1~255数据


在start_write=1时把突发长度,地址寄存

把页写指令移位输出

指令输出完成后,把页写地址移位输出

地址输出完成后,把dout移位输出


页读指令(03h)允许从存储器中顺序读取一个或多个数据字节。 该指令通过将 /CS 引脚驱动为低电平,然后将指令代码“03h”后跟一个 24 位地址(A23-A0)移入 DI 引脚来启动。时序如下:

发送完地址之后,总线会输出spi_din数据,高位先发,把总线输出的spi_din缓存到FIFO里
页读模块框图如下:

输入输出信号:
| 名称 | 输入/输出 | 位宽 | 解释 |
| start_read | input | 1 | 开始页读 |
| read_addr | input | 24 | 读地址 |
| brust_length | input | 9 | 突发长度 |
| rd_fifo_en | input | 1 | fifo读使能 |
| rd_fifo_data | output | 8 | fifo读出的数据 |
| rd_fifo_empty | output | 1 | fifo空 |
| rd_fifo_full | output | 1 | fifo满 |
| spi_cs | output | 1 | 片选线 |
| spi_clk | output | 1 | 时钟线 |
| spi_dout | output | 1 | spi输出 |
| spi_din | input | 1 | spi输入 |
| mux_read | output | 1 | 请求总线 |
| mux_read_done | input | 1 | 总线应答 |
| read_done | output | 1 | 页读完成
|
流程图如下:

0(初始状态):页写启动信号=1时,把读地址和突发长度寄存
1(指令解析):把指令03h输入
2(请求开放总线):请求总线开放,当总线应答后跳转
3(发送指令1):把指令最高位给spi_dout,cnt_bit+1,跳转发送指令2;当cnt_bit==8时,把地址最高位给spi_dout,跳转发送地址2
4(发送指令2):把8位指令进行移位,跳转发送指令1
5(发送地址1):把地址最高位给spi_dout,cnt_bit+1,跳转发送地址2;当cnt_bit==8时,跳转接收2
6(发送指令2):把24位地址进行移位,跳转发送地址1
7(接收1):cnt_bit+1,跳转接收2;当cnt_bit==8,cnt_byte+1,fifo写使能打开,跳转计算模块
8(接收2):把读数据使能打开,跳转接收1
9(计算):判断cnt_byte是否等于突发长度
10(停止页读):请求总线停止
11(请求总线停止):等待总线应答后跳转
12(页读完成):read_done=1
编写代码如下:
- module read_top(
- input clk ,
- input reset ,
- input rd_clk ,
- input start_read ,//开始页读
- input [23:0] read_addr , //页读地址
- input [8:0] brust_length ,//突发长度
- input rd_fifo_en ,//fifo读使能
- output [7:0] rd_fifo_data ,//从fifo读出的数据
- output rd_fifo_empty ,
- output rd_fifo_full ,
- output reg spi_cs ,
- output reg spi_clk ,
- output reg spi_dout ,
- input spi_din ,
- output reg mux_read ,
- input mux_read_done ,
- output reg read_done
-
- );
- //例化fifo
- reg wr_en;
- reg [7:0] valid_data ;//读出的8位数据
-
- fifo_rd fifo_rd (
- .wr_clk(clk), // input wire wr_clk
- .rd_clk(rd_clk), // input wire rd_clk
- .din(valid_data), // input wire [7 : 0] din
- .wr_en(wr_en), // input wire wr_en
- .rd_en(rd_fifo_en), // input wire rd_en
- .dout(rd_fifo_data), // output wire [7 : 0] dout
- .full(rd_fifo_full), // output wire full
- .empty(rd_fifo_empty) // output wire empty
- );
-
-
- reg [7:0] cnt_bit ;
- reg [7:0] cnt_byte ;
- reg [7:0] valid_cmd ;//页读指令03h
- reg [23:0] read_addr_reg ;//页读地址
- reg [8:0] brust_length_reg ;//突发长度
-
- //状态机
- reg [4:0] state;
-
- always@(posedge clk)begin
- if(reset)begin
- spi_cs <=1'd1;
- spi_clk <=1'd0;
- spi_dout <=1'd1;
- cnt_bit <=8'd0;
- cnt_byte <=8'd0;
- read_done <=1'd0;
- valid_cmd <=8'd0 ;
- read_addr_reg <=24'd0 ;
- brust_length_reg<= 9'd0 ;
- mux_read<=1'd0;
- wr_en<=1'd0;
- valid_data<=8'd0;
- state<=4'd0;
- end
- else begin
- case(state)
- 4'd0:begin
- if(start_read)begin
- state<=4'd1;
- read_addr_reg<=read_addr;
- brust_length_reg<=brust_length;
- end
- else begin
- state<=4'd0;
- end
- end
- 4'd1:begin
- valid_cmd<=8'h03;
- state<=4'd2;
- end
- 4'd2:begin
- mux_read<=1'd1;
- if(mux_read_done)begin
- state<=4'd3;
- end
- else begin
- state<=4'd2;
- end
- end
- 4'd3:begin
- if(cnt_bit==8'd8)begin
- cnt_bit<=8'd0;
- spi_cs <=1'd0;
- spi_clk <=1'd0;
- spi_dout <=read_addr_reg[23];
- state<=4'd6;
- end
- else begin
- cnt_bit<=cnt_bit+8'd1;
- spi_cs <=1'd0;
- spi_clk <=1'd0;
- spi_dout <=valid_cmd[7];
- state<=4'd4;
- end
- end
- 4'd4:begin
- spi_cs <=1'd0;
- spi_clk <=1'd1;
- valid_cmd <={valid_cmd[6:0],valid_cmd[7]};
- state<=4'd3;
- end
- 4'd5:begin
- if(cnt_bit==8'd23)begin
- cnt_bit<=8'd0;
- spi_cs <=1'd0;
- spi_clk <=1'd0;
- spi_dout <=1'd1;
- state<=4'd7;
- end
- else begin
- cnt_bit<=cnt_bit+8'd1;
- spi_cs <=1'd0;
- spi_clk <=1'd0;
- spi_dout <=read_addr_reg[23];
- state<=4'd6;
- end
- end
- 4'd6:begin
- spi_cs <=1'd0;
- spi_clk <=1'd1;
- read_addr_reg <={read_addr_reg[22:0],read_addr_reg[23]};
- state<=4'd5;
- end
- 4'd7:begin
- if(cnt_bit==8'd7)begin
- cnt_byte<=cnt_byte+8'd1;
- spi_cs<=1'd0;
- spi_clk<=1'd0;
- cnt_bit<=8'd0;
- wr_en <=1'd1;//开启写使能,valid_data给fifo的din
- state<=4'd9;
- end
- else begin
- cnt_bit<=cnt_bit+8'd1;
- spi_cs <=1'd0;
- spi_clk <=1'd0;
- state<=4'd8;
- end
- end
- 4'd8:begin
- spi_cs <=1'd0;
- spi_clk <=1'd1;
- valid_data<={valid_data[6:0],spi_din};//把总线输出的数据移位进valid_data
- state<=4'd7;
- end
- 4'd9:begin
- if(cnt_byte==brust_length_reg)begin
- cnt_byte<=8'd0;
- spi_cs<=1'd1;
- spi_clk<=1'd0;
- cnt_bit<=8'd0;
- wr_en <=1'd0;
- state<=4'd10;
- end
- else begin
- spi_cs<=1'd0;
- spi_clk<=1'd1;
- valid_data<={valid_data[6:0],spi_din};//把总线输出的数据移位进valid_data
- state<=4'd7;
- end
- end
- 4'd10:begin
- mux_read<=1'd0;
- state<=4'd11;
- end
- 4'd11:begin
- if(mux_read_done)begin
- state<=4'd12;
- end
- else begin
- state<=4'd11;
- end
- end
- 4'd12:begin
- read_done <=1'd0;
- state<=4'd0;
- end
- endcase
- end
- end
-
- endmodule
编写TB代码:
- module TB_read_top(
-
- );
-
- reg clk ;
- reg reset ;
- reg rd_clk ;
- reg start_read ;//开始页读
- reg [23:0] read_addr ; //页读地址
- reg [8:0] brust_length ;//突发长度
- reg rd_fifo_en ;//fifo读使能
- wire [7:0] rd_fifo_data ;//从fifo读出的数据
- wire rd_fifo_empty ;
- wire rd_fifo_full ;
- wire spi_cs ;
- wire spi_clk ;
- wire spi_dout ;
- reg spi_din ;
- wire mux_read ;
- reg mux_read_done ;
- wire read_done ;
-
- read_top inst_read_top(
- .clk (clk) ,
- .reset (reset) ,
- .rd_clk (rd_clk) ,
- .start_read (start_read) ,//开始页读
- .read_addr (read_addr) , //页读地址
- .brust_length (brust_length) ,//突发长度
- .rd_fifo_en (rd_fifo_en) ,//fifo读使能
- .rd_fifo_data (rd_fifo_data) ,//从fifo读出的数据
- .rd_fifo_empty (rd_fifo_empty) ,
- .rd_fifo_full (rd_fifo_full) ,
- .spi_cs (spi_cs) ,
- .spi_clk (spi_clk) ,
- .spi_dout (spi_dout) ,
- .spi_din (spi_din) ,
- .mux_read (mux_read) ,
- .mux_read_done (mux_read_done) ,
- .read_done (read_done)
- );
-
- initial begin
- clk=0;
- rd_clk=0;
- reset=1;
- start_read<=1'd0;
- #100
- reset=0;
- start_read<=1'd1;
- #40
- start_read<=1'd0;
- end
-
- always #20 clk=~clk;
- always #20 rd_clk=~rd_clk;
-
-
- //状态机
- reg [1:0]state;
- always@(posedge clk)begin
- if(reset)begin
- start_read<=1'd0;
- read_addr<=24'd0;
- brust_length<=9'd0;
- spi_din<=1'd0;
- state<=2'd0;
- end
- else begin
- spi_din<=~spi_din;
- case(state)
- 2'd0:begin
- read_addr<=24'h123456;
- brust_length<=9'd10;
- if(mux_read)begin
- mux_read_done<=1'd1;
- state<=2'd1;
- end
- end
- 2'd1:begin
- if(!mux_read)begin
- mux_read_done<=1'd1;
- state<=2'd2;
- end
- end
- 2'd2:begin
- start_read<=1'd0;
- read_addr<=24'h0;
- brust_length<=9'd0;
- state<=2'd3;
- end
- endcase
- end
- end
-
-
- endmodule
仿真结果如下:


打拍+寄存


发送指令

发送地址
