FPGA入门:实现IIC通信全攻略

FPGA入门:实现IIC通信全攻略

目录

一、IIC 核心原理与时序

1. IIC 总线特性

2. 关键时序定义

3. 通信流程(主机写从机为例)

二、FPGA 实现 IIC 的核心模块

三、Verilog 代码实现(以 50MHz 时钟、100kHz IIC 速率为例)

1. 时序发生器模块(IIC Timing Generator)

2. 状态机控制器模块(IIC State Machine)

3. 数据收发模块(IIC SDA Control)

4. 顶层模块(IIC Top Module)

四、软件功能实现与验证

1. 硬件验证环境

2. 引脚约束(以 Xilinx Vivado 为例)

3. 功能验证流程(以写 AT24C02 为例)

五、关键注意事项与扩展功能

1. 注意事项

2. 扩展功能

六、总结


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. 通信流程(主机写从机为例)

  1. 主机发送START条件;
  2. 主机发送从机地址 + 写位(7 位地址 + 1 位写 = 0);
  3. 从机回复ACK
  4. 主机发送数据字节(8 位);
  5. 从机回复ACK
  6. (可选)重复步骤 4-5 发送多个数据字节;
  7. 主机发送STOP条件,结束通信。

二、FPGA 实现 IIC 的核心模块

FPGA 实现 IIC 需拆解为 4 个核心子模块,通过顶层模块整合:

  1. 时序发生器:生成 SCL 时钟和时序触发信号(如 SCL 上升沿、下降沿标志);
  2. 状态机控制器:控制 IIC 通信流程(START→地址→数据→ACK→STOP);
  3. 数据收发模块:处理 SDA 线上的数据发送和接收;
  4. 顶层模块:连接各子模块,提供外部接口(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 为例)

  1. 硬件连接
    • FPGA 的 SDA→AT24C02 的 SDA;
    • FPGA 的 SCL→AT24C02 的 SCL;
    • FPGA 的 VCC→AT24C02 的 VCC(3.3V);
    • FPGA 的 GND→AT24C02 的 GND;
    • (可选)AT24C02 的 WP 引脚接 GND(允许写入)。
  2. 软件配置
    • 从机地址:AT24C02 的默认地址为0b1010000(7 位,A2/A1/A0 引脚接 GND 时);
    • 写操作:wr_en=0
    • 待发送数据:tx_data=8'h55(示例数据);
    • 触发通信:iic_en置高(可通过按键或代码自动触发)。
  3. 验证步骤
    • 将 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_donerx_done),通知 CPU/MCU 处理数据;
  • 速率可配置:将 IIC 速率的分频系数定义为参数,支持标准模式(100kHz)、快速模式(400kHz)切换;
  • CRC 校验:在数据帧末尾添加 CRC 校验位,提高通信可靠性(适用于长距离或噪声环境)。

六、总结

FPGA 实现 IIC 的核心是时序控制状态机管理,通过硬件逻辑严格遵循 IIC 协议的时序规范,实现双向数据通信。相比单片机的软件 IIC,FPGA 的硬件 IIC 具有实时性强、传输速率高、可并行处理的优势,适用于高速、高可靠性的 IIC 通信场景。实际应用中,可根据从设备的特性(如 EEPROM、传感器)调整状态机逻辑,扩展多字节读写、总线仲裁等功能,适配复杂的 IIC 系统。

Read more

微服务链路追踪实战:SkyWalking vs Zipkin 架构深度解析与性能优化指南

微服务链路追踪实战:SkyWalking vs Zipkin 架构深度解析与性能优化指南

目录 1. 链路追踪:分布式系统的“X光机” 1.1 从单体到微服务:排查困境的演变 1.2 链路追踪的核心价值矩阵 2. 核心原理解析:Trace、Span与上下文传播 2.1 基本概念:一次请求的完整“病历” 2.2 上下文传播:Trace ID的“接力赛” 2.3 采样算法:平衡精度与开销的智慧 3. SkyWalking深度解析:无侵入监控的艺术 3.1 架构全景:从Agent到UI的完整链路 3.2 字节码增强:Java Agent的魔法 3.3 生产环境配置模板 3.4 性能特性与调优 4.

By Ne0inhk
Rust异步Web框架Axum的深入原理与高级用法

Rust异步Web框架Axum的深入原理与高级用法

Rust异步Web框架Axum的深入原理与高级用法 一、Axum框架的架构与核心组件 1.1 Axum框架的设计理念 💡Axum是基于Tokio异步运行时的Rust Web框架,由Tokio团队官方维护,具有以下核心设计理念: 1. 模块化与可扩展性:通过中间件、请求提取器和响应映射器等组件,实现高度模块化的架构,允许开发者根据需求灵活组合功能。 2. 类型安全:利用Rust的类型系统确保请求处理逻辑的正确性,减少运行时错误。 3. 异步优先:完全基于Tokio异步运行时,充分利用现代硬件的并发能力。 4. 低门槛:提供简单易用的API,同时保持足够的灵活性,适合不同经验水平的开发者。 1.2 Axum框架的核心组件 1.2.1 请求提取器 请求提取器负责从HTTP请求中提取所需的数据,如路径参数、查询参数、请求体等。Axum提供了多种内置的请求提取器,并允许开发者自定义提取器。 内置请求提取器示例: useaxum::{extract::Path,response::IntoResponse,routing::get,

By Ne0inhk
数据库SQL防火墙构建主动防御,让恶意SQL无处遁形

数据库SQL防火墙构建主动防御,让恶意SQL无处遁形

在数字化转型的浪潮中,数据已成为企业的核心资产。然而,SQL注入攻击如同潜伏在阴影中的“不速之客”,时刻威胁着数据库的安全。即使开发团队严守预编译、输入过滤等防线,遗留代码、第三方组件的漏洞或人为疏忽仍可能给攻击者可乘之机。难道只能被动挨打、疲于补漏吗? 金仓数据库(KingbaseES)V009R002C014版本内置的SQL防火墙,给出了一种更聪明的答案——从数据库内核层构建主动防御,让恶意SQL无处遁形,安全团队从此告别“亡羊补牢”,真正实现“规则先行”。 一、SQL注入:那个偷偷溜进房子的“不速之客” SQL注入的原理并不复杂,却极其致命:攻击者将恶意代码伪装成正常输入,欺骗数据库执行非预期操作。 举个简单的例子:一个登录表单中,用户在用户名栏输入 ' OR '1'='1,后台的查询语句可能就变成了: SELECT * FROM users WHERE OR '1'='

By Ne0inhk
Spring Cloud Alibaba 2026 最新实战手册

Spring Cloud Alibaba 2026 最新实战手册

✨道路是曲折的,前途是光明的! 📝 专注C/C++、Linux编程与人工智能领域,分享学习笔记! 🌟 感谢各位小伙伴的长期陪伴与支持,欢迎文末添加好友一起交流! 📚 目录 * 一、引言:微服务架构的新篇章 * 二、Spring Cloud Alibaba 核心架构 * 三、环境搭建与项目初始化 * 四、核心组件实战 * 五、微服务治理最佳实践 * 六、性能优化与监控 * 七、总结与展望 一、引言:微服务架构的新篇章 1.1 为什么选择 Spring Cloud Alibaba? 在云原生时代,微服务架构已成为企业级应用的主流选择。Spring Cloud Alibaba 作为国内领先的微服务解决方案,凭借以下优势脱颖而出: Spring Cloud Alibaba 优势 生态完善 中文文档丰富 阿里生产验证

By Ne0inhk