最近在做的项目中,有个需要支持远程升级固件的需求。大体架构就是通过上位机把需更新的固件下发到FPGA中,然后通过FPGA写入用来存放固件的Flash里。调试了一段时间,总算实现了这一功能,在实现的过程中,网上前辈们的分享帮了我很大的忙,所以作为回馈,我也把实现过程中相关的知识点记录下来与大家分享,可能有说得不对的地方,欢迎指正。打算分三篇文章来讲述。
flash闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。flash按照内部存储结构不同,大体上可以分为两种:nor flash和nand flash。nor flash数据线和地址线分开,可以实现ram一样的随机寻址功能,可以读取任何一个字节。但是擦除仍要按块来擦。nand flash同样是按块擦除,但是数据线和地址线复用,不能利用地址线随机寻址。读取只能按页来读取。NOR Flash的读取,用户可以直接运行装载在NOR FLASH里面的代码。NAND Flash没有采取内存RAM的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512个字节,采用这种技术的Flash比较廉价。用户不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flah以外,还作上了一块小的NOR Flash来运行启动代码。
现在有些芯片为了减少启动引脚,可以采用QSPI serial flash作为启动存储器。其采用spi/qspi 的方式进行串行的读取数据,减小引脚消耗。这个只是通信方式的改变,其内部结构一般还是nor flash或者nand flash。这个我们就不去深究了。
Flash常用的指令操作有:读ID、擦除(有三种方式)、页编程、读操作(读寄存器、读数据)、写操作(设置寄存器)等。一般来说,Flash的地址是24bit,最大只能支持16MB,面对更大容量的Flash,需要将地址配置为32bit,也就是需要将flash设置成4-byte模式。
下面以微邦的一款flash芯片W25Q128手册为例,描述一下上述的主要操作指令。不同厂家的芯片可能指令码可能会不大一样,注意区别。涉及到的时序,以标准的四线SPI总线为例,因为这种模式用的最普遍。
FLASH常用芯片指令表(部分)
指令 |
第一字节(指令编码) |
第二字节 |
第三字节 |
第四字节 |
第五字节 |
第六字节 |
第七-N字节 |
Write Enable |
06h |
|
|
|
|
|
|
Write Disable |
04h |
|
|
|
|
|
|
Read Status Register |
05h |
(S7–S0) |
|
|
|
|
|
Write Status Register |
01h |
(S7–S0) |
|
|
|
|
|
Read Data |
03h |
A23–A16 |
A15–A8 |
A7–A0 |
(D7–D0) |
(Next byte) |
continuous |
Fast Read |
0Bh |
A23–A16 |
A15–A8 |
A7–A0 |
dummy |
(D7–D0) |
(Next Byte) continuous |
Fast Read Dual Output |
3Bh |
A23–A16 |
A15–A8 |
A7–A0 |
dummy |
I/O = (D6,D4,D2,D0) O = (D7,D5,D3,D1) |
(one byte per 4 clocks, continuous) |
Page Program |
02h |
A23–A16 |
A15–A8 |
A7–A0 |
D7–D0 |
Next byte |
Up to 256 bytes |
Block Erase(64KB) |
D8h |
A23–A16 |
A15–A8 |
A7–A0 |
|
|
|
Sector Erase(4KB) |
20h |
A23–A16 |
A15–A8 |
A7–A0 |
|
|
|
Chip Erase |
C7h |
|
|
|
|
|
|
Power-down |
B9h |
|
|
|
|
|
|
Release Power- down / Device ID |
ABh |
dummy |
dummy |
dummy |
(ID7-ID0) |
|
|
Manufacturer/ Device ID |
90h |
dummy |
dummy |
00h |
(M7-M0) |
(ID7-ID0) |
|
JEDEC ID |
9Fh |
(M7-M0) 生产厂商 |
(ID15-ID8) 存储器类型 |
(ID7-ID0) 容量 |
|
|
|
该表中的第一列为指令名,第二列为指令编码,第三至第N列的具体内容根据指令的不同而有不同的含义。表中“A0~A23”指FLASH芯片内部存储器组织的地址;“M0~M7”为厂商号(MANUFACTURER ID);“ID0-ID15”为FLASH芯片的ID;“dummy”指该处可为任意数据;“D0~D7”为FLASH内部存储矩阵的内容。
通过指令表中的读ID指令“JEDEC ID”可以获取这两个编号,该指令编码为“9F h”,其中“9F h”是指16进制数“9F” (相当于C语言中的0x9F)。紧跟指令编码的三个字节分别为FLASH芯片输出的“(M7-M0)”、“(ID15-ID8)”及“(ID7-ID0)” 。通过read ID的指令,我们可以获取芯片的相关ID信息。
扇区擦除指令将指定扇区(4K字节)内的所有存储器设置为全部擦除状态置1(FFh)。需要注意的是,在执行擦除之前, 必须执行一次写使能指令指令(状态寄存器位WEL必须等于1)。
另外两种擦除方式,大同小异,就不展开说了,需要注意的是,Chip_erase在指令码后面不需要带目标擦除区域的起始地址。
页编程,一次写入256个字节的数据到flash。需注意的是,在执行页编程之前,需执行一次写使能指令。
这里的写操作,主要是指设置单个寄存器的值,例如将flash设置成4字节地址模式,设置写使能等等。
设置状态寄存器的值,指令码后面,紧跟着对应的想要设置的寄存器值即可。
这里读操作主要可以分两种,一种是读状态寄存器值,另外一种是读去flash存储的数据。
值得注意的是,读状态寄存器指令在任何时候都可以执行,即使在编程,擦除或写入时,也可以随时使用读状态寄存器指令。
常规读数据:
读数据指令允许从存储器中顺序读取一个或多个数据字节。该通过将/ CS引脚驱动为低电平然后移位指令代码“03h”然后移位a来启动指令24位地址(A23-A0)进入DI引脚。 代码和地址位在上升沿锁存CLK引脚。 收到地址后,寻址存储单元的数据字节将被移出在CLK的下降沿处的DO引脚上,最高有效位(MSB)优先。地址会自动增加。也就是说在下发读指令的时候,只需要配置目标区域起始地址即可。然后数据会源源不断地被读出来,直到片选信号cs被拉高,读数结束。
快速读:
快速读取指令与常规读取数据指令类似,只是它可以在FR最高频率下工作(参见交流电气特性),提高读取速度。其中dummy clock时间段里的数据可以忽略。(该指令目前我用的比较少,没实践过,就不展开了)