I2C Bus(IIC,Inter-Integrated Circuit Bus) 是由Philips公司(现被NXP 收购)推出的一种在电子通信控制领域常用的通信协议。它由时钟线(SCL)和数据线(SDA)两根线构成通信线路,利用上拉电阻将它们拉成高电平(表示总线空闲), 其典型的电压准位为+3.3V或+5V,具有电路简单、连接线少、控制简单、通信速率高等优点。
在I2C总线上每一位数据都由一个同步时钟没冲所对应,也就是在SCL串行时钟的配合下,数据在SDA上从高位向低位一次串行传送每一位数据。I2C通信时序图如下:
I2C 总线在空闲时SDA和SCL都处于高电平状态(由上拉电阻拉成高电平),当主设备要开始一次I2C通信时就发送一个START(S)信号,这个起始位就可以告诉所有I2C 从机,主设备需要开始进行 I2C 通信了;当要结束一次 I2C 通信时,则发送一个 STOP§信号结束本次通信。
首先在主机发送了起始信号后,第二个时序要给出通讯的目标从机的物理地址。并且I2C总线是一种能够实现半双工通信的同步串行通信协议,从主设备的角度来看具有读/写从设备的功能。
这时候I2C的读写地址如上图所示有8位(bit),其中有7bit为物理地址,还有1bit用于表示读/写方向。所以这样的I2C从设备读写通常是一个字节,其中高7位是上面描述的物理地址,最低位用来表示读写方向(0表示写操作,1表示读操作)。
当主机往I2C总线上传输器件的地址时,所有的从机接受到了这个地址后与自己的地址相比较,如果相同则发送一个应答ACK信号,不相同则不发送 应答信号,当主机收到了应答信号后表示建立连接成功,而未收到则表示寻址失败(可以在总线时序的第九个时钟时的电平高低进行判断)。
主/从机在之后的数据通信中,数据接收方(可能是主机也可能是从机)收到传输的一个字节数据后,需要给出响应,此时处在第九个时钟,发送端释放 SDA 线控制权,将 SDA 电平拉高,由接收方控制。
主机在收到从机的应答信号之后,开始给从机发送数据。SDA 数据线上的每个字节必须是 8 位,每次传输的字节数量没有限制,每个字节发送完成之后,从机必须跟一个应答信号。
I2C 总线通信时数据位传输采用MSB(最高位优先)方式发送,其中高电平表示数据位 1,低电平表示数据位 0。当传输的数据位需要改变时(如上一个位发送的是 1,下一个位要发送 0),必须发生在 SCL 为低电平期间。即规定所有设备在SCL高时候,进行数据的获取,SCL低时候,进行数据的变化。
I2C 总线是一种同步、半双工、采用电平信号收发的串行总线,其速率支持:
主机在检测到总线为空闲时,发送一个启动信号“S”,开始一次通信的开始;
主机接着发送一个从设备地址,它由 7bit 物理地址和 1bit 读写控制位 R/W 组成(此时 R/W=0 写操作);
相对应的从机收到命令字节后向主机回馈应答信号 ACK(ACK=0);
主机收到从机的应答信号后开始发送第一个字节的数据;
从机收到数据后返回一个应答信号 ACK;
主机收到应答信号后再发送下一个数据字节;
主机发完最后一个字节并收到 ACK 后,向从机发送一个停止信号 P 结束本次通信并释放总线;
从机收到 P 信号后也退出与主机之间的通信;
主机发送启动信号后,接着发送地址字节(其中 R/W=1 读操作);
对应的从机收到地址字节后,返回一个应答信号并向主机发送数据;
主机收到数据后向从机反馈一个应答信号 ACK;
从机收到应答信号后再向主机发送下一个数据;
当主机完成接收数据后,向从机发送一个 NAK,从机收到非应答信号后便停止发送;
主机发送非应答信号后,再发送一个停止信号,释放总线结束通信;
I.MX6ull有四个I2C外设,但是我们的IGKBoard开发板只使能了两路I2C外设,如下:
I2C2 ---> 触摸屏控制器,rtc时钟,摄像头控制器
I2C1 ---> 40pin扩展口使用,由DToverlay进行开启
GPIO01_IO03 ---> I2C1_SDA
GPIO01_IO03 ---> I2C1_SCL
我们需要使能I2C1,所以我们需要和之前一样修改开发板的DTOverlay配置文件,添加管脚对I2C的支持,eMMC介质的boot分区下的config.txt的路径:/run/media/mmcblk1p1/config.txt
修改如下:
# Enable I2C overlay
dtoverlay_i2c1=yes
重启后可以在/dev
下查看I2C的设备节点是否存在,或者我们可以查看/sys/bus/i2c/devices
中设备文件来查看I2C驱动是否加载:
ls -l /dev/i2c-*
ls /sys/bus/i2c/devices/
当中由于驱动是从0开始编写的,所以当中i2c-0是对饮I2C1的设备节点,而还有1开头的链接文件是I2C2下的设备。
sht20.c
/*********************************************************************************
* Copyright: (C) 2023 Deng Yonghao
* All rights reserved.
*
* Filename: sht20.c
* Description: This file is SHT20 get temperature and humidity code
*
* Version: 1.0.0(2023年03月24日)
* Author: Deng Yonghao
* ChangeLog: 1, Release initial version on "2023年03月24日 20时52分18秒"
*
********************************************************************************/
/*
* Hardware connection:
*
* Sht20 Module RaspberryPi/IGKBoard
* VCC <-----> 3.3V
* SDA <-----> SDA
* SCL <-----> SCL
* GND <-----> GND
*/
#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
static inline void msleep(unsigned long ms);
static inline void dump_buf(const char *prompt, uint8_t *buf, int size);
int sht2x_init(char *i2c_dev);
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];
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_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);
}
/* 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);
}
/* 打印 buf值*/
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 ;
}
/* 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;
}
/* 获取sht20的唯一序列号 详情可以查看数据手册*/
int sht2x_get_serialnumber(int fd, uint8_t *serialnumber, int size)
{
uint8_t buf[4];
if( fd<0 || !serialnumber || size!=8 )
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__ );
return -1;
}
/* Read SerialNumber from Location 1 */
memset(buf, 0, sizeof(buf));
buf[0] = 0xfa; /* command for readout on-chip memory */
buf[1] = 0x0f; /* on-chip memory address */
write(fd, buf, 2);
memset(buf, 0, sizeof(buf));
read(fd, buf, 4);
serialnumber[5]=buf[0]; /* Read SNB_3 */
serialnumber[4]=buf[1]; /* Read SNB_2 */
serialnumber[3]=buf[2]; /* Read SNB_1 */
serialnumber[2]=buf[3]; /* Read SNB_0 */
/* Read SerialNumber from Location 2 */
memset(buf, 0, sizeof(buf) );
buf[0]=0xfc; /* command for readout on-chip memory */
buf[1]=0xc9; /* on-chip memory address */
write(fd, buf, 2);
memset(buf, 0, sizeof(buf) );
read(fd, buf, 4);
serialnumber[1]=buf[0]; /* Read SNC_1 */
serialnumber[0]=buf[1]; /* Read SNC_0 */
serialnumber[7]=buf[2]; /* Read SNA_1 */
serialnumber[6]=buf[3]; /* Read SNA_0 */
dump_buf("SHT2x Serial number: ", serialnumber, 8);
return 0;
}
CC=arm-linux-gnueabihf-gcc
APP_NAME=sht20
all:clean
@${CC} ${APP_NAME}.c -o ${APP_NAME}
clean:
@rm -f ${APP_NAME}
下载交叉编译后的可执行文件,并赋予它可执行权限:
然后运行程序,并可以向sht20传感器哈一口气,可以见得温湿度变化: