阜新市网站建设_网站建设公司_VPS_seo优化
2026/1/11 1:59:06 网站建设 项目流程

从零构建温度采集系统:Ego1开发板实战全解析

最近带学生做FPGA大作业,发现很多人卡在“温度传感器数据采集”这个项目上。其实这看似复杂的系统,拆解开来不过就是信号怎么来、数据怎么传、结果怎么用三个问题。

今天我就以Xilinx Ego1开发板为平台,带你完整走一遍基于Vivado的温度采集系统设计流程——不讲空话,只说实战中真正踩过的坑和验证有效的解法。


为什么选TMP102?不是所有传感器都适合FPGA新手

项目第一步永远是选型。面对热敏电阻、DS18B20、LM35、TMP102一堆选项,我为什么推荐TMP102?

因为对FPGA初学者来说,数字接口 > 模拟接口,标准协议 > 私有协议

  • 热敏电阻需要外接ADC + 校准曲线拟合,直接劝退;
  • DS18B20虽然也是数字输出,但用的是单总线(1-Wire),时序极其严格,一个周期差几纳秒就丢包;
  • TMP102走I²C,两根线、有规范、速率稳定,哪怕你代码写得糙一点,只要主循环够快,基本都能通。

更重要的是:它不需要外部元件。你在面包板上插个芯片,接两个4.7kΩ上拉电阻到3.3V,SCL/SDA连到FPGA引脚,就能通信。这对调试太友好了。

它的关键参数你也得记住:

参数数值
地址(默认)0x48
分辨率12位(0.0625°C/LSB)
测温范围-55°C ~ +128°C
接口速率支持100kHz / 400kHz

这些数字会直接影响你的状态机设计和数据处理逻辑。


FPGA如何“假装”是一个I²C主机?手把手教你写软核驱动

Artix-7没有硬核I²C控制器,所以我们必须“模拟”出整个协议过程——这就是所谓的Bit-Banging

别被术语吓到,其实就是让FPGA按顺序控制SCL和SDA的电平变化,模仿出起始条件、地址传输、应答检测等动作。

关键挑战:时序精度

I²C协议要求:
- 起始条件:SCL高时,SDA由高变低;
- 数据稳定期:SCL为高时,SDA不能变;
- 数据采样点:SCL下降沿写入,上升沿读取。

如果你用50MHz系统时钟(周期20ns),要生成100kHz的SCL(周期10μs),那每个SCL高低电平至少要维持50个时钟周期。

所以第一步:先做一个分频器

localparam CLK_DIV = 50_000_000 / (4 * 100_000) - 1; // ≈124 reg [7:0] clk_cnt; reg i2c_clk; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin clk_cnt <= 0; i2c_clk <= 0; end else if (clk_cnt >= CLK_DIV) begin clk_cnt <= 0; i2c_clk <= ~i2c_clk; end else begin clk_cnt <= clk_cnt + 1; end end

这里我们把SCL分成四段,每段约2.5μs,这样可以在中间点进行数据切换和采样,避开边沿抖动区。

💡 小技巧:实际设计中建议使用三段式状态机,并在SCL低电平时更新SDA,在SCL高电平时采样SDA,避免违反“数据必须在SCL高期间保持稳定”的规定。

状态机才是核心:一步步发指令读数据

下面是简化版的状态流转图:

IDLE → START → 发送设备地址+写 → 等ACK → 发寄存器地址 → 等ACK → RESTART → 发设备地址+读 → 等ACK → 连续读2字节 → NACK → STOP

对应Verilog中的枚举类型:

typedef enum logic [3:0] { IDLE, START, ADDR_WR, WAIT_ACK1, REG_PTR, WAIT_ACK2, RESTART, ADDR_RD, WAIT_ACK3, READ_MSB, READ_LSB, SEND_NACK, STOP, DONE } i2c_state_t;

重点在于:每一个状态只做一件事,比如“发送一位地址”或“等待ACK”,而不是在一个状态里塞进多个操作。

双向IO怎么处理?inout端口的经典玩法

SDA是双向引脚,作为主机时既要能输出命令,又要能接收从机返回的ACK/NACK。

解决方法是引入三态控制:

inout sda; reg sda_out; reg sda_en; // 1=输出模式,0=输入模式(高阻) assign sda = sda_en ? sda_out : 1'bz;

当你要发送数据时,sda_en=1sda_out赋值;当你想读ACK时,设sda_en=0,然后读sda引脚的真实电平。

⚠️ 坑点提醒:一定要确保在释放SDA前,其他设备没有同时驱动!否则可能造成短路。


数据拿到之后怎么办?别忘了符号扩展!

TMP102返回的是16位数据,格式如下:

bit[15:12]: 温度整数部分(补码) bit[11:8]: 小数部分(0.0625°C步长) bit[7:0]: 保留(读出来可能是随机值)

例如收到16'h8080,你以为是正数?错!最高位是1,说明是负温度。

正确解析方式:

wire signed [15:0] raw = {rx_data[15], rx_data[15], rx_data[15], rx_data[15], rx_data[15:4]}; // 扩展成完整补码 wire [15:0] abs_val = (raw[15]) ? (~raw + 1) : raw; real temp_c = $itor(abs_val) * 0.0625; if (raw[15]) temp_c = -temp_c;

当然,你也可以在纯逻辑中用组合逻辑判断符号位并取反加一,但要注意延迟匹配。


更高级玩法:MicroBlaze + AXI 让开发效率翻倍

如果你已经掌握了纯逻辑设计,下一步可以尝试加入MicroBlaze 软核处理器,把复杂的数据处理交给C语言来做。

为什么这么做?

想想看:你现在要用数码管显示温度,还要支持小数点闪烁、负号显示、单位标注……这些逻辑用Verilog写起来又臭又长。

但如果有一个CPU呢?

你可以:
- 写一段C代码格式化字符串;
- 通过UART打印到电脑;
- 或者调用LCD驱动库直接显示;
- 甚至加个按键实现“切换摄氏/华氏”。

这一切只需要在Vivado里添加一个MicroBlaze IP核,再配一个AXI GPIO或自定义AXI Lite外设即可。

如何连接你的I²C模块?

最简单的办法:把你原来的temp_data输出接到一个只读寄存器上,然后把这个寄存器挂到AXI总线上。

在SDK中就可以这样访问:

uint16_t raw = Xil_In16(XPAR_TEMP_SENSOR_0_BASEADDR); float temp = (int16_t)(raw >> 4) * 0.0625f; xil_printf("Temp: %.2f°C\n", temp);

是不是瞬间清爽了?

而且一旦出了问题,你可以打printf调试,不像纯逻辑只能靠ILA抓波形。

✅ 实战建议:前期先用纯逻辑验证I²C通信是否正常;后期迁移到MicroBlaze提升交互能力。


实际搭建时最容易翻车的几个点

别以为仿真过了就能跑通,现场调试才是真考验。

1. 上拉电阻没接 or 阻值不对

I²C是开漏输出,必须外加上拉电阻!常见的错误包括:
- 忘记接;
- 接成10kΩ导致上升沿太慢(超过300ns);
- 只给SCL加上拉,SDA没接。

标准做法:SCL和SDA各接一个4.7kΩ3.3V电源

2. 引脚约束写错电压标准

Ego1开发板使用LVCMOS33电平。如果你在XDC文件里写成了LVCMOS25,轻则通信不稳定,重则烧IO。

正确的约束示例:

set_property PACKAGE_PIN "P14" [get_ports i2c_scl] set_property IOSTANDARD LVCMOS33 [get_ports i2c_scl] set_property PACKAGE_PIN "P15" [get_ports i2c_sda] set_property IOSTANDARD LVCMOS33 [get_ports i2c_sda]

3. 时钟分频算错,导致速率超标

有人直接拿50MHz除以100k得到500,然后计数到500翻转SCL——这是错的!

因为你每个状态至少占一个时钟周期,而SCL一个完整周期需要两次翻转(高→低→高),所以实际频率是clk_freq / (2 * count)

更安全的做法是:宁可慢一点,也不要超限。跑50kHz也能读数据,但跑500kHz可能直接失败。

4. 多次读取之间没有延时

TMP102内部转换需要时间(典型35ms)。如果你连续发起读操作,很可能拿到旧数据。

解决方案:
- 在状态机里加一个“delay”状态,持续若干毫秒;
- 或者查手册启用“one-shot”模式,每次触发才采样。


如何快速定位问题?ILA是你最好的朋友

Integrated Logic Analyzer(ILA)是Vivado内置的在线逻辑分析仪,能把FPGA内部信号实时抓出来看。

建议你至少监控这几组信号:
-scl,sda:观察实际波形是否符合I²C协议;
-curr_state:确认状态机有没有卡住;
-rx_data,done:检查数据是否正确接收。

设置触发条件也很关键:
- 触发条件设为start == 1
- 捕获深度设为1024点以上;
- 采样时钟选比I²C快至少4倍的时钟。

你会发现,很多时候你以为SDA拉低了,实际上因为竞争条件根本没生效。


最后一点思考:这个项目到底教会了我们什么?

完成一次温度采集,远不止“读个数”那么简单。

它让你亲手实践了:
-硬件描述语言的真实用途:不是写代码,而是“描述电路行为”;
-跨时钟域的基本意识:高速逻辑如何与低速外设握手;
-软硬件分工的设计思维:哪些该用逻辑实现,哪些交给软件更高效;
-从仿真到实物的鸿沟跨越:理论再完美,不上电就不知道哪里漏了上拉。

而这,正是现代嵌入式系统工程师的核心能力。


未来你可以在这个基础上继续拓展:
- 加一个PWM风扇,做成闭环温控;
- 接Pmod WIFI,把温度上传云端;
- 用BRAM缓存历史数据,画趋势图;
- 甚至部署轻量级神经网络做异常检测。

但所有这一切,都要从你第一次成功读出那个0x0080开始。

所以,现在就打开Vivado,新建工程吧。
遇到问题别怕,评论区见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询