FPGA实现I²C EEPROM读写驱动的三段式状态机设计
1. EEPROM读写驱动的FPGA实现原理与工程实践
在嵌入式系统中,I²C(Inter-Integrated Circuit)总线因其硬件资源占用少、布线简洁、支持多主多从结构等优势,被广泛应用于EEPROM、温度传感器、实时时钟等低速外设的通信控制。然而,在FPGA平台上实现可靠的I²C主机驱动,并非简单地将时序波形“画”出来即可。它涉及状态机建模、时序精度控制、信号电平切换、应答检测、错误恢复等多个关键工程环节。本节以紫光同创PGL22G FPGA平台为载体,结合AT24C64 EEPROM器件特性,深入剖析一个工业级三段式状态机I²C驱动模块的设计逻辑、参数推导与调试经验。所有分析均基于实际可综合、可仿真的RTL代码,不依赖任何软核或IP核,强调对底层数字电路行为的精确掌控。
1.1 I²C协议核心时序约束与FPGA实现挑战
I²C总线由两条开漏(Open-Drain)信号线构成:SCL(Serial Clock Line)和SDA(Serial Data Line)。二者均需通过上拉电阻连接至VDD,因此默认状态下均为高电平。这种电气特性决定了其驱动方式的根本差异:FPGA输出端口不能直接驱动高电平,而必须通过“输出低电平”或“高阻态(Z)”来控制总线状态。当端口配置为高阻态时,上拉电阻将总线拉高;当端口输出低电平时,总线被强制拉低。这一机制是理解整个驱动设计的起点。
I²C标准模式(Standard Mode)要求SCL时钟频率为100 kHz,快速模式(Fast Mode)为400 kHz。以100 kHz为例,其周期为10 μs,高电平时间(t HIGH )最小为4.0 μs,低电平时间(t LOW )最小为4.7 μs;起始条件(START)定义为SCL为高时SDA由高变低;停止条件(STOP)则为SCL为高时SDA由低变高;数据位在SCL低电平时更新,在SCL高电平时采样。这些看似简单的规则,在FPGA上实现时却面临三大挑战:
- 时钟域同步问题 :外部输入的
r1c_es1c(即i2c_start)信号通常来自异步按键或其它模块,若未经两级寄存器同步便直接用于状态跳转,极易引发亚稳态,导致状态机误判甚至死锁。 - 双向信号控制冲突 :SDA线在不同阶段承担不同角色——发送数据时为主机输出,接收应答/数据时则需切换为输入。若未严格管理
sdio_dir(方向控制)与sdio_out(输出值)的配合,极易出现“输出与输入争抢总线”的竞争冒险,造成总线电平异常。 - 应答(ACK)检测的时序窗口 :从设备在接收到8位数据后,必须在第9个SCL周期的高电平期间将SDA拉低作为应答。主机必须在此精确的时间窗口内完成SDA方向切换、采样并判断电平,任何延迟都将导致ACK丢失或误判。
上述挑战无法通过“经验性编码”解决,必须建立在对协议物理层与数字电路行为的双重理解之上。下文所述的状态机设计,正是为系统性应对这些挑战而构建。
1.2 三段式状态机架构:分离时序、组合与输出逻辑
本设计采用经典的三段式有限状态机(FSM)架构,其核心思想是将状态机的三个核心功能在代码层面进行物理隔离,从而提升可读性、可维护性与综合质量。这三段分别是:
- 第一段:时序逻辑(State Register)
使用同步复位(rst_n)的寄存器,仅负责在每个时钟上升沿将next_state的值锁存到current_state。这是整个状态机的“记忆单元”,其输出current_state是后续所有逻辑的唯一输入源。该段代码简洁且无歧义:verilog always @(posedge clk_i or negedge rst_n) begin if (!rst_n) current_state <= IDLE; else current_state <= next_state; end
其工程意义在于:确保状态转换的绝对可靠性。所有状态跳转都发生在确定的时钟边沿,避免了组合逻辑环路可能引入的毛刺与亚稳态传播。 - 第二段:组合逻辑(Next State Logic)
根据current_state与当前所有相关输入信号(如i2c_start、ack_sda_in、cnt等),通过纯组合逻辑(always @(*))