FPGA入门:实现IIC通信全攻略
目录
三、Verilog 代码实现(以 50MHz 时钟、100kHz IIC 速率为例)
1. 时序发生器模块(IIC Timing Generator)
2. 状态机控制器模块(IIC State Machine)
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。
verilog
// 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:发送停止条件。
verilog
// 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 // SCL为高时,拉低SDA(START条件) sda_out <= 1'b0; state <= ADDR; // 进入地址发送状态 end end // 发送地址+读写位:8位,SCL低电平时切换数据,高电平时有效 ADDR: begin if (scl_negedge) begin // SCL下降沿,切换下一位数据 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 // SCL为高时,拉高SDA(STOP条件) 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 信号。
verilog
// 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 控制模块,提供外部接口,方便用户调用。
verilog
// 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 支持内部上拉,可直接配置):
xdc
# 系统时钟(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置高(可通过按键或代码自动触发)。
- 从机地址:AT24C02 的默认地址为
- 验证步骤:
- 将 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 系统。