协议详解
I2C主要靠2根线来控制,一根是SDA(串行数据线),一根是SCL(串行时钟线),通过对SCL和SDA线电平高低的控制,来产生I2C总线协议所需要的信号进行数据传递。空闲时,两根线一般被接上上拉电阻拉高,保持高电平。
I2C总线上分为主设备与从设备,而且每一个设备都会对应一个唯一的地址,通常为7位,最后一位用来作为传输方向的标致,主从设备就通过地址来确定通讯双方。
在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
I2C协议规定,总线上数据的传输必须以一个其实信号作为开始条件,以一个结束信号作为传输的停止条件。这些信号有主设备控制产生。
主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否定应答位。
从上图可知
1.SCL为高,SDA由高跳变为低,表示起始条件;
2.SCL没产生一个时钟信号,SDA上传递1个位的数据;
3.传输完一个字节后,由从设备拉低SDA,回传给主设备一个ACK才能表示这一字节传输完成;
4.SCL为高,SDA由低跳变为高,表示主设备结束对总线的控制,总线重新处于空闲状态。
按地址传输:
I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前 要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来 数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。
1.起始条件结束后,前7个位为从设备地址,第8个位用作传输方向的标致,即主设备 --> 从设备 或者从设备 --> 主设备;
2.每传输完一个字节,都需要从设备回传一个ACK位;
3.传输结束。
主从设备数据的传输,是通过读写文件描述符的方式展开的,下面是3种读写操作:
用户手册:Users Guide SHT20
SHT20是使用标准I2C接口的温湿度传感器,通过sht20的datasheet我们可以了解很多信息,例如:
1111 1110是16进制的0xFE,当使用这个模块前,需要给从设备写入这个十六进制数,从而在不重启电源的情况下让sht20传感器重启并初始化各项指标。
又如:
向从设备发送 1110 0011 表示温度测量命令,1110 0101 表示湿度测量命令,前面的1110表示主机模式,而我们应该将命令改为:1111 0011 与 1111 0101 代表非主机模式
只要区别是主机模式时,SCL线被封锁,有传感器进行控制,非主机模式的SCL线处于开放状态,可进行其他通信.
详细请参考sht20用户手册。
sudo raspi-config
前面提到,I2C总线上的每个设备都有自己的地址,用下面命令可查看地址:
pi@raspberrypi:~ $ sudo i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
可以看到,我的sht20传感器位于0x40这个地址上
接下来就是获取温湿度的代码了:
/*********************************************************************************
* Copyright: (C) 2020 Xiao yang IoT System Studio
* All rights reserved.
*
* Filename: sht20.c
* Description: 获取温湿度
*
* Version: 1.0.0(06/20/2020)
* Author: Lu Xiaoyang <[email protected]>
* ChangeLog: 1, Release initial version on "06/20/2020 09:06:49 AM"
*
********************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SOFTRESET 0xFE
#define TRIGGER_TEMPERATURE_NO_HOLD 0xF3 //监测温度
#define TRIGGER_HUMIDITY_NO_HOLD 0xF5 //监测湿度
//#define I2C_API_IOCTL /* Use I2C userspace driver ioctl API */
#define I2C_API_RDWR /* Use I2C userspace driver read/write API */
static inline void msleep(unsigned long ms);
static inline void dump_buf(const char *prompt, uint8_t *buf, int size);
int sht2x_init(void); int sht2x_softreset(int fd);
int sht2x_get_serialnumber(int fd, uint8_t *serialnumber, int size);
int sht2x_get_temp_humidity(int fd, float *temp, float *rh);
int main(int argc, char **argv)
{
int fd;
float temp;
float rh;
uint8_t serialnumber[8];
fd = sht2x_init();
if(fd < 0)
{
printf("SHT2x initialize failure\n");
return 1;
}
if( sht2x_softreset(fd) < 0 )
{
printf("SHT2x softreset failure\n");
return 2;
}
if( sht2x_get_serialnumber(fd, serialnumber, 8) < 0)
{
printf("SHT2x get serial number failure\n");
return 3;
}
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);
}
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);
}
static inline void dump_buf(const char *prompt, uint8_t *buf, int size)
{
int i;
if( !buf )
{
return ;
}
if( prompt )
{
printf("%s ", prompt);
}
for(i=0; i<size; i++)
{
printf("%02x ", buf[i]);
}
printf("\n");
return ;
}
int sht2x_init(void)
{
int fd;
if( (fd=open("/dev/i2c-1", 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); /* 我的sht20设备地址 */
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] = SOFTRESET;
write(fd, buf, 1);
msleep(50);
return 0;
}
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));
read(fd, buf, 3); //读取温度
dump_buf("Temperature sample data: ", buf, 3);
*temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 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]) / 65536.0) - 6;
return 0;
}
sht20温湿度传感器获取数据的方式与ds18b20温度传感器有着很大的差别,一个是通过I2C协议通讯的到相关信息,而后者则是通过一线协议在文件中读取,较为简单。