芯片简介
SHT3x-DIS 是一款温湿度传感器,I2C 接口,通信速度最高可达 1MHz,测量精度 ± 1.5 % R H , ± 0. 1 ∘ C \pm 1.5\%\mathrm{RH},\ \pm 0.1^\circ C ±1.5%RH, ±0.1∘C 。数字输出经过校准和线性化,并进行了温度补偿。
SHT3x-DIS 内部结构及电路示意如上图,由于 SDA 与SCL 为开漏,因此须接上拉电阻。除此之外,SDA 与 SCL 应串联限流电阻。
引脚说明:
- SDA
I2C 串行数据总线,双向,最高支持 1 M H z 1\rm MHz 1MHz,大于 400 k H z 400\rm kHz 400kHz 的通信需符合 I2C 快速模式标准。
- SCL
I2C 串行时钟总线,双向,支持在 0 ∼ 1 M H z 0\sim1\mathrm{MHz} 0∼1MHz 间自由选择。
- nRESET
复位引脚,低电平有效,复位脉冲至少维持 1us 才能可靠地使芯片复位。若不使用建议悬空(芯片内部有 50 k Ω 50k\Omega 50kΩ 的上拉电阻),或接 2 k Ω 2k\Omega 2kΩ 以上的上拉电阻到 VDD。
- ALERT
告警引脚,当温湿度超过设置的阈值时,该引脚给出高电平。若不使用,必须悬空。(datasheet 提到告警阈值是可编程的,但是没看到给出设置的方法)
- ADDR
地址引脚,当该引脚接地时,设置 I2C 设备地址为 0x44,当该引脚拉高时,设置地址为 0x45。ADDR 引脚不可悬空,必须接地或接高电平。
- VDD
供电引脚,2.15V 到 5.5V。
- VSS
Ground。
- R
Reserved,无电气作用,接地。
采样模式
SHT3x-DIS 支持 I2C 快速模式,最高 1MHz。向传感器发送命令后,至少 1ms 后才可发送下一条命令。
SH3x-DIS 的数据和命令都映射到 16bit 的地址空间,并使用 CRC 校验进行保护。16bit 命令已经包含了 3bit 的 CRC 校验和,同时传感器的每次数据接收与发送,均使用 8 位 CRC 校验。
当进行写操作时,必须提供 CRC 校验,SHT-3x-DIS 只接受具有正确校验和的数据;当进行读操作时,SHT3x-DIS 提供 8bit CRC 校验。
当芯片上电后,经过一段时间后(1ms@5V,1.5ms@2.4V)自动进入空闲状态,准备好接收指令。在未处在接收指令或测量状态时,芯片自动进入空闲状态,以实现节能。
测量通信序列由 7bit 设备地址,0 作为写入位,16bit 测量命令组成。传感器对每个字节的正确接收做出确认,在第 8 个 SCL 下降沿后给出 SDA=L 以示正确接收到了该字节。在接收完测量命令后,传感器开始测量湿度和温度。
单次采集模式
该模式下,传感器每接收到一次测量命令,进行一次温湿度测量,温度和湿度均使用 16bit 数字量表示。
单次采集模式的 16bit 指令如下表所示
- 可重复性(Repeatability)
可重复性影响采样的持续时间,重复性越高,测量时间越长(功耗越大),测量精度越高。三种可重复性下的测量时间以及对应的温湿度测量精度如下表
- 时钟拉伸(Clock Stretching)
在 disable Clock Stretching 时,若 Master 发出读取报头时传感器尚未完成数据采样,将回复不确认 NACK (SDA=H)。
当 enable Clock Stretching 时,传感器相应读报头,随后下拉 SCL ,直到测量完成。一旦数据测量完成,传感器将释放 SCL,并传输测量数据。
两种时钟拉伸状态下的传输时序如上图,白色部分由微控制器给出,灰色部分由传感器给出。
上分支所示是 Clock Stretching disabled 的情况,若数据尚未完成采样,传感器 NACK 读报头;当数据准备好之后,Mater 需再次发送读报文头,传感器 ACK 读报文头,并依次给出温度、湿度数据。
下分支对应 Clock Stretching enabled 的情况,Master 只需要发出一次读报文头,若传感器数据尚未准备好,将 NACK 读报文头,并强制拉低 SCL 以终止数据传输,在数据准备好后释放 SCL,并随着 SCL 依次给出温度、湿度数据。
16bit 数字量与实际物理量的对应关系如下
- 相对湿度
R H = S R H 2 16 − 1 × 100 % RH=\frac{S_{RH}}{2^{16}-1}\times100\% RH=216−1SRH×100%
- 温度
T ( ∘ C ) = − 45 + 175 ⋅ S T 2 16 − 1 T(^\circ C)=-45+175\cdot\frac{S_T}{2^{16}-1} T(∘C)=−45+175⋅216−1ST
周期采集模式
此模式下,一个测量命令将产生一个测量数据流,每个数据对由 16bit 温度和 16bit 湿度组成。
- 周期采集命令
周期采集模式的命令如下
表中 mps 表示每秒采集几次(measurement per second),有 0.5 , 1 , 2 , 4 , 10 m p s 0.5,1,2,4,10\ mps 0.5,1,2,4,10 mps 共 5 种测量频率。周期采集模式下,没有 Clock Stretching。
周期采集模式还有一个 ART (accelerated response time) 模式,发出 ART 命令后,传感器将以 4Hz 的频率进行数据采集。
- 读取周期采集数据
要读取数据,使用如下的读取时序(注意 Fetch Data Command 后没有停止信号 P,而是紧接着一个 restart 信号 S,随后给出读报文头)
若传感器内没有数据,I2C 读取头将回应 NACK,通信停止。若有数据,将回应 ACK,并给出温湿度数据,读取完成后将清空数据存储器。
- 终止周期采集模式
采用如下指令终止周期采集模式,回到 Single Shot 模式。恢复为单次采集模式至少要 1ms。
复位 Reset
SHT3x-DIS 有如下几种复位方式:
- Interface Reset
接口复位,保持 SDA=H,切换 SCL 9次以上。该复位仅重置接口,不清空采样数据存储器。
- Soft Reset
软复位,通过发送命令进行复位,软复位将重置传感器,并重新加载校准数据。
- Reset through General Call
通过 I2C 一般呼叫模式产生复位,功能上与通过 nRESET 复位相同。这种复位会使挂载在同一根 I2C 总线上的所有支持一般呼叫的设备重置。一般呼叫的 I2C 序列如下
- Reset through the nReset Pin
拉低 nRESET 引脚至少 1us 可使 SHT3x-DIS 复位。
- Hard Reset
硬复位,下电后重新上电。
加热器 Heater
SHT3x-DIS 内部有一个加热器,仅用于合理性验证(而不是为了什么保持传感器工作温度之类的),默认关闭。通过以下命令开关加热器。无法控制加热到多高温度,温度变化在几摄氏度(受环境影响)。
状态寄存器
状态寄存器(16bit)包含关于加热器的运行状态、警报模式以及最后一个命令和最后一个写入序列的执行状态等信息。
读取状态寄存器的命令如下
寄存器含义如下
通过如下命令清空状态寄存器
FPGA驱动实现
SHT3x-DIS 驱动代码
- SHT3x_singleShot.v
/*
* file : SHT3x_singleShot.v
* author : 今朝无言
* Lab : WHU-EIS-LMSWE
* date : 2023-03-20
* version : v1.0
* description : SHT3x-DISn单采集模式 (Single Shot Mode)
* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
*/
module SHT3x_singleShot(
input clk_1M, //4倍SCL,采用1MHz时钟,SCL_freq = 250kHz
input rst_n,
input samp_en, //上升沿有效
output busy,
output alert,
output reg [15:0] T, //温度,数字量
output reg [15:0] RH, //相对湿度,数字量
inout SCL,
inout SDA,
input ALERT,
output nRESET
);
// 物理量 <- 数字量
// T(℃)=-45+175*S_T/65535
// RH=S_RH/65535*100%
localparam ADDR = 7'h44; //I2C设备地址
localparam RW_W = 1'b0;
localparam RW_R = 1'b1;
localparam CMD_SINGLE_SHOT = 16'h2400; //Sigle Shot, High Repeatability, Clock Stretching Disabled
//---------------------I2C Master State Define----------------------
// I2C Master sub 状态定义
localparam IDLE = 8'h01; //空闲,释放SCL/SDA
localparam START = 8'h02; //起始,SCL=H,SDA=D
localparam SEND_DATA = 8'h04; //发送数据
localparam GET_DATA = 8'h08; //读取数据,释放SDA
localparam CHECK_ACK = 8'h10; //检查SDA的ACK/NACK,释放SDA
localparam ACK = 8'h20; //发出ACK,SDA=L
localparam NACK = 8'h40; //发出NACK,SDA=H
localparam STOP = 8'h80; //停止,SCL=H,SDA=R
//------------------------SHT State Define-------------------------
// ---Send Command---
//IDLE,START,SEND_ADDR_W,CHECK_ACK1,SEND_COMMAND_MSB,CHECK_ACK2,SEND_COMMAND_LSB,CHECK_ACK3,STOP
localparam C_IDLE = 8'h00;
localparam C_START = 8'h01;
localparam C_SEND_ADDR_W = 8'h02;
localparam C_CHECK_ACK1 = 8'h03;
localparam C_SEND_COMMAND_MSB = 8'h04;
localparam C_CHECK_ACK2 = 8'h05;
localparam C_SEND_COMMAND_LSB = 8'h06;
localparam C_CHECK_ACK3 = 8'h07;
localparam C_STOP = 8'h08;
// ---Get Data---
//IDLE,START,SEND_ADDR_R,CHECK_ACK,GET_T_MSB,ACK1,GET_T_LSB,ACK2,GET_CRC1,ACK3,
//GET_RH_MSB,ACK4,GET_RH_LSB,ACK5,GET_CRC2,ACKH6,STOP
localparam D_IDLE = 8'h10;
localparam D_START = 8'h11;
localparam D_SEND_ADDR_R = 8'h12;
localparam D_CHECK_ACK = 8'h13;
localparam D_GET_T_MSB = 8'h14;
localparam D_ACK1 = 8'h15;
localparam D_GET_T_LSB = 8'h16;
localparam D_ACK2 = 8'h17;
localparam D_GET_CRC1 = 8'h18;
localparam D_ACK3 = 8'h19;
localparam D_GET_RH_MSB = 8'h1a;
localparam D_ACK4 = 8'h1b;
localparam D_GET_RH_LSB = 8'h1c;
localparam D_ACK5 = 8'h1d;
localparam D_GET_CRC2 = 8'h1e;
localparam D_ACK6 = 8'h1f;
localparam D_STOP = 8'h20;
// ---SHT3x state---
localparam SHT_IDLE = 8'h01;
localparam SHT_COMMAND_START = 8'h02; // IDLE -> START
localparam SHT_SEND_COMMAND = 8'h04; // I2C busy, Send Command
localparam SHT_SAMPLING = 8'h08; // wait 20ms
localparam SHT_DATA_START = 8'h10; // IDLE -> START
localparam SHT_GET_DATA = 8'h20; // I2C busy, Get Data
localparam SHT_STOP = 8'h40; // T <- T_tmp, RH <- RH_tmp
//------------------------------------------------------------------
reg [7:0] I2C_state = IDLE; //该state输入I2C_Master_sub
reg [7:0] SHT_state = SHT_IDLE;
reg [7:0] SHT_next_state = SHT_IDLE;
reg [7:0] C_state = C_IDLE;
reg [7:0] C_next_state = C_IDLE;
reg [7:0] D_state = D_IDLE;
reg [7:0] D_next_state = D_IDLE;
reg start_flag = 1'b0;
reg C_start_flag = 1'b0;
reg D_start_flag = 1'b0;
reg [7:0] wrdat_buf = 8'd0;
wire [7:0] rddat_tmp;
wire check_ack;
wire change_state;
reg [15:0] T_tmp = 16'd0;
reg [15:0] RH_tmp = 16'd0;
//--------------------------state trans------------------------------
always @(posedge change_state or negedge rst_n) begin
if(~rst_n) begin
SHT_state <= SHT_IDLE;
end
else begin
SHT_state <= SHT_next_state;
end
end
always @(posedge change_state) begin
case(SHT_state)
SHT_COMMAND_START, SHT_SEND_COMMAND: begin
C_state <= C_next_state;
end
default: begin
C_state <= C_state;
end
endcase
end
always @(posedge change_state) begin
case(SHT_state)
SHT_DATA_START, SHT_GET_DATA: begin
D_state <= D_next_state;
end
default: begin
D_state <= D_state;
end
endcase
end
//--------------------Send Command State Machine---------------------
always @(*) begin
case(C_state)
C_IDLE: begin
if(C_start_flag) begin
C_next_state <= C_START;
end
else begin
C_next_state <= C_IDLE;
end
end
C_START: begin
C_next_state <= C_SEND_ADDR_W;
end
C_SEND_ADDR_W: begin
C_next_state <= C_CHECK_ACK1;
end
C_CHECK_ACK1: begin
C_next_state <= C_SEND_COMMAND_MSB;
end
C_SEND_COMMAND_MSB: begin
C_next_state <= C_CHECK_ACK2;
end
C_CHECK_ACK2: begin
C_next_state <= C_SEND_COMMAND_LSB;
end
C_SEND_COMMAND_LSB: begin
C_next_state <= C_CHECK_ACK3;
end
C_CHECK_ACK3: begin
C_next_state <= C_STOP;
end
C_STOP: begin
C_next_state <= C_IDLE;
end
default: begin
C_next_state <= C_IDLE;
end
endcase
end
//----------------------Get Data State Machine-----------------------
always @(*) begin
case(D_state)
D_IDLE: begin
if(D_start_flag) begin
D_next_state <= D_START;
end
else begin
D_next_state <= D_IDLE;
end
end
D_START: begin
D_next_state <= D_SEND_ADDR_R;
end
D_SEND_ADDR_R: begin
D_next_state <= D_CHECK_ACK;
end
D_CHECK_ACK: begin
D_next_state <= D_GET_T_MSB;
end
D_GET_T_MSB: begin
D_next_state <= D_ACK1;
end
D_ACK1: begin
D_next_state <= D_GET_T_LSB;
end
D_GET_T_LSB: begin
D_next_state <= D_ACK2;
end
D_ACK2: begin
D_next_state <= D_GET_CRC1;
end
D_GET_CRC1: begin
D_next_state <= D_ACK3;
end
D_ACK3: begin
D_next_state <= D_GET_RH_MSB;
end
D_GET_RH_MSB: begin
D_next_state <= D_ACK4;
end
D_ACK4: begin
D_next_state <= D_GET_RH_LSB;
end
D_GET_RH_LSB: begin
D_next_state <= D_ACK5;
end
D_ACK5: begin
D_next_state <= D_GET_CRC2;
end
D_GET_CRC2: begin
D_next_state <= D_ACK6;
end
D_ACK6: begin
D_next_state <= D_STOP;
end
D_STOP: begin
D_next_state <= D_IDLE;
end
default: begin
D_next_state <= D_IDLE;
end
endcase
end
//----------------------I2C State Machine-----------------------
always @(*) begin
case({C_state,D_state})
{C_IDLE, D_IDLE}: begin
I2C_state <= IDLE;
end
//---Send Command---
{C_START, D_IDLE}: begin
I2C_state <= START;
end
{C_SEND_ADDR_W, D_IDLE}: begin
I2C_state <= SEND_DATA;
end
{C_CHECK_ACK1, D_IDLE}: begin
I2C_state <= CHECK_ACK;
end
{C_SEND_COMMAND_MSB, D_IDLE}: begin
I2C_state <= SEND_DATA;
end
{C_CHECK_ACK2, D_IDLE}: begin
I2C_state <= CHECK_ACK;
end
{C_SEND_COMMAND_LSB, D_IDLE}: begin
I2C_state <= SEND_DATA;
end
{C_CHECK_ACK3, D_IDLE}: begin
I2C_state <= CHECK_ACK;
end
{C_STOP, D_IDLE}: begin
I2C_state <= STOP;
end
//---Get Data---
{C_IDLE, D_START}: begin
I2C_state <= START;
end
{C_IDLE, D_SEND_ADDR_R}: begin
I2C_state <= SEND_DATA;
end
{C_IDLE, D_CHECK_ACK}: begin
I2C_state <= CHECK_ACK;
end
{C_IDLE, D_GET_T_MSB}: begin
I2C_state <= GET_DATA;
end
{C_IDLE, D_ACK1}: begin
I2C_state <= ACK;
end
{C_IDLE, D_GET_T_LSB}: begin
I2C_state <= GET_DATA;
end
{C_IDLE, D_ACK2}: begin
I2C_state <= ACK;
end
{C_IDLE, D_GET_CRC1}: begin
I2C_state <= GET_DATA;
end
{C_IDLE, D_ACK3}: begin
I2C_state <= ACK;
end
{C_IDLE, D_GET_RH_MSB}: begin
I2C_state <= GET_DATA;
end
{C_IDLE, D_ACK4}: begin
I2C_state <= ACK;
end
{C_IDLE, D_GET_RH_LSB}: begin
I2C_state <= GET_DATA;
end
{C_IDLE, D_ACK5}: begin
I2C_state <= ACK;
end
{C_IDLE, D_GET_CRC2}: begin
I2C_state <= GET_DATA;
end
{C_IDLE, D_ACK6}: begin
I2C_state <= ACK;
end
{C_IDLE, D_STOP}: begin
I2C_state <= STOP;
end
default: begin
I2C_state <= IDLE;
end
endcase
end
//--------------------------SHT State Machine--------------------------
always @(*) begin
case(SHT_state)
SHT_IDLE: begin
if(start_flag) begin
SHT_next_state <= SHT_COMMAND_START;
end
else begin
SHT_next_state <= SHT_IDLE;
end
end
SHT_COMMAND_START: begin
SHT_next_state <= SHT_SEND_COMMAND;
end
SHT_SEND_COMMAND: begin
if(C_state==C_IDLE) begin
SHT_next_state <= SHT_SAMPLING;
end
else begin
SHT_next_state <= SHT_SEND_COMMAND;
end
end
SHT_SAMPLING: begin
if(~delay_busy) begin
SHT_next_state <= SHT_DATA_START;
end
else begin
SHT_next_state <= SHT_SAMPLING;
end
end
SHT_DATA_START: begin
SHT_next_state <= SHT_GET_DATA;
end
SHT_GET_DATA: begin
if(D_state==D_IDLE) begin
SHT_next_state <= SHT_STOP;
end
else begin
SHT_next_state <= SHT_GET_DATA;
end
end
SHT_STOP: begin
SHT_next_state <= SHT_IDLE;
end
default: begin
SHT_next_state <= SHT_IDLE;
end
endcase
end
//---------------------------I2C Master sub----------------------------
I2C_Master_sub I2C_Master_sub_inst(
.clk (clk_1M),
.wrdat_buf (wrdat_buf),
.rddat_tmp (rddat_tmp),
.check_ack (check_ack),
.SCL (SCL),
.SDA (SDA),
.change_state (change_state),
.state (I2C_state)
);
//---------------------------Delay----------------------------
reg delay_en = 1'b0;
wire delay_busy;
monostable_flipflop #(.Width(32))
monostable_flipflop_inst(
.clk (clk_1M),
.rst_n (1'b1),
.delay_N (32'd100_000), //delay 100ms
.reset_N (1'b1),
.in (delay_en),
.out (delay_busy)
);
//------------------------------Control-------------------------------
// ---samp_en edge detect---
reg samp_en_d0;
reg samp_en_d1;
wire samp_en_pe;
always @(posedge clk_1M) begin
samp_en_d0 <= samp_en;
samp_en_d1 <= samp_en_d0;
end
assign samp_en_pe = samp_en_d0 & (~samp_en_d1);
// ---start flag---
//启动samp进程
always @(posedge clk_1M) begin
if(samp_en_pe && ~busy) begin
start_flag <= 1'b1;
end
else if(SHT_state == SHT_COMMAND_START) begin
start_flag <= 1'b0;
end
else begin
start_flag <= start_flag;
end
end
// ---busy---
assign busy = (SHT_state==SHT_IDLE)? 1'b0 : 1'b1;
// ---Send Command start flag---
always @(*) begin
case(SHT_state)
SHT_COMMAND_START: begin
C_start_flag <= 1'b1;
end
default: begin
C_start_flag <= 1'b0;
end
endcase
end
// ---Get Data start flag---
always @(*) begin
case(SHT_state)
SHT_DATA_START: begin
D_start_flag <= 1'b1;
end
default: begin
D_start_flag <= 1'b0;
end
endcase
end
// ---delay_en---
always @(*) begin
if(C_state==C_STOP) begin
delay_en <= 1'b1;
end
else begin
delay_en <= 1'b0;
end
end
// ---T_tmp---
always @(posedge clk_1M) begin
case(D_state)
D_START: begin
T_tmp <= 16'd0;
end
D_ACK1: begin
T_tmp[15:8] <= rddat_tmp;
end
D_ACK2: begin
T_tmp[7:0] <= rddat_tmp;
end
default: begin
T_tmp <= T_tmp;
end
endcase
end
// ---RH_tmp---
always @(posedge clk_1M) begin
case(D_state)
D_START: begin
RH_tmp <= 16'd0;
end
D_ACK4: begin
RH_tmp[15:8] <= rddat_tmp;
end
D_ACK5: begin
RH_tmp[7:0] <= rddat_tmp;
end
default: begin
RH_tmp <= RH_tmp;
end
endcase
end
// ---wrdat_buf---
always @(posedge clk_1M) begin
case({C_state, D_state})
{C_IDLE, D_IDLE}: begin
wrdat_buf <= 8'd0;
end
{C_START, D_IDLE}: begin
wrdat_buf <= {ADDR, RW_W};
end
{C_CHECK_ACK1, D_IDLE}: begin
wrdat_buf <= CMD_SINGLE_SHOT[15:8];
end
{C_CHECK_ACK2, D_IDLE}: begin
wrdat_buf <= CMD_SINGLE_SHOT[7:0];
end
{C_CHECK_ACK3, D_IDLE}: begin
wrdat_buf <= 8'd0;
end
{C_IDLE, D_START}: begin
wrdat_buf <= {ADDR, RW_R};
end
{C_IDLE, D_CHECK_ACK}: begin
wrdat_buf <= 8'd0;
end
default: begin
wrdat_buf <= wrdat_buf;
end
endcase
end
// ---T/RH---
always @(posedge clk_1M) begin
case(SHT_state)
SHT_STOP: begin
T <= T_tmp; //缓存T/RH,给出稳定的输出结果
RH <= RH_tmp;
end
default: begin
T <= T;
RH <= RH;
end
endcase
end
//-------------------------alert & nReset----------------------------
assign alert = ALERT;
assign nRESET = 1'b1;
endmodule
- I2C_Master_sub.v
该模块是 I2C Master 的 SCL/SDA 状态输出控制模块,关于 I2C 如何使用 Verilog 实现详见我之前的博文
/*
* file : I2C_Master_sub.v
* author : 今朝无言
* Lab : WHU-EIS-LMSWE
* date : 2023-03-19
* version : v1.0
* description : I2C master 的 SDA/SCL 控制模块(通过 state)
*/
module I2C_Master_sub(
input clk, //4倍SCL
input [7:0] wrdat_buf,
output reg [7:0] rddat_tmp,
output reg check_ack, //检查Slave给出的ACK信号,若为NACK,输出一个高电平脉冲
inout SCL,
inout SDA,
output reg change_state, //上升沿时 top 模块应执行 state <- next_state
input [7:0] state
);
localparam IDLE = 8'h01; //空闲,释放SCL/SDA
localparam START = 8'h02; //起始,SCL=H,SDA=D
localparam SEND_DATA = 8'h04; //发送数据
localparam GET_DATA = 8'h08; //读取数据,释放SDA
localparam CHECK_ACK = 8'h10; //检查SDA的ACK/NACK,释放SDA
localparam ACK = 8'h20; //发出ACK,SDA=L
localparam NACK = 8'h40; //发出NACK,SDA=H
localparam STOP = 8'h80; //停止,SCL=H,SDA=R
reg SCL_link = 1'b0;
reg SDA_link = 1'b0;
reg SCL_buf = 1'b1; //o_buf
reg SDA_buf = 1'b1;
wire SCL_ibuf; //i_buf
wire SDA_ibuf;
reg [3:0] bit_cnt = 4'd15;
//----------------------IO_BUF-----------------------------
//IOBUF for SCL
IOBUF IOBUF_SCL(
.O (SCL_ibuf), // Buffer的输出,接采集信号
.IO (SCL), // connect directly to top-level port
.I (SCL_buf), // Buffer的输入,接要输出到FPGA外的信号
.T (~SCL_link) // =1时,O <- IO;=0时,IO <- I
);
//IOBUF for SDA
IOBUF IOBUF_SDA(
.O (SDA_ibuf),
.IO (SDA),
.I (SDA_buf),
.T (~SDA_link)
);
//---------------------clk div-----------------------------
//将一个SCL周期划分为4份,便于逻辑实现
reg [1:0] clk_cnt = 2'd0;
always @(posedge clk) begin
clk_cnt <= clk_cnt + 1'b1;
end
//---------------------SCL_link-----------------------------
always @(posedge clk) begin
case(state)
IDLE: begin
SCL_link <= 1'b0;
end
START, SEND_DATA, GET_DATA, CHECK_ACK, ACK, NACK, STOP: begin
SCL_link <= 1'b1;
end
default: begin
SCL_link <= 1'b0;
end
endcase
end
//---------------------SDA_link-----------------------------
always @(posedge clk) begin
case(state)
IDLE, GET_DATA, CHECK_ACK: begin
SDA_link <= 1'b0;
end
START, SEND_DATA, ACK, NACK, STOP: begin
SDA_link <= 1'b1;
end
default: begin
SDA_link <= 1'b0;
end
endcase
end
//---------------------SCL_buf-----------------------------
always @(posedge clk) begin
case(state)
IDLE: begin //1111
SCL_buf <= 1'b1;
end
START: begin //1110
case(clk_cnt)
2'd0, 2'd1, 2'd2: begin
SCL_buf <= 1'b1;
end
2'd3: begin
SCL_buf <= 1'b0;
end
default: ;
endcase
end
STOP: begin //0111
case(clk_cnt)
2'd1, 2'd2, 2'd3: begin
SCL_buf <= 1'b1;
end
2'd0: begin
SCL_buf <= 1'b0;
end
default: ;
endcase
end
SEND_DATA, GET_DATA, CHECK_ACK, ACK, NACK: begin //0110
case(clk_cnt)
2'd1, 2'd2: begin
SCL_buf <= 1'b1;
end
2'd0, 2'd3: begin
SCL_buf <= 1'b0;
end
default: ;
endcase
end
default: begin //1111
SCL_buf <= 1'b1;
end
endcase
end
//---------------------bit_cnt-----------------------------
always @(posedge clk) begin
case(state)
SEND_DATA, GET_DATA: begin
case(clk_cnt)
2'd2: begin
bit_cnt <= bit_cnt - 1'b1;
end
default: ;
endcase
end
START, ACK, NACK, CHECK_ACK: begin
bit_cnt <= 4'd7;
end
default: begin
bit_cnt <= 4'd15;
end
endcase
end
//--------------------rddat_tmp----------------------------
always @(posedge clk) begin
case(state)
GET_DATA: begin
case(clk_cnt)
2'd1: begin
rddat_tmp[bit_cnt] <= SDA_ibuf;
end
default: ;
endcase
end
default: begin
rddat_tmp <= rddat_tmp;
end
endcase
end
//--------------------check_ack----------------------------
always @(posedge clk) begin
case(state)
CHECK_ACK: begin
case(clk_cnt)
2'd1: begin
check_ack <= SDA_ibuf;
end
default: begin
check_ack <= check_ack;
end
endcase
end
default: begin
check_ack <= 0;
end
endcase
end
//---------------------SDA_buf-----------------------------
always @(posedge clk) begin
case(state)
IDLE: begin
SDA_buf <= 1'b1;
end
START: begin //1100,从而在SCL=H时,产生SDA=D
case(clk_cnt)
2'd0, 2'd1: begin
SDA_buf <= 1'b1;
end
2'd2, 2'd3: begin
SDA_buf <= 1'b0;
end
default: ;
endcase
end
SEND_DATA: begin //在clk_cnt=0给出数据,从而在clk_cnt=1,2时(SCL=H)保持SDA的稳定
case(clk_cnt)
2'd0: begin
SDA_buf <= wrdat_buf[bit_cnt];
end
default: ;
endcase
end
GET_DATA: begin
SDA_buf <= 1'b1;
end
CHECK_ACK: begin
SDA_buf <= 1'b0;
end
ACK: begin
SDA_buf <= 1'b0;
end
NACK: begin
SDA_buf <= 1'b1;
end
STOP: begin //0011,从而在SCL=H时,产生SDA=R
case(clk_cnt)
2'd0, 2'd1: begin
SDA_buf <= 1'b0;
end
2'd2, 2'd3: begin
SDA_buf <= 1'b1;
end
default: ;
endcase
end
default: begin
SDA_buf <= 1'b1;
end
endcase
end
//-------------------change_state---------------------------
always @(posedge clk) begin
case(state)
IDLE, ACK, NACK, CHECK_ACK, STOP: begin
case(clk_cnt)
2'd3: begin
change_state <= 1'b1;
end
default: begin
change_state <= 1'b0;
end
endcase
end
SEND_DATA, GET_DATA: begin
case(bit_cnt)
4'd15: begin
case(clk_cnt)
2'd3: begin
change_state <= 1'b1;
end
default: begin
change_state <= 1'b0;
end
endcase
end
default: begin
change_state <= 1'b0;
end
endcase
end
default: begin
case(clk_cnt)
2'd3: begin
change_state <= 1'b1;
end
default: begin
change_state <= 1'b0;
end
endcase
end
endcase
end
endmodule
- monostable_flipflop.v
单稳态电路,用于实现延时。单采集模式下,Sampling 阶段会持续 20 ms 左右,期间 SHT3x-DIS 会 NACK 读请求头
/*
* file : monostable_flipflop.v
* author : 今朝无言
* Lab : WHU-EIS-LMSWE
* date : 2023-03-19
* version : v1.0
* description : 单稳态触发器
*/
module monostable_flipflop(
input clk,
input rst_n,
input [Width-1:0] delay_N,
input reset_N,
input in,
input out
);
parameter Width = 32;
reg [Width-1:0] delay_N_buf = 1;
reg [Width-1:0] cnt = 0;
always @(posedge clk or posedge reset_N) begin
if(reset_N) begin
delay_N_buf <= delay_N;
end
else begin
delay_N_buf <= delay_N_buf;
end
end
reg in_d0;
reg in_d1;
wire in_pe;
assign in_pe = in_d0 & (~in_d1);
always @(posedge clk) begin
in_d0 <= in;
in_d1 <= in_d0;
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
cnt <= 0;
end
else if(in_pe) begin
cnt <= delay_N_buf;
end
else if(cnt > 0) begin
cnt <= cnt - 1'b1;
end
else begin
cnt <= cnt;
end
end
assign out = (|cnt)? 1 : 0;
endmodule
实机测试结果
笔者的 SHT35-DIS 芯片湿度采集似乎有问题,一直在跳变,温度采集是很稳定的,大概在 3 0 ∘ C 30^\circ C 30∘C ,这是由于传感器芯片靠近 FPGA 的缘故。后来外接 SHT30-DIS 型号的温湿度芯片,测试结果正常(53%, 22. 5 ∘ C 22.5^\circ C 22.5∘C)。
- 参考文献
[1] Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf