文章目錄
一.I2C協議
1.1 I2C總線特徵
I2C主要靠2根線來控制,一根是SDA(串行數據線),一根是SCL(串行時鐘線),通過對SCL和SDA線電平高低的控制,來產生I2C總線協議所需要的信號進行數據傳遞。空閒時,兩根線一般被接上上拉電阻拉高,保持高電平。
1.2 I2C總線特徵
I2C總線上分爲主設備與從設備,而且每一個設備都會對應一個唯一的地址,通常爲7位,最後一位用來作爲傳輸方向的標緻,主從設備就通過地址來確定通訊雙方。
在通常的應用中,我們把CPU帶I2C總線接口的模塊作爲主設備,把掛接在總線上的其他設備都作爲從設備。
1.3 I2C總線協議
I2C協議規定,總線上數據的傳輸必須以一個其實信號作爲開始條件,以一個結束信號作爲傳輸的停止條件。這些信號有主設備控制產生。
- 起始條件
總線處於空閒時,SCL與SDA均保持高電平,在此狀態下,SDA線由高電平轉爲低電平,表示產生了一個起始信號 - 結束信號
當SCL爲高而SDA由低到高跳變,則表示產生了一個結束信號
數據傳輸原理:
主設備在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.傳輸結束。
1.4 常見的幾種傳輸
主從設備數據的傳輸,是通過讀寫文件描述符的方式展開的,下面是3種讀寫操作:
-
主設備向從設備寫數據
-
主設備從從設備中讀數據
-
主設備往從設備中寫數據,然後重啓起始條件,緊接着從從設備中讀取數據;或者是主設備從從設備中讀數據,然後重啓起始條件,緊接着主設備往從設備中寫數據
二. sht2x溫溼度傳感器模塊
用戶手冊:Users Guide SHT20
SHT20是使用標準I2C接口的溫溼度傳感器,通過sht20的datasheet我們可以瞭解很多信息,例如:
1111 1110是16進制的0xFE,當使用這個模塊前,需要給從設備寫入這個十六進制數,從而在不重啓電源的情況下讓sht20傳感器重啓並初始化各項指標。
又如:
向從設備發送 1110 0011 表示溫度測量命令,1110 0101 表示溼度測量命令,前面的1110表示主機模式,而我們應該將命令改爲:1111 0011 與 1111 0101 代表非主機模式
只要區別是主機模式時,SCL線被封鎖,有傳感器進行控制,非主機模式的SCL線處於開放狀態,可進行其他通信.
詳細請參考sht20用戶手冊。
三. 基於樹莓派的溫溼度傳感器的應用
3.1 使能i2c驅動
sudo raspi-config
重啓樹莓派才能生效。
3.2 接線
前面提到,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這個地址上
接下來就是獲取溫溼度的代碼了:
3.3 C代碼
/*********************************************************************************
* 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 <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <sys/stat.h>
#include <linux/i2c.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 //監測溼度
//#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;
}
3.4 運行實現
sht20溫溼度傳感器獲取數據的方式與ds18b20溫度傳感器有着很大的差別,一個是通過I2C協議通訊的到相關信息,而後者則是通過一線協議在文件中讀取,較爲簡單。