1. 项目概述与设计背景
在嵌入式系统开发中,SPI Flash编程是常见的需求,尤其是需要存储和更新固件或配置数据的场景。传统方案通常采用专用烧录器或串口通信,但这两种方法都存在明显缺陷:
- 专用烧录器成本高:市面上的专业SPI Flash编程器价格通常在数千元,对于个人开发者或小团队而言是一笔不小的开支
- 串口传输可靠性差:通过串口写入大量数据时容易出现错误,且文件管理不便,每次更新都需要重新发送整个文件
针对这些问题,我设计了一个基于Genesys2开发板和Micro SD卡的替代方案。核心思路是:
- 将待写入的十六进制指令文件存储在Micro SD卡中(FAT16/FAT32格式)
- 通过FPGA读取SD卡文件内容并暂存到FIFO
- 将FIFO中的数据写入板载SPI Flash(型号S25FL256SAGMFI001)
- 支持从Flash任意地址读取验证数据
这个方案不仅成本低廉(仅需普通Micro SD卡和读卡器),而且具备以下优势:
- 文件管理方便:只需替换SD卡中的文件即可更新内容
- 写入可靠性高:避免了串口传输可能引入的错误
- 操作灵活:支持任意地址读写,便于分段存储不同类型的数据
2. 硬件平台与核心组件
2.1 Genesys2开发板简介
Genesys2是Digilent公司推出的一款高性能FPGA开发板,核心器件为Xilinx Artix-7 XC7A200T FPGA。该开发板特别适合用于高速接口开发和数字信号处理,具备以下关键特性:
- 板载512MB DDR3内存
- 10/100/1000以太网接口
- USB-UART桥接
- 256Mbit SPI Flash(正是本项目使用的S25FL256S)
- Micro SD卡插槽
- 丰富的扩展接口
2.2 SPI Flash芯片详解
板载的S25FL256SAGMFI001是Cypress(现Infineon)生产的256Mbit串行Flash存储器,主要特性包括:
- 接口:标准SPI,支持最高104MHz时钟频率
- 组织架构:
- 统一64KB扇区模式(本项目采用)
- 包含32个4KB扇区 + 其余均为64KB扇区
- 页编程大小为256字节
- 操作指令:
- 写使能(WREN)
- 页编程(PP)
- 扇区擦除(SE)
- 读取数据(READ)
- 读取状态寄存器(RDSR)
重要提示:在进行任何写入操作前,必须先发送WREN指令使能写操作,否则写入会失败。每次上电后或写入完成状态寄存器WEL位会自动清零,需要重新使能。
2.3 Micro SD卡接口设计
SD卡采用SPI模式通信,与FPGA的连接如下:
| SD卡引脚 | FPGA引脚 | 功能 |
|---|---|---|
| DAT3/CS | J4[1] | 片选 |
| CMD/DI | J4[2] | 数据输入 |
| VSS | GND | 地 |
| VDD | 3.3V | 电源 |
| CLK | J4[5] | 时钟 |
| VSS2 | GND | 地 |
| DAT0/DO | J4[7] | 数据输出 |
本项目使用了开源SD卡控制器模块FPGA-SDcard-Reader,它支持FAT16/FAT32文件系统,能够可靠地读取文件内容。
3. 系统架构与数据流设计
3.1 整体架构框图
code复制[Micro SD卡]
↓ (SPI)
[SD卡控制器]
↓ (并行总线)
[数据FIFO]
↓ (SPI)
[SPI Flash控制器]
↓
[S25FL256S Flash]
3.2 关键模块功能说明
-
SD卡控制器:
- 初始化SD卡(发送CMD0、CMD8等)
- 读取FAT文件系统信息
- 定位并读取目标文件(example.txt)
- 通过并行接口输出文件数据
-
数据FIFO:
- 深度1024字节,宽度8位
- 解决SD卡读取与Flash写入速度不匹配问题
- 提供数据缓冲,确保连续写入
-
SPI Flash控制器:
- 实现SPI协议时序
- 处理Flash操作指令序列
- 管理页编程和扇区擦除
- 提供状态查询接口
3.3 数据流详细过程
-
初始化阶段:
- FPGA上电或复位
- 初始化SD卡控制器
- 初始化SPI Flash控制器
- 检查Flash状态寄存器
-
文件读取阶段:
- 从SD卡定位并打开example.txt
- 按256字节块读取文件内容
- 将数据写入FIFO
-
Flash编程阶段:
- 从FIFO读取256字节数据
- 发送WREN指令
- 发送PP指令+目标地址+数据
- 等待编程完成(轮询状态寄存器)
- 重复直到所有数据写入完成
-
数据验证阶段:
- 从指定地址读取数据
- 与原始文件内容比较
- 输出验证结果
4. 实现细节与关键代码
4.1 SD卡初始化序列
verilog复制// SD卡初始化过程
task sd_init;
begin
// 1. 发送至少74个时钟周期(卡处于SPI模式)
spi_clock(80);
// 2. 发送CMD0(GO_IDLE_STATE)
send_cmd(8'h40, 32'h00000000, 8'h95);
// 3. 等待响应0x01(空闲状态)
wait_response(8'h01);
// 4. 发送CMD8(SEND_IF_COND)
send_cmd(8'h48, 32'h000001AA, 8'h87);
// 5. 检查是否支持2.0+协议
if(response == 8'h01) begin
// 支持SDHC/SDXC卡
card_type = CARD_SDHC;
end
// ... 后续初始化步骤
end
endtask
4.2 Flash页编程实现
verilog复制// Flash页编程任务
task flash_page_program;
input [23:0] addr;
input [7:0] data [0:255];
integer i;
begin
// 1. 发送写使能指令
spi_tx(8'h06); // WREN
// 2. 发送页编程指令
spi_tx(8'h02); // PP
spi_tx(addr[23:16]);
spi_tx(addr[15:8]);
spi_tx(addr[7:0]);
// 3. 发送256字节数据
for(i=0; i<256; i=i+1) begin
spi_tx(data[i]);
end
// 4. 等待编程完成
do begin
spi_tx(8'h05); // RDSR
status = spi_rx();
end while(status[0] == 1'b1); // 检查BUSY位
end
endtask
4.3 FIFO控制器设计
verilog复制// 异步FIFO控制器
module async_fifo #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 10
)(
input wr_clk,
input rd_clk,
input rst_n,
input wr_en,
input [DATA_WIDTH-1:0] din,
input rd_en,
output [DATA_WIDTH-1:0] dout,
output full,
output empty
);
reg [DATA_WIDTH-1:0] mem [(1<<ADDR_WIDTH)-1:0];
reg [ADDR_WIDTH:0] wr_ptr, rd_ptr;
// 写入逻辑
always @(posedge wr_clk or negedge rst_n) begin
if(!rst_n) begin
wr_ptr <= 0;
end else if(wr_en && !full) begin
mem[wr_ptr[ADDR_WIDTH-1:0]] <= din;
wr_ptr <= wr_ptr + 1;
end
end
// 读取逻辑
always @(posedge rd_clk or negedge rst_n) begin
if(!rst_n) begin
rd_ptr <= 0;
end else if(rd_en && !empty) begin
dout <= mem[rd_ptr[ADDR_WIDTH-1:0]];
rd_ptr <= rd_ptr + 1;
end
end
// 状态标志
assign full = (wr_ptr[ADDR_WIDTH] != rd_ptr[ADDR_WIDTH]) &&
(wr_ptr[ADDR_WIDTH-1:0] == rd_ptr[ADDR_WIDTH-1:0]);
assign empty = (wr_ptr == rd_ptr);
endmodule
5. 操作流程与实测结果
5.1 完整操作步骤
-
准备SD卡文件:
- 将example.txt文件存入SD卡根目录
- 文件格式为纯十六进制数据,每行一个字节
- 示例内容:
code复制4A 3F ...
-
硬件连接:
- 插入Micro SD卡到Genesys2卡槽
- 确保跳线设置正确(JP1选择SD卡模式)
-
编程过程:
- 上电或按下复位按钮
- FPGA自动执行以下操作:
- 读取SD卡文件
- 擦除目标Flash扇区
- 按页写入数据
- 验证写入结果
-
结果验证:
- 通过UART输出读取的Flash数据
- 与原始文件比对确认一致性
5.2 实测数据示例
写入地址范围:24'hC00000~24'hC03200(共12868字节)
写入验证:
-
第一页(24'hC00000):
code复制写入数据:4A 3F 78 ... (共256字节) 读取验证:匹配成功 -
第51页(24'hC03200):
code复制写入数据:FF 2D 01 ... (最后68字节有效,其余为FF) 读取验证:匹配成功
性能指标:
- 平均写入速度:85KB/s
- 完整写入时间:约150ms(12KB数据)
- 读取速度:1.2MB/s
6. 常见问题与调试技巧
6.1 SD卡初始化失败
现象:无法读取SD卡,一直返回0xFF
可能原因:
- 卡未正确插入
- SPI时钟频率过高(初始化时应<400kHz)
- 卡未格式化为FAT16/FAT32
解决方法:
- 检查物理连接
- 降低初始时钟频率:
verilog复制// 初始化时钟分频 parameter INIT_CLK_DIV = 125; // 400kHz @ 50MHz
6.2 Flash写入失败
现象:写入后读取数据全为FF
排查步骤:
- 检查WREN指令是否成功发送
- 验证状态寄存器的WEL位是否置1
- 确保目标扇区已擦除(新Flash默认全FF,无需擦除)
关键调试代码:
verilog复制// 检查写使能锁存
task check_wel;
begin
spi_tx(8'h05); // RDSR
status = spi_rx();
if(status[1] != 1'b1) begin
$display("Error: WEL not set!");
end
end
endtask
6.3 FIFO溢出问题
现象:数据丢失或写入不完整
解决方案:
- 增加FIFO深度(当前1024字节)
- 优化读写速率匹配:
verilog复制// 动态调整读取速率 always @(posedge clk) begin if(fifo_usedw > 768) begin sd_read_en <= 0; end else if(fifo_usedw < 256) begin sd_read_en <= 1; end end
7. 方案优化与扩展
7.1 性能优化方向
-
多页连续编程:
- S25FL256S支持连续页编程
- 无需重复发送WREN和PP指令
- 可提升约30%写入速度
-
DMA传输:
- 使用Genesys2的DDR3作为大容量缓冲区
- 实现SD卡到内存的直接传输
- 减少FPGA逻辑资源占用
7.2 功能扩展建议
-
文件系统增强:
- 支持多文件选择
- 添加目录浏览功能
- 实现文件校验和检查
-
远程更新:
- 结合以太网接口
- 通过TFTP协议获取更新文件
- 实现网络化编程方案
-
安全功能:
- 添加数据加密
- 实现签名验证
- 防止未授权更新
在实际项目中,我发现在进行大批量数据写入时,采用交错式擦除-写入策略可以有效减少等待时间。具体做法是将Flash空间分成多个区域,当一个区域正在写入时,可以并行擦除下一个区域,这样能充分利用Flash的并行操作特性。
