温湿度传感器 SHT3x-DIS 详解 & FPGA驱动

芯片简介

  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.1C 。数字输出经过校准和线性化,并进行了温度补偿。

在这里插入图片描述

在这里插入图片描述

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} 01MHz 间自由选择。

  • 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,[email protected])自动进入空闲状态,准备好接收指令。在未处在接收指令或测量状态时,芯片自动进入空闲状态,以实现节能。

  测量通信序列由 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=2161SRH×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+1752161ST

周期采集模式

  此模式下,一个测量命令将产生一个测量数据流,每个数据对由 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 30C ,这是由于传感器芯片靠近 FPGA 的缘故。后来外接 SHT30-DIS 型号的温湿度芯片,测试结果正常(53%, 22. 5 ∘ C 22.5^\circ C 22.5C)。

  • 参考文献

[1] Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf

猜你喜欢

转载自blog.csdn.net/qq_43557686/article/details/129699941