FPGA基于SPI实现对flash读写操作
概括
内容用FPGA芯片Cylone IV E:EP4CE6F17C8 实现对flash读写操作,数据通过uart寄存在FIFO_0,按键_0按下读取数据通过SPI协议写入flash,再按键_1按下通过SPI协议读出数据寄存在FIFO_2经uart协议输出
一、SPI协议、flash讲解
1、SPI协议
SPI(Serial Peripheral interface)是由摩托罗拉公司定义的一种串行外围设备接口, 是一种全双工、同步的通信总线,只需要四根信号线即可,节约引脚,同时有利于 PCB 的布局。正是出于这种简单易用的特性,现在越来越多的芯片集成了 SPI 通信协议,如 FLASH、AD 转换器等。

SPI 协议支持一主多从、全双工、半双工模式。
SPI 通信需要四根线,分别为 cs_n、sclk、mosi 和 miso。其中 cs_n、sclk、mosi 是由主机输出给从机,而 miso 由从机输出给主机。cs_n 用于控制从机是否被选中,也就是说只有片选信号有效时,对从机的操作才有效;sclk 是由主机产生的同步时钟,用于同步数据;mosi 和 miso 是主机发送和接收的数据脚。传输数据时,以 MSB 的形式传输
SPI 通信一般有 4 种不同的模式,不同的从设备在出厂时被厂家配置为其中几种通信模式,通信模式是不允许用户修改的。主设备和从设备必须在同一模式下进行通信,否则数据会接收错误。SPI 的通信模式是由 CPOL(时钟极性)和 CPHA(时钟相位)来决定的;
模式 0:CPOL = 0,CPHA = 0;
模式 1:CPOL = 0,CPHA = 1;
模式 2:CPOL = 1,CPHA = 0;
模式 3:CPOL = 1,CPHA = 1。

当 CPOL = 1 时,sclk 在空闲时为高电平,发起通信后的第一个时钟沿为下降沿;
当 CPOL = 0 时,SPI 时钟信号 sclk 空闲时为低电平,发起通信后的第一个时钟沿为上升沿
当 CPHA = 1 时,时钟的第一个变化沿(上升沿或者下降沿)数据开始改变, 那么也就意味着时钟的第 2 个变化沿(与第一个变化沿相反)锁存数据;
当 CPHA = 0 时,数据在时钟的第一个变化沿之前就已经改变,并且保持稳定,也就意味着在时钟的第一个变化沿锁存数据。
2、flash
M25P16 是一款带有写保护机制的串行 Flash 存储芯片,容量为 16Mbit(2MB);一共
分为 32 个 sector,每个 sector 共 256B 存储容量,每个 sector 擦写次数为 10 万次、数
据保存期限至少 20 年。支持 SPI 通信模式 0 和模式 3。在对 Flash 芯片写数据之前,需要先执行擦除操作,将所有数据变为 FF。
flash指令表

实现读写操作只用了WREN、RDID、WRSR、READ、PP、SE命令
(1)WREN
在手册已经说了写使能必须在每个PP、SE、BE、WRSR指令前设置,在写使能发送结束后片选信号拉高

(2)RDID
读取标识(RDID)指令允许读取8位的制造商标识,然后是两个字节的设备标识。制造商标识由JEDEC指定,意法半导体的识别值为20小时。设备标识由设备制造商指定,表示第一个字节的内存类型(20小时),以第二个字节表示设备的内存容量(15小时)。同样数据读取结束后片选信号拉高

(3)WRSR
读取状态寄存器(RDSR)指令允许读取状态寄存器。状态寄存器可以在任何时候被读取,即使在一个程序、擦除或写状态寄存器周期正在进行中。这个命令我认为是在读取其状态的一个命令可有可无有需要可以去看手册,其时序图如下

(4)READ
首先通过驱动芯片选择低来选择设备。读取数据字节(READ)指令的指令代码后面是一个3字节地址,每个位在串行时钟的上升边缘期间被锁定然后,在该地址的内存内容在串行数据输出上移出,在串行时钟的下降边缘期间
这里指出下flash只支持SPI的模式0、模式3,
时序图如下

(5)PP
页面程序(PP)指令允许在内存中编程字节(将位从1改为0)当页面程序周期正在进行时,可以读取状态寄存器以检查正在写入(WIP)位的值。在自定时页面程序周期中,正在写入(WIP)位为1,完成时为0。

(6)SE
扇区擦除(SE)指令将选定扇区内的所有位设置为1(FFh)。在接受它之前,必须先执行了写入启用(WREN)指令。在写入启用(WREN)指令被解码后,设备将设置写入启用锁存器(WEL)。扇区擦除(SE)指令通过驱动芯片选择低输入,然后是指令代码和串行数据输入.

下表有时钟频率、擦除、写入、片选信号拉高再次拉低的时间


二、状态图


三、代码
1、代码分析
always @(*)begin
case (spi_mode)
0 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
sck <= 0;
end
else if(cnt_sck > `SCK_HALF-1)begin
sck <= 1;
end
else begin
sck <= 0;
end
end
1 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
sck <= 1;
end
else if(cnt_sck > `SCK_HALF-1)begin
sck <= 0;
end
else begin
sck <= 0;
end
end
2 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1 ))begin
sck <= 1;
end
else if(cnt_sck > `SCK_HALF-1 )begin
sck <= 0;
end
else begin
sck <= 1;
end
end
3 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
sck <= 0;
end
else if(cnt_sck > `SCK_HALF-1 )begin
sck <= 1;
end
else begin
sck <= 1;
end
end
default: sck <= sck;
endcase
end
SPI时钟的产生思路是以时钟去对齐数据,这里SPI四种模式都写出来了的再数据读取只写了模式0

数据传输时序图

因为这次没画完时序图,代码写的时候有点小乱,整里了下
2、全部代码
(1)param
//参数定义
//spi时钟参数
`define SCK_PERIOD 20
`define SCK_HALF 10
//spi command
`define CMD_WREN 8'h06 //
`define CMD_WRDI 8'h04 //
`define CMD_RDID 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 //
`define CMD_DP 8'hb9 //
`define CMD_RES 8'hab //
//spi byte
`define CMD_BYTE 1 // 命令1字节
`define ADDR_BYTE 3 // 地址3字节
`define ID_BYTE 3 // id3字节
`define DATA_BYTE 1 // id3字节
`define RDSR_BYTE 100 // 读状态寄存器最大可读1000次
`define DELAY_5MS 250_000 // 5ms 数据写入后需要等待5ms才能读出
//波特率
`define BAUD_9600 5208
`define BAUD_19200 2604
`define BAUD_38400 1302
`define BAUD_115200 434
`define STOP_BIT 1'b1 //数据停止位
`define START_BIT 1'b0 //数据开始位
(2)test
module test(
input clk ,
input rst_n ,
input [2:0] key_in ,
input uart_rxd,
input miso ,
output sck ,
output mosi ,
output cs_n ,
output uart_txd
);
//中间信号定义
wire [2:0] key_out ;
wire [7:0] rx_byte ;
wire rx_byte_vld ;
wire [7:0] dout_m ;
wire [7:0] dout ;
wire dout_vld ;
wire spi_vld ;
wire en ;
wire [7:0] din_m ;
wire busy_m ;
wire wr_done ;
wire busy_t ;
//模块例化
key_filter_fsm # (.KEY_W(3),.TIME_20MS(1_000_000))u_key_filter_fsm
(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input [KEY_W - 1:0] */.key_in (key_in ),
/*output [KEY_W - 1:0] */.key_out (key_out )
);
uart_rx u_uart_rx
(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.rx_din (uart_rxd ),
/*input [1:0] */.baud_sel (2'b0 ),
/*output [7:0] */.rx_byte (rx_byte ),
/*output */.rx_byte_vld (rx_byte_vld )
);
wr_control u_wr_control(
/*input */.</

本文介绍了使用FPGA(Cyclone IV EP4CE6F17C8)通过SPI协议读写Flash存储器的实现过程。首先,详细解释了SPI协议的工作原理和Flash芯片(如M25P16)的特性,包括WREN、RDID、WRSR、READ、PP和SE等关键指令。接着,展示了SPI时钟产生的状态图和代码实现,以及完整的FPGA模块设计,包括SPI主控器、写控制模块、读取模块和Flash写入模块。最后,通过实例验证了读写操作的正确性。整个设计实现了通过UART接口接收数据,按键触发读写操作,并通过UART输出读取结果。

2万+

被折叠的 条评论
为什么被折叠?



