I2C协议下的SHT20传感器温湿度采样详解
关于I2C协议的文章有很多,在本博客中不涉及I2C协议的基础知识,主要是讲解如何利用SHT20传感器采样温湿度。
SHT20基础知识
在编写代码之前需要先了解SHT20的数据手册,了解发生采样温度、湿度的命令,以及如何根据采样结果计算具体的温湿度的值。数据手册的中文版本
通过官方提供的信息和i2c-tools了解sht20的从设备地址为0x40,常见的命令如下所示:
我们需要使用的命令如红色框中所示,其中T表示温度,RH表示相对湿度。非保持主机是指在SHT20测量期间会释放i2c总线,使得主机可以处理其他从设备的通信任务。软复位用于在无需关闭和再次打开电源的情况下,重新启动传感器系统。在接收到这个命令之后,传感器系统开始重新初始化,并恢复默认设置状态。
在了解了测量命令后,还需知道SHT20的通信时序。
如上图所示,SHT20通信的步骤,大体分为两步:
由上图可知,SHT20返回的数据包含3个字节,其中前两个字节包含测量数据,而第三个字节是校验和。高字节’0110 0011‘,低字节’0101 0010‘,低字节的最后两位是状态位,由图中可知,第43位为1(1:湿度,0:温度),表示这段数据得到的是湿度值。因此可以得知实际有效位只有14位,在计算的过程中需要将最后两位置为0,用16进制表示就是0x6350=25424。得到这个值后再根据以下公式计算湿度值,结果表示为%:
温度的计算公式:
不同测量类型和分辨率的最长测量时间如下:
硬件准备
将SHT20的VCC引脚接在引脚1(3.3V),GND接在引脚6,SDA接在引脚3, SCL接在引脚5.
树莓派默认情况下没有使能i2c接口,因此需要先使能i2c接口
sudo raspi-config
进入配置界面,使用⬆,⬇选择5 Interfacing Opions,按enter进入,选择yes,最后选择ok,按esc键退出配置界面。(如果配置界面出现白色的,只有第一行,不用担心,使用上下箭头选择后也会出现5)
配置完成后,检查i2c驱动是否使能成功
ls /dev/*i2c*
出现 /dev/i2c-0或dev/i2c-1即是成功。
安装i2c-tools
sudo apt-get install i2c-tools
使用i2c-tools查看SHT20地址
i2cdetect -y 1
1表示查看的总线设备。在ls /dev/*i2c*后返回的是 /dev/i2c-0,这个地方就填0,返回的是 /dev/i2c-1,就填1.
代码解析
代码流程:
初始化i2c总线设备—>发送测量命令—> 读取测量数据—>根据公式计算测量值—>得到最终结果
1. 初始化i2c总线
int sht2x_init(char *i2c_dev)
{
int fd;
if(i2c_dev == NULL)
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
fd = open(i2c_dev, O_RDWR);
if(fd < 0)
{
printf("i2c device open failed: %s\n", strerror(errno));
return -1;
}
ioctl(fd, I2C_TENBIT, 0);
ioctl(fd, I2C_SLAVE, 0x40);
if(sht2x_softreset(fd) < 0)
{
printf("SHT2x softreset failure\n");
return -2;
}
return fd;
}
int sht2x_softreset(int fd)
{
uint8_t buf[4];
if( fd<0 )
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
/* software reset SHT2x */
memset(buf, 0, sizeof(buf));
/* 发送软复位命令 并休眠需要的时间 */
buf[0] = 0xFE;
write(fd, buf, 1);
msleep(50);
return 0;
}
2. 获取温湿度
分别读取温度和湿度值
发送测量温度的命令:0xF3 = 1111 0011,由表7可知,当分辨率为14位时,测量温度需要的时间最长为85ms,测量完成后读取3个字节的数据,前两个字节包含测量数据,最后一个字节是CRC校验和。根据公式计算温度值
发送测量湿度的命令:0xF5 = 1111 1001。
int sht2x_get_temp_humidity(int fd, float *temp, float *rh)
{
uint8_t buf[4];
if( fd<0 || !temp || !rh )
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
memset(buf, 0, sizeof(buf));
/* 发送非主机保持的温度测量命令 */
buf[0]=TRIGGER_TEMPERATURE_NO_HOLD;
write(fd, buf, 1);
msleep(85);
memset(buf, 0, sizeof(buf));
/* 读取三个字节的数据,最后一个字节为crc检验值 */
read(fd, buf, 3);
dump_buf("Temperature sample data: ", buf, 3);
/* 计算实际温度值*/
*temp = 175.72 * (((((int) buf[0]) << 8) + (buf[1] & 0xfc)) / 65536.0) - 46.85;
memset(buf, 0, sizeof(buf));
/* 发送非主机保持的湿度测量命令 */
buf[0] = TRIGGER_HUMIDITY_NO_HOLD;
write(fd, buf, 1);
msleep(29);
memset(buf, 0, sizeof(buf));
read(fd, buf, 3);
dump_buf("Relative humidity sample data: ", buf, 3);
/* 计算实际湿度值*/
*rh = 125 * (((((int) buf[0]) << 8) + (buf[1] & 0xfc)) / 65536.0) - 6;
return 0;
}
3. 完整可运行的代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <sys/stat.h>
#include <linux/i2c-dev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#define SOFTRESET 0xFE
#define TRIGGER_TEMPERATURE_NO_HOLD 0xF3
#define TRIGGER_HUMIDITY_NO_HOLD 0xF5
static inline void msleep(unsigned long ms);
int sht2x_init(char *i2c_dev);
int sht2x_get_temp_humidity(int fd, float *temp, float *rh);
int main(int argc, char **argv)
{
int fd;
float temp;
float rh;
if( argc != 2)
{
printf("This program used to do I2C test by sht20 temperature & humidity module.\n");
printf("Usage: %s /dev/i2c-x\n", argv[0]);
return 0;
}
fd = sht2x_init(argv[1]);
if(fd < 0)
{
printf("SHT2x initialize failure\n");
return 1;
}
if( sht2x_get_temp_humidity(fd, &temp, &rh) < 0 )
{
printf("SHT2x get get temperature and relative humidity failure\n");
return 3;
}
printf("Temperature=%lf ℃ relative humidity=%lf%\n", temp, rh);
close(fd);
}
/* ms级休眠函数 */
static inline void msleep(unsigned long ms)
{
struct timespec cSleep;
unsigned long ulTmp;
cSleep.tv_sec = ms / 1000;
if (cSleep.tv_sec == 0)
{
ulTmp = ms * 10000;
cSleep.tv_nsec = ulTmp * 100;
}
else
{
cSleep.tv_nsec = 0;
}
nanosleep(&cSleep, 0);
}
/* sht20命令复位 */
int sht2x_softreset(int fd)
{
uint8_t buf[4];
if( fd<0 )
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
/* software reset SHT2x */
memset(buf, 0, sizeof(buf));
/* 发送软复位命令 并休眠需要的时间 */
buf[0] = SOFTRESET;
write(fd, buf, 1);
msleep(50);
return 0;
}
/* 初始化sht20 形参为i2c总线节点 */
int sht2x_init(char *i2c_dev)
{
int fd;
if( (fd=open(i2c_dev, O_RDWR)) < 0)
{
printf("i2c device open failed: %s\n", strerror(errno));
return -1;
}
/* set I2C mode and SHT2x slave address */
ioctl(fd, I2C_TENBIT, 0); /* Not 10-bit but 7-bit mode */
ioctl(fd, I2C_SLAVE, 0x40); /* set SHT2x slava address 0x40*/
if( sht2x_softreset(fd) < 0 )
{
printf("SHT2x softreset failure\n");
return -2;
}
return fd;
}
/* 获取sht20的温度湿度值 */
int sht2x_get_temp_humidity(int fd, float *temp, float *rh)
{
uint8_t buf[4];
if( fd<0 || !temp || !rh )
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
/* send trigger temperature measure command and read the data */
memset(buf, 0, sizeof(buf));
/* 发送非主机保持的温度测量命令 */
buf[0]=TRIGGER_TEMPERATURE_NO_HOLD;
write(fd, buf, 1);
/* 等待需要的时间 数据手册可知 */
msleep(85); /* datasheet: typ=66, max=85 */
memset(buf, 0, sizeof(buf));
/* 读取三个字节的数据,最后一个字节为crc检验值 */
read(fd, buf, 3);
dump_buf("Temperature sample data: ", buf, 3);
/* 计算实际温度值*/
*temp = 175.72 * (((((int) buf[0]) << 8) + (buf[1] & 0xfc)) / 65536.0) - 46.85;
/* send trigger humidity measure command and read the data */
memset(buf, 0, sizeof(buf));
/* 发送非主机保持的湿度测量命令 */
buf[0] = TRIGGER_HUMIDITY_NO_HOLD;
write(fd, buf, 1);
msleep(29); /* datasheet: typ=22, max=29 */
memset(buf, 0, sizeof(buf));
read(fd, buf, 3);
dump_buf("Relative humidity sample data: ", buf, 3);
/* 计算实际湿度值*/
*rh = 125 * (((((int) buf[0]) << 8) + (buf[1] & 0xfc)) / 65536.0) - 6;
return 0;
}
作者:Yi_xiong