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

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 interfaceinput             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 interfaceoutput 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 )beginif(!sys_rst_n)beginidel_flag0<=1'b1;idel_flag1<=1'b1;endelse beginidel_flag0<=idel_flag;idel_flag1<=idel_flag0;end
end//请求数据 + 把数据放入buffer 
always @(posedge clk_100m or negedge sys_rst_n )beginif(!sys_rst_n)w_data_req<=1'b0;else if((bit_cnt+2)%8==0&&bit_cnt>=30&&clk_cnt==0&&current_state==WRITE) //提前2个时钟周期w_data_req<=1'b1;elsew_data_req<=1'b0;
endalways @(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&&current_state==READ)									rd_data_buffer<={rd_data_buffer[6:0],spi_miso};elserd_data_buffer<=rd_data_buffer;
endalways @(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&&current_state==READ)data_check<=data_check+1'd1;elsedata_check<=data_check;
endalways @(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&&current_state==READ)r_data<=rd_data_buffer;elser_data<=r_data;
endalways @(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&&current_state==READ&&cmd_cnt==6)beginif(data_check!=r_data)erro_flag<=1'd1;elseerro_flag<=erro_flag;endelseerro_flag<=erro_flag;
end//*把数据放入buffer 提前一个周期
always @(posedge clk_100m or negedge sys_rst_n )beginif(!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&&current_state==WRITE&&bit_cnt>=32)data_buffer<={data_buffer[6:0],data_buffer[7]};elsedata_buffer<=data_buffer;
end//*----位移cmd指令存储器 开始:cs选中且dely未生效,提前了100mhz的周期------------
//使50mhz时数据提前半个周期获得
always @(posedge clk_100m or negedge sys_rst_n )beginif(!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};elsecmd_buffer<=cmd_buffer;
end//取出地址每一位
always @(posedge clk_100m or negedge sys_rst_n )beginif(!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]};elseaddr_buffer<=addr_buffer;
end//------------使能后clk_cnt输出50M时钟用于操作信号--------------
always @(posedge clk_100m or negedge sys_rst_n )beginif(!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 )beginif(!sys_rst_n)dely_cnt<=1'd0;else if(spi_cs==0)beginif(dely_cnt<1)dely_cnt<=dely_cnt+1'd1;elsedely_cnt<=dely_cnt;endelsedely_cnt<=1'd0;
end//*-----------------结束的延时计时器------------------------------------
always @(posedge clk_100m or negedge sys_rst_n )beginif(!sys_rst_n)dely_state_cnt<=1'd0;else if(spi_cs)beginif(dely_state_cnt<400000000)dely_state_cnt<=dely_state_cnt+1'd1;elsedely_state_cnt<=dely_state_cnt;endelsedely_state_cnt<=1'd0;
end//*-------------------------bit读写计数---------------------
always @(posedge clk_100m or negedge sys_rst_n )beginif(!sys_rst_n)bit_cnt<=11'd0;else if(dely_cnt==1)beginif(clk_cnt==1'b1)bit_cnt<=bit_cnt+1'd1;elsebit_cnt<=bit_cnt;endelsebit_cnt<=11'd0;
end

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

//三段式状态机
always @(posedge clk_100m or negedge sys_rst_n )beginif(!sys_rst_n)current_state<=IDLE;elsecurrent_state<=next_state;
endalways @(*)begincase(current_state)IDLE: beginif(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;elsenext_state=IDLE;endWEL: beginif(stdone&&bit_cnt>=8)next_state=IDLE;elsenext_state=WEL;endS_ERA: beginif(stdone)next_state=IDLE;elsenext_state=S_ERA;endC_ERA: begin		if(stdone)next_state=IDLE;elsenext_state=C_ERA;endREAD: begin 		if(stdone&&bit_cnt>=8)next_state=IDLE;elsenext_state=READ;endWRITE: begin		if(stdone&&bit_cnt>=8)next_state=IDLE;elsenext_state=WRITE;endR_STA_REG: begin		if(stdone)next_state=IDLE;elsenext_state=R_STA_REG;enddefault: next_state=IDLE;			endcase				
endalways @(posedge clk_100m or negedge sys_rst_n )beginif(!sys_rst_n) beginspi_cs<=1'b1;spi_clk<=1'b0;spi_clk0<=1'b0;spi_mosi<=1'b0;	stdone<=1'b0;		endelse begincase(current_state)IDLE: beginspi_cs<=1'b1;spi_clk<=1'b0;spi_mosi<=1'b0;				endWEL: beginstdone<=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];endelse if(bit_cnt==8&&clk_cnt==0)beginstdone<=1'b1;spi_clk<=1'b0;						spi_mosi<=1'b0;						endelse if(bit_cnt==8&&clk_cnt==1)beginspi_cs<=1'b1;						endendC_ERA: beginstdone<=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];endelse if(bit_cnt==8&&clk_cnt==0)beginstdone<=1'b1;				    spi_clk<=1'b0;spi_mosi<=1'b0;	endelse if(bit_cnt==8&&clk_cnt==1)beginspi_cs<=1'b1;						endendS_ERA: beginstdone<=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];endelse if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)beginspi_cs<=1'b0;spi_clk0<=~spi_clk0;spi_clk<=spi_clk0;spi_mosi<=addr_buffer[23];endelse if(bit_cnt==32&&clk_cnt==0) beginspi_cs<=1'b1;spi_clk<=1'b0;spi_mosi<=1'b0;stdone<=1'b1;endendREAD: beginstdone<=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];endelse if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin					    spi_clk0<=~spi_clk0;spi_clk<=spi_clk0;spi_mosi<=addr_buffer[23];endelse if(bit_cnt>=32&&bit_cnt<2080)begin						spi_clk0<=~spi_clk0;spi_clk<=spi_clk0;spi_mosi<=1'b0;						endelse if(bit_cnt==2080&&clk_cnt==0) begin						spi_clk<=1'b0;spi_mosi<=1'b0;stdone<=1'b1;						endelse if(bit_cnt==2080&&clk_cnt==1) beginspi_cs<=1'b1;endendWRITE: beginstdone<=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];endelse if(bit_cnt>=8&&bit_cnt<32&&spi_cs==0)begin					   spi_clk0<=~spi_clk0;spi_clk<=spi_clk0;spi_mosi<=addr_buffer[23];endelse if(bit_cnt>=32&&bit_cnt<2080)begin						spi_clk0<=~spi_clk0;spi_clk<=spi_clk0;spi_mosi<=data_buffer[7];endelse if(bit_cnt==2080&&clk_cnt==0) beginspi_clk<=1'b0;spi_mosi<=1'b0;stdone<=1'b1;endelse if(bit_cnt==2080&&clk_cnt==1) beginspi_cs<=1'b1;endendR_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];endelse 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)beginspi_clk<=1'b0;spi_cs<=1'b1;stdone<=1'b1;endelse if(~spi_cs&&dely_cnt==1)beginspi_clk0<=~spi_clk0;spi_clk<=spi_clk0;end	   			         	 end default: beginstdone<=1'b0;spi_cs<=1'b1;spi_clk<=1'b0;spi_clk0<=1'b0;spi_mosi<=1'b0;				        endendcaseend
endendmodule

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 )beginif(!sys_rst_n)flash_start<=0;else if(flash_start<=5)flash_start<=flash_start+1;elseflash_start<=flash_start;
endalways @(posedge sys_clk or negedge sys_rst_n )beginif(!sys_rst_n)cmd_cnt<=0;else if(flash_start==4)spi_start<=1'b1;else if(idel_flag_r&&cmd_cnt<10)begincmd_cnt<=cmd_cnt+1;spi_start<=1'b1;endelse begincmd_cnt<=cmd_cnt;spi_start<=1'b0;end
endalways @(posedge sys_clk or negedge sys_rst_n )beginif(!sys_rst_n)spi_data<=8'd0;else if(w_data_req)spi_data<=spi_data+1'b1;elsespi_data<=spi_data;
endalways @(*)begincase(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
endendmodule

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/222878.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

以太网通讯协议小结--持续更新中

一、以太网介绍 以太网是一种产生较早&#xff0c;使用相当广泛的局域网技术&#xff0c;局域网就是一个区域的网络互联&#xff0c;可以使办公室也可以是学校等等&#xff0c;大小规模不一。 目前以太网根据速度等级分类大概分为&#xff1a;标准以太网&#xff08;10Mbit/s…

数据结构——利用堆进行对数组的排序

今天文章的内容是关于我们如何利用堆的特性对我们的数组进行排序&#xff0c;还有就是我们的TopK的问题&#xff0c;这次我们放在的是文件种&#xff0c;我们放入一亿个数字&#xff0c;然后我们取出一亿个数字中最大的十个数&#xff0c;利用上章堆的问题进行解决。 首先就是我…

HTML+CSS+JS网页设计

文章目录 作品介绍一、代码演示1.登录、注册&#xff0c;获取当前时间2.轮播图3.家乡简介4.热门景点5.特色美食6.页尾 二、效果图总结 作品介绍 HTML页面主要由&#xff1a;登录、注册跳转页面&#xff0c;轮播图&#xff0c;家乡简介&#xff0c;热门景点&#xff0c;特色美食…

OpenCV入门11——图像的分割与修复

文章目录 图像分割的基本概念实战-分水岭法(一)实战-分水岭法(二)GrabCut基本原理实战-GrabCut主体程序的实现实战-GrabCut鼠标事件的处理实战-调用GrabCut实现图像分割meanshift图像分割视频前后景分离其它对视频前后影分离的方法图像修复 图像分割是计算机视觉中的一个重要领…

羊大师:不同时段喝羊奶,效果会有何不同?

羊大师&#xff1a;不同时段喝羊奶&#xff0c;效果会有何不同&#xff1f; 羊奶是一种营养丰富、口感香浓的健康饮品&#xff0c;被广大消费者所喜爱。然而&#xff0c;你是否知道&#xff0c;不同的喝奶时间对身体的影响也是不同的呢&#xff1f;在不同时段喝羊奶&#xff0…

ECShop 2.x/3.x SQL注入/远程代码执行漏洞

漏洞描述 ECShop是一个B2C独立商店系统&#xff0c;供企业和个人快速建立个性化的在线商店。本系统是一个基于PHP语言和MYSQL数据库架构的跨平台开源程序。 在 2017 年及之前的版本中&#xff0c;存在一个 SQL 注入漏洞&#xff0c;该漏洞可能会注入有效负载并最终导致代码执…

【JavaEE初阶】 博客系统项目--前端页面设计实现

文章目录 &#x1f332;主要内容&#x1f38d;预期效果&#x1f6a9;博客列表页效果&#x1f6a9;博客详情页&#x1f6a9;博客登录页&#x1f6a9;博客编辑页 &#x1f340;实现博客列表页&#x1f6a9;实现导航栏&#x1f388;页面主体部分 &#x1f384;实现博客详情页&…

新闻研究导刊杂志社新闻研究导刊杂志新闻研究导刊编辑部2023年第21期目录

新闻研究导刊杂志社新闻研究导刊杂志新闻研究导刊编辑部2023年第21期目录 研究论文 视频新闻的短视频化趋势与受众接受度分析 郭晓楷; 1-4 新媒体时代铸牢中华民族共同体意识的理论逻辑与实践路径研究 衷振华; 5-7 区域形象名片化构建背景下的新媒体传播策略研究 宋…

MySQL死锁,死锁产生的4个必要条件,死锁案例, 如何避免死锁

文章目录 MySQL死锁了怎么办&#xff08;死锁的产生及解决方案&#xff09;&#xff1f;1、 死锁与产生死锁的四个必要条件1.1 什么是死锁1.2 死锁产生的4个必要条件 2、死锁案例2.1 表锁死锁2.2 行锁死锁2.3 共享锁转换为排他锁 3、死锁排查4、 如何避免死锁5、死锁的排查6、 …

在线教育行业内卷突围,持续激活平台用户体验是关键

在线教育并不等同于K12教育。 众所周知&#xff0c;越来越卷的考试制度&#xff0c;已经被家长、学生们的谩骂和吐槽淹没了好几层。各种减负、杜绝课后补课等条例纷纷出台&#xff0c;不断的挤压着K12教育企业的生存空间。 于是乎&#xff0c;大家都认为&#xff0c;在线教育…

语义SLAM论文、代码和数据集汇总

文章目录 2023Large-scale Autonomous Flight with Real-time Semantic SLAM under Dense Forest Canopy 2023 Large-scale Autonomous Flight with Real-time Semantic SLAM under Dense Forest Canopy 摘要&#xff1a;语义地图使用一组语义上有意义的对象来表示环境。这种…

UCP通信

一&#xff0c;概括 二 &#xff0c;常用方法 三&#xff0c;实现步骤&#xff08;一发一收&#xff09; 四&#xff0c;案例&#xff08;一发一收&#xff09; &#xff08;1&#xff09;&#xff1a;客户端 &#xff08;2&#xff09;&#xff1a;服务端 &#xff08;3&…