FPGA驱动SPI Flash的读写时序与Verilog实现
1. SPI Flash基础与FPGA控制原理SPI Flash是一种采用串行外设接口(SPI)的闪存芯片在嵌入式系统中广泛用于存储固件、配置参数等数据。与并行Flash相比SPI Flash引脚数量少通常4-6线、封装尺寸小特别适合空间受限的应用场景。FPGA作为可编程逻辑器件通过硬件描述语言(Verilog/VHDL)可以灵活实现SPI控制器直接操作Flash存储器。以常见的M25P16为例这款16Mbit容量的SPI Flash支持最高50MHz时钟频率。实际项目中我发现要稳定驱动这类芯片关键在于两点一是正确配置SPI通信模式二是准确理解Flash的操作指令集。这就像与人交流既要说对方听得懂的语言SPI时序又要用正确的词汇表达意图操作指令。2. SPI模式选择与时序设计2.1 SPI工作模式解析SPI有四种工作模式由CPOL(时钟极性)和CPHA(时钟相位)两个参数决定CPOL0时钟空闲时为低电平CPOL1时钟空闲时为高电平CPHA0数据在时钟第一个边沿采样CPHA1数据在时钟第二个边沿采样M25P16支持模式0(CPOL0,CPHA0)和模式3(CPOL1,CPHA1)。实测中模式3稳定性更好其特点是时钟空闲时为高电平数据在下降沿输出FPGA发送数据在上升沿采样FPGA接收2.2 Verilog时序实现技巧下面是用Verilog实现SPI主机的核心代码段// SPI模式3CPOL1, CPHA1 always (posedge sys_clk) begin if(state SPI_DATA) begin // 时钟信号翻转 spi_clk_reg ~spi_clk_reg; // 下降沿发送数据 if(spi_clk_reg 1b1) spi_mosi_reg write_data_reg[7]; // 上升沿接收数据 if(spi_clk_reg 1b0) read_data_reg {read_data_reg[6:0], spi_miso}; end end特别要注意的是第一个数据位的处理。在模式3下由于第一个下降沿前没有时钟边沿需要在初始状态就设置好MOSI电平。我曾在项目中因为这个细节导致首字节数据错误调试了整整一天才发现问题。3. Flash指令集详解与操作流程3.1 关键指令解析M25P16的指令集包含约20条命令但实际常用的是以下几个WREN (06h)写使能。任何写入或擦除操作前必须执行相当于解锁开关。READ (03h)读取数据。发送24位地址后可连续读取地址自动递增。PP (02h)页编程。每次写入最多256字节需先擦除对应区域。SE (D8h)扇区擦除。以64KB为单位擦除耗时约0.5-1秒。RDID (9Fh)读取厂商ID。常用于硬件调试和SPI通路验证。3.2 典型操作时序读取数据流程拉低CS片选信号发送READ指令(03h)发送24位地址3字节连续读取数据拉高CS结束传输写入数据流程发送WREN指令发送PP指令(02h)发送24位地址发送1-256字节数据等待TPP时间典型值0.7ms这里有个易错点PP指令只能将bit从1改为0若要写入新数据必须先执行擦除操作将目标区域恢复为全1状态。我在早期项目中就犯过直接写入未擦除区域的错误导致数据异常。4. Verilog实现完整架构4.1 模块划分建议完整的Flash控制器建议分为两个模块SPI主机模块处理底层时序提供字节级读写接口Flash控制模块解析高层指令管理操作流程module flash_control( input sys_clk, input sys_rstn, // 用户接口 input read_req, input [23:0] read_addr, output [7:0] read_data, // SPI接口 output spi_clk, output spi_mosi, input spi_miso ); // 状态机定义 localparam IDLE 3d0; localparam CMD 3d1; localparam ADDR 3d2; localparam DATA 3d3; reg [2:0] state; reg [7:0] cmd_reg; reg [23:0] addr_reg;4.2 状态机设计要点Flash操作本质上是状态机驱动的过程。以页编程为例IDLE状态等待用户请求WREN状态发送写使能指令PP_CMD状态发送页编程指令ADDR状态发送目标地址DATA状态传输数据字节WAIT状态等待编程完成实际项目中建议为每个重要操作添加超时检测。我曾遇到Flash芯片异常导致永久卡死的情况后来加入超时机制后系统稳定性大幅提升。5. 工程实践中的经验分享5.1 时序约束关键点在FPGA工程中必须添加正确的时序约束# SPI时钟约束假设系统时钟50MHz create_clock -period 20 [get_ports sys_clk] set_output_delay -clock [get_clocks sys_clk] -max 2 [get_ports {spi_mosi spi_clk}]特别要注意跨时钟域信号的处理。如果SPI时钟分频自系统时钟需要使用适当的同步器处理控制信号。5.2 常见问题排查读取全FF或00检查CS信号是否正常确认SPI模式设置正确测量电源电压是否稳定写入失败是否先执行了WREN目标区域是否已擦除等待时间是否足够TPP/TSE间歇性错误检查PCB布线SPI走线要短且等长添加去耦电容0.1uF靠近VCC引脚降低时钟频率测试记得第一次调试时我遇到随机数据错误最后发现是PCB上SPI走线过长10cm导致信号完整性问题。将Flash芯片移近FPGA后问题立即解决。6. 性能优化技巧6.1 双缓冲读取技术对于大数据量读取可采用双缓冲机制// 双缓冲实现 reg [7:0] buffer0[0:255]; reg [7:0] buffer1[0:255]; reg buffer_sel; always (posedge sys_clk) begin if(read_ack) begin if(!buffer_sel) buffer0[addr_lsb] read_data; else buffer1[addr_lsb] read_data; if(addr_lsb 8hFF) buffer_sel ~buffer_sel; end end这种方法允许在读取一个缓冲区的数据时后台同时填充另一个缓冲区显著提高吞吐量。6.2 批量擦除策略Flash擦除耗时较长扇区擦除约0.5s整片擦除可达10s建议上电时检查是否需要全局擦除运行时采用脏块标记管理空闲时执行后台擦除操作在某个数据采集项目中我通过预擦除空闲扇区的方法将关键写入延迟从毫秒级降低到微秒级满足了实时性要求。