news 2026/2/25 8:45:44

Vivado使用教程:I2C接口系统设计完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vivado使用教程:I2C接口系统设计完整指南

Vivado实战手记:从零搭建FPGA上的I2C传感器系统

最近在调试一个温湿度采集项目时,又碰上了那个老朋友——I2C总线。不是ACK没回来,就是起始信号被拉长到变形。这种问题,在MCU上靠软件重试还能勉强应付;但在实时性要求高的场景里,比如工业控制或高速数据采集,中断延迟和调度抖动就成了硬伤。

这时候,FPGA的优势就凸显出来了:我能自己掌控每一个时钟周期

今天这篇笔记,不讲虚的,咱们就以Xilinx Artix-7平台为例,用Vivado一步步实现一个完整的I2C主机控制器,并连接SHT31传感器做数据读取。整个过程涵盖协议理解、状态机设计、引脚约束、仿真验证到板级调试,适合有一定Verilog基础、想真正把I2C“握在手里”的工程师。


为什么要在FPGA上写I2C?MCU不行吗?

先说清楚动机,不然容易陷入“为了用FPGA而用FPGA”的误区。

常见的做法是让Zynq的PS端通过Linux I2C子系统去读传感器,但这种方式有几个痛点:

  • 中断延迟不可控:Linux内核调度可能带来毫秒级延迟,对某些应用来说太慢。
  • 资源浪费:为几个低速外设占用一个CPU核心,性价比不高。
  • 启动依赖操作系统:裸机系统或Bootloader阶段无法访问I2C设备。

而在FPGA逻辑中实现I2C主控器,好处很明显:

✅ 完全自主的时序控制
✅ 零中断延迟,响应确定性强
✅ 可与其他模块并行运行(如同时监控多个传感器)
✅ 资源开销极小(几百个LUTs搞定)

所以,如果你需要的是高可靠性 + 精确时序 + 低负载的I2C通信,FPGA状态机方案几乎是最佳选择。


I2C协议的本质:两个线,四种动作

别被文档里的几十页电气特性吓住,I2C的核心其实就四件事:

  1. Start:SCL高电平时,SDA由高变低
  2. Stop:SCL高电平时,SDA由低变高
  3. Send Bit:SCL为低时设置SDA,上升沿采样
  4. Ack/Nack:接收方在第9个时钟拉低SDA表示确认

所有通信都是这四个动作的组合。例如发送一个字节地址,流程就是:

Start → 发8位地址(含R/W)→ 等ACK → 发数据 → 等ACK → Stop

关键点在于时序精度。标准模式下SCL周期10μs,建立时间t_SU,STA ≥ 4.7μs,保持时间t_HD,STA ≥ 4.0μs。这些值必须严格满足,否则挂在总线上的设备可能识别失败。

我们使用的系统时钟通常是50MHz(20ns周期),因此可以通过计数器精确分频生成符合规范的SCL波形。


工程怎么建?别跳过这一步

打开Vivado,新建RTL工程,命名建议清晰些,比如i2c_sensor_hub

器件选型根据开发板来,如果是Basys3,那就是xc7a35tcpg236-1。记得勾选“Do not specify sources”,后续手动添加文件更灵活。

小技巧:如果使用官方开发板,在Board界面直接选型号,Vivado会自动加载封装信息和默认IO标准,省去查手册的麻烦。

创建完成后,立刻新建两个目录:

src/ ├── i2c_master.v └── uart_tx.v constraints/ └── pin.xdc

模块化管理代码和约束,后期维护轻松得多。


自己写I2C控制器:状态机才是灵魂

虽然Zynq有AXI-IIC IP可用,但对于纯逻辑设计或低成本FPGA,还是得自己动手。下面是一个精简但实用的I2C主机状态机实现。

核心状态定义

typedef enum logic [3:0] { IDLE, START_COND, SEND_ADDR, WAIT_ADDR_ACK, SEND_CMD_MSB, WAIT_CMD_ACK, RESTART, SEND_ADDR_READ, WAIT_READ_ACK, READ_BYTE, SEND_NACK, STOP_COND } i2c_state_t;

这个状态机专用于向SHT31发送测量命令并读回数据。相比通用控制器,它做了针对性优化:固定地址(0x44)、固定命令(0x2C06)、单次读取6字节。

分频与时序控制

假设输入时钟50MHz,目标SCL = 100kHz,则每个半周期需计数:

(50_000_000 / 100_000) / 2 = 250

