APP访问硬件肯定是需要驱动程序的, 对于I2C设备,内核提供了驱动程序drivers/i2c/i2c-dev.c,通过它可以直接使用下面I2C控制器驱动程序来访问I2C设备。 框架如下:
i2c-tools是一套好用的工具,也是一套示例代码。
# IMX6ULL
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/book/Documents/100ask_firefly-rk3288/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
# 修改I2C-Tools的Makefile指定交叉编译工具链
CC ?= gcc
AR ?= ar
STRIP ?= strip
# 改为(指定交叉编译工具链前缀, 去掉问号):
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar
STRIP = $(CROSS_COMPILE)strip
# 在Makefile中,“?=”在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果。
make
# 执行make时,是动态链接,需要把libi2c.so也放到单板上
# 想静态链接的话,执行:make USE_STATIC_LIB=1
# 把tools目录下的可执行文件放到/usr/bin目录下
# 把lib下的/usr/lib/libi2c.so.0.1.1复制到/usr/lib下
# 然后再/usr/lib目录下执行 ln -s libi2c.so.0.1.1 libi2c.so.0
# 列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)
root@npi:~# ./i2cdetect -l
i2c-1 i2c 21a4000.i2c I2C adapter
i2c-4 i2c i2c-0-mux (chan_id 0) I2C adapter
i2c-0 i2c 21a0000.i2c I2C adapter
# 打印某个I2C Adapter的Functionalities, I2CBUS为0、1、2等整数
root@npi:~# ./i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read yes
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes
# 看看有哪些I2C设备, I2CBUS为0、1、2等整数
# --表示没有该地址对应的设备,
# UU表示有该设备并且它已经有驱动程序,
# 数值表示有该设备但是没有对应的设备驱动
root@npi:~# ./i2cdetect -y -a 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
root@npi:~# ./i2cget
Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x08 - 0x77, or 0x00 - 0x7f if -a is given)
MODE is one of:
b (read byte data, default)
w (read word data)
c (write byte/read byte)
Append p for SMBus PEC
# 使用示例
# 读一个字节: I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
i2cget -f -y I2CBUS CHIP-ADDRESS
# 读某个地址上的一个字节:CHIP-ADDRESS表示设备地址, DATA-ADDRESS: 芯片上寄存器地址
# MODE:有2个取值, b-使用`SMBus Read Byte`先发出DATA-ADDRESS, 再读一个字节, 中间无P信号
# c-先write byte, 在read byte,中间有P信号
i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE
# 读某个地址上的2个字节,同上,MODE:w-表示先发出DATA-ADDRESS,再读2个字节
i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE
root@npi:~# ./i2cset
Usage: i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x08 - 0x77, or 0x00 - 0x7f if -a is given)
MODE is one of:
c (byte, no value)
b (byte data, default)
w (word data)
i (I2C block data)
s (SMBus block data)
Append p for SMBus PEC
# 写一个字节: I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址;DATA-ADDRESS就是要写的数据
# value是8位数值,b表示一个byte
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE [b]
# value是16位数值,w表示一个word
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE w
# value是多个数值,s是smbus的多个字节写入
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN s
# value是多个数值,i是i2c的多个字节写入
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN i
root@npi:~# ./i2ctransfer
Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]...
I2CBUS is an integer or an I2C bus name
DESC describes the transfer in the form: {r|w}LENGTH[@address]
1) read/write-flag 2) LENGTH (range 0-65535, or '?')
3) I2C address (use last one if omitted)
DATA are LENGTH bytes for a write message. They can be shortened by a suffix:
= (keep value constant until LENGTH)
+ (increase value by 1 until LENGTH)
- (decrease value by 1 until LENGTH)
p (use pseudo random generator until LENGTH with value as seed)
Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50):
# i2ctransfer 0 w1@0x50 0x64 r8
Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0):
# i2ctransfer 0 w17@0x50 0x42 0xff-
i2ctransfer -f -y 2 w2@0x40 0x00 0x06 r1
其中参数为2表示i2c-2,w2表示写2个字节,0x40是i2c设备地址(7位地址),0x00 0x06是数据,r1表示读取一个字节
AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:
使用SMBus协议
root@npi:~# ./i2cset -f -y 0 0x1e 0 0x3
root@npi:~# ./i2cset -f -y 0 0x1e 0 0x4
# 读取光强值
root@npi:~# ./i2cget -f -y 0 0x1e 0xc w
0x0017
# 用手电照射后,读取光强值
root@npi:~# ./i2cget -f -y 0 0x1e 0xc w
0x15fe
# 读取距离值
root@npi:~# ./i2cget -f -y 0 0x1e 0xe w
0x0f02
# 用手捂住读取距离值
root@npi:~# ./i2cget -f -y 0 0x1e 0xe w
0xbf8f
使用I2C协议
i2ctransfer -f -y 0 w2@0x1e 0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2
i2ctransfer -f -y 0 w1@0x1e 0xe r2
阅读i2ctransfer.c源码,这个是使用普通的i2c方式读写数据
首先需要打开i2c设备,使用open_i2c_dev函数
//i2cbusses.c
int open_i2c_dev(int i2cbus, char *filename, size_t size, int quiet)
{
//...
len = snprintf(filename, size, "/dev/i2c/%d", i2cbus); //通过i2cbus号去打开/dev/i2c/x文件
//...
file = open(filename, O_RDWR);
if (file < 0 && (errno == ENOENT || errno == ENOTDIR)) {
len = snprintf(filename, size, "/dev/i2c-%d", i2cbus); //如果没打开成功/dev/i2c/x文件,就去打开/dev/i2c-x文件
//...
file = open(filename, O_RDWR);
}
//...错误处理
return file;
}
然后设置i2c的从地址,使用set_slave_addr函数,调用了ioctl(file, I2C_SLAVE, address)
//i2cbusses.c
int set_slave_addr(int file, int address, int force)
{
//ioctl(file, I2C_SLAVE, address)
//ioctl(file, I2C_SLAVE_FORCE, address)
if (ioctl(file, force ? I2C_SLAVE_FORCE : I2C_SLAVE, address) < 0) {
fprintf(stderr,
"Error: Could not set address to 0x%02x: %s\n",
address, strerror(errno));
return -errno;
}
return 0;
}
然后构造传输的内容
struct i2c_msg msgs[I2C_RDRW_IOCTL_MAX_MSGS];
msgs[nmsgs].addr = address;
msgs[nmsgs].flags = flags;
msgs[nmsgs].len = len;
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */
/* makes only sense in kernelspace */
/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
调用函数传输i2c数据
struct i2c_rdwr_ioctl_data rdwr;
rdwr.msgs = msgs;
rdwr.nmsgs = nmsgs;
nmsgs_sent = ioctl(file, I2C_RDWR, &rdwr);
阅读i2cget.c、i2cset.c源码,这个是使用smbus.c方式读写数据
首先需要打开i2c设备,使用open_i2c_dev函数,同上
然后设置i2c的从地址,使用set_slave_addr函数,调用了ioctl(file, I2C_SLAVE, address),同上
使用分装好的smbus函数,在include/i2c/smbus.h中有大量的smbus函数
extern __s32 i2c_smbus_access(int file, char read_write, __u8 command,
int size, union i2c_smbus_data *data);
extern __s32 i2c_smbus_write_quick(int file, __u8 value);
extern __s32 i2c_smbus_read_byte(int file);
extern __s32 i2c_smbus_write_byte(int file, __u8 value);
extern __s32 i2c_smbus_read_byte_data(int file, __u8 command);
extern __s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value);
extern __s32 i2c_smbus_read_word_data(int file, __u8 command);
extern __s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value);
extern __s32 i2c_smbus_process_call(int file, __u8 command, __u16 value);
/* Returns the number of read bytes */
extern __s32 i2c_smbus_read_block_data(int file, __u8 command, __u8 *values);
extern __s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length,
const __u8 *values);
extern __s32 i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 length,
__u8 *values);
extern __s32 i2c_smbus_write_i2c_block_data(int file, __u8 command, __u8 length,
const __u8 *values);
/* Returns the number of read bytes */
extern __s32 i2c_smbus_block_process_call(int file, __u8 command, __u8 length,
__u8 *values);
这些接口都调用了一个函数i2c_smbus_access()
__s32 i2c_smbus_access(int file, char read_write, __u8 command,
int size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args;
__s32 err;
args.read_write = read_write;
args.command = command;
args.size = size;
args.data = data;
err = ioctl(file, I2C_SMBUS, &args);
if (err == -1)
err = -errno;
return err;
}
参考文档:https://www.kernel.org/doc/html/latest/i2c/dev-interface.html