用FPGA玩转SPI Flash:手把手教你实现M25P16的UART读写控制器(附完整Verilog代码)
2026/5/5 13:56:31 网站建设 项目流程

FPGA实战:构建SPI Flash控制器与UART交互系统

在嵌入式系统开发中,非易失性存储解决方案扮演着关键角色。SPI Flash以其紧凑的封装、低功耗特性和简单的接口,成为众多FPGA项目的理想选择。本文将深入探讨如何利用FPGA实现一个完整的SPI Flash控制器,并通过UART接口构建人机交互通道,为开发者提供一套可复用的存储解决方案。

1. SPI Flash基础与M25P16特性解析

SPI(Serial Peripheral Interface)是一种同步串行通信协议,广泛应用于嵌入式系统中的外设连接。M25P16是意法半导体推出的16Mbit串行Flash存储器,采用标准的SPI接口,工作电压范围为2.7V至3.6V,支持高达50MHz的时钟频率。

M25P16关键特性:

  • 16Mbit存储容量,组织为32个扇区,每扇区256页,每页256字节
  • 支持标准SPI模式(0,3)和双线SPI模式
  • 页编程时间典型值0.7ms,扇区擦除时间典型值0.6s
  • 数据保存期限长达20年
  • 每个扇区可承受至少100,000次擦写循环

提示:在实际工程中,建议避免频繁擦写同一扇区,可通过磨损均衡算法延长Flash寿命。

SPI Flash的访问遵循严格的命令序列,主要操作包括:

// 常用SPI Flash指令定义 localparam WRITE_ENABLE = 8'h06; // 写使能 localparam PAGE_PROGRAM = 8'h02; // 页编程 localparam READ_DATA = 8'h03; // 读取数据 localparam SECTOR_ERASE = 8'hD8; // 扇区擦除 localparam BULK_ERASE = 8'hC7; // 整片擦除

2. 系统架构设计与模块划分

完整的SPI Flash控制器系统包含多个功能模块,各模块协同工作实现数据的可靠存储与检索。系统采用模块化设计,便于功能扩展和维护。

2.1 主要功能模块

  1. 顶层控制模块(flash_wr_rd_top)

    • 系统时钟和复位管理
    • 模块实例化与信号路由
    • 全局参数配置
  2. Flash写操作模块(flash_wr)

    • 写使能控制
    • 页编程时序生成
    • 地址管理
    • 数据缓冲
  3. Flash读操作模块(flash_rd)

    • 读指令时序生成
    • 数据捕获与同步
    • FIFO缓冲管理
    • 状态机控制
  4. UART通信模块(uart_rx/uart_tx)

    • 串行数据收发
    • 波特率生成
    • 数据帧处理
  5. 按键处理模块(key_filter)

    • 机械消抖
    • 边沿检测
    • 命令触发

2.2 关键接口信号

信号名称方向描述
sys_clk输入系统时钟(50MHz)
rst_n输入低电平有效复位信号
key_be输入全擦除按键输入
key_rd输入读操作按键输入
miso输入SPI从设备输出主设备输入
cs_n输出SPI片选信号(低电平有效)
sck输出SPI时钟信号
mosi输出SPI主设备输出从设备输入
uart_rx输入UART接收数据线
uart_tx输出UART发送数据线

3. 状态机设计与时序控制

SPI Flash操作的核心在于精确的时序控制。本系统采用有限状态机(FSM)实现各操作流程,确保信号时序符合器件规格要求。

3.1 写操作状态机

写操作包含写使能、地址发送和数据编程三个阶段,状态转移如下:

// 写操作状态定义 localparam IDLE = 3'd0; // 空闲状态 localparam WR_EN = 3'd1; // 写使能指令发送 localparam DELAY = 3'd2; // 时序等待 localparam PP = 3'd3; // 页编程指令发送 localparam ADDR = 3'd4; // 地址发送 localparam DATA = 3'd5; // 数据发送 // 状态转移逻辑 always @(*) begin case(curr_state) IDLE: if(wr_start) next_state = WR_EN; else next_state = IDLE; WR_EN: if(cmd_done) next_state = DELAY; else next_state = WR_EN; DELAY: if(delay_done) next_state = PP; else next_state = DELAY; PP: if(cmd_done) next_state = ADDR; else next_state = PP; ADDR: if(addr_done) next_state = DATA; else next_state = ADDR; DATA: if(data_done) next_state = IDLE; else next_state = DATA; default: next_state = IDLE; endcase end

3.2 读操作状态机

读操作相对简单,但仍需严格遵循器件时序要求:

// 读操作状态定义 localparam RD_IDLE = 2'b00; // 空闲状态 localparam RD_CMD = 2'b01; // 读指令发送 localparam RD_ADDR = 2'b10; // 地址发送 localparam RD_DATA = 2'b11; // 数据接收 // 状态转移逻辑 always @(*) begin case(rd_state) RD_IDLE: if(rd_start) next_rd_state = RD_CMD; else next_rd_state = RD_IDLE; RD_CMD: if(cmd_done) next_rd_state = RD_ADDR; else next_rd_state = RD_CMD; RD_ADDR: if(addr_done) next_rd_state = RD_DATA; else next_rd_state = RD_ADDR; RD_DATA: if(data_done) next_rd_state = RD_IDLE; else next_rd_state = RD_DATA; default: next_rd_state = RD_IDLE; endcase end

注意:状态机设计中必须考虑各状态间的时序要求,特别是命令、地址和数据阶段之间的延迟时间。

4. 关键代码实现与优化

4.1 SPI接口时序生成

SPI时钟(sck)由系统时钟分频得到,通过精确控制时钟边沿实现数据同步:

// SPI时钟生成 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin sck <= 1'b0; sck_cnt <= 2'd0; end else if(state != IDLE) begin sck_cnt <= sck_cnt + 1'b1; if(sck_cnt == 2'd1) sck <= 1'b1; else if(sck_cnt == 2'd3) sck <= 1'b0; end else begin sck <= 1'b0; sck_cnt <= 2'd0; end end // MOSI数据输出 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin mosi <= 1'b0; bit_cnt <= 3'd0; end else if(sck_cnt == 2'd0) begin case(state) WR_EN: mosi <= WR_EN_INST[7 - bit_cnt]; PP: mosi <= PP_INST[7 - bit_cnt]; ADDR: mosi <= addr[23 - bit_cnt]; DATA: mosi <= wr_data[7 - bit_cnt]; default:mosi <= 1'b0; endcase if(state != IDLE) bit_cnt <= bit_cnt + 1'b1; end end

4.2 数据缓冲与FIFO管理

为协调SPI Flash的高速读取和UART的低速发送,系统采用FIFO作为数据缓冲:

// FIFO写控制 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin wr_req <= 1'b0; wr_data <= 8'd0; end else if(miso_valid) begin wr_req <= 1'b1; wr_data <= miso_shift; end else begin wr_req <= 1'b0; end end // FIFO读控制 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rd_req <= 1'b0; wait_cnt <= 16'd0; end else if(fifo_rd_en) begin if(wait_cnt == WAIT_MAX) begin wait_cnt <= 16'd0; rd_req <= 1'b1; end else begin wait_cnt <= wait_cnt + 1'b1; rd_req <= 1'b0; end end else begin rd_req <= 1'b0; end end

4.3 UART接口实现

UART模块实现与PC机的串行通信,采用标准的8N1格式:

// UART接收状态机 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rx_state <= IDLE; rx_data <= 8'd0; bit_cnt <= 4'd0; end else begin case(rx_state) IDLE: if(!uart_rx) begin // 检测起始位 rx_state <= START; baud_cnt <= BAUD_CNT_MAX/2; end START: if(baud_cnt == 0) begin rx_state <= DATA; bit_cnt <= 4'd0; baud_cnt <= BAUD_CNT_MAX; end else baud_cnt <= baud_cnt - 1; DATA: if(baud_cnt == 0) begin rx_data[bit_cnt] <= uart_rx; if(bit_cnt == 3'd7) rx_state <= STOP; else bit_cnt <= bit_cnt + 1; baud_cnt <= BAUD_CNT_MAX; end else baud_cnt <= baud_cnt - 1; STOP: if(baud_cnt == 0) begin rx_state <= IDLE; rx_done <= 1'b1; end else baud_cnt <= baud_cnt - 1; default: rx_state <= IDLE; endcase end end

5. 系统调试与性能优化

5.1 功能验证方法

  1. 仿真验证

    • 使用ModelSim等工具进行RTL级仿真
    • 验证状态机跳转和时序控制
    • 检查数据通路完整性
  2. 板级调试

    • 使用逻辑分析仪捕获SPI信号
    • 通过SignalTap II进行实时调试
    • 串口调试助手验证数据完整性

5.2 常见问题与解决方案

  • SPI通信失败

    • 检查时钟极性(CPOL)和相位(CPHA)设置
    • 确认片选信号时序符合要求
    • 验证信号电平匹配(3.3V vs 5V)
  • 数据写入后读取错误

    • 确保写操作后留有足够的编程时间(tPP)
    • 检查地址是否对齐到页边界
    • 验证写使能(WEL)标志是否置位
  • UART通信不稳定

    • 核对波特率设置(发送端和接收端一致)
    • 检查硬件连接(交叉连接TX和RX)
    • 增加适当的流控机制

5.3 性能优化技巧

  1. 提高吞吐量

    • 采用双缓冲机制重叠操作
    • 实现多页连续编程
    • 使用DMA加速数据传输
  2. 降低功耗

    • 动态调整SPI时钟频率
    • 实现深度睡眠模式
    • 优化状态机减少活跃时间
  3. 增强可靠性

    • 添加CRC校验机制
    • 实现坏块管理
    • 增加重试机制应对偶发错误
// 双缓冲实现示例 reg [7:0] buffer0[0:255]; reg [7:0] buffer1[0:255]; reg buffer_sel; // 缓冲切换逻辑 always @(posedge clk) begin if(wr_done && !buffer_sel) begin buffer_sel <= 1'b1; start_program_buffer1 <= 1'b1; end else if(wr_done && buffer_sel) begin buffer_sel <= 1'b0; start_program_buffer0 <= 1'b1; end end

在完成这个项目的过程中,最耗时的部分不是代码编写而是时序调试。特别是在页编程操作后立即尝试读取时,经常会得到旧数据。经过多次试验发现,即使在状态机中加入了规格书要求的tPP等待时间,某些批次的M25P16仍需要额外延迟。最终通过在写操作状态机中增加可配置的延迟参数解决了这一问题,这也提醒我们在实际项目中,器件规格书提供的时间参数可能需要留有一定余量。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询