I2C(Inter-Integrated Circuit BUS)是I2C BUS简称,中文为集成电路总线,是目前应用最广泛的总线之一。
I2C总线在物理连接上由两条线组成:
这两条数据线需要接上拉电阻(如果不接上拉电阻,这两个引脚就处于悬空状态,然而悬空状态引脚的电平是无法确定的)。这样的话,总线在空闲的时候,SCL与SDA都会处于高电平状态。
I2C总线在工作的时候是按照一定协议规则来运行的,并且支持多个从机进行通信的。我们可以通过不同的设备地址来区分这些从机。总线连接的框图如图所示
起始信号:通过前面的介绍,我们可以知道I2C总线在空闲时刻,SCL和SDA都是处于高电平的状态。在时钟线SCL保持高电平的期间,数据线SDA从高电平向低电平的跳变则称作I2C的起始信号。
停止信号:在时钟线SCL保持高电平的期间,数据线SDA从低电平向高电平的跳变则称作I2C的停止信号。
应答位信息:在8位数据传输结束后,发送端将SDA拉高,释放对SDA的控制,此时,接收端获得了对SDA的控制。如果拉低,就代表发出了应答信号(ACK),如果拉高,则代表发出了非应答信号(NACK),然后主机拉高SCL,将该信号发送回发送端
应答信号(ACK):在8位数据发送完成后,接收端把SDA拉低,则代表发送一个应答信号。它可以用来表示一个字节数据成功接收,也可以在主机为接收端时(主机进行读操作),未收到最后一个字节前,表示发送器可继续发送数据。
非应答信号(NACK):在8位数据发送完成后,接收端没有对SDA进行操作,则代表一个非应答信号。它可以用来表示一个字节数据没有成功传输,也可以在主机为接收端时(主机进行读操作),它收到最后一个字节后,主机(接收端)应发送一个NACK信号,以通知从机(发送端)结束数据发送,并释放数据总线,以便主机(接收端)发送一个停止信号停止通信。
我们在主机和从机建立通信之前,首先要确定两点。
第一个问题,我们在介绍I2C协议的时候说过,我们可以通过地址来区分不同的从设备。所以通过从设备地址我们可以区分我们需要通信的对象。
第二个问题,我们可以通过标志位来判断是要读还是写。如果置0那就是写,置1就是读
综合上述内容,I2C通信在起始信号发起后,首先会发送7位的地址和1位的读写标志位,一共8bit的数据(发送的流程和之前所说的数据传输一致,这里不再赘述)。用来确定通信的对象以及数据的流向。
上图就是I2C的完整通信流程了。结合上面所讲的内容,相信各位会很容易理解这张图。简单介绍一下上述流程,首先是起始信号发起,然后发送地址,从机给应答,然后发送8位数据,从机给应答,然后再发送8位数据,从机给应答。最后是结束信号发起,通信结束。
从图中可知时序如下:
I2C_TOOLS的下载链接:https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
作者在这里下载的是i2c-tools-4.3的版本
下载完将压缩包解压到tinkerR-Android9/external目录下(这是安卓系统代码,可以参考环境搭建这章获取下载方式)
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
LOCAL_SRC_FILES := tools/i2cbusses.c tools/util.c
LOCAL_MODULE := i2c-tools
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES:=tools/i2cdetect.c
LOCAL_MODULE:=i2cdetect
LOCAL_CPPFLAGS += -DANDROID
LOCAL_SHARED_LIBRARIES:=libc
LOCAL_STATIC_LIBRARIES := i2c-tools
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES:=tools/i2cget.c
LOCAL_MODULE:=i2cget
LOCAL_CPPFLAGS += -DANDROID
LOCAL_SHARED_LIBRARIES:=libc
LOCAL_STATIC_LIBRARIES := i2c-tools
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES:=tools/i2cset.c
LOCAL_MODULE:=i2cset
LOCAL_CPPFLAGS += -DANDROID
LOCAL_SHARED_LIBRARIES:=libc
LOCAL_STATIC_LIBRARIES := i2c-tools
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES:=tools/i2cdump.c
LOCAL_MODULE:=i2cdump
LOCAL_CPPFLAGS += -DANDROID
LOCAL_SHARED_LIBRARIES:=libc
LOCAL_STATIC_LIBRARIES := i2c-tools
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include $(LOCAL_PATH) $(LOCAL_PATH)/$(KERNEL_DIR)/include
include $(BUILD_EXECUTABLE)
LOCAL_PATH:= $(call my-dir)
################### i2c-tools #########################
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE := i2c-tools
LOCAL_SRC_FILES := \
tools/i2cbusses.c \
tools/util.c \
lib/smbus.c
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
#LOCAL_CFLAGS := -g -Wall -Werror -Wno-unused-parameter
include $(BUILD_STATIC_LIBRARY)
################### i2cdetect #########################
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE:=i2cdetect
LOCAL_SRC_FILES:= \
tools/i2cdetect.c
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES:= \
libc
LOCAL_STATIC_LIBRARIES := \
i2c-tools
LOCAL_CPPFLAGS += -DANDROID
include $(BUILD_EXECUTABLE)
#################### i2cget ###########################
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE:=i2cget
LOCAL_SRC_FILES:= \
tools/i2cget.c
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES:= \
libc
LOCAL_STATIC_LIBRARIES := \
i2c-tools
LOCAL_CPPFLAGS += -DANDROID
include $(BUILD_EXECUTABLE)
##################### i2cset ##########################
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE:=i2cset
LOCAL_SRC_FILES:= \
tools/i2cset.c
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES:= \
libc
LOCAL_STATIC_LIBRARIES := \
i2c-tools
LOCAL_CPPFLAGS += -DANDROID
include $(BUILD_EXECUTABLE)
##################### i2cdump #########################
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE:=i2cdump
LOCAL_SRC_FILES:= \
tools/i2cdump.c
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES:= \
libc
LOCAL_STATIC_LIBRARIES := \
i2c-tools
LOCAL_CPPFLAGS += -DANDROID
include $(BUILD_EXECUTABLE)
################### i2ctransfer #######################
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE:=i2ctransfer
LOCAL_SRC_FILES:= \
tools/i2ctransfer.c
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES:= \
libc
LOCAL_STATIC_LIBRARIES := \
i2c-tools
LOCAL_CPPFLAGS += -DANDROID
include $(BUILD_EXECUTABLE)
./docker_builder/docker-builder-run.sh //进入docker环境
source build/envsetup.sh //配置环境变量
lunch WW_Tinker_Edge_R-userdebug //选择板级配置
cd external/i2c-tools-4.3/
mm //安卓源码单编命令
4.编译成功提示如下
5.编译成功后,会在out/target/product/rk3399pro/system/bin目录下生成五个可执行文件,如下:
i2cdetect i2cdump i2cget i2cset i2ctransfer//此项只有4.x以上版本才有
rk3399pro:/ # i2cdetect -l
i2c-0 i2c rk3x-i2c I2C adapter
i2c-1 i2c rk3x-i2c I2C adapter
i2c-2 i2c rk3x-i2c I2C adapter
i2c-4 i2c rk3x-i2c I2C adapter
i2c-8 i2c rk3x-i2c I2C adapter
i2c-9 i2c DesignWare HDMI I2C adapter
i2c-10 i2c DP-AUX I2C adapter
rk3399pro:/ # i2cdetect -y -r 1 //此处1代表总线设备号 即检测的是i2c-1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 51 52 53 -- -- -- -- -- -- -- -- -- -- -- -- //此处代表挂载的设备地址分别为0x50 0x51 0x52 0x53
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
rk3399pro:/ # i2cdump -f -y 1 0x50 //检测i2c-1总线上地址为0x50的从设备
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 50 eb f6 3f f9 83 32 31 31 30 39 33 33 39 38 32 P?????2110933982
10: 30 30 31 30 31 00 00 00 00 00 ff ff ff ff ff ff 00101...........
20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
30: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
rk3399pro:/ # i2cdump -f -y 1 0x30 0xFC 0x3F //往i2c-1总线上地址为0x30的设备寄存器地址0xFC上写入0x3F
rk3399pro:/ # i2cget -f -y 1 0x50 0x01 //往i2c-1总线上地址为0x50的设备获取0x01寄存器的值
0xeb
//往i2c-1上0x30器件 0x3288寄存器 写入0xFA
rk3399pro:/ # i2ctransfer -f -y 1 w3@0x30 0x32 0x88 0xFA
//往i2c-1上0x30器件 0x3288寄存器 读取2个字节数据
rk3399pro:/ # i2ctransfer -f -y 1 w2@0x30 0x32 0x88 r2
这里测试使用的I2C设备号为6,从机地址为0x20。而I2C-6默认状态是关闭的,需要手动在设备树中打开它。
&i2c6 {
status = "okay";
};
// 1.用i2cdetect检测有多少组i2c总线在系统上
rk3399pro:/data # i2cdetect -l
i2c-0 i2c rk3x-i2c I2C adapter
i2c-1 i2c rk3x-i2c I2C adapter
i2c-2 i2c rk3x-i2c I2C adapter
i2c-4 i2c rk3x-i2c I2C adapter
i2c-6 i2c rk3x-i2c I2C adapter
i2c-8 i2c rk3x-i2c I2C adapter
i2c-9 i2c DesignWare HDMI I2C adapter
i2c-10 i2c DP-AUX I2C adapter
// 2.用i2cdetect检测挂在在i2c总线上的器件
rk3399pro:/ # i2cdetect -y -r 6 //此处6代表总线设备号 即检测的是i2c-6
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
// 3.用i2cdump查看器件所有寄存器的值
rk3399pro:/ # i2cdump -f -y 6 0x20 //检测i2c-6总线上地址为0x20的从设备
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 50 eb f6 3f f9 83 32 31 31 30 39 33 33 39 38 32 P?????2110933982
10: 30 30 31 30 31 00 00 00 00 00 ff ff ff ff ff ff 00101...........
20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
30: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ................
//4.用i2cset来设置单个寄存器的值
rk3399pro:/ # i2cdump -f -y 6 0x20 0xFC 0x3F //往i2c-6总线上地址为0x20的设备寄存器地址0xFC上写入0x3F
//5.用i2cget来获取单个寄存器的值
rk3399pro:/ # i2cget -f -y 6 0x20 0x01 //往i2c-6总线上地址为0x20的设备获取0x01寄存器的值
0xeb
//6.i2ctransfer使用,i2ctransfer支持16bit和32bit寄存器的读写,而i2cset和i2cget只支持8bit的寄存器
//往i2c-6上0x20器件 0x3288寄存器 写入0xFA
rk3399pro:/ # i2ctransfer -f -y 6 w3@0x20 0x32 0x88 0xFA
//往i2c-6上0x20器件 0x3288寄存器 读取2个字节数据
rk3399pro:/ # i2ctransfer -f -y 6 w2@0x20 0x32 0x88 r2
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
/* slave device所对应的I2C控制器的设备节点 */
#define I2C_DEVICE "/dev/i2c-6"
/* slave_device的I2C设备地址 */
#define SLAVE_DEVICE_ADDR 0x20
/*函数名:slave_device_write
**功能:向slave device写数据
**参数:fd:slave device对应I2C控制器设备节点的文件名
** dev_addr:slave device的I2C从设备地址
** reg_addr:slave device的寄存器地址
** data_buf:要向slave device写数据的数据buf
** len:要写多少个字节。本例中当前最大支持为8个字节
**返回值:负数表示操作失败,其他为成功
*/
int slave_device_write(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
{
int ret;
unsigned char msg_buf[9];
struct i2c_rdwr_ioctl_data data;
struct i2c_msg messages;
/* 1. 构建msg_buf*/
/* 1.1. 将要操作的寄存器首地址赋给要进行I2C数据通信的首字节数据 */
msg_buf[0] = reg_addr;
/* 1.2. 将要向slave_device写数据的数据buf赋在I2C数据通信中slave device寄存器的后面 */
if (len < 9) { /* 本demo最大支持一次向slave device写一页大小的8个字节数据 */
memcpy((void *) &msg_buf[1], data_buf, len); //第1位之后是数据
} else {
printf("This function supports up to 8 bytes at a time !!!\n");
return -1;
}
/* 2. 构建 struct i2c_msg messages */
/* 2.1. 赋值slave device的I2C从设备地址 */
messages.addr = dev_addr;
/* 2.2. 赋值flags为本次I2C通信完成写功能 */
messages.flags = 0;
/* 2.3. 赋值len为数据buf的长度 + slave device寄存器地址的数据长度 */
messages.len = len+1;
/* 2.4. 构建消息包的数据buf*/
messages.buf = msg_buf;
/* 3. 构建struct i2c_rdwr_ioctl_data data */
/* 3.1. 将准备好的消息包赋值给i2c_rdwr_ioctl_data中的msgs消息*/
data.msgs = &messages;
/* 3.2. 由于本次I2C通信只有写动作,所以消息数为1次 */
data.nmsgs = 1;
/* 4. 调用驱动层的读写组合的I2C数据传输 */
if(ioctl(fd, I2C_RDWR, &data) < 0)
{
printf("I2C_RDWR err \n");
return -1;
}
/* 5. 等待I2C总线写入完成 */
sleep(1);
return 0;
}
/*函数名:slave_device_read
**功能:从slave_device读数据
**参数:fd:slave_device对应I2C控制器设备节点的文件名
** dev_addr:slave_device的I2C从设备地址
** reg_addr:slave_device的寄存器地址
** data_buf:存放从slave_device读数据的buf
** len:要读多少个字节。
**返回值:负数表示操作失败,其他为成功
*/
int slave_device_read(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
{
int ret;
unsigned char msg_buf[9];
struct i2c_rdwr_ioctl_data data;
struct i2c_msg messages[2];
/* 1. 构建 struct i2c_msg messages */
/* 1.1. 构建第一条消息 messages[0] */
/* 1.1.1. 赋值slave_device的I2C从设备地址 */
messages[0].addr = dev_addr;
/* 1.1.2. 赋值flags为本次I2C通信完成写动作 */
messages[0].flags = 0;
/* 1.1.3. 赋值len为slave_device寄存器地址的数据长度是1 */
messages[0].len = 1;
/* 1.1.4. 本次写动作的数据是要读取slave_device的寄存器首地址*/
messages[0].buf = ®_addr;
/* 1.2. 构建第二条消息 messages[1] */
/* 1.2.1. 赋值slave_device的I2C从设备地址 */
messages[1].addr = dev_addr;
/* 1.1.2. 赋值flags为本次I2C通信完成读动作 */
messages[1].flags = I2C_M_RD;
/* 1.1.3. 赋值len为要读取slave_device寄存器数据长度len */
messages[1].len = len;
/* 1.1.4. 本次读动作的数据要存放的buf位置*/
messages[1].buf = data_buf;
/* 2. 构建struct i2c_rdwr_ioctl_data data */
/* 2.1. 将准备好的消息包赋值给i2c_rdwr_ioctl_data中的msgs消息*/
data.msgs = messages;
/* 2.2. 由于本次I2C通信既有写动作也有读动作,所以消息数为2次 */
data.nmsgs = 2;
/* 3. 调用驱动层的读写组合的I2C数据传输 */
if(ioctl(fd, I2C_RDWR, &data) < 0)
{
printf("I2C_RDWR err \n");
return -1;
}
/* 4. 等待I2C总线读取完成 */
sleep(1);
return 0;
}
int main()
{
int fd,i,ret=0;
unsigned char w_add=0x10;
/* 将要读取的数据buf*/
unsigned char rd_buf[8] = {0};
/* 要写的数据buf*/
unsigned char wr_buf[8] = {0};
printf("hello,this is I2C_RDWR i2c test \n");
/* 打开slave_device对应的I2C控制器文件 */
fd =open(I2C_DEVICE, O_RDWR);
if (fd< 0)
{
printf("open"I2C_DEVICE"failed \n");
}
/* 把要写入的数据写入到后面的buf中 */
for(i=0;i<8;i++)
wr_buf[i]=i;
/* 通过I2C_RDWR完成向slave_device读数据的功能 */
slave_device_write(fd,SLAVE_DEVICE_ADDR,w_add,wr_buf,8);
/* 通过I2C_RDWR完成向slave_device写数据的功能 */
slave_device_read(fd,SLAVE_DEVICE_ADDR,w_add,rd_buf,8);
for(i=0;i<8;i++)
{
printf("rd_buf is :%d\n",rd_buf[i]);
}
/* 完成操作后,关闭slave_device对应的I2C控制器的设备文件 */
close(fd);
return 0;
}