FPGA模块——SPI协议(读写FLASH)

(1)FLASH芯片 W25Q16BV(2)SPI协议(3)芯片部分命令1.Write Enable(06h)2.Chip Erase (C7h / 60h)3.写指令(02h)4.读指令(03h)

(4)代码1. FPGA做主机的SPI协议2. SPI协议的使用

(1)FLASH芯片 W25Q16BV

芯片引脚图:

内部结构图: 存储区域总共分成了32块,每块64KB。每块又分成了16个部分,每个部分4KB。方便进行读取和局部操作。 电路设计

(2)SPI协议

SPI的四种模式

这里使用这个模式: 主机和从机在时钟上升沿放入要输出的数据,在时钟下降沿读取要输入的数据。 8个时钟后交换一个字节8位数据(高位在前)。

(3)芯片部分命令

有个输入时序的要求 开始时CS拉低等待(tSLCH要求最小5ns)再开始, 结束时CS拉高等待(tSHSL用100ns )再进行下一次操作。

这个寄存器的第一位数据可以判断操作是否完成(BUSY位)

1.Write Enable(06h)

写使能:开始时CS拉低等待(tSLCH要求最小5ns)再开始,结束时CS拉高等待(tSHSL取100ns )再进行下一次操作。

2.Chip Erase (C7h / 60h)

整片擦除,要判断操作是否完成

3.写指令(02h)

数据写多了会把之前的数据覆盖掉,要判断操作是否完成。

4.读指令(03h)

要判断操作是否完成

(4)代码

1. FPGA做主机的SPI协议

对信号进行同步和提前准备: 100m时钟和clk_cnt配合进行数据的读取和输出(clk_cnt有等于1和0的时候) spi_clk基于100m时钟输出一个相当于clk_cnt的延时半个周期的时钟,确保输入输出数据稳定。

module spi_drive(

input clk_100m ,

input sys_rst_n ,

//user interface

input spi_start ,//spi开启使能。

input [7:0 ] spi_cmd ,//FLAH操作指令

input [23:0] spi_addr ,//FLASH地址

input [7:0 ] spi_data ,//FLASH写入的数据

input [3:0 ] cmd_cnt ,

output idel_flag_r ,//空闲状态标志的上升沿

output reg w_data_req ,//FLASH写数据请求

output reg [7:0] r_data ,//FLASH读出的数据

output reg erro_flag ,//读出的数据错误标志

//spi interface

output reg spi_cs ,//SPI从机的片选信号,低电平有效。

output reg spi_clk ,//主从机之间的数据同步时钟。

output reg spi_mosi ,//数据引脚,主机输出,从机输入。

input spi_miso //数据引脚,主机输入,从机输出。

);

//状态机

parameter IDLE =4'd0;//空闲状态

parameter WEL =4'd1;//写使能状态

parameter S_ERA =4'd2;//扇区擦除状态

parameter C_ERA =4'd3;//全局擦除

parameter READ =4'd4;//读状态

parameter WRITE =4'd5;//写状态

parameter R_STA_REG =4'd6;

//指令集

parameter WEL_CMD =8'h06;

parameter S_ERA_CMD =8'h20;

parameter C_ERA_CMD =8'hc7;

parameter READ_CMD =8'h03;

parameter WRITE_CMD =8'h02;

parameter R_STA_REG_CMD=8'h05;

//wire define

wire idel_flag;

//reg define

reg[3:0] current_state ;

reg[3:0] next_state ;

reg[7:0 ] data_buffer ;

reg[7:0 ] cmd_buffer ;

reg[7:0 ] sta_reg ;

reg[23:0] addr_buffer ;

reg[31:0] bit_cnt ;

reg clk_cnt ;

reg dely_cnt ;

reg[31:0] dely_state_cnt ;

reg[7:0 ] rd_data_buffer ;

reg spi_clk0 ;

reg stdone ;

reg[7:0 ] data_check ;

reg idel_flag0 ;

reg idel_flag1 ;

//*****************************************************

//** main code

//*****************************************************

//*抓取上升沿

assign idel_flag=(current_state==IDLE)?1:0;//空闲状态标志

assign idel_flag_r=idel_flag0&&(~idel_flag1);//空闲状态标志的上升沿

//*抓取上升沿要用的

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)begin

idel_flag0<=1'b1;

idel_flag1<=1'b1;

end

else begin

idel_flag0<=idel_flag;

idel_flag1<=idel_flag0;

end

end

//请求数据 + 把数据放入buffer

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

w_data_req<=1'b0;

else if((bit_cnt+2)%8==0&&bit_cnt>=30&&clk_cnt==0&¤t_state==WRITE) //提前2个时钟周期

w_data_req<=1'b1;

else

w_data_req<=1'b0;

end

always @(posedge clk_100m or negedge sys_rst_n )begin//读出的数据移位寄存

if(!sys_rst_n)

rd_data_buffer<=8'd0;

else if(bit_cnt>=32&&bit_cnt<=2080&&clk_cnt==0&¤t_state==READ)

rd_data_buffer<={rd_data_buffer[6:0],spi_miso};

else

rd_data_buffer<=rd_data_buffer;

end

always @(posedge clk_100m or negedge sys_rst_n )begin//检查读出的数据是否正确

if(!sys_rst_n)

data_check<=8'd0;

else if(bit_cnt%8==0&&bit_cnt>=40&&clk_cnt==1&¤t_state==READ)

data_check<=data_check+1'd1;

else

data_check<=data_check;

end

always @(posedge clk_100m or negedge sys_rst_n )begin//读出的数据

if(!sys_rst_n)

r_data<=8'd0;

else if(bit_cnt%8==0&&bit_cnt>38&&clk_cnt==1&¤t_state==READ)

r_data<=rd_data_buffer;

else

r_data<=r_data;

end

always @(posedge clk_100m or negedge sys_rst_n )begin//读出的数据错误标志

if(!sys_rst_n)

erro_flag<=1'd0;

else if(bit_cnt>32&&bit_cnt<=2080&¤t_state==READ&&cmd_cnt==6)begin

if(data_check!=r_data)

erro_flag<=1'd1;

else

erro_flag<=erro_flag;

end

else

erro_flag<=erro_flag;

end

//*把数据放入buffer 提前一个周期

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

data_buffer<=8'd0;

else if((bit_cnt+1)%8==0&&bit_cnt>30&&clk_cnt==1)//*把数据放入buffer 提前一个周期

data_buffer<=spi_data;

else if(clk_cnt==1&¤t_state==WRITE&&bit_cnt>=32)

data_buffer<={data_buffer[6:0],data_buffer[7]};

else

data_buffer<=data_buffer;

end

//*----位移cmd指令存储器 开始:cs选中且dely未生效,提前了100mhz的周期------------

//使50mhz时数据提前半个周期获得

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

cmd_buffer<=8'd0;

else if(spi_cs==0&&dely_cnt==0)

cmd_buffer<=spi_cmd;

else if(clk_cnt==1&&(current_state==WEL||current_state==S_ERA||current_state==C_ERA

||current_state==READ||current_state==WRITE||current_state==R_STA_REG)&&bit_cnt<8)

