基于 FPGA 的 SPI 控制 FLASH 读写
一、SPI 简介
SPI 是 Serial Peripheral Interface 的缩写,即串行外围设备接口。由 Motorola 公司推出的一种全双工、同步串行总线接口,只需要四根信号线即可实现多个芯片之间的主从连接结构,节约引脚,同时有利于 PCB 布局。它主要应用在 Flash 存储器、EEPROM 存储器、ADC、DAC、RTC 等,实现主控器与芯片之间的串行数据传输。
[图片]
SPI 通信需要四根信号线,分别为 SCLK、MOSI、MISO 和 CS_N。其中 CS_N、SCLK、MOSI 是由主机输出给从机,而 MISO 由从机输出给主机。
- SCLK (Serial Clock): 串行时钟线 (Master>Slave),控制数据交换的时机和速率;
- MOSI (Master Out Slave In): 主机输出,从机输入 (Master>Slave),用于 SPI 设备发送数据;
- MISO (Master In Slave Out): 主机输入,从机输出 (Slave>Master),用于 SPI 设备接收数据;
- CS_N (Chip Select): 片选信号 (Master->Slave),以控制与哪个从机通信。
[图片]
[图片]
SPI 通讯设备之间的常见连接方式:一主机一从机、一主机多从机。其中一主机多从机连接方式又分常规多片选模式和菊花链 (星链) 模式。
SPI 数据传输需要注意以下几点:
- SPI 总线在一次数据传输过程中,只能有一个主机和一个从机通信,并且主机和从机可以同时向对方进行数据传输;
- 当 SPI 总线上有多个 SPI 接口的设备时,应注意区分各设备的主从地位,在某时刻只能有一个设备作为主机;
- 从机只能在主机发起通信时,才能接收或向主机传输数据;
- SPI 每次数据传输可以为 8 位或 16 位为单位,每次传输的单位数不受限制,且大多以 MSB 的方式进行传输。
[图片]
SPI 传输模式有四种,通过 CPOL(时钟极性)与 CPHA(时钟相位)控制;CPOL 为 0 时代表时钟空闲状态为低,1 时为高;CPHA 为 0 时代表数据在时钟周期的第一个改变沿(前沿)采样,在第二个改变沿(后沿)输出(改变),为 1 时代表数据在时钟周期的第一个改变沿(前沿)输出,在第二个改变沿(后沿)采样(改变)。
| CPOL\CPHA | 0 | 1 |
|---|---|---|
| 0 | 模式 0 | 模式 1 |
| 1 | 模式 2 | 模式 3 |
二、FLASH_M25P16 简介
M25P16 是一款 16 兆位 (2M×8)串行闪存,配备先进的写保护机制,通过高速 SPI 兼容总线进行访问。该存储器支持使用页编程指令以 1 至 256 字节为单位进行编程。其存储结构由 32 个扇区组成,每个扇区包含 256 页,每页宽度为 256 字节。因此整个存储器可视为由 8192 页(即 2,097,152 字节)构成。存储器可通过批量擦除指令进行整体擦除,或使用扇区擦除指令逐扇区擦除。
| 扇区 | 最小地址 | 最大地址 |
|---|---|---|
| 31 | 1F0000h | 1FFFFFh |
| 30 | 1E0000h | 1EFFFFh |
| 29 | 1D0000h | 1DFFFFh |
| 28 | 1C0000h | 1CFFFFh |
| 27 | 1B0000h | 1BFFFFh |
| 26 | 1A0000h | 1AFFFFh |
| 25 | 190000h | 19FFFFh |
| 24 | 180000h | 18FFFFh |
| 23 | 170000h | 17FFFFh |
| 22 | 160000h | 16FFFFh |
| 21 | 150000h | 15FFFFh |
| 20 | 140000h | 14FFFFh |
| 19 | 130000h | 13FFFFh |
| 18 | 120000h | 12FFFFh |
| 17 | 110000h | 11FFFFh |
| 16 | 100000h | 10FFFFh |
| 15 | 0F0000h | 0FFFFFh |
| 14 | 0E0000h | 0EFFFFh |
| 13 | 0D0000h | 0DFFFFh |
| 12 | 0C0000h | 0CFFFFh |
| 11 | 0B0000h | 0BFFFFh |
| 10 | 0A0000h | 0AFFFFh |
| 9 | 090000h | 09FFFFh |
| 8 | 080000h | 08FFFFh |
| 7 | 070000h | 07FFFFh |
| 6 | 060000h | 06FFFFh |
| 5 | 050000h | 05FFFFh |
| 4 | 040000h | 04FFFFh |
| 3 | 030000h | 03FFFFh |
| 2 | 020000h | 02FFFFh |
| 1 | 010000h | 01FFFFh |
| 0 | 000000h | 00FFFFh |
信号描述
[图片]
串行数据输出 (Q):该输出信号用于将数据以串行方式从设备中传输。数据在串行时钟 (C) 的下降沿被移出。 串行数据输入 (D):该输入信号用于将数据以串行方式输入设备。它接收指令、地址及待编程数据。值在串行时钟 (C) 的上升沿被锁存。 串行时钟(C):该输入信号提供串行接口的时序控制。串行数据输入 (D) 中的指令、地址或数据会在串行时钟 (C) 的上升沿被锁存。串行数据输出 (Q) 的数据在串行时钟 (C) 的下降沿后发生变化。 芯片选择 (S):当此输入信号为高电平时,设备被取消选中且串行数据输出 (Q) 处于高阻抗状态。除非正在进行内部编程、擦除或写入状态寄存器周期,否则设备将处于待机模式(非深度休眠模式)。将芯片选择 (S) 拉低可激活设备,使其进入活动电源模式。上电后,在开始任何指令前需等待芯片选择 (S) 的下降沿。 保持(HOLD):保持(HOLD)信号用于暂停与设备的串行通信,但不取消选中状态。在保持状态下,串行数据输出 (Q) 处于高阻抗状态,而串行数据输入 (D) 和串行时钟 (C) 则不受影响。要启动保持状态,必须选中设备并将芯片选择 (S) 拉低。 写保护 (W):该输入信号的主要目的是冻结受程序或擦除指令保护的存储器区域的大小(由状态寄存器的 BP2、BP1 和 BP0 位的值指定)
其 SPI 外设运行在以下两种模式之一: CPOL=0,CPHA=0 CPOL=1,CPHA=1 对于这两种模式,输入数据会在串行时钟 (C) 的上升沿被锁存,输出数据则从串行时钟 (C) 的下降沿开始传输。当总线主控处于待机模式且不传输数据时,两种模式的区别在于时钟极性:当 CPOL=0 且 CPHA=0 时,C 保持为 0 当 CPOL=1 且 CPHA=1 时,C 保持为 1。
[图片]
功能操作
所有指令、地址和数据均以最高有效位为先进行设备输入输出。当芯片选择信号 (S) 被置为低电平时,串行数据输入端口 (D) 会在串行时钟信号 (C) 的第一个上升沿进行采样。随后,每个字节指令代码必须通过串行数据输入端口 (D) 以最高有效位为先进行输入,每个比特位会在串行时钟信号 (C) 的上升沿被锁存。每条指令序列均以一个字节指令代码开头,根据指令类型不同,可能后接地址字节、数据字节、两者兼有或完全省略。对于读取数据字节(READ)、高速读取数据字节(Fast_Read)、读取状态寄存器(RDSR)、读取标识符(RDID)、深度休眠释放(DP)以及读取电子签名(RES)等指令,其输入指令序列后会接续数据输出序列。当数据输出序列中的任意一位完成传输后,芯片选择信号 (S) 可被置为高电平。对于页程序(PP)、扇区擦除(SE)、批量擦除(BE)、写入状态寄存器(WRSR)、写入使能(WREN)、写入禁用(WRDI)或深度休眠(DP)等指令,芯片选择信号 (S) 必须在字节边界处精确置高,否则该指令将被拒绝且无法执行。即当芯片选择 (S) 被驱动为低电平之后的时钟脉冲数是 8 的整数倍时,芯片选择 (S) 必须保持高电平。所有在写入状态寄存器周期、编程周期或擦除周期内对存储阵列的访问尝试都将被忽略,且内部的写入状态寄存器周期、编程周期或擦除周期将继续正常运行。
| 功能 | 命令(二进制) | 命令(16 进制) |
|---|---|---|
| 写使能(WREN) | 0000_0110 | 06h |
| 读 ID(RDID) | 1001_1111 | 9Fh |
| 读状态(RDSR) | 0000_0101 | 05h |
| 字节读(READ) | 0000_0011 | 03h |
| 快速读(FAST_READ) | 0000_1011 | 0Bh |
| 页读(PP) | 0000_0010 | 02h |
| 扇区擦除(SE) | 1101_1000 | D8h |
| 全擦除(BE) | 1011_1001 | B9h |
写使能 写入使能(WREN)指令用于设置写入使能锁存器(WEL)位。在执行每页程序(PP)、扇区擦除(SE)、批量擦除(BE)和写入状态寄存器(WRSR)指令前,必须先设置该位。输入写入使能(WREN)指令时,需先将芯片选择 (S) 置低电平,发送指令代码,随后再将芯片选择 (S) 置高电平。
[图片]
读 ID 读取标识符(RDID)指令用于读取 8 位制造商标识符,随后是两个字节的设备标识符。制造商标识符由 JEDEC 分配,本次设备为 BAh。设备标识符由设备制造商指定,其中第一个字节(60h)表示存储器类型,第二个字节(15h)表示设备的存储容量。 在擦除或编程周期进行期间,任何读取标识符(RDID)指令都不会被解码,且对正在进行的周期没有任何影响。 首先通过将芯片选择 (S) 置低电平来选定设备。随后,输入指令的 8 位代码被移入。接着,存储在内存中的 24 位设备标识符通过串行数据输出端口 (Q) 逐位移出,每个比特位均在串行时钟信号 (C) 的下降沿期间完成移出操作。 操作顺序如图所示。 Read Identification(RDID)指令通过在数据输出期间的任何时刻将芯片选择 (S) 置高来终止。 当 Chip Select (S) 被驱动为高电平时,设备进入待机电源模式。一旦进入待机电源模式,设备就等待被选中,以便接收、解码和执行指令。
[图片]
读状态寄存器 读取状态寄存器(RDSR)指令用于读取状态寄存器。即使在程序、擦除或写入状态寄存器周期进行期间,也可以随时读取状态寄存器。当处于这些周期中的任一周期时,建议在向设备发送新指令前检查写入进行中(WIP)位。如表所示,也可以持续读取状态寄存器。
| 状态寄存器格式 | SRWD | 0 | 0 | BP2 | BP1 | BP0 | WEL | WIP |
|---|
WIP 位:写入进行中(WIP)位用于指示存储器是否处于写入状态寄存器、编程或擦除周期的忙状态。 WEL 位:写入使能锁存器(WEL)位用于指示内部写入使能锁存器的状态。 BP2、BP1、BP0 位:块保护(BP2、BP1、BP0)位是非易失性存储单元,用于定义软件保护编程和擦除指令的区域范围。 SRWD 位:状态寄存器写入禁用(SRWD)位与写保护 (W) 信号协同工作。通过 SRWD 位和写保护 (W) 信号,设备可切换至硬件保护模式。在此模式下,状态寄存器的非易失性位(SRWD、BP2、BP1、BP0)将变为只读位。
[图片]
字节读 该设备的选通首先通过驱动芯片选择 (S) 低电平实现。读取数据字节(READ)指令的控制码后接 3 字节地址(A23-A0),每个地址位在串行时钟 (C) 上升沿被锁存。随后,存储器内存内容在该地址处通过串行数据输出端口 (Q) 进行移位输出,每个地址位在串行时钟 (C) 下降沿以最大频率 fR 完成移位操作。 操作顺序如图所示。 所寻址的第一个字节可以位于任何位置。 在每个数据字节被移出后,地址将自动递增到下一个更高地址。因此,整个内存都可以通过一条读取数据字节(READ)指令来读取。 当达到最高地址时,addresscounter 将翻转为 000000h,从而允许无限期地继续读取序列。 读取数据字节(READ)指令通过将芯片选择 (S) 置高电平来终止。芯片选择 (S) 可在数据输出过程中的任何时刻被置高电平。任何读取数据字节(READ)指令在擦除、编程或写入周期进行期间,将被拒绝且不会对正在进行的周期产生任何影响。
[图片]
快速读 该设备的选通首先通过驱动芯片选择 (S) 低电平实现。执行'高速读取数据字节(FAST_READ)'指令时,其代码序列包含 3 字节地址(A23-A0)和一个虚拟字节,这些数据位会在串行时钟 (C) 上升沿被锁存。随后,存储器中对应地址的数据会以最大频率 fC,在串行时钟 (C) 下降沿期间逐位输出到串行数据输出端口 (Q)。 操作顺序如图所示。 所寻址的第一个字节可以位于任何位置。 地址在每次数据字节被移位输出后,会自动递增到下一个更高地址。因此,整个内存只需一条'高速读取数据字节指令(FAST_READ)'即可完成全部读取。当达到最高地址时,地址计数器将回滚至 000000h,从而实现无限次的连续读取操作。 高速读取数据字节指令(FAST_READ)通过将芯片选择信号 (S) 置高电平来终止。芯片选择信号 (S) 可在数据输出过程中的任意时刻被置高电平。任何正在进行擦除、编程或写入周期的高速读取数据字节指令(FAST_READ),都将被拒绝,且不会对当前进行的周期产生任何影响。
[图片]
页写 页面编程(PP)指令允许在内存中对字节进行编程(将位从 1 变为 0)。在接收该指令之前,必须先执行写使能(WREN)指令。解码完写使能(WREN)指令后,设备会设置写使能锁存器(WEL)。 页面程序(PP)指令的输入方式是:首先将芯片选择信号 (S) 置低电平,随后在串行数据输入端口 (D) 发送指令代码、三个地址字节及至少一个数据字节。若地址的最低 8 位(A7-A0)不全为零,则所有超出当前页面范围的数据传输,都将从该页面的起始地址开始编程(即地址的最低 8 位 A7-A0 均为零时的起始地址)。 在整个序列持续时间内,必须将芯片选择 (S) 驱动为低电平。 操作顺序如图所示。 当向设备发送的数据超过 256 字节时,系统会丢弃先前锁存的数据,并确保最后 256 个数据字节在同一页面内被正确编程。若发送的数据少于 256 字节,则会在指定地址正确编程,且不会影响同一页面的其他数据字节。芯片选择 (S) 信号必须在最后一个数据字节的第七位完成锁存后保持高电平,否则页面编程(PP)指令将无法执行。 当 Chip Select (S) 被驱动为高电平时,就会启动自定时的页面程序周期(持续时间 tPP)。在页面程序周期进行期间,可以读取状态寄存器以检查 Write In Progress(WIP)位的值。 在自定时的页面程序周期中,Write In Progress(WIP)位为 1,当该周期结束时为 0。在周期结束前的某个未指定时间,Write Enable Latch(WEL)位被复位。
[图片]
扇区擦除 扇区擦除(SE)指令将选定扇区内的所有位设置为 1(FFh)。在该操作被接受之前,必须先执行写使能(WREN)指令。当写使能(WREN)指令被解码后,设备会设置写使能锁存器(WEL)。 扇区擦除(SE)指令的输入方式为:先将芯片选择信号 (S) 置低电平,随后输入指令代码,并通过串行数据输入端口 (D) 发送三个地址字节。扇区内的任意地址(参见表 3)均可作为扇区擦除(SE)指令的有效地址。整个操作过程中,芯片选择信号 (S) 必须始终保持低电平状态。该指令序列的具体实现如图所示。 当最后一个地址字节的第八位被锁存后,芯片选择信号 (S) 必须驱动高电平,否则扇区擦除指令(SE)将无法执行。芯片选择 (S) 一旦被驱动高电平,自定时扇区擦除周期(持续时间 tSE) 即刻启动。在扇区擦除周期进行期间,可读取状态寄存器以检查写入进行中(WIP)位的数值。该位在自定时扇区擦除周期内保持 1,在周期结束后转为 0。在周期完成前的某个未指定时刻,写入使能锁存器(WEL)位会被复位。
[图片]
全擦除 全擦除(BE)指令会将所有位设置为 1(FFh)。在该指令被接受之前,必须先执行写使能(WREN)指令。在解码完写使能(WREN)指令后,设备会设置写使能锁存器(WEL)。 通过驱动芯片选择 (S) 低电平,随后在串行数据输入 (D) 上输入指令代码,即可输入批量擦除(BE)指令。在整个序列期间,必须将芯片选择 (S) 保持为低电平。该指令序列如图所示。 当指令代码的第八位被锁存后,芯片选择信号 (S) 必须驱动为高电平,否则批量擦除指令将无法执行。一旦芯片选择信号 (S) 被驱动为高电平,自定时批量擦除周期(持续时间是 istBE) 就会启动。在批量擦除周期进行期间,可以通过读取状态寄存器来检查写入进行中(WIP)位的值。 在自定时的批量擦除周期期间,写入进行中(WIP)位为 1,当该周期完成时为 0。在周期完成之前的某个未指定时间,写入使能锁存器(WEL)位被复位。 只有当所有块保护(BP2、BP1、BP0)位为 0 时,才会执行批量擦除(BE)指令。如果一个或多个扇区受到保护,则会忽略批量擦除(BE)指令。
[图片]
注意时序
| 操作 | 最小时间 | 最大时间 | 单位 |
|---|---|---|---|
| 命令间的间隔 | 100 | 、 | ns |
| 页写 | 1.4 | 5 | ms |
| 扇区擦除 | 1 | 3 | s |
| 全擦除 | 17 | 40 | s |
三、设计思路
框图设计
[图片]
功能实现:
- 读 ID 操作,按下按键 key1,将读出的 ID 显示在数码管,格式 xx.xx.xx; 同时,通过 UART 发送至上位机显示,显示格式为 设备 ID:xx xx xx;
- 读数据操作,按下按键 key2,读出 3 个字节数据,将读出的输出显示在数码管 xx.xx.xx; 同时,通过 UART 发送至上位机显示,显示格式为 读数据:xx xx xx;
- 擦除操作,按下按键 key3,进行扇区擦除;
- 写数据操作,PC 端通过串口连续发送多字节数据,每三个数据进行一次页编程操作。
状态机设计
扇出状态机: 由于在 spi_ctrl 中有许多子模块在传输数据,这里要多扇出的数据进行操作,这里用一个简单的状态机实现
[图片]
写数据状态机: 由于写数据涉及到多种命令、地址的发送,还有扇区擦除,写数据,读状态数据等操作,所以设计一个状态机实现一系列的功能。
[图片]
四、上板验证
1、读 ID
[图片]
按下 key2
[图片]
数码管显示出本设备的 flash 的 id 为 BA_60_15.
[图片]
同时通过串口发送 id 至 PC 显示出来
2、读数据
按下 key3
[图片]
数码管显示出第一组数据,此数据为先前写入的数据 12_34_56
[图片]
同时 PC 也显示出数据;
连续按下,多次读出数据
[图片]
读出 78_90_11,后续为写入数据显示为 ff。
3、扇区擦除 + 写数据
按下复位 key1+ 扇区擦除 key4,擦除完成再读数据
[图片]
读出数据全为 ff,擦除成功;复位并通过串口发送 A1 B2 C3 D4 E5 F6 99
[图片]
连续按下读数据 key3
[图片]
A1 B2 C3 D4 E5 F6 被发送至 flash 写入,而 99 暂存在 fifo 中未写入,在此的 fifo 设置的为每三个数据发送给 flash 进行存储。
验证成功。
五、总结
本次的 SPI 控制 flash 读写项目完成了基本功能,在理解 SPI 协议后会比较容易,相较于 IIC 和 UART,SPI 没有过多的协议层上数据的约束,理解起来也比较简单。
六、代码
模块较多,具体代码请参考相关仓库。


