在嵌入式系统开发中,外部存储扩展是常见需求。W25Qxx系列SPI Flash因其高性价比、小封装和易用性,成为众多项目的首选存储方案。这套Verilog代码实现了一个完整的FPGA控制方案,通过UART接口与上位机通信,能够对W25Q128/W25Q64/W25Q32/W25Q16等系列Flash芯片进行全功能控制。
这套系统的核心价值在于:
系统硬件架构围绕三个核心组件构建:
FPGA主控:采用Altera Cyclone IV E系列,主要考虑因素:
SPI Flash:支持全系列W25Qxx芯片,关键参数对比:
| 型号 | 容量 | 页大小 | 扇区大小 | 块大小 | 最高SPI时钟 |
|---|---|---|---|---|---|
| W25Q16 | 2MB | 256B | 4KB | 64KB | 104MHz |
| W25Q32 | 4MB | 256B | 4KB | 64KB | 104MHz |
| W25Q64 | 8MB | 256B | 4KB | 64KB | 104MHz |
| W25Q128 | 16MB | 256B | 4KB | 64KB | 104MHz |
时钟管理是系统稳定性的关键,设计中采用三级时钟架构:
verilog复制altpll #(
.clk0_divide_by(3),
.clk0_duty_cycle(50),
.clk0_multiply_by(12),
.clk0_phase_shift("0"),
.inclk0_input_frequency(41666) // 24MHz
) pll_inst (
.inclk0(clk_24m),
.c0(clk_100m),
.locked(pll_locked)
);
关键提示:PLL锁定信号(pll_locked)必须用于系统复位控制,确保时钟稳定前不进行任何操作。
UART模块采用经典的"过采样"设计,在100MHz时钟下实现精确的115200bps通信:
接收状态机包含5个状态:
关键采样点计算:
verilog复制localparam BAUD_DIV = 100_000_000 / 115200; // 868
always @(posedge clk_100m) begin
if(state == DATA_BITS) begin
if(bit_cnt == BAUD_DIV*3/4) // 在比特周期75%处采样
rxd_shift <= {rxd_shift[6:0], rxd_pin};
end
end
发送时序采用"比特周期计数器"实现:
verilog复制always @(posedge clk_100m) begin
if(tx_state != IDLE) begin
if(baud_cnt == BAUD_DIV-1) begin
baud_cnt <= 0;
bit_cnt <= bit_cnt + 1;
end else begin
baud_cnt <= baud_cnt + 1;
end
end
end
SPI控制器采用模块化设计,支持6种基本操作模式:
所有SPI操作共享相同的状态机框架:
verilog复制localparam [2:0]
SPI_IDLE = 0,
SPI_CS_LOW = 1,
SPI_CLK_HIGH = 2,
SPI_CLK_LOW = 3,
SPI_CS_HIGH = 4;
always @(posedge clk_100m) begin
case(spi_state)
SPI_IDLE: if(start) spi_state <= SPI_CS_LOW;
SPI_CS_LOW: spi_state <= SPI_CLK_HIGH;
SPI_CLK_HIGH: begin
spi_clk <= 1'b1;
spi_state <= SPI_CLK_LOW;
end
// ...其他状态转移
endcase
end
以"写指令+读数据"操作为例:
经验分享:SPI时钟相位(CPHA)和极性(CPOL)必须与Flash规格书一致。W25Qxx通常使用模式0(CPOL=0, CPHA=0)。
任何写入/擦除操作前必须发送写使能:
verilog复制task send_write_enable;
begin
spi_start(8'h06, 0, 0, 0); // 06h是写使能指令
wait(spi_done);
// 必须等待t_WEL时间(典型值50us)
delay_us(100); // 留足余量
end
endtask
页编程(最大256字节)的关键步骤:
verilog复制reg [7:0] page_buffer [0:255];
integer i;
for(i=0; i<256; i=i+1) begin
spi_write_byte(page_buffer[i]);
end
verilog复制module w25qxx_code_uart_top(
input wire clk_24m, // 24MHz主时钟
input wire rst_n, // 低电平复位
// SPI接口
output wire spi_sck,
output wire spi_cs_n,
output wire spi_mosi,
input wire spi_miso,
// UART接口
input wire uart_rxd,
output wire uart_txd,
// 状态指示
output wire [3:0] leds
);
verilog复制initial begin
wait(pll_locked);
reset_flash();
read_id();
if(id_correct)
uart_send("READY");
else
uart_send("ERROR");
end
默认12.5MHz时钟可提升至50MHz:
verilog复制// 修改PLL输出为200MHz
// SPI分频系数设为4
parameter SPI_DIV = 4; // 200MHz/4 = 50MHz
注意:高速SPI需要缩短走线长度,并做好阻抗匹配。
通过片选信号扩展支持多Flash:
verilog复制reg [3:0] spi_cs_n;
always @(*) begin
case(flash_select)
2'b00: spi_cs_n = 4'b1110;
2'b01: spi_cs_n = 4'b1101;
// ...其他片选
endcase
end
添加DMA引擎实现自动数据传输:
verilog复制dma_engine dma(
.clk(clk_100m),
.start(dma_start),
.src_addr(src_addr),
.dst_addr(dst_addr),
.len(len),
.done(dma_done)
);
在Cyclone IV EP4CE10平台上测试结果:
| 操作类型 | 数据量 | 理论时间 | 实测时间 |
|---|---|---|---|
| 扇区擦除(4KB) | 1 | 50ms | 52ms |
| 页编程(256B) | 1 | 0.8ms | 0.85ms |
| 连续读取 | 1KB | 0.82ms | 0.9ms |
| ID读取 | 2B | 20us | 22us |
主要修改点:
关键参数宏定义:
verilog复制`define FLASH_SIZE 128 // 单位Mb
`define UART_BAUD 115200
`define SPI_DIV 8 // 分频系数
`define FIFO_DEPTH 8192 // FIFO深度
这套代码在实际项目中已经验证过稳定性,我在多个工业控制项目中成功应用。最难调试的部分其实是SPI的时序同步,建议新手可以先用低速时钟(如1MHz)验证功能,再逐步提高频率。