2026/1/8 9:37:13
网站建设
项目流程
专业网站开发报价,龙岩e龙岩网,小程序开发需求方案,下载的主题看起来页面缩小了wordpressVivado实战手记#xff1a;从零搭建FPGA上的I2C传感器系统 最近在调试一个温湿度采集项目时#xff0c;又碰上了那个老朋友——I2C总线。不是ACK没回来#xff0c;就是起始信号被拉长到变形。这种问题#xff0c;在MCU上靠软件重试还能勉强应付#xff1b;但在实时性要求高…Vivado实战手记从零搭建FPGA上的I2C传感器系统最近在调试一个温湿度采集项目时又碰上了那个老朋友——I2C总线。不是ACK没回来就是起始信号被拉长到变形。这种问题在MCU上靠软件重试还能勉强应付但在实时性要求高的场景里比如工业控制或高速数据采集中断延迟和调度抖动就成了硬伤。这时候FPGA的优势就凸显出来了我能自己掌控每一个时钟周期。今天这篇笔记不讲虚的咱们就以Xilinx Artix-7平台为例用Vivado一步步实现一个完整的I2C主机控制器并连接SHT31传感器做数据读取。整个过程涵盖协议理解、状态机设计、引脚约束、仿真验证到板级调试适合有一定Verilog基础、想真正把I2C“握在手里”的工程师。为什么要在FPGA上写I2CMCU不行吗先说清楚动机不然容易陷入“为了用FPGA而用FPGA”的误区。常见的做法是让Zynq的PS端通过Linux I2C子系统去读传感器但这种方式有几个痛点中断延迟不可控Linux内核调度可能带来毫秒级延迟对某些应用来说太慢。资源浪费为几个低速外设占用一个CPU核心性价比不高。启动依赖操作系统裸机系统或Bootloader阶段无法访问I2C设备。而在FPGA逻辑中实现I2C主控器好处很明显✅ 完全自主的时序控制✅ 零中断延迟响应确定性强✅ 可与其他模块并行运行如同时监控多个传感器✅ 资源开销极小几百个LUTs搞定所以如果你需要的是高可靠性 精确时序 低负载的I2C通信FPGA状态机方案几乎是最佳选择。I2C协议的本质两个线四种动作别被文档里的几十页电气特性吓住I2C的核心其实就四件事StartSCL高电平时SDA由高变低StopSCL高电平时SDA由低变高Send BitSCL为低时设置SDA上升沿采样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。这些值必须严格满足否则挂在总线上的设备可能识别失败。我们使用的系统时钟通常是50MHz20ns周期因此可以通过计数器精确分频生成符合规范的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_tri0,sda_outbit_val- 读数据时sda_tri1, 从sda_in采样内部无需外加上拉电阻硬件自动处理。实战案例读取SHT31温湿度传感器SHT31是个典型I2C设备支持多种测量模式。我们选用高重复率单次测量命令0x2C 0x06返回6字节数据温度2字节 CRC 湿度2字节 CRC。主要工作流程上电后初始化I2C控制器和UART模块每2秒触发一次采集- 发送Start- 写设备地址0x44 1 | W- 发送命令0x2C06- Restart- 写地址读标志0x44 1 | R- 连续读6字节前5字节发ACK最后一字节发NACK- Stop解析原始值转换为℃和%RH打包成字符串通过UART发送到PC加入容错机制现场调试最怕总线锁死。为此我们在状态机中加入超时检测reg [15:0] timeout_cnt; wire timeout (timeout_cnt 16d50000); // 约1ms50MHz一旦进入等待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 1b1; end观察状态机是否能在一定周期后超时退出。使用ILA抓真实波形这是最有效的调试手段。在Vivado中添加Integrated Logic Analyzer核监控关键信号statesclsda_outsda_incnt烧录后打开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侧做管理经验总结五个你必须知道的坑SDA切换时机错误必须确保SDA变化发生在SCL为低期间。任何在SCL高时改动SDA的行为都会误触发Start/Stop。忘记释放SDA导致冲突读操作时务必把T信号置高否则你在强驱动总线别的主设备根本没法通信。分频不准导致速率超标计算分频系数时别忘了减1。例如要计250次初值应该是249。跨时钟域未同步如果I2C模块工作在衍生时钟域与系统时钟交互的信号如trigger,done必须打两拍同步。CRC校验不做处理像SHT31这类带CRC的传感器收到的数据必须校验。否则环境干扰可能导致数据错乱而不自知。最后一点思考FPGA的价值不在“快”而在“准”很多人以为FPGA厉害是因为速度快其实不然。在I2C这种百kHz级别的通信中FPGA真正的优势是确定性行为。你可以做到- 每隔整整2.000秒发起一次采集- 在微秒级精度内响应外部事件- 多个传感器轮询无抖动这才是嵌入式系统走向工业级可靠性的关键。掌握这套基于Vivado的状态机设计方法不只是学会了一个接口更是建立起一种思维方式把协议变成电路把软件逻辑硬化为硬件行为。下次当你面对SPI、One-Wire甚至自定义私有协议时也会更有底气地说一句“让我来写个状态机试试。”如果你正在做类似项目欢迎留言交流踩过的坑。也可以告诉我你想看哪个传感器的驱动实现我可以继续拆解。