文章目录
前言
通过控制flash芯片进一步熟悉spi协议
一、flash与eeprom芯片
flash 存储器: flash 存储器是一种非易失性存储器,它具有 ram 和 rom 的一些特点。与 rom 类似,flash 存储器的内容在断电时不会丢失,但与 ram 类似,它可以通过编程来修改存储的内容。flash 存储器通常用于嵌入式系统中存储程序代码、配置数据等。
eeprom(电可擦除可编程只读存储器): eeprom 是一种可编程的非易失性存储器,可以通过电擦除和编程来修改存储的内容。eeprom 具有 ram 的特点,可以随机读写,但也具有 rom 的特点,存储的内容在断电时不会丢失。eeprom 通常用于存储配置数据、参数设置等。
flash 存储器和 eeprom(electrically erasable programmable read-only memory)之间的主要区别在于它们的擦除和编程方法、速度、寿命和应用范围等方面。
-
擦除和编程方法:
flash 存储器通常使用扇区擦除(sector erase)的方式来擦除数据,并使用编程器(programmer)来进行编程。扇区擦除意味着需要一次性擦除一整个存储扇区,然后才能进行写入操作。eeprom 可以通过逐字节擦除和编程来修改数据,因此擦除和编程更加灵活和精细。 -
速度:由于 flash 存储器通常采用扇区擦除的方式,擦除和编程操作比 eeprom 慢。eeprom 在擦除和编程时的速度相对较快,因为它可以逐字节地擦除和编程。
-
寿命:flash 存储器和 eeprom 的擦除和编程操作都会导致存储器单元的磨损,从而影响存储器的寿命。由于 flash 存储器通常需要一次性擦除整个扇区,因此其寿命可能受到擦除次数的限制。eeprom 的擦除和编程操作更加精细,因此其寿命可能比 flash 存储器更长。
-
应用范围:flash 存储器通常用于存储大容量的程序代码、固件更新等。eeprom 通常用于存储配置数据、参数设置、小容量数据等。
-
总的来说,flash 存储器和 eeprom 都是非易失性存储器,但它们在擦除和编程方法、速度、寿命和应用范围等方面存在一些差异,因此在选择存储器时需要根据具体的应用需求进行选择
以下内容为补充的更加详细的参考,来自:https://zhuanlan.zhihu.com/p/27621418
rom最初不能编程,出厂什么内容就永远什么内容,不灵活。后来出现了prom,可以自己写入一次,要是写错了,只能换一片,自认倒霉。人类文明不断进步,终于出现了可多次擦除写入的eprom,每次擦除要把芯片拿到紫外线上照一下,想一下你往单片机上下了一个程序之后发现有个地方需要加一句话,为此你要把单片机放紫外灯下照半小时,然后才能再下一次,这么折腾一天也改不了几次。历史的车轮不断前进,伟大的eeprom出现了,拯救了一大批程序员,终于可以随意的修改rom中的内容了。
eeprom的全称是“电可擦除可编程只读存储器”,即electrically erasable programmable read-only memory。是相对于紫外擦除的rom来讲的。但是今天已经存在多种eeprom的变种,变成了一类存储器的统称。
狭义的eeprom:
这种rom的特点是可以随机访问和修改任何一个字节,可以往每个bit中写入0或者1。这是最传统的一种eeprom,掉电后数据不丢失,可以保存100年,可以擦写100w次。具有较高的可靠性,但是电路复杂/成本也高。因此目前的eeprom都是几十千字节到几百千字节的,绝少有超过512k的。
flash:
flash属于广义的eeprom,因为它也是电擦除的rom。但是为了区别于一般的按字节为单位的擦写的eeprom,我们都叫它flash。flash做的改进就是擦除时不再以字节为单位,而是以块为单位,一次简化了电路,数据密度更高,降低了成本。 上m字节的rom一般都是flash。
flash又分nand flash和nor flash,nor型存储内容以编码为主,其功能多与运算相关;nand型主要功能是存储资料,如数码相机中所用的记忆卡。 -
nor flash:主要用来执行片上程序
优点:具有很好的读写性能和随机访问性能,因此它先得到广泛的应用;
缺点:单片容量较小且写入速度较慢,决定了其应用范围较窄。 -
nand flash:主要用在大容量存储场合
优点:优秀的读写性能、较大的存储容量和性价比,因此在大容量存储领域得到了广泛的应用;
缺点:不具备随机访问性能。
nor flash数据线和地址线分开,可以实现ram一样的随机寻址功能,可以读取任何一个字节。但是擦除仍要按块来擦。
nand flash同样是按块擦除,但是数据线和地址线复用,不能利用地址线随机寻址。读取只能按页来读取。(nandflash按块来擦除,按页来读,norflash没有页)
由于nandflash引脚上复用,因此读取速度比nor flash慢一点,但是擦除和写入速度比nor flash快很多。nand flash内部电路更简单,因此数据密度大,体积小,成本也低。因此大容量的flash都是nand型的。小容量的2~12m的flash多是nor型的。
使用寿命上,nand flash的擦除次数是nor的数倍。而且nand flash可以标记坏块,从而使软件跳过坏块。nor flash 一旦损坏便无法再用。因为nor flash可以进行字节寻址,所以程序可以在nor flash中运行。嵌入式系统多用一个小容量的nor flash存储引导代码,用一个大容量的nand flash存放文件系统和内核。
二、flash(w25q64jv)常见操作
2.1、flash常见操作
2.2、w25q64jv芯片存储大小
2.3、状态寄存器:
busy:忙信号,写、擦除操作后自动置0,
wel:写使能信号,写数据、擦除、写状态寄存器时需要置1.
一般只用这两,有其他需求需要看芯片手册。
2.4、时序图
该芯片存储较大,因此不支持页擦除,最小为扇区擦除
写使能:spi采用模式0或3
写失能:
读状态寄存器:0h05则返回第一段状态寄存器数值(7-0bit),0h35为第二段,0h15为第三段,第三段时qspi才有的。
读数据: 读数据时候先传输指令03h,然后地址(24bit分别对应8位扇区地址、8位页地址和8位字节地址),然后flash返回读数据
页编程: 写数据之前需要先执行擦除,至少写一个byte数据,最大可写246byte,此时字节地址应该为0,若不是从0开始,当写到当前页最后一个地址时,不会跳到下一页,会返回到当前页0地址开始写起。
扇区擦除 给扇区擦除指令和地址即可,地址当中的页地址、字节地址没关系,只看扇区地址(本人没有验证过,但看到过这样一句话,有兴趣可以自己验证一下,验证好记得戳一戳我hhh)
块擦除(32kb块):
块擦除(64kb块):
全片擦除:
读厂商id:
三、程序设计框图
完整代码参考github:https://github.com/shun6-6/flash_spi
3.1、flash_drive模块
flash_drive模块与用户模块和flash芯片连接
用户模块输入读写操作指令,以及相应的读写数据地址和长度,flash_drive模块结果处理后通过spi将数据写入flash或从中读取数据。
以下为接口代码:
module flash_drive#(
parameter p_data_width = 8 ,
parameter p_spi_cpol = 0 ,
parameter p_spi_cphl = 0 ,
parameter p_read_dwidth = 8 ,
parameter p_op_len = 32
)(
input i_clk ,
input i_rst ,
/*--------user接口--------*/
input [1 :0] i_operation_type ,
input [23:0] i_operation_addr ,
input [8 :0] i_operation_byte_num ,
input i_operation_valid ,
output o_operation_ready ,
input [p_data_width - 1 : 0] i_write_data ,
input i_write_sop ,
input i_write_eop ,
input i_write_valid ,
output [p_data_width - 1 : 0] o_read_data ,
output o_read_sop ,
output o_read_eop ,
output o_read_valid ,
/*--------spi接口--------*/
output o_spi_cs ,
output o_spi_clk ,
output o_spi_mosi ,
input i_spi_miso
);
3.2、flash_ctrl模块
flash_ctrl模块将用户操作指令以及数据进行处理,并产生相应指令驱动spi_drive模块。
对于写数据过程:用户模块会将写指令、写地址以及写数据发送至flash_ctrl模块,flash_ctrl模块将数据存至本地fifo,并发送写使能指令和写数据指令,并且根据spi_drive模块的i_user_write_req信号把fifo当中存入的用户写数据一个个取出,这是因为用户输入的的数据都是并行的8bit数据,而spi是穿行传输的,所以需要spi_drive串行传输完一个byte之后,向flash_ctrl模块发起下一个byte请求,也就是i_user_write_req信号。注:在写同一个地址的时候,一定要先擦除,再写,这是因为flash芯片只能把1变为0,不可以把0变为1,需要重新擦除,将芯片变为全1,才可以继续写。
对于读数据过程:用户模块输入读指令、读地址以及要读取的数据长度信息告知flash_ctrl模块,flash_ctrl模块会产生相应的读指令以控制spi_drive模块读flash,并且将读到的数据先存入fifo,最后完整的传递给用户模块。
flash_ctrl模块接口代码:
module flash_ctrl#(
parameter p_data_width = 8 ,//数据位宽
parameter p_spi_cpol = 0 ,//spi时钟极性:0/1表示空闲时钟电平为0/1
parameter p_spi_cphl = 0 ,//spi时钟相位:0/1表示数据采集沿为时钟第1/2跳变沿
parameter p_read_dwidth = 8 ,//读数据位宽
parameter p_op_len = 32 //操作数据长度
)(
input i_clk ,
input i_rst ,
/*--------用户接口--------*/
input [1 :0] i_operation_type ,//操作类型 1:read 2:write
input [23:0] i_operation_addr ,//操作地址
input [8 :0] i_operation_byte_num ,//max write 256 byte
input i_operation_valid ,//操作有效信号
output o_operation_ready ,//操作准备信号
input [p_data_width - 1 : 0] i_write_data ,//写数据
input i_write_sop ,//写数据-开始信号
input i_write_eop ,//写数据-结束信号
input i_write_valid ,//写数据有效
output [p_data_width - 1 : 0] o_read_data ,//读数据
output o_read_sop ,//读数据-开始信号
output o_read_eop ,//读数据-结束信号
output o_read_valid ,//读数据有效
/*--------驱动接口--------*/
output [p_op_len - 1 : 0] o_user_op_data ,//操作数据(数据8+地址24)
output [1:0] o_user_op_type ,//操作类型(读写数据,读写指令)
output [15:0] o_user_op_len ,//操作数据长度(读写数据8+24,指令8)
output [15:0] o_user_clk_len ,//时钟周期,读写数据时为8+24+8*字节数
output o_user_op_valid ,//用户数据有效信号
input i_user_op_ready ,//驱动准备信号
output [p_data_width - 1 : 0] o_user_write_data ,//写数据
input i_user_write_req ,//写数据请求
input [p_read_dwidth - 1 : 0] i_user_read_data ,//读数据
input i_user_read_valid //读数据有效
);
flash_ctrl模块状态机描述代码:
//fsm
localparam p_st_idle = 11'b00000000001,//空闲状态,握手成功后进入运行状态
p_st_run = 11'b00000000010,//运行状态,如果是读指令则进入读数据状态,否则为擦除或者写数据指令,都需要先进入写使能状态
p_st_w_en = 11'b00000000100,//写使能状态,若为写数据指令则进入写指令状态,否则进入擦除状态
p_st_w_ins = 11'b00000001000,//写数据指令状态
p_st_w_data = 11'b00000010000,//写数据状态
p_st_r_ins = 11'b00000100000,//读数据指令状态
p_st_r_data = 11'b00001000000,//读数据状态
p_st_clear = 11'b00010000000,//擦除状态
p_st_busy = 11'b00100000000,//读忙状态寄存器
p_st_busy_chk = 11'b01000000000,//检查返回的忙状态寄存器状态,若为忙则进入p_st_busy_wait状态,不忙则说明读数据结束,返回空闲状态
p_st_busy_wait = 11'b10000000000;//读忙等待状态,计数256后再次返回p_st_busy读忙状态
flash_ctrl模块状态机跳转代码:
always @(*)begin
case (r_st_cur)
p_st_idle : r_st_nxt = w_user_op_active ? p_st_run : p_st_idle ;
p_st_run : r_st_nxt = ri_operation_type == p_read_type ? p_st_r_ins : p_st_w_en;
p_st_w_en : r_st_nxt = w_spi_op_active ?
ri_operation_type == p_write_type ? p_st_w_ins : p_st_clear
: p_st_w_en ;
p_st_w_ins : r_st_nxt = w_spi_op_active ? p_st_w_data : p_st_w_ins ;
p_st_w_data : r_st_nxt = i_user_op_ready ? p_st_busy : p_st_w_data;
p_st_r_ins : r_st_nxt = w_spi_op_active ? p_st_r_data : p_st_r_ins ;
p_st_r_data : r_st_nxt = i_user_op_ready ? p_st_busy : p_st_r_data;
p_st_clear : r_st_nxt = w_spi_op_active ? p_st_busy : p_st_clear ;
p_st_busy : r_st_nxt = w_spi_op_active ? p_st_busy_chk : p_st_busy ;
p_st_busy_chk : r_st_nxt = ri_user_read_valid ?
ri_user_read_data[0] ? p_st_busy_wait : p_st_idle
: p_st_busy_chk;
p_st_busy_wait : r_st_nxt = r_st_cnt == 255 ? p_st_busy : p_st_busy_wait;
default : r_st_nxt = p_st_idle;
endcase
end
3.3、spi_drive模块
该模块则是按照spi协议和flash的相关操作指令,将输入的指令以及数据发送给flash。
spi_drive模块接口代码:
module spi_drive#(
parameter p_data_width = 8 ,
parameter p_spi_cpol = 0 ,
parameter p_spi_cphl = 0 ,
parameter p_read_dwidth = 8 ,
parameter p_op_len = 32 //操作数据长度
)(
input i_clk ,
input i_rst ,
output o_spi_cs ,//spi片选信号
output o_spi_clk ,//spi时钟线
output o_spi_mosi ,//spi主机输出
input i_spi_miso ,//spi主机输入
input [p_op_len - 1 : 0] i_user_op_data ,//操作数据(数据8+地址24)
input [1:0] i_user_op_type ,//操作类型(读写数据,读写指令)
input [15:0] i_user_op_len ,//操作数据长度(读写数据8+24,指令8)
input [15:0] i_user_clk_len ,//时钟周期,读写数据时为8+24+8*字节数
input i_user_op_valid ,//用户数据有效信号
output o_user_op_ready ,//主机准备信号
input [p_data_width - 1 : 0] i_user_write_data ,//写数据
output o_user_write_req ,//写数据请求
output [p_read_dwidth - 1 : 0] o_user_read_data ,//读数据
output o_user_read_valid //读数据有效
);
spi协议的实现过程:
该flash芯片支持的spi是模式0和3,波形图当中是模式0。
run运行信号在片选信号cs拉低时同步拉高,同时开启一个1bit计数器r_spi_cnt,该计数器波形与spi_clk一致,因此以此计数器可以表示当前时钟的上升沿或者下降沿,模式0是在时钟的下降沿改变数据,上升沿采样数据。
发表评论