即每250个系统时钟翻转一次SCL。实际代码中用一个倒计数器:

always @(posedge clk or negedge rst_n) begin if (!rst_n) cnt <= 0; else if (cnt_en) cnt <= (cnt == 0) ? 249 : cnt - 1; end

cnt == 0时表示到达SCL边沿,可以更新SDA或采样数据。

SDA三态处理

这是最容易出错的地方。SDA是双向信号,输出时驱动,输入时要释放(高阻态)。Xilinx原语IOBUF正好派上用场:

IOBUF i2c_sda_buf ( .I (sda_out), // FPGA输出驱动 .O (sda_in), // 外部输入采样 .T (sda_tri), // 高电平时为输入(三态) .IO (i2c_sda_pin) // 引脚连接 );

在状态机中:
- 写数据时:sda_tri=0,sda_out=bit_val
- 读数据时:sda_tri=1, 从sda_in采样

内部无需外加上拉电阻,硬件自动处理。


实战案例:读取SHT31温湿度传感器

SHT31是个典型I2C设备,支持多种测量模式。我们选用高重复率单次测量命令0x2C 0x06,返回6字节数据(温度2字节 + CRC + 湿度2字节 + CRC)。

主要工作流程

  1. 上电后初始化I2C控制器和UART模块
  2. 每2秒触发一次采集:
    - 发送Start
    - 写设备地址(0x44 << 1 | W)
    - 发送命令0x2C06
    - Restart
    - 写地址+读标志(0x44 << 1 | R)
    - 连续读6字节,前5字节发ACK,最后一字节发NACK
    - Stop
  3. 解析原始值,转换为℃和%RH
  4. 打包成字符串,通过UART发送到PC

加入容错机制

现场调试最怕总线锁死。为此我们在状态机中加入超时检测:

reg [15:0] timeout_cnt; wire timeout = (timeout_cnt == 16'd50000); // 约1ms@50MHz

一旦进入等待ACK状态超过阈值,立即强制跳转至STOP并置错误标志。这样即使传感器掉线,系统也不会卡死。


引脚约束与物理层配置

别小看.xdc文件,很多通信失败其实是约束写错了。

# I2C pins set_property PACKAGE_PIN G14 [get_ports i2c_sda] set_property PACKAGE_PIN F14 [get_ports i2c_scl] set_property IOSTANDARD LVCMOS33 [get_ports {i2c_sda i2c_scl}] # 启用内部弱上拉(若外部无上拉电阻) set_property PULLUP true [get_ports {i2c_sda i2c_scl}] # UART TX to PC set_property PACKAGE_PIN E18 [get_ports uart_tx] set_property IOSTANDARD LVCMOS33 [get_ports uart_tx]

注意:不要同时启用内部上拉和外部4.7kΩ电阻,会导致高电平被拉低。推荐优先使用外部精密电阻,稳定性更好。


如何验证?仿真 + ILA双管齐下

Testbench模拟ACK异常

写个简单的testbench注入NACK,看看你的控制器会不会无限等待:

// 在第9个cycle强制拉高SDA(模拟无应答) initial begin # (9 * 10ns); sda_in <= 1'b1; end

观察状态机是否能在一定周期后超时退出。

使用ILA抓真实波形

这是最有效的调试手段。在Vivado中添加Integrated Logic Analyzer核,监控关键信号:

  • state
  • scl
  • sda_out
  • sda_in
  • cnt

烧录后打开Hardware Manager,设置触发条件(如state == START_COND),就能看到真实的起始信号是否合规。

你会发现,有时候明明代码没错,但因为布线延迟导致SCL和SDA边沿不对齐。这时候就需要调整综合属性或手动布局。


Zynq用户怎么看?要不要用AXI-IIC?

如果你用的是Zynq-7000或UltraScale+ MPSoC,确实可以用IP Catalog里的AXI IIC核,优点是:

  • 底层时序由硬件保证
  • 支持DMA传输,减轻CPU负担
  • 可配合Linux设备树使用

但它也有局限:

❌ 不适合裸机快速启动
❌ 调试不够透明,出了问题难定位
❌ 占用PS侧资源

我的建议是:

  • 快速原型 → 用AXI-IIC
  • 裸机/实时系统 → 自研状态机
  • 多设备复杂交互 → 结合两者,FPGA侧做主控,PS侧做管理

经验总结:五个你必须知道的坑

  1. SDA切换时机错误
    必须确保SDA变化发生在SCL为低期间。任何在SCL高时改动SDA的行为都会误触发Start/Stop。

  2. 忘记释放SDA导致冲突
    读操作时务必把T信号置高,否则你在强驱动总线,别的主设备根本没法通信。

  3. 分频不准导致速率超标
    计算分频系数时别忘了减1。例如要计250次,初值应该是249。

  4. 跨时钟域未同步
    如果I2C模块工作在衍生时钟域,与系统时钟交互的信号(如trigger,done)必须打两拍同步。

  5. CRC校验不做处理
    像SHT31这类带CRC的传感器,收到的数据必须校验。否则环境干扰可能导致数据错乱而不自知。


最后一点思考:FPGA的价值不在“快”,而在“准”

很多人以为FPGA厉害是因为速度快,其实不然。在I2C这种百kHz级别的通信中,FPGA真正的优势是确定性行为

你可以做到:
- 每隔整整2.000秒发起一次采集
- 在微秒级精度内响应外部事件
- 多个传感器轮询无抖动

这才是嵌入式系统走向工业级可靠性的关键。

掌握这套基于Vivado的状态机设计方法,不只是学会了一个接口,更是建立起一种思维方式:把协议变成电路,把软件逻辑硬化为硬件行为

下次当你面对SPI、One-Wire甚至自定义私有协议时,也会更有底气地说一句:“让我来写个状态机试试。”

如果你正在做类似项目,欢迎留言交流踩过的坑。也可以告诉我你想看哪个传感器的驱动实现,我可以继续拆解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/22 18:17:41

基于运放的有源滤波电路:项目应用实例详解

工业传感器信号去噪实战&#xff1a;一文讲透运放有源滤波设计精髓 你有没有遇到过这样的问题&#xff1f; 压力传感器输出明明很稳定&#xff0c;但ADC采样的数据却“跳来跳去”&#xff0c;尤其是在电机启动或变频器运行时&#xff0c;读数直接飘飞。你以为是软件滤波没做好…

作者头像 李华
网站建设 2026/2/23 23:50:57

AI开发者福音:PyTorch-CUDA镜像支持一键部署

AI开发者福音&#xff1a;PyTorch-CUDA镜像支持一键部署 在深度学习项目开发中&#xff0c;你是否曾经历过这样的场景&#xff1a;花了一整天时间配置环境&#xff0c;结果 torch.cuda.is_available() 依然返回 False&#xff1f;或者团队成员因为 CUDA 版本不一致&#xff0c;…

作者头像 李华
网站建设 2026/2/25 6:39:55

installing PyTorch this may take a few minutes… 终于有替代方案了!

PyTorch 安装太慢&#xff1f;现在有更聪明的解法了 在深度学习的世界里&#xff0c;最让人又爱又恨的一幕莫过于&#xff1a;刚打开终端准备大展身手&#xff0c;输入 pip install torch 后屏幕缓缓打出“installing PyTorch… this may take a few minutes…”——然后你盯着…

作者头像 李华
网站建设 2026/2/17 3:15:13

不用手动装CUDA!PyTorch镜像已内置完整工具包

不用手动装CUDA&#xff01;PyTorch镜像已内置完整工具包 在深度学习项目的日常开发中&#xff0c;你是否经历过这样的场景&#xff1a;刚拿到一块新GPU服务器&#xff0c;满心欢喜准备开始训练模型&#xff0c;结果花了整整一个下午才把CUDA、cuDNN和PyTorch的版本配对成功&a…

作者头像 李华
网站建设 2026/2/14 21:15:03

传输线效应处理:高速电路板PCB一文说清

高速PCB设计避坑指南&#xff1a;传输线效应从理论到实战你有没有遇到过这样的情况&#xff1f;电路原理图明明“连通了”&#xff0c;可一上电&#xff0c;DDR就是跑不起来&#xff0c;眼图闭合得像眯着的眼睛&#xff1b;PCIe链路频繁误码&#xff0c;反复改版却找不到根源。…

作者头像 李华
网站建设 2026/2/7 2:38:40

从Git Commit到模型训练:全流程自动化脚本示例

从Git Commit到模型训练&#xff1a;全流程自动化脚本示例 在现代AI研发中&#xff0c;一个常见的尴尬场景是&#xff1a;开发者在本地调试完模型、信心满满地提交代码后&#xff0c;CI系统却报出“torch not found”或“CUDA version mismatch”这类环境问题。更糟的是&#x…

作者头像 李华