FPGA实战:从零构建I2C控制器驱动SHT3x温湿度传感器
1. 硬件设计基础
在开始FPGA驱动开发前,我们需要深入理解I2C总线协议和SHT3x传感器的硬件特性。I2C作为一种两线制串行总线,由飞利浦公司开发,采用主从架构,支持多主多从通信。SHT3x系列是Sensirion推出的高精度数字温湿度传感器,典型精度达到±1.5%RH和±0.1°C。
关键硬件参数对比:
| 特性 | SHT30-DIS | SHT31-DIS | SHT35-DIS |
|---|---|---|---|
| 测量范围(RH) | 0-100% | 0-100% | 0-100% |
| 精度(RH) | ±2% | ±2% | ±1.5% |
| 测量范围(温度) | -40~125°C | -40~125°C | -40~125°C |
| 精度(温度) | ±0.2°C | ±0.2°C | ±0.1°C |
| 供电电压 | 2.4-5.5V | 2.4-5.5V | 2.4-5.5V |
| I2C速率 | 最高1MHz | 最高1MHz | 最高1MHz |
注意:实际电路设计时,SCL和SDA线必须接上拉电阻(典型值4.7kΩ),并建议串联22Ω限流电阻保护接口。
FPGA与SHT3x的典型连接方式:
module top( input clk_50M, output [7:0] led, inout SCL, inout SDA ); // 时钟分频生成1MHz主时钟 wire clk_1M; clock_divider #(.DIV(50)) clk_div( .clk_in(clk_50M), .clk_out(clk_1M) ); // SHT3x驱动实例化 SHT3x_driver sht3x( .clk_1M(clk_1M), .SCL(SCL), .SDA(SDA), .temp_data(temp), .humidity_data(humidity) ); endmodule2. I2C协议状态机设计
FPGA实现I2C控制器的核心在于精确的状态机设计。与MCU的库函数调用不同,FPGA需要完全掌控每个时钟边沿的信号变化。我们将I2C通信分解为8个基本状态:
- IDLE:总线空闲状态,释放SCL和SDA
- START:产生起始条件(SCL高时SDA下降沿)
- SEND_DATA:发送8位数据(含地址和命令)
- GET_DATA:接收8位数据
- CHECK_ACK:检测从机应答
- ACK:主机产生应答
- NACK:主机产生非应答
- STOP:产生停止条件(SCL高时SDA上升沿)
Verilog状态机实现片段:
localparam [7:0] IDLE = 8'h01, START = 8'h02, SEND_DATA= 8'h04, GET_DATA = 8'h08, CHECK_ACK= 8'h10, ACK = 8'h20, NACK = 8'h40, STOP = 8'h80; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin state <= IDLE; end else begin case(state) IDLE: if(start) state <= START; START: state <= SEND_ADDR; SEND_DATA: if(bit_cnt == 0) state <= CHECK_ACK; // 其他状态转换... endcase end end提示:I2C时序要求严格,建议使用4倍于目标SCL频率的时钟(如1MHz SCL对应4MHz FPGA时钟)来精确控制信号边沿。
3. SHT3x驱动实现详解
SHT3x的完整驱动流程包括初始化、发送测量命令、等待采样、读取数据四个阶段。与Arduino等MCU平台不同,FPGA实现需要显式处理每个时序细节。
单次测量模式操作序列:
- 发送启动信号(START)
- 发送设备地址+写位(0x44<<1 | 0)
- 发送测量命令高字节(如0x24)
- 发送测量命令低字节(如0x00)
- 发送停止信号(STOP)
- 等待至少15ms采样时间
- 发送启动信号(START)
- 发送设备地址+读位(0x44<<1 | 1)
- 读取温度高字节+ACK
- 读取温度低字节+ACK
- 读取温度CRC+ACK
- 读取湿度高字节+ACK
- 读取湿度低字节+ACK
- 读取湿度CRC+NACK
- 发送停止信号(STOP)
关键Verilog代码实现:
// 命令定义 localparam [15:0] CMD_SINGLE_HIGH = 16'h2400, // 高重复性,禁用时钟拉伸 CMD_SINGLE_MED = 16'h240B, // 中重复性 CMD_SINGLE_LOW = 16'h2416; // 低重复性 // 状态定义 localparam [7:0] S_IDLE = 8'h01, S_SEND_CMD = 8'h02, S_WAIT = 8'h04, S_READ_DATA = 8'h08, S_DONE = 8'h10; // 数据转换 wire [15:0] temp_linear = {temp_data[15:8], temp_data[7:0]}; wire [15:0] rh_linear = {rh_data[15:8], rh_data[7:0]}; // 转换为实际物理量 assign temperature = -45 + 175 * temp_linear / 65535.0; assign humidity = 100 * rh_linear / 65535.0;4. 时序约束与调试技巧
FPGA驱动I2C设备时,时序约束至关重要。我们需要在XDC或SDC文件中添加适当的约束,确保信号满足传感器要求。
关键时序约束示例:
# 时钟约束 create_clock -name clk_1M -period 1000 [get_ports clk_1M] # 输入延迟约束 set_input_delay -clock [get_clocks clk_1M] -max 2 [get_ports SDA] set_input_delay -clock [get_clocks clk_1M] -min 1 [get_ports SDA] # 输出延迟约束 set_output_delay -clock [get_clocks clk_1M] -max 2 [get_ports SCL] set_output_delay -clock [get_clocks clk_1M] -min 1 [get_ports SCL]常见问题排查指南:
无应答(NACK):
- 检查设备地址是否正确(0x44或0x45)
- 确认上拉电阻已连接(典型4.7kΩ)
- 测量电源电压是否在2.4-5.5V范围内
数据错误:
- 验证CRC校验计算
- 检查时序是否符合传感器规格
- 确保在命令之间留有足够延迟(>1ms)
信号完整性问题:
- 缩短走线长度或降低SCL频率
- 增加串联阻尼电阻(22-100Ω)
- 使用示波器检查信号质量
实际项目中,我发现SHT35对PCB布局更为敏感,建议将传感器远离发热元件(如FPGA芯片本身),并通过I2C缓冲器(如PCA9306)隔离长距离信号线。