名称:spi flash(M25P16芯片)实验设计Verilog代码Quartus AX301开发板
软件:Quartus
语言:Verilog
代码功能:
由于 FPGA 是基于 SRAM 结构的,程序掉电后会丢失,所以需要一个外置 Flash 保存程序,FPGA 每次上电后去读取 Flash 中的配置程序,在 ALINX 开发板中,很多使用的是 SPI 接口的 nor flash,这种 flash 只需要 4 根 IO。FPGA 的配置 flash 是特殊的 IO,上电时工作,FPGA 要使用这些IO 来读取 Flash,读取完成后释放这些 IO 交给用户使用。
本实验做一个 SPI 主设备控制器,然后按照 spi Flash 数据手册的命令要求发出擦除、编程、读取等指令,每次上电后将 flash 中第一个字节读取并显示出来,按键按下时,数字加 1 再写回 flash。
主要学习 spi 接口、spi flash 操作等,由于篇幅有限,本文不详细介绍 SPI 协议和 spi flash 的操作时序。
FPGA代码Verilog/VHDL代码资源下载:www.hdlcode.com
本代码已在AX301开发板验证,AX301开发板如下,其他开发板可以修改管脚适配:
设计文档:
SPI Flash 实验
FPGA设计
1 实验简介
由于 FPGA 是基于 SRAM 结构的,程序掉电后会丢失,所以需要一个外置 Flash 保存程序,FPGA 每次上电后去读取 Flash 中的配置程序,在 ALINX 开发板中,很多使用的是 SPI 接口的 nor flash,这种 flash 只需要 4 根 IO。FPGA 的配置 flash 是特殊的 IO,上电时工作,FPGA 要使用这些IO 来读取 Flash,读取完成后释放这些 IO 交给用户使用。
本实验做一个 SPI 主设备控制器,然后按照 spi Flash 数据手册的命令要求发出擦除、编程、读取等指令,每次上电后将 flash 中第一个字节读取并显示出来,按键按下时,数字加 1 再写回 flash。
主要学习 spi 接口、spi flash 操作等,由于篇幅有限,本文不详细介绍 SPI 协议和 spi flash 的操作时序,但这些知识都是本实验基础。
2 实验原理
2.1 硬件介绍
如图所示,AX301、AX4010 开发板上有一个 SPI Flash,通常是做为 FPGA 的程序配置 Flash,但是也可以做为用户 Flash 使用,我们可以把自己的少量数据保存在 Flash 中。
AX301、AX4010 开发板 SPI flash
2.2 Flash 时序和命令
对一个器件进行操作前,我们首先要了解 Flash 的各种特性,特别是和我们操作相关的特性,大部分芯片厂商会提供芯片的数据手册,这些芯片手册一般可以通过芯片厂商网站获取,有些厂商需要签订保密协议才能提供数据手册。所以获取芯片数据手册也是非常重要的学习内容,首先通过搜索引擎搜索,在没有搜索结果时可到芯片厂商官网找找,很多芯片数据手册下载是需要注册登录,然后再下载。注意:在进行试验前请先阅读配套资料芯片手册文件夹下的FLASH datasheet,搞清楚flash 命令,地址和数据之间的时序关系。
2.2.1 SPI 模式
SPI 可以通过 CPOL,CPHA 来配置模式,这对于刚接触 SPI 协议比较费劲,暂且不去理会。SPI Flash 支持 2 种配置模式(These devices can be driven by a microcontroller with its SPI peripheral running in either of the two following modes):
CPOL=0, CPHA=0
CPOL=1, CPHA=1
这 2 种数据模式,数据输入都是在串行时钟的上升沿锁存数据,在串行时钟的下降沿送出数据。(For these two modes, input data is latched in on the rising edge of Serial Clock (C), and output data is available from the falling edge of Serial Clock (C)).
2 种 SPI 模式数据波形
2.2.2 Flash 的主要操作
页编程(Page programming)
编程指令就是讲 Flash 的数据位由 1 变成 0,只能由 1 变成 0,如果要从 0 变成 1,只能使用 擦除操作。要编程一个数据字节,需要两个指令:写使能(WREN),这是一个字节和一个页编程(pp)指令,它由四字节加上数据组成。为了提高性能,页编程(PP)指令最多允许 256 字节, 当然这些数据都必须在一页内,不能跨页连续读取。从页编程指令时序图可以看出,SPI 需要先发 送一个字节的指令,再发出 3 个字节的地址,然后再发出数据,最大 256 个数据。将数据写入后 检查状态寄存器 WIP 位(状态寄存器最低位)的值,若为 1 表示处于数据写入周期,若为 0 表示 写入周期完成,可以进行下一步操作。
页编程指令时序
页编程之前需要写使能有效,需要先发送写使能指令,指令时序如下图,写使能只有一个字 节。可以反复发送写使能。
写使能指令时序
块擦除指令(Bulk Erase)
块擦除指令(BE)可以把整个 flash 都变成 1,同样,在块擦除之前需要先发送写使能指令。 Flash 的擦除需要的时间很长,容量不同时间会有差异,一般需要几分钟擦除整片芯片。块擦除指 令发出后,我们通过不断读取状态寄存器(Status Register)来查询擦除是否完成。
hdlcode.com
块擦除指令时序
扇区擦除指令(Sector Erase)
扇区擦除(SE)指令可以按照扇区擦除 Flash。和块擦除不同的是,扇区擦除是要指定扇区地 址,扇区擦除前也需要发送写使能指令。
扇区擦写指令
读数据指令(Read Data Bytes)
读 flash 是非常常见的操作,首先拉低片选信号,然后发出读指令,3 个字节的读地址,然后 就可以持续读出数据,地址自动累加。器件处于擦除或数据写入周期时,数据读取指令无效并且 对当前周期无任何影响。
读数据指令
flash 的其他指令这里不再介绍,其他指令如下图表格。
flash 指令列表
3 程序设计
spi flash 读写相对比较复杂,本实验将 flash 操作分解为 3 层,最底层为 SPI 驱动层,每次写一个字节返回一个字节,然后是 flash 指令层,flash 指令层通过 spi 主控制器读写数据,完成最基本的 flash 各种指令,然后是 flash 擦除、编程、读写层,为其他模块提供可直接操作 flash 的接口。
SPI主设备控制器
(spi master)
Flash擦除、编程、 读写控制
(spi flash ctrl )
spi flash 控制器框图
为了检验 flash 掉电不丢失的功能,实验设计了一个状态机,上电一段时间后读取 flash 的第 一个字节,并通过数码管显示出来,如果按键按下,将数字加 1,再写回 flash,这样下次上电会 保持新写入的数据。
spi master 状态机设计,主要完成一个字节 spi 数据的读写,由于是全双工的,写一个字节的 同时也读一个字节。首先空闲状态“IDLE”接收到写请求后进入“DCLK_IDLE”状态,这个状态为 spi 时钟沿变化保持一定的时间,用来控制 spi 时钟的周期,然后进入 spi 时钟沿的变化状态,一 个字节上升沿和下降沿一共 16 个数据沿。在最后一个数据沿进入“LAST_HALF_CYCLE”状态,为 让最后一个沿也保持一定的时间,再进入应答状态,完成一次写请求。
spi master 模块状态图
spi_master 模块中模拟了一个 spi 时钟,在状态机进入到‘DCLK_EDGE’时进行翻转
在‘spi_flash_top’模块中例化‘spi_master’模块时已经设定‘clk_div’的值为 0,目的是将 模拟的 spi 时钟‘DCLK_reg’进行 4 分频,也就是当‘clk_div=0’整个模块运行时,从状态‘IDLE’ 跑到状态‘DCLE_EDGE’需要 4 个‘sys_clk’周期。至于其他不能够理解的地方请大家详细了解 spi 总线时序和 flash 读写时序后再来看或许会有更深的认识。当然,最直观的方法还是仿真
信号名称 | 方向 | 说明 |
sys_clk | in | 时钟输入 |
rst | in | 异步复位输入,高复位 |
nCS | out | spi 片选信号,等于 nCS_ctrl。 |
DCLK | out | spi 串行时钟 |
MOSI | out | spi 串行数据输出 |
MISO | in | spi 串行数据输入 |
CPOL | in | Clock Polarity,spi 时钟的极性
0:空闲状态为 0 1:空闲状态为 1 |
CPHA | in | Clock Phase,spi 时钟的相位,
0:第一个沿采样, 1:第二个沿采样 |
nCS_ctrl | in | nCS 控制 |
clk_div | in | spi 时钟频率控制
spi 时钟=系统时钟/(2*(2+ clk_div)) clk_div 最小值可以为 0,当为 0 时,spi 时钟是系统 时钟的 1/4 |
wr_req | in | 写一个字节请求 |
wr_ack | out | 写应答,高有效 |
data_in | in | 数据 |
data_out | out | 返回的数据,当写应答时有效 |
spi master 端口说明
spi _flash_cmd 模块状态机设计,如下图所示,在收到命令请求以后进入“S_CMD_LATCH”命 令锁存状态,将请求的命令记录下来,然后进入“S_CS_LOW”状态,拉低 spi 的片选信号,再进入“S_WR_CMD_CODE”状态,发送一个字节的命令码,如果这个命令只有一个字节,进入“S_KEEP_CS_LOW”状态,保持一个周期的片选拉低,然后进入“S_CS_HIGH”状态,拉高片选。 如果命令后面还有地址等数据,进入“S_WRITE_BYTES”写数据状态,或进入“S_READ_BYTES” 读。需要注意,在 spi 数据接口‘data_recv’向数据输出接口‘data_out’传送数据时,是舍弃了 3 个字节的地址位,只传送数据位。而在产生‘data_req’信号时‘byte_cnt’却是到 2,为了满 足数据写入的时序要求,这里提前了一个时钟周期
spi_flash_cmd 状态机
信号名称 | 方向 | 说明 |
sys_clk | in | 时钟输入 |
rst | in | 异步复位输入,高复位 |
cmd | in | 命令编码 |
cmd_valid | in | 命令有效 |
cmd_ack | out | 命令应答 |
addr | in | flash 地址 |
data_in | in | 命令有写操作时的数据 |
size | in | 命令+数据长度(字节) |
data_req | out | 命令有写操作时请求数据,其他 data_in 一个时钟周 期 |
data_out | out | 命令有读操作时读出的数据 |
data_valid | out | 命令有读操作时读有效 |
CS_reg | out | 对 spi master 接口,spi 片选控制 |
wr_req | out | 对 spi master 接口,spi 写请求 |
wr_ack | in | 对 spi master 接口,spi 写应答 |
send_data | out | 对 spi master 接口,spi 写数据 |
data_recv | in | 对 spi master 接口,spi 读数据 |
spi_flash_cmd 模块端口
spi_flash_ctrl 模块主要完成 flash 擦除、编程、读操作。擦除前需要写使能有效、等待擦除完
成等多项 flash 命令。状态机如下图所示:
“S_IDLE”:空闲状态
“S_WREN”:写使能命令状态
“S_READ”:读状态
“S_WRITE”:写状态(编程)
“S_SE”:扇区擦除
“S_BE”:块擦除
“S_CK_STATE”:状态寄存器检查,用来检测是否擦除完成等。
“S_ACK”:请求应答
spi_flash_ctrl 状态机
信号名称 | 方向 | 说明 |
sys_clk | in | 时钟输入 |
rst | in | 异步复位输入,高复位 |
flash_read | in | flash 读请求 |
flash_write | in | flash 写请求 |
flash_bulk_erase | in | 块擦除请求 |
flash_sector_erase | in | 扇区擦除请求 |
flash_read_ack | out | 读应答 |
flash_write_ack | out | 写应答 |
flash_bulk_erase_ack | out | 块擦除应答 |
flash_sector_erase_ack | out | 扇区擦除应答 |
flash_read_addr | in | 读请求地址 |
flash_write_addr | in | 写请求地址 |
flash_sector_addr | in | 扇区擦除地址 |
flash_write_data_in | in | 写请求数据 |
flash_read_size | in | 读字节大小 |
flash_write_size | in | 写字节大小 |
flash_write_data_req | out | 写数据拉取,提前 flash_write_data_in 一个时钟 |
flash_read_data_out | out | 读数据 |
flash_read_data_valid | out | 读数据有效 |
cmd | out | 连接 spi_flash_cmd 模块,命令编码 |
cmd_valid | out | 连接 spi_flash_cmd 模块,命令有效 |
cmd_ack | in | 连接 spi_flash_cmd 模块,命令应答 |
addr | out | 连接 spi_flash_cmd 模块,flash 地址 |
data_in | out | 连接 spi_flash_cmd 模块,命令有写操作时的数据 |
size | out | 连接 spi_flash_cmd 模块,命令+数据长度(字节) |
data_req | in | 连接 spi_flash_cmd 模块,命令有写操作时请求数 据,其他 data_in 一个时钟周期 |
data_out | in | 连接 spi_flash_cmd 模块,命令有读操作时读出的数 据 |
data_valid | in | 连接 spi_flash_cmd 模块,命令有读操作时读有效 |
spi_flash_ctrl 端口
在这个模块状态机的‘S_CK_STATE’状态,进行状态转移的条件不仅有命令应答信号还需要判断‘state_reg’寄存器的最低位,需要知道的是‘state_reg’的最低位就是 WIP 位,显示 SPI 是否在写入状态,为 0 时表示该状态不忙。同时,在 spi_flash_ctrl 模块中,我们调用了一个宏定义模块,和 C 语言里的宏定义类似,宏定义模块里面定义了对 flash 操作的各种命令,其用法和格式请参照例程。
4 实验现象
将程序下载到开发板以后,数码管显示一个数字,这个数字是 flash 的第一个字节,通过按下key1 键,数字会加一,同时擦除了 flash,并将新的数据写入,重新上电后,加载下载程序,数码管将显示最后一次按按键的数字。注意:由于 flash 擦写需求一定的时间,按键不能按的太快。
部分代码展示:
`define CMD_WREN 8'h06 `define CMD_WRDI 8'h04 `define CMD_RDID 8'hAB //EPCS4 EPCS16 is 0'hab st spi flash is 8'h9f `define CMD_RDSR 8'h05 `define CMD_WRSR 8'h01 `define CMD_READ 8'h03 `define CMD_FAST_READ 8'h0b `define CMD_PP 8'h02 `define CMD_SE 8'hd8 `define CMD_BE 8'hc7
点击链接获取代码文件:http://www.hdlcode.com/index.php?m=home&c=View&a=index&aid=1345