关于I2C协议的文章有很多,在本博客中不涉及I2C协议的基础知识,主要是讲解如何利用SHT20传感器采样温湿度。
SHT20基础知识
在编写代码之前需要先了解SHT20的数据手册,了解发生采样温度、湿度的命令,以及如何根据采样结果计算具体的温湿度的值。数据手册的中文版本
通过官方提供的信息和i2c-tools了解sht20的从设备地址为0x40,常见的命令如下所示:
我们需要使用的命令如红色框中所示,其中T表示温度,RH表示相对湿度。非保持主机是指在SHT20测量期间会释放i2c总线,使得主机可以处理其他从设备的通信任务。软复位用于在无需关闭和再次打开电源的情况下,重新启动传感器系统。在接收到这个命令之后,传感器系统开始重新初始化,并恢复默认设置状态。
在了解了测量命令后,还需知道SHT20的通信时序。
如上图所示,SHT20通信的步骤,大体分为两步:
- 写入模式,对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总线
- 参数合法性检验
- 以读写模式打开i2c总线
- 使用ioctl配置i2c总线,I2C_TENBIT用于设置从设备的地址长度,0表示使用7位地址(1表示10位地址);I2C_SLAVE用于设置从设备地址,SHT20的地址为0x40.
- 使用软复位1111 1110,软复位的休眠时间不超过15毫秒。
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; }