cmd_buffer<={cmd_buffer[6:0],1'b1};

else

cmd_buffer<=cmd_buffer;

end

//取出地址每一位

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

addr_buffer<=8'd0;

else if(spi_cs==0&&dely_cnt==0)

addr_buffer<=spi_addr;

else if(clk_cnt==1&&(current_state==READ||current_state==WRITE)&&bit_cnt>=8&&bit_cnt<32)

addr_buffer<={addr_buffer[22:0],addr_buffer[23]};

else

addr_buffer<=addr_buffer;

end

//------------使能后clk_cnt输出50M时钟用于操作信号--------------

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

clk_cnt<=1'd0;

else if(dely_cnt==1)

clk_cnt<=clk_cnt+1'd1;

else

clk_cnt<=1'd0;

end

//*---------cs选中器件后的信号输出的 dely_cnt 可以认为是使能操作------------

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

dely_cnt<=1'd0;

else if(spi_cs==0)begin

if(dely_cnt<1)

dely_cnt<=dely_cnt+1'd1;

else

dely_cnt<=dely_cnt;

end

else

dely_cnt<=1'd0;

end

//*-----------------结束的延时计时器------------------------------------

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

dely_state_cnt<=1'd0;

else if(spi_cs)begin

if(dely_state_cnt<400000000)

dely_state_cnt<=dely_state_cnt+1'd1;

else

dely_state_cnt<=dely_state_cnt;

end

else

dely_state_cnt<=1'd0;

end

//*-------------------------bit读写计数---------------------

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

bit_cnt<=11'd0;

else if(dely_cnt==1)begin

if(clk_cnt==1'b1)

bit_cnt<=bit_cnt+1'd1;

else

bit_cnt<=bit_cnt;

end

else

bit_cnt<=11'd0;

end

状态机 :每个状态该干什么,怎么转移 修改里面的命令和转态就可以移植到其他的地方了。

//三段式状态机

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n)

current_state<=IDLE;

else

current_state<=next_state;

end

always @(*)begin

case(current_state)

IDLE: begin

if(spi_start&&spi_cmd==WEL_CMD)

next_state=WEL;

else if(spi_start&&spi_cmd==C_ERA_CMD)

next_state=C_ERA;

else if(spi_start&&spi_cmd==S_ERA_CMD)

next_state=S_ERA;

else if(spi_start&&spi_cmd==READ_CMD)

next_state=READ;

else if(spi_start&&spi_cmd==WRITE_CMD)

next_state=WRITE;

else if(spi_start&&spi_cmd==R_STA_REG_CMD)

next_state=R_STA_REG;

else

next_state=IDLE;

end

WEL: begin

if(stdone&&bit_cnt>=8)

next_state=IDLE;

else

next_state=WEL;

end

S_ERA: begin

if(stdone)

next_state=IDLE;

else

next_state=S_ERA;

end

C_ERA: begin

if(stdone)

next_state=IDLE;

else

next_state=C_ERA;

end

READ: begin

if(stdone&&bit_cnt>=8)

next_state=IDLE;

else

next_state=READ;

end

WRITE: begin

if(stdone&&bit_cnt>=8)

next_state=IDLE;

else

next_state=WRITE;

end

R_STA_REG: begin

if(stdone)

next_state=IDLE;

else

next_state=R_STA_REG;

end

default: next_state=IDLE;

endcase

end

always @(posedge clk_100m or negedge sys_rst_n )begin

if(!sys_rst_n) begin

spi_cs<=1'b1;

spi_clk<=1'b0;

spi_clk0<=1'b0;

spi_mosi<=1'b0;

stdone<=1'b0;

end

else begin

case(current_state)

IDLE: begin

spi_cs<=1'b1;

spi_clk<=1'b0;

spi_mosi<=1'b0;

end

WEL: begin

stdone<=1'b0;

spi_cs<=1'b0;

if(dely_cnt==1&&bit_cnt<8) begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=cmd_buffer[7];

end

else if(bit_cnt==8&&clk_cnt==0)begin

stdone<=1'b1;

spi_clk<=1'b0;

spi_mosi<=1'b0;

end

else if(bit_cnt==8&&clk_cnt==1)begin

spi_cs<=1'b1;

end

end

C_ERA: begin

stdone<=1'b0;

if(dely_state_cnt==10)

spi_cs<=1'b0;

else if(dely_cnt==1&&bit_cnt<8) begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=cmd_buffer[7];

end

else if(bit_cnt==8&&clk_cnt==0)begin

stdone<=1'b1;

spi_clk<=1'b0;

spi_mosi<=1'b0;

end

else if(bit_cnt==8&&clk_cnt==1)begin

spi_cs<=1'b1;

end

end

S_ERA: begin

stdone<=1'b0;

if(dely_state_cnt==10)

spi_cs<=1'b0;

else if(dely_cnt==1&&bit_cnt<8) begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=cmd_buffer[7];

end

else if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin

spi_cs<=1'b0;

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=addr_buffer[23];

end

else if(bit_cnt==32&&clk_cnt==0) begin

spi_cs<=1'b1;

spi_clk<=1'b0;

spi_mosi<=1'b0;

stdone<=1'b1;

end

end

READ: begin

stdone<=1'b0;

if(dely_state_cnt==10)

spi_cs<=1'b0;

else if(dely_cnt==1&&bit_cnt<8) begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=cmd_buffer[7];

end

else if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=addr_buffer[23];

end

else if(bit_cnt>=32&&bit_cnt<2080)begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=1'b0;

end

else if(bit_cnt==2080&&clk_cnt==0) begin

spi_clk<=1'b0;

spi_mosi<=1'b0;

stdone<=1'b1;

end

else if(bit_cnt==2080&&clk_cnt==1) begin

spi_cs<=1'b1;

end

end

WRITE: begin

stdone<=1'b0;

if(dely_state_cnt==10)

spi_cs<=1'b0;

else if(dely_cnt==1&&bit_cnt<8) begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=cmd_buffer[7];

end

else if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=addr_buffer[23];

end

else if(bit_cnt>=32&&bit_cnt<2080)begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=data_buffer[7];

end

else if(bit_cnt==2080&&clk_cnt==0) begin

spi_clk<=1'b0;

spi_mosi<=1'b0;

stdone<=1'b1;

end

else if(bit_cnt==2080&&clk_cnt==1) begin

spi_cs<=1'b1;

end

end

R_STA_REG:begin

stdone<=1'b0;

if(dely_state_cnt==10)

spi_cs<=1'b0;

else if(dely_cnt==1&&bit_cnt<8)begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=cmd_buffer[7];

end

else if(bit_cnt==8)begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

spi_mosi<=1'b0;

end

else if(~spi_miso&&bit_cnt%8==0)begin

spi_clk<=1'b0;

spi_cs<=1'b1;

stdone<=1'b1;

end

else if(~spi_cs&&dely_cnt==1)begin

spi_clk0<=~spi_clk0;

spi_clk<=spi_clk0;

end

end

default: begin

stdone<=1'b0;

spi_cs<=1'b1;

spi_clk<=1'b0;

spi_clk0<=1'b0;

spi_mosi<=1'b0;

end

endcase

end

end

endmodule

2. SPI协议的使用

首先系统开始运行,来几个周期延伸。 spi_start信号只是一个周期脉冲。 idel_flag_r是进入空闲状态的标志位也就是意味着上一步操作完成。 cmd计数指令不断加来切换不同的命令。 spi_cmd 输出命令

module flash_rw(

input sys_clk ,

input sys_rst_n ,

input idel_flag_r ,

input w_data_req ,

output reg[3:0 ] cmd_cnt ,

output reg spi_start ,//spi开启使能。

output reg[7:0 ] spi_cmd ,

output reg[7:0 ] spi_data

);

//指令集

parameter WEL_CMD =16'h06;

parameter S_ERA_CMD =16'h20;

parameter C_ERA_CMD =16'hc7;

parameter READ_CMD =16'h03;

parameter WRITE_CMD =16'h02;

parameter R_STA_REG_CMD=8'h05 ;

//reg define

reg[3:0] flash_start;

//SPI 要写入的数据

always @(posedge sys_clk or negedge sys_rst_n )begin

if(!sys_rst_n)

flash_start<=0;

else if(flash_start<=5)

flash_start<=flash_start+1;

else

flash_start<=flash_start;

end

always @(posedge sys_clk or negedge sys_rst_n )begin

if(!sys_rst_n)

cmd_cnt<=0;

else if(flash_start==4)

spi_start<=1'b1;

else if(idel_flag_r&&cmd_cnt<10)begin

cmd_cnt<=cmd_cnt+1;

spi_start<=1'b1;

end

else begin

cmd_cnt<=cmd_cnt;

spi_start<=1'b0;

end

end

always @(posedge sys_clk or negedge sys_rst_n )begin

if(!sys_rst_n)

spi_data<=8'd0;

else if(w_data_req)

spi_data<=spi_data+1'b1;

else

spi_data<=spi_data;

end

always @(*)begin

case(cmd_cnt)

0:spi_cmd=WEL_CMD;

1:spi_cmd=C_ERA_CMD;

2:spi_cmd=R_STA_REG_CMD;

3:spi_cmd=WEL_CMD;

4:spi_cmd=WRITE_CMD;

5:spi_cmd=R_STA_REG_CMD;

6:spi_cmd=READ_CMD;

7:spi_cmd=WEL_CMD;

8:spi_cmd=S_ERA_CMD;

9:spi_cmd=R_STA_REG_CMD;

10:spi_cmd=READ_CMD;

default:;

endcase

end

endmodule

推荐文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。