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

Linux 进程信号深度解析(上):信号的产生与本质(含完整案例)

Linux 进程信号深度解析(上):信号的产生与本质(含完整案例)

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 一. 信号的快速认知:从生活场景到技术本质 * 1.1 生活角度理解信号 * 1.2 技术视角的信号定义 * 1.3 查看系统信号:kill -l 命令 * 二. 信号的产生:5 种核心方式(含完整案例) * 2.1 系统命令产生信号(kill 命令) * 2.2 终端按键产生信号(键盘,最常用) * 2.2.1 Ctrl+C:SIGINT(2

By Ne0inhk
Flutter for OpenHarmony:data_assets — 资源映射与自动装配实践(适配鸿蒙 HarmonyOS Next ohos)

Flutter for OpenHarmony:data_assets — 资源映射与自动装配实践(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 前言 在大型鸿蒙(OpenHarmony)工程中,手动管理静态资源路径极其容易出错。data_assets 提供了一套严谨的代码生成方案,能自动扫描资源并将其转换为强类型的 Dart 类,从根本上消灭了资源引用的运行时错误。 一、核心价值 1.1 基础概念 data_assets 的核心是资源到代码的静态映射。 引用 Assets.homeIcon 编译期校验路径 导致 assets/data: JSON, PNG, SVG DataAssets 生成器 assets.dart: 强类型索引类 鸿蒙业务逻辑 错误的文件名 编译失败提示 1.2 进阶概念 * Type Safety (类型安全):将字符串路径转化为

By Ne0inhk

Ubuntu24.04.3——ROS2一键安装

这篇文章在开局需要叠个甲,这片文章基本上是摘自于B站up鱼香ROS机器人的动手学ROS2文章(链接:动手学ROS2),如有侵权,请联系我删除,相关视频参考【鱼香ROS】动手学ROS2|ROS2基础入门到实践教程|小鱼带你手把手学习ROS2_哔哩哔哩_bilibili 一、一键安装ROS2 首先启动虚拟机或者启动双系统中的ubuntu,打开终端(快捷键Alt+Ctrl+T) 输入下面的指令 wget http://fishros.com/install -O fishros && . fishros 输入密码 在选项界面选择1-一键安装 注意这里的24.=版本的ubuntu只有jazzy和rolling版本,我选的是jazzy版本,选什么版本会导致之后你的终端命令的一些代码会有改动。 出现如图所示,即ROS2安装完成 2.出现问题可以这样卸载 sudo apt remove ros-jazzy-* sudo apt autoremove 3.ROS2到底装哪里了

By Ne0inhk
ubuntu24.04+5090显卡驱动安装踩坑

ubuntu24.04+5090显卡驱动安装踩坑

⭐安装ubuntu24.04 在选择进入 try or install ubuntu 之后会出现持续黑屏现象, 卡在了 booting a command list 解决方案: 选中 try or install ubuntu  按键盘 "e" 进入编辑模式 找到下列位置并添加  nomodeset acpi=off noapic 参数 按下 键盘F10,就可以正常安装 ubuntu 24.04系统了 ⭐安装显卡驱动前置条件 第一步 升级内核 uname -a 查看内核版本 安装5090显卡 必须要将内核版本升级到 6.13 ,用`mainline`工具安装 1.

By Ne0inhk