SHT3x-DIS手册链接
SHT3x-DIS是Sensirion新一代的温湿度传感器,精度为±2%RH和±0.3℃,输入电压范围从2.4V到5.5V,采用IIC总线接口,速率可达1MHz。测量温湿度范围分别为是-40℃ ~ 125℃和0 ~ 100%。
先贴源码以及芯片文档下载链接。
一、电路组成
从下图可以看到SHT3x内部集成了湿度传感器和温度传感器,通过ADC采样输入到数据处理和线性化单元,同时带有校正储存器,处理环境对器件测量的影响。通过数字接口IIC读取数据。带警报引脚,可通过修改寄存器的值设定阈值,当测量的温湿度超过阈值时它会被置位。
引脚分布如下,1和4号脚是IIC总线接口;2号脚是决定地址的引脚,当ADDR接VSS时芯片地址为0x44,接VCC时芯片地址为0x45;3号脚警报引脚(当不使用时浮空),当温湿度超过设定的阈值则该脚会被置位;5号和8号脚是电源引脚;6号引脚是复位引脚(当不使用时接VDD),低电平有效;7号脚是为了封装而保留的引脚。
下图是设备地址分配情况。
典型电路如下,由于SCL和SDA为开漏输出,驱动能力不足,需要接上拉电阻。VDD和VSS之间接一个小电容滤除高频杂波,另外nRESET和ALERT根据情况进行选择,若不需要使用,nRESET接高电平,ALERT浮空。Die Pad接地即可。
二、通讯指令说明
写好IIC驱动程序后,便可以开始和SHT3x进行通讯了,下面是SHT3x的所有指令说明。
(一)、单次获取数据指令
单次获取数据指令(Measurement Commands for Single Shot Data Acquisition Mode)的详细数据格式如下图。首先从表格最上面开始,Repeatability指的是重复性(重复性越高精度越高,可参考手册的Sensor Performance部分),Clock Stretching指的是时钟延伸,它们的作用下面再讲述。数据流动过程如下。
- 发送起始信号以及由高7位的器件地址和最低位的写信号(WR=0) 组成的一字节地址,等待应答信号。(注意地址位于高7位,所以传址的时候需要将地址左移一位并加上读1/写0信号,如ADDR<<1 | WR)
- 发送指令的高字节(Most Significant Byte,MSB)并等待应答信号。
- 发送指令的低字节(Least Significant Byte,LSB)并等待应答信号,之后发送停止信号。
- 等待一段时间(测量正在进行)
- 发送起始信号以及由高7位的器件地址和最低位的读信号(RD=1) 组成的一字节地址,然后根据选择的Clock Stretching从两个方向选择。假如失能了时钟延长功能,则等待非应答信号,发送停止信号,延迟一段时间(这步很重要!!延迟时间大约为50ms左右)等待转换结束,然后发送八位的应答信号并等待应答信号,之后便是逐字节分别读取温度和湿度的高字节、低字节以及CRC校验字节,每字节接收完都要发送应答信号,最后发送停止信号即可。而如果使能了是时钟延长功能,则总线的SCL由SHT3x控制,我们只需要通过while(SCL==0)阻塞程序,等待其释放总线然后MCU读取数据即可。
(二)、周期获取数据指令
周期获取数据指令(Measurement Commands for Periodic Data Acquisition Mode)的详细数据格式如下图。周期获取数据需要先配置读取模式然后再进行读取。
1、配置模式
周期读取数据的配置主要是配置重复性和每秒测量次数。数据发送的方式同上。
2、读取数据
读取的步骤和上述单次读取数据指令的流程类似,下面大概说一下。
- 发送起始信号,发送写地址,等待应答信号,注意如果传感器没有测量完,它只会返回非应答信号。
- 发送16位命令0xE000。
- 读取初始数据
(三)、加快响应时间指令
加快响应时间指令(accelerated response time,ART),用于周期读取数据指令,开启后传感器开始采集频率为4Hz的数据。它的使用方法跟上述配置模式相同,在读取之前配置好就可以了。
- 发送起始信号,发送写地址,等待应答信号
- 发送16位命令0x2B32,等待应答信号
- 接着继续配置或者读取数据
(四)、停止周期读取数据指令
停止周期读取数据指令(Break command / Stop Periodic Data Acquisition Mode),有时为了实现低功耗或暂时不需要传感器测量数据,可以通过该指令进行关闭。
- 发送起始信号,发送写地址,等待应答信号
- 发送16位命令0x3093,等待应答信号,发送停止信号
(五)、复位
复位(RESET)的方式有很多种。
1、IIC接口复位
当通讯设备丢失时,在接下来的信号序列将会复位串口接口,此序列仅重置接口。状态寄存器保留其内容。可以通过下面步骤实现。
- SDA线置位,翻转SCL线的电平9次以上
- 接下来必须在下一个命令之前执行传输开始序列。
2、软复位/重新初始化
软复位/重新初始化(Soft Reset / Re-Initialization)的指令格式如下。
- 发送起始信号,发送写地址,等待应答信号
- 发送16位命令0x30A2,等待应答信号,发送停止信号
3、一般呼叫复位指令
一般呼叫复位指令(Reset through General Call)的指令格式如下。
- 发送起始信号,发送通用呼叫地址0x00,等待应答信号
- 发送第二个字节0x06,等待应答信号
4、通过nRESET引脚复位
拉低nRESET引脚的电平(至少350ns)将会产生硬复位信号重置传感器。
5、硬复位
重新上电。
(六)、加热器开启/关闭指令
该指令的用法同上,至于该加热器的作用是什么我也不清楚,明明是测温用的还要加热?!大概是想在温度很低的环境但想要它还能正常工作所以才设置这功能吧。
(七)、读取状态寄存器
状态寄存器包含有关加热器运行状态、警报模式以及最后一个命令的执行状态和最后一个写序列的信息。
- 发送起始信号,发送写地址,发送16位命令
- 发送起始信号,发送读地址,依次接收状态寄存器的高字节、低字节和CRC校验,每次接收都需要发送应答信号,直到接收的最后一个字节发送非应答信号,发送停止信号。
状态寄存器每一位代表的含义如下所示。
我们试一下读取状态寄存器看返回什么。可以发现二进制数1000 0000 0001 0000 1110 0001都为上面表格的默认值,至于CRC校验要根据给定公式对前两字节进行计算和比较。下面来讲CRC校验。
(八)、CRC校验
循环冗余校验(CRC)其实就是一种异或计算(模2运算)。获取的CRC校验码就是模2运算后的余数。
CRC校验计算的代码如下。
static u8 CRC_Check(u8 *check_data, u8 num, u8 check_crc)
{
uint8_t bit; // bit mask
uint8_t crc = 0xFF; // calculated checksum
uint8_t byteCtr; // byte counter
// calculates 8-Bit checksum with given polynomial x8+x5+x4+1
for(byteCtr = 0; byteCtr < num; byteCtr++)
{
crc ^= (*(check_data+byteCtr));
//crc校验,最高位是1就^0x31
for(bit = 8; bit > 0; --bit)
{
if(crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc = (crc << 1);
}
}
if(crc==check_crc)
return 1;
else
return 0;
}
(九)、转换温湿度数据
由于从传感器获取到的数据不是最终的温湿度,所以我们需要根据公式进行转换,转换公式如下,在计算之前我们要对温湿度数据进行整合。
tem = ((buff[0]<<8) | buff[1]);//温度拼接
hum = ((buff[3]<<8) | buff[4]);//湿度拼接
Temperature= (175.0*(float)tem/65535.0-45.0) ; // T = -45 + 175 * tem / (2^16-1)
Humidity= (100.0*(float)hum/65535.0); // RH = hum*100 / (2^16-1)
三、代码
(一)、IIC程序
1、iic.c
#include "iic.h"
/*
*********************************************************************************************************
* 函 数 名: i2c_Delay
* 功能说明: I2C总线延迟,最快400KHz
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
__NOP();__NOP();__NOP();__NOP(); __NOP();__NOP();__NOP();__NOP();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Start
* 功能说明: CPU发起I2C总线启动信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
I2C_SDA_1();
I2C_SCL_1();
i2c_Delay();
I2C_SDA_0();
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Start
* 功能说明: CPU发起I2C总线停止信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
I2C_SDA_0();
I2C_SCL_1();
i2c_Delay();
I2C_SDA_1();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_SendByte
* 功能说明: CPU向I2C总线设备发送8bit数据
* 形 参:_ucByte : 等待发送的字节
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
/* 先发送字节的高位bit7 */
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
I2C_SDA_1();
}
else
{
I2C_SDA_0();
}
i2c_Delay();
I2C_SCL_1();
i2c_Delay();
I2C_SCL_0();
if (i == 7)
{
I2C_SDA_1(); // 释放总线
}
_ucByte <<= 1; /* 左移一个bit */
i2c_Delay();
}
}
/*
*********************************************************************************************************
* 函 数 名: i2c_ReadByte
* 功能说明: CPU从I2C总线设备读取8bit数据
* 形 参:无
* 返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(u8 ack)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
I2C_SCL_1();
i2c_Delay();
if (I2C_SDA_READ())
{
value++;
}
I2C_SCL_0();
i2c_Delay();
}
if(ack==0)
i2c_NAck();
else
i2c_Ack();
return value;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_WaitAck
* 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
* 形 参:无
* 返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
uint8_t try_time = 100;//连接超时次数
I2C_SDA_1(); //CPU释放SDA总线
i2c_Delay();
I2C_SCL_1(); //CPU驱动SCL = 1, 此时器件会返回ACK应答
i2c_Delay();
while(I2C_SDA_READ())//等待SHT30应答
{
try_time--;
i2c_Delay();
if(try_time==0)//超时,无响应
{
I2C_SCL_0();
i2c_Delay();
return 1;
}
}
I2C_SCL_0();
i2c_Delay();
return 0;
}
/*
*********************************************************************************************************
* 函 数 名: i2c_Ack
* 功能说明: CPU产生一个ACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_Ack(void)
{
I2C_SDA_0(); /* CPU驱动SDA = 0 */
i2c_Delay();
I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
I2C_SDA_1(); /* CPU释放SDA总线 */
}
/*
*********************************************************************************************************
* 函 数 名: i2c_NAck
* 功能说明: CPU产生1个NACK信号
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_NAck(void)
{
I2C_SDA_1(); /* CPU驱动SDA = 1 */
i2c_Delay();
I2C_SCL_1(); /* CPU产生1个时钟 */
i2c_Delay();
I2C_SCL_0();
i2c_Delay();
}
/*
*********************************************************************************************************
* 函 数 名: i2c_GPIO_Config
* 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void i2c_GPIO_Config(void)
{
//个人觉得程序初始化就是要通俗易懂,所以习惯用库函数进行
//但是像改变IIC引脚电平状态,由于需要频繁操作,所以最好还是使用寄存器操作
GPIO_InitTypeDef GPIO_InitStrcuture;
RCC_APB2PeriphClockCmd(IIC_CLOCK,ENABLE);
GPIO_InitStrcuture.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
GPIO_InitStrcuture.GPIO_Pin = IIC_SDA_PIN|IIC_SCL_PIN;
GPIO_InitStrcuture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IIC_GPIO,&GPIO_InitStrcuture);
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CheckDevice
* 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* 形 参:_Address:设备的I2C总线地址
* 返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
i2c_GPIO_Config(); /* 配置GPIO */
i2c_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(_Address|I2C_WR);
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
i2c_Stop(); /* 发送停止信号 */
return ucAck;
}
2、iic.h
#ifndef __IIC_H
#define __IIC_H
#include "stm32f10x.h"
#include <stdbool.h>
#include <stdlib.h>
#include <inttypes.h>
#include "bsp_systick.h"
#include "bsp_sys.h"
#define IIC_CLOCK RCC_APB2Periph_GPIOA
#define IIC_GPIO GPIOA
#define IIC_SCL_PIN GPIO_Pin_6
#define IIC_SDA_PIN GPIO_Pin_7
/* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
#define I2C_SCL_1() PAout(6)=1
#define I2C_SCL_0() PAout(6)=0
#define I2C_SDA_1() PAout(7)=1
#define I2C_SDA_0() PAout(7)=0
#define I2C_SDA_READ() GPIO_ReadInputDataBit(IIC_GPIO,IIC_SDA_PIN)
#define I2C_WR 0 /* 写控制bit */
#define I2C_RD 1 /* 读控制bit */
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_ReadByte(uint8_t ack);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);
void i2c_GPIO_Config(void);
#endif
(二)、SHT30程序
1、sht30.c
#include "sht30.h"
float_data humidity;//存储的湿度数据
float_data temperature;//存储的温度数据
u8 status[3];//状态寄存器
u8 status_crc;//CRC校验计算
u8 crc_check;//crc结果
//如果读取不到数据,有可能是读取的时间设得太短导致,我这里的时间是最小可实现读取的时间!!!
//CRC校验
static u8 CRC_Check(u8 *check_data, u8 num, u8 check_crc)
{
uint8_t bit; // bit mask
uint8_t crc = 0xFF; // calculated checksum
uint8_t byteCtr; // byte counter
// calculates 8-Bit checksum with given polynomial x8+x5+x4+1
for(byteCtr = 0; byteCtr < num; byteCtr++)
{
crc ^= (*(check_data+byteCtr));
//crc校验,最高位是1就^0x31
for(bit = 8; bit > 0; --bit)
{
if(crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc = (crc << 1);
}
}
if(crc==check_crc)
return 1;
else
return 0;
}
//根据所选获取数据的方式初始化
void SHT30_Init(void)
{
i2c_GPIO_Config();
SHT30_General_RESET();//软复位设备
delay_ms(10);
}
//发送两字节指令,stop确定是否发送停止信号
void SHT30_SendBytes(u16 cmd,u8 stop)
{
i2c_Start();
i2c_SendByte(SHT30_ADDR<<1 | I2C_WR);//写7位I2C设备地址加0作为写取位,1为读取位
i2c_WaitAck();
i2c_SendByte(cmd>>8);
i2c_WaitAck();
i2c_SendByte(cmd&0xFF);
i2c_WaitAck();
if(stop)
i2c_Stop();
}
//软复位
void SHT30_SOFTRESET(void)
{
SHT30_SendBytes(0x30A2,1);
}
//通用复位
void SHT30_General_RESET(void)
{
i2c_Start();
i2c_SendByte(0x00);
i2c_WaitAck();
i2c_SendByte(0x06);
i2c_WaitAck();
}
//获取状态寄存器数据
void SHT30_Get_Status(u8 *buffer)
{
SHT30_SendBytes(0xF32D,0);
delay_ms(3);
i2c_Start();
i2c_SendByte(SHT30_ADDR<<1 | I2C_RD);//写7位I2C设备地址加0作为写取位,1为读取位
if(i2c_WaitAck()==0)
{
buffer[0]=i2c_ReadByte(1);
buffer[1]=i2c_ReadByte(1);
buffer[2]=i2c_ReadByte(1);
i2c_Stop();
}
}
//清空状态寄存器
void SHT30_Clear_Status(void)
{
SHT30_SendBytes(0x3041,1);
}
//返回0代表状态寄存器读取成功,1代表读取错误
u8 SHT30_Status(void)
{
SHT30_Get_Status(status);
if(CRC_Check(status,2,*(status+2)))
return 0;//正确
else
return 1;//错误
}
//单次读取数据
void SHT30_Single_Shot(u8 *buffer)
{
u8 try_time=100;
SHT30_SendBytes(0x2C06,1);
delay_ms(20);//很重要!不然读不出数据
i2c_Start();
i2c_SendByte(SHT30_ADDR<<1 | I2C_RD);//写7位I2C设备地址加0作为写取位,1为读取位
while(i2c_WaitAck())
{
try_time--;
delay_us(50);
if(try_time==0)
return;
}
buffer[0]=i2c_ReadByte(1);
buffer[1]=i2c_ReadByte(1);
buffer[2]=i2c_ReadByte(1);
buffer[3]=i2c_ReadByte(1);
buffer[4]=i2c_ReadByte(1);
buffer[5]=i2c_ReadByte(0);
i2c_Stop();
}
//开启/关闭加热器
void SHT30_Heater(u8 enable)
{
if(enable)
SHT30_SendBytes(0x306D,1);
else
SHT30_SendBytes(0x3066,1);
}
//加速响应指令
void SHT30_ART(void)
{
SHT30_SendBytes(0x2B32,0);
}
//配置周期读取重复性和采样速率
void SHT30_Periodic_SetRepeat(void)
{
SHT30_SendBytes(0x2737,0);//高重复度,mps为10
}
//配置周期读取总配置
void SHT30_Periodic_Config(void)
{
//配置
SHT30_Periodic_SetRepeat();
SHT30_ART();
}
//周期读取数据 如果使用就要在初始化时加上SHT30_Periodic_Config()函数
void SHT30_Periodic(u8 *buffer)
{
u8 try_time=100;
//获取数据
SHT30_SendBytes(0xE000,0);
delay_ms(3);//很重要!不然读不出数据
i2c_Start();
i2c_SendByte(SHT30_ADDR<<1 | I2C_RD);//写7位I2C设备地址加0作为写取位,1为读取位
while(i2c_WaitAck())
{
try_time--;
delay_us(50);
if(try_time==0)
return;
}
buffer[0]=i2c_ReadByte(1);
buffer[1]=i2c_ReadByte(1);
buffer[2]=i2c_ReadByte(1);
buffer[3]=i2c_ReadByte(1);
buffer[4]=i2c_ReadByte(1);
buffer[5]=i2c_ReadByte(0);
i2c_Stop();
}
//中断指令/停止周期获取数据功能
void SHT30_Stop_Break(void)
{
SHT30_SendBytes(0x3093,1);
delay_ms(15);
}
//获取SHT3x温湿度
void SHT30_Read(void)
{
u8 buff[6];//获取raw数据
u16 tem,hum;//拼接温湿度数据
u8 crcT,crcH;//温度和湿度的CRC校验
float Temperature=0;//转换后的温湿度
float Humidity=0;
SHT30_Single_Shot(buff);
//SHT30_Periodic(buff);
tem = ((buff[0]<<8) | buff[1]);//温度拼接
hum = ((buff[3]<<8) | buff[4]);//湿度拼接
//计算温度和湿度CRC校验码
crcT = CRC_Check(buff,2,buff[2]); //温度
crcH = CRC_Check(buff+3,2,buff[5]); //湿度
if(crcT&&crcH)//判断CRC校验是否对
{
//根据手册计算公式计算
Temperature= (175.0*(float)tem/65535.0-45.0) ; // T = -45 + 175 * tem / (2^16-1)
Humidity= (100.0*(float)hum/65535.0); // RH = hum*100 / (2^16-1)
if((Temperature>=-20)&&(Temperature<=125)&&(Humidity>=0)&&(Humidity<=100))//过滤超出测量范围的错误数据
{
temperature.fdata=Temperature;
humidity.fdata=Humidity;
}
}
}
2、sht30.h
#ifndef __SHT30_H
#define __SHT30_H
//#include "bsp_iic.h"
#include "iic.h"
#define SHT30_ADDR 0x44
typedef union
{
float fdata;
unsigned char cdata[4];
}float_data;//定义联合体存储float数据,float类型的存储符合IEEE标准,可用于传输数据
void SHT30_Init(void);
void SHT30_SOFTRESET(void);
void SHT30_General_RESET(void);
void SHT30_Stop_Break(void);
void SHT30_Read(void);
void SHT30_SendBytes(u16 cmd,u8 stop);
void SHT30_ART(void);
void SHT30_Single_Shot(u8 *buffer);
void SHT30_Periodic(u8 *buffer);
void SHT30_Heater(u8 enable);
#endif
参考资料:
SHT30使用手册(SHT3x_Datasheet_digital.pdf)