嗨,各位FPGA探索者!今天我们来聊聊项目开发中几乎绕不开的两个“老朋友”——I2C和SPI。你是不是也经常需要让FPGA跟各种传感器、存储芯片或者显示屏“对话”?没错,I2C和SPI就是实现这种板级“悄悄话”最经典、最常用的两种串行通信协议。掌握它们,就像拿到了打开外部世界大门的钥匙,是从爱好者迈向专业工程师的必经之路。
这篇文章,我们就一起把这两个协议掰开揉碎,从核心原理到Verilog代码实现,再到仿真验证,给你讲得明明白白。准备好了吗?我们开始吧!
先看本质:I2C和SPI,到底有啥不一样?
在动手写代码之前,咱们得先搞清楚这俩兄弟各自的脾气。简单来说,I2C像是个“节约派”,用最少的线办最多的事;而SPI则是个“速度派”,追求极致的通信效率。下面这个表格帮你快速抓住重点:
| 特性 | I2C | SPI |
|---|---|---|
| 线路数量 | 2根线搞定(SCL时钟线,SDA数据线) | 通常4根线(SCLK, MOSI, MISO, SS片选),也可以精简到3根 |
| 聊天方式 | 半双工,一根数据线来回用(像对讲机) | 全双工,可以同时发和收(像打电话) |
| 连接方式 | “总线式”,一根线上可以挂很多设备,靠地址找人 | “点对点式”,一个主人(Master)带多个小弟(Slave),靠片选信号点名 |
| 速度 | 标准模式100kbps,快速模式400kbps等 | 通常快得多,轻松跑到几十Mbps |
| 协议复杂度 | 稍高,有起始、停止、应答等“礼节” | 相对简单,主要关心时钟极性和相位 |
| FPGA实现关键 | 状态机设计、处理双向IO(三态门)是核心 | 时钟域处理、管理多个从设备的片选信号是关键 |
动手实战:I2C Master的Verilog实现要点
在FPGA里,我们通常扮演“主人”(Master)的角色,去控制外部的“客人”(Slave设备)。实现I2C Master的核心,就是设计一个靠谱的有限状态机(FSM)。
状态机怎么设计?
想象一下一次完整的I2C通信流程:空闲 -> 发出开始信号 -> 发送设备地址 -> 等待对方应答 -> 发送或接收数据 -> 发出停止信号。我们的状态机就要严格遵循这个剧本,典型状态包括:IDLE, START, SEND_ADDR, CHECK_ACK, SEND_DATA, RECV_DATA, STOP 等。
难点攻克:双向SDA线怎么处理?
这是I2C Verilog实现中最容易卡壳的地方。SDA这根线很“忙”,它有时候要输出数据,有时候又要读取从机发来的数据。怎么办呢?标准做法是使用“三态门”控制:
- 定义两个寄存器:
sda_out(要输出的数据)和sda_oe(输出使能信号)。 - 在顶层模块,用一行条件赋值实现三态门:
assign SDA = (sda_oe == 1'b1) ? sda_out : 1'bz;// oe为1时输出数据,为0时呈高阻态(相当于断开,让从机控制) - 再定义一个信号来读取SDA线上的实际电平:
assign sda_in = SDA;
时钟SCL的生成
SCL时钟完全由主机产生。通常是从系统主时钟分频得到。记住黄金法则:在SCL为高电平期间,SDA上的数据必须保持稳定;数据的变化只能发生在SCL为低电平的时候。这样才能满足建立和保持时间的要求。
来看一段状态机的核心代码片段(感受一下节奏):
always @(posedge clk) begin
case(state)
START: begin
if (clk_cnt == DIVIDER) begin
sda_oe <= 1'b1; // 使能输出
sda_out <= 1'b0; // 拉低SDA,产生起始条件
// ... 状态转移和其他逻辑
end
end
// ... 其他状态
endcase
end看到这里,你对I2C的实现是不是有了更具体的画面?别急,关于SPI的实现精要、仿真验证的“组合拳”怎么打,我们下回接着聊。记住,理解协议是骨架,写出健壮的代码是血肉,而充分的验证则是给你的设计穿上盔甲。一步步来,你一定能搞定!





