跳到主要内容FPGA 实现 IIC 通信原理与 Verilog 代码详解 | 极客日志编程语言算法
FPGA 实现 IIC 通信原理与 Verilog 代码详解
本文介绍 FPGA 中 IIC 通信的实现方法,涵盖总线特性、时序定义及通信流程。详细拆解时序发生器、状态机控制器、数据收发模块等核心设计,提供基于 50MHz 时钟的 Verilog 代码示例。包含硬件验证环境搭建、引脚约束配置及 AT24C02 读写验证流程,适用于高速高可靠性通信场景。
IIC(Inter-Integrated Circuit,集成电路间总线)是一种两线制同步串行通信协议,由 SDA(串行数据线)和 SCL(串行时钟线)两根线组成,广泛应用于 FPGA 与传感器、EEPROM、LCD 等外设的通信。FPGA 实现 IIC 功能的核心是通过硬件逻辑模拟 IIC 的时序(起始 / 停止条件、数据位传输、应答机制),无需依赖软件驱动,具有实时性强、可靠性高的特点。以下从四方面详细说明:
原理、核心模块设计、Verilog 代码实现、软件验证
一、IIC 核心原理与时序
1. IIC 总线特性
- 两线制:SDA(双向数据)和 SCL(单向时钟,由主机生成);
- 同步通信:数据传输与 SCL 时钟同步,时钟频率决定通信速率(标准模式 100kHz、快速模式 400kHz、高速模式 3.4MHz);
- 多主从架构:支持多个主机和多个从机,通过从机地址区分设备;
- 应答机制:每个字节传输后,接收方需发送 ACK(低电平)或 NACK(高电平)确认。
2. 关键时序定义
| 时序阶段 | 描述 |
|---|
| 起始条件(START) | SCL 为高电平时,SDA 从高电平拉低(下降沿触发); |
| 停止条件(STOP) | SCL 为高电平时,SDA 从低电平拉高(上升沿触发); |
| 数据位传输 | SCL 为高电平时,SDA 上的数据有效(主机发送 / 从机接收),低电平时可切换数据; |
| 应答位(ACK/NACK) | 数据位传输完毕后,SCL 高电平时,接收方拉低 SDA(ACK)或保持高电平(NACK); |
3. 通信流程(主机写从机为例)
- 主机发送START条件;
- 主机发送从机地址 + 写位(7 位地址 + 1 位写 = 0);
- 从机回复ACK;
- 主机发送数据字节(8 位);
- 从机回复ACK;
- (可选)重复步骤 4-5 发送多个数据字节;
- 主机发送STOP条件,结束通信。
二、FPGA 实现 IIC 的核心模块
FPGA 实现 IIC 需拆解为 4 个核心子模块,通过顶层模块整合:
- 时序发生器:生成 SCL 时钟和时序触发信号(如 SCL 上升沿、下降沿标志);
- 状态机控制器:控制 IIC 通信流程(START→地址→数据→ACK→STOP);
- 数据收发模块:处理 SDA 线上的数据发送和接收;
- 顶层模块:连接各子模块,提供外部接口(SDA、SCL、系统时钟、复位、读写控制等)。
三、Verilog 代码实现(以 50MHz 时钟、100kHz IIC 速率为例)
1. 时序发生器模块(IIC Timing Generator)
功能:根据系统时钟生成 SCL 时钟(100kHz)和时序触发信号(上升沿、下降沿标志),为状态机提供同步基准。
- 系统时钟频率:
CLK_FREQ = 50MHz = 50_000_000 Hz;
- IIC 标准速率:
IIC_RATE = 100kHz = 100_000 Hz;
- SCL 周期:
T_SCL = 1 / IIC_RATE = 10us;
- 系统时钟周期:
T_CLK = 1 / CLK_FREQ = 20ns;
- 每个 SCL 周期的系统时钟数:
CLK_PER_SCL = CLK_FREQ / IIC_RATE = 500;
- SCL 高低电平各占一半:
CLK_HIGH = CLK_LOW = CLK_PER_SCL / 2 = 250。
// IIC 时序发生器模块
module iic_timing_gen(
input clk, // 系统时钟(50MHz)
input rst_n, // 复位信号(低电平有效)
input iic_en, // IIC 使能信号(高电平有效)
output reg scl, // IIC 时钟 SCL
output reg scl_posedge, // SCL 上升沿标志(1 个时钟周期高)
output reg scl_negedge // SCL 下降沿标志(1 个时钟周期高)
);
// 时序计数器(0~499,对应 SCL 周期 10us)
reg [8:0] clk_cnt = 9'd0; // 500 的计数范围需 9 位(2^9=512)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
clk_cnt <= 9'd0;
scl <= 1'b1; // IIC 空闲时 SCL 为高电平
scl_posedge <= 1'b0;
scl_negedge <= 1'b0;
end
else if (iic_en) begin
// 仅当 IIC 使能时生成时序
clk_cnt <= clk_cnt + 9'd1;
// 生成 SCL 下降沿(计数到 249 时,SCL 从高变低)
if (clk_cnt == 9'd249) begin
scl <= 1'b0;
scl_negedge <= 1'b1;
scl_posedge <= 1'b0;
end
// 生成 SCL 上升沿(计数到 499 时,SCL 从低变高,计数器清零)
else if (clk_cnt == 9'd499) begin
clk_cnt <= 9'd0;
scl <= 1'b1;
scl_posedge <= 1'b1;
scl_negedge <= 1'b0;
end
// 其他时刻,时序标志清零
else begin
scl_posedge <= 1'b0;
scl_negedge <= 1'b0;
end
end
else begin
// IIC 未使能时,SCL 保持高电平,计数器清零
clk_cnt <= 9'd0;
scl <= 1'b1;
scl_posedge <= 1'b0;
scl_negedge <= 1'b0;
end
end
endmodule
2. 状态机控制器模块(IIC State Machine)
功能:作为 IIC 通信的核心,控制整个通信流程的状态切换,输出数据发送 / 接收控制信号。
IDLE:空闲状态(SDA、SCL 均为高);
START:发送起始条件;
ADDR:发送从机地址 + 读写位;
ACK1:等待从机对地址的应答;
TX_DATA:发送数据字节;
ACK2:等待从机对数据的应答;
STOP:发送停止条件。
// IIC 状态机控制器模块
module iic_state_machine(
input clk, // 系统时钟(50MHz)
input rst_n, // 复位信号(低电平有效)
input iic_en, // IIC 使能信号
input scl_posedge, // SCL 上升沿标志
input scl_negedge, // SCL 下降沿标志
input ack, // 从机应答信号(低电平为 ACK)
input wr_en, // 写使能(1=写,0=读)
input [6:0] slave_addr, // 从机地址(7 位)
input [7:0] tx_data, // 待发送数据
output reg sda_dir, // SDA 方向控制(1=输出,0=输入)
output reg sda_out, // SDA 输出数据
output reg tx_done, // 发送完成标志
output reg rx_done, // 接收完成标志
output reg [7:0] rx_data // 接收数据
);
// 状态定义
localparam IDLE = 4'd0;
localparam START = 4'd1;
localparam ADDR = 4'd2;
localparam ACK1 = 4'd3;
localparam TX_DATA = 4'd4;
localparam ACK2 = 4'd5;
localparam RX_DATA = 4'd6;
localparam NACK = 4'd7;
localparam STOP = 4'd8;
// 状态寄存器
reg [3:0] state = IDLE;
// 位计数器(0~7,用于计数地址/数据位)
reg [2:0] bit_cnt = 3'd0;
// 地址 + 读写位寄存器(7 位地址 +1 位读写)
reg [7:0] addr_reg = 8'd0;
// 数据寄存器(存储发送/接收的数据)
reg [7:0] data_reg = 8'd0;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
bit_cnt <= 3'd0;
addr_reg <= 8'd0;
data_reg <= 8'd0;
sda_dir <= 1'b1; // 默认 SDA 为输出
sda_out <= 1'b1; // 空闲时 SDA 为高
tx_done <= 1'b0;
rx_done <= 1'b0;
rx_data <= 8'd0;
end
else if (iic_en) begin
case (state)
// 空闲状态:等待使能信号,初始化参数
IDLE: begin
tx_done <= 1'b0;
rx_done <= 1'b0;
bit_cnt <= 3'd0;
addr_reg <= {slave_addr, wr_en}; // 地址 + 读写位(wr_en=0 写,1 读)
state <= START; // 进入起始条件状态
end
// 起始条件:SCL 高电平时,SDA 拉低
START: begin
if (scl_posedge) begin
sda_out <= 1'b0;
state <= ADDR; // 进入地址发送状态
end
end
// 发送地址 + 读写位:8 位,SCL 低电平时切换数据,高电平时有效
ADDR: begin
if (scl_negedge) begin
if (bit_cnt == 3'd7) begin // 8 位地址发送完毕
bit_cnt <= 3'd0;
state <= ACK1; // 进入等待 ACK 状态
end
else begin
bit_cnt <= bit_cnt + 3'd1;
sda_out <= addr_reg[7 - bit_cnt - 3'd1]; // 从高位到低位发送
end
end
end
// 等待地址应答:SCL 高电平时检测 SDA(从机拉低为 ACK)
ACK1: begin
if (scl_posedge) begin
if (!ack) begin // 收到 ACK,继续通信
if (wr_en == 1'b0) begin // 写操作,进入数据发送状态
state <= TX_DATA;
data_reg <= tx_data; // 锁存待发送数据
end
else begin // 读操作,进入数据接收状态(需先释放 SDA)
sda_dir <= 1'b0; // SDA 切换为输入
state <= RX_DATA;
end
end
else begin // 未收到 ACK,结束通信
state <= STOP;
end
end
end
// 发送数据:8 位,流程同地址发送
TX_DATA: begin
if (scl_negedge) begin
if (bit_cnt == 3'd7) begin // 8 位数据发送完毕
bit_cnt <= 3'd0;
state <= ACK2; // 进入等待数据 ACK 状态
end
else begin
bit_cnt <= bit_cnt + 3'd1;
sda_out <= data_reg[7 - bit_cnt - 3'd1]; // 从高位到低位发送
end
end
end
// 等待数据应答:同 ACK1
ACK2: begin
if (scl_posedge) begin
if (!ack) begin // 收到 ACK,可继续发送下一个数据(此处简化为单次发送)
state <= STOP; // 进入停止条件状态
end
else begin // 未收到 ACK,结束通信
state <= STOP;
end
end
end
// 接收数据:8 位,SCL 高电平时采样 SDA
RX_DATA: begin
if (scl_posedge) begin
// SCL 高电平时采样数据
rx_data[7 - bit_cnt] <= ack; // 从高位到低位接收(ack 为 SDA 输入信号)
if (bit_cnt == 3'd7) begin // 8 位数据接收完毕
bit_cnt <= 3'd0;
sda_dir <= 1'b1; // SDA 切换为输出,准备发送 NACK
state <= NACK; // 进入发送 NACK 状态
end
else begin
bit_cnt <= bit_cnt + 3'd1;
end
end
end
// 发送 NACK:接收完毕后,主机发送 NACK(SDA 保持高)
NACK: begin
if (scl_negedge) begin
sda_out <= 1'b1; // NACK
state <= STOP; // 进入停止条件状态
end
end
// 停止条件:SCL 高电平时,SDA 拉高
STOP: begin
if (scl_posedge) begin
sda_out <= 1'b1;
state <= IDLE; // 回到空闲状态
tx_done <= (wr_en == 1'b0) ? 1'b1 : 1'b0; // 写完成标志
rx_done <= (wr_en == 1'b1) ? 1'b1 : 1'b0; // 读完成标志
end
end
default: state <= IDLE;
endcase
end
else begin
// IIC 未使能时,回到空闲状态
state <= IDLE;
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
end
endmodule
3. 数据收发模块(IIC SDA Control)
功能:处理 SDA 线的双向通信,根据状态机的sda_dir信号切换 SDA 为输入或输出,同时检测从机的 ACK 信号。
// IIC SDA 控制模块
module iic_sda_control(
input clk, // 系统时钟(50MHz)
input rst_n, // 复位信号(低电平有效)
input sda_dir, // SDA 方向控制(1=输出,0=输入)
input sda_out, // SDA 输出数据
output reg sda_in, // SDA 输入数据
output reg ack // 从机应答信号(低电平为 ACK)
);
// SDA 双向 IO 缓冲器(模拟 FPGA 的双向 IO 逻辑)
reg sda_io = 1'b1; // SDA 线电平
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sda_io <= 1'b1;
sda_in <= 1'b1;
ack <= 1'b1;
end
else begin
// 输出模式:SDA 线由 sda_out 控制
if (sda_dir == 1'b1) begin
sda_io <= sda_out;
end
else begin
sda_io <= 1'b1; // 输入模式,释放 SDA(由从机控制)
end
// 输入模式:采样 SDA 线电平
sda_in <= sda_io;
// 检测 ACK 信号(SDA 低电平为 ACK)
ack <= sda_io;
end
end
// 输出 SDA 线电平(顶层模块需将此信号映射到 FPGA 的双向 IO 引脚)
assign sda = sda_io;
endmodule
4. 顶层模块(IIC Top Module)
功能:整合时序发生器、状态机、SDA 控制模块,提供外部接口,方便用户调用。
// IIC 顶层模块
module iic_top(
input clk, // 系统时钟(50MHz)
input rst_n, // 复位信号(低电平有效)
input iic_en, // IIC 使能信号(高电平触发通信)
input wr_en, // 读写控制(1=读,0=写)
input [6:0] slave_addr, // 从机地址(7 位)
input [7:0] tx_data, // 待发送数据(写操作)
output scl, // IIC 时钟 SCL
inout sda, // IIC 数据线 SDA
output tx_done, // 发送完成标志
output rx_done, // 接收完成标志
output [7:0] rx_data // 接收数据(读操作)
);
// 中间信号
wire scl_posedge; // SCL 上升沿标志
wire scl_negedge; // SCL 下降沿标志
wire sda_dir; // SDA 方向控制
wire sda_out; // SDA 输出数据
wire sda_in; // SDA 输入数据
wire ack; // 从机应答信号
// 实例化时序发生器模块
iic_timing_gen u_timing_gen(
.clk(clk),
.rst_n(rst_n),
.iic_en(iic_en),
.scl(scl),
.scl_posedge(scl_posedge),
.scl_negedge(scl_negedge)
);
// 实例化状态机控制器模块
iic_state_machine u_state_machine(
.clk(clk),
.rst_n(rst_n),
.iic_en(iic_en),
.scl_posedge(scl_posedge),
.scl_negedge(scl_negedge),
.ack(ack),
.wr_en(wr_en),
.slave_addr(slave_addr),
.tx_data(tx_data),
.sda_dir(sda_dir),
.sda_out(sda_out),
.tx_done(tx_done),
.rx_done(rx_done),
.rx_data(rx_data)
);
// 实例化 SDA 控制模块
iic_sda_control u_sda_control(
.clk(clk),
.rst_n(rst_n),
.sda_dir(sda_dir),
.sda_out(sda_out),
.sda_in(sda_in),
.ack(ack)
);
// 双向 SDA 引脚连接(FPGA 的 IO 口需配置为双向模式)
assign sda = (sda_dir == 1'b1) ? sda_out : 1'bz; // 输出模式:sda_out 控制;输入模式:高阻
endmodule
四、软件功能实现与验证
1. 硬件验证环境
- FPGA 开发板(如 Xilinx Artix-7、Altera Cyclone IV);
- IIC 从设备(如 EEPROM 芯片 AT24C02、温度传感器 DS18B20);
- 杜邦线(连接 FPGA 的 SDA、SCL 引脚与从设备,需外接 4.7kΩ 上拉电阻,IIC 总线空闲时 SDA/SCL 为高);
- 电源、下载器(JTAG)。
2. 引脚约束(以 Xilinx Vivado 为例)
需将顶层模块的 SDA、SCL 映射到 FPGA 的双向 IO 引脚,并配置上拉电阻(部分 FPGA 支持内部上拉,可直接配置):
# 系统时钟(50MHz)
set_property PACKAGE_PIN E3 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
# 复位信号(低电平有效)
set_property PACKAGE_PIN C2 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
# IIC 时钟 SCL(配置为推挽输出)
set_property PACKAGE_PIN D1 [get_ports scl]
set_property IOSTANDARD LVCMOS33 [get_ports scl]
set_property PULLUP true [get_ports scl] # 内部上拉(无外部电阻时使用)
# IIC 数据 SDA(配置为双向 IO)
set_property PACKAGE_PIN D2 [get_ports sda]
set_property IOSTANDARD LVCMOS33 [get_ports sda]
set_property PULLUP true [get_ports sda] # 内部上拉(无外部电阻时使用)
# 其他控制信号(可选,如 iic_en、wr_en 可连接到按键或寄存器)
set_property PACKAGE_PIN F1 [get_ports iic_en]
set_property IOSTANDARD LVCMOS33 [get_ports iic_en]
set_property PACKAGE_PIN F2 [get_ports wr_en]
set_property IOSTANDARD LVCMOS33 [get_ports wr_en]
3. 功能验证流程(以写 AT24C02 为例)
- 硬件连接:
- FPGA 的 SDA→AT24C02 的 SDA;
- FPGA 的 SCL→AT24C02 的 SCL;
- FPGA 的 VCC→AT24C02 的 VCC(3.3V);
- FPGA 的 GND→AT24C02 的 GND;
- (可选)AT24C02 的 WP 引脚接 GND(允许写入)。
- 软件配置:
- 从机地址:AT24C02 的默认地址为
0b1010000(7 位,A2/A1/A0 引脚接 GND 时);
- 写操作:
wr_en=0;
- 待发送数据:
tx_data=8'h55(示例数据);
- 触发通信:
iic_en置高(可通过按键或代码自动触发)。
- 验证步骤:
- 将 Verilog 代码综合、实现并生成比特流文件(.bit),下载到 FPGA;
- 触发
iic_en信号,FPGA 发送 IIC 写命令到 AT24C02;
- (可选)通过 IIC 读取器(如 Arduino+IIC 库)读取 AT24C02 中对应地址的数据,若为
0x55,证明 IIC 写功能正常;
- 重复步骤 2-3,验证读操作(
wr_en=1),读取 AT24C02 的数据并通过 FPGA 的 LED 或串口输出,确认读功能正常。
五、关键注意事项与扩展功能
1. 注意事项
- 上拉电阻:IIC 总线必须外接上拉电阻(4.7kΩ~10kΩ),否则 SDA/SCL 无法保持高电平,通信失败;
- 双向 IO 配置:FPGA 的 SDA 引脚需配置为双向模式(
INOUT),代码中通过1'bz(高阻)实现输入模式;
- 时序精确性:SCL 的上升沿 / 下降沿必须严格与数据位同步,否则会导致数据传输错误;
- 从机地址:确保从机地址正确(需根据从设备的 A2/A1/A0 引脚配置调整);
- 复位状态:复位时 SDA/SCL 必须置高,避免总线处于异常状态。
2. 扩展功能
- 多字节读写:在状态机中添加数据长度计数器,支持连续读写多个字节(如 AT24C02 的页写入);
- 总线仲裁:添加总线仲裁逻辑,支持多主机通信(检测 SDA 是否被其他主机拉低,避免冲突);
- 中断机制:添加中断信号(如
tx_done、rx_done),通知 CPU/MCU 处理数据;
- 速率可配置:将 IIC 速率的分频系数定义为参数,支持标准模式(100kHz)、快速模式(400kHz)切换;
- CRC 校验:在数据帧末尾添加 CRC 校验位,提高通信可靠性(适用于长距离或噪声环境)。
六、总结
FPGA 实现 IIC 的核心是时序控制和状态机管理,通过硬件逻辑严格遵循 IIC 协议的时序规范,实现双向数据通信。相比单片机的软件 IIC,FPGA 的硬件 IIC 具有实时性强、传输速率高、可并行处理的优势,适用于高速、高可靠性的 IIC 通信场景。实际应用中,可根据从设备的特性(如 EEPROM、传感器)调整状态机逻辑,扩展多字节读写、总线仲裁等功能,适配复杂的 IIC 系统。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online