这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战
下篇文章简略剖析了怎样编撰一个Linux下的I2C设备驱动程序。编撰驱动程序虽然有一定的门槛,须要熟悉内核各类相关的开发规范,有时为了快速的测试一款I2C设备的功能,临时编撰驱动程序可能会促使工期比较紧张;而且有时I2C设备非常的简单,因此编撰一个单独的驱动程序未免有点“兴师动众”。
i2c-dev框架挺好的解决了里面的问题,使用该框架可以使我们在用户空间上编撰I2C通讯程序。i2c-dev在内核中封装了关于I2C通讯所须要的所有通讯细节,通过ioctl插口将这种功能曝露给用户空间程序调用。用户应用程序使用open/read/write/ioctl系统嗲用就可实现与I2C设备的通讯。
基本原理
在Linux系统下,每位使能的I2C适配器,在/dev目录下就会创建一个字符设备文件(主设备号89),比如/dev/i2c-0,通过这个设备文件,就可以实现与I2C设备的通讯,其实,I2C设备必须首先挂载在该I2C适配器之下。
i2c-dev的内核代码可以参考这儿。其主要的功能就是创建I2C适配器字符设备,并提供如下的功能:
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.compat_ioctl = compat_i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
可以看见i2c-dev提供了read、write、ioctl功能。这儿须要注意的是:read和write方式不支持RepStart模式,也就是每次调用只能发送/接收一个字节的数据,这对于操作稍稍复杂点的I2C设备的局限性太大,通常不会使用这两个插口。ioctl提供了I2C复合数据传输和SMbus传输两种更为通用的数据传输方法linux iic驱动,它们支持一次发送、接收多个字节数据,所以,通常选择这些方法与I2C设备进行通讯。
使用方法
Linux内核I2C系统向用户空间提供了两个主要的i2c-dev插口文件:linux/i2c.h和linux/i2c-dev.h,这两个文件里定义了用户空间与I2C设备进行通讯的各类规范,例如配置设备地址,设置超时时间,设置重试次数以及选用何种I2C通讯方法等。
配置命令
配置命令主要是提供给ioctl系统调用使用,主要命令如下:
i2c-dev.h
#define I2C_RETRIES 0x0701 /* 通信未响应时的重试次数*/
#define I2C_TIMEOUT 0x0702 /* 设置通信的超时时间,单位:jiffies */
/* NOTE: Slave address is 7 or 10 bits, but 10-bit addresses
* are NOT supported! (due to code brokenness)
*/
#define I2C_SLAVE 0x0703 /* 设置从设备地址 */
#define I2C_SLAVE_FORCE 0x0706 /* 当该设备地址被某个驱动程序使用时,强制设置设备地址*/
#define I2C_TENBIT 0x0704 /* 0 for 7 bit addrs, != 0 for 10 bit */
#define I2C_FUNCS 0x0705 /* 获取适配器支持的功能掩码 */
#define I2C_RDWR 0x0707 /* Combined R/W transfer (one STOP only) */

#define I2C_PEC 0x0708 /* != 0 to use PEC with SMBus */
#define I2C_SMBUS 0x0720 /* SMBus transfer */
上述命令的参数如下:
具体到用户空间的i2c通讯,对于这种配置命令的使用方法通常如下:
调用ioctl(xxx_fd,I2C_SLAVE/I2C_SLAVE_FORCE,xx),设置i2c设备通讯地址。调用ioctl(xxx_fd,I2C_RETRIES,xx),设置通讯未响应时的重试次数调用ioctl(xxx_fd,I2C_TIMEOUT,xx),设置通讯超时时间调用ioctl(xxx_fd,I2C_FUNCS,xx)linux iic驱动,查询适配支持的功能网段调用ioctl(xxx_fd,I2C_RDWR,xx)或则ioctl(xxx_fd,I2C_SMBUS,xx)进行数据传输数据传输
数据传输分为两种形式:I2C复合数据传输和SMbus传输,下边分别介绍一下。i2c.h头文件里定义了I2C适配器功能定义。
#define I2C_FUNC_I2C 0x00000001
#define I2C_FUNC_10BIT_ADDR 0x00000002
#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_IGNORE_NAK etc. */
#define I2C_FUNC_SMBUS_PEC 0x00000008
#define I2C_FUNC_NOSTART 0x00000010 /* I2C_M_NOSTART */
#define I2C_FUNC_SLAVE 0x00000020
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_QUICK 0x00010000
#define I2C_FUNC_SMBUS_READ_BYTE 0x00020000
#define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000
#define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000
#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000
#define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000
#define I2C_FUNC_SMBUS_PROC_CALL 0x00800000
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_HOST_NOTIFY 0x10000000
I2C复合数据传输
所谓的I2C复合数据传输,就是基于I2C通讯时序,发送复合的数据,该模式支持RepStart,可以改变数据的发送方向,每次传输过程只有一个结束讯号。该功能须要I2C适配器对于I2C_FUNC_I2C功能的支持。发送和接收的数据通过i2c_msg进行封装,关于i2c_msg我们在怎么编撰一个Linux下的I2C设备驱动程序有过介绍,之后将i2c_msg字段加入到i2c_rdwr_ioctl_data结构中,
struct i2c_rdwr_ioctl_data {
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};
最后,通过ioctl(xxx_fd,I2C_RDWR,xx)进行数据传输。
下边是使用i2c_msg传输数据的示例。
_u8 _buf[] = {0x01, 0x02, 0x03};
_u8 write_buf[16] = {0};
struct i2c_msg[2] = msgs{
{
.addr = 0x48,
.flags = 0, //写数据
.len = 3,
.buf = write_buf,
}
{
.addr = 0x0f,
.flags = I2C_M_RD,//读数据
.len = 6,
.buf = read_buf,
};
struct i2c_rdwr_ioctl_data ioctl_data = {
.msgs = msgs,
.nmsgs = 2,
};
/**/
fd = open("/dev/i2c-x", O_RDWR);
/**/
ioctl(fd, I2C_SLAVE, addr);
/**/
ioctl(fd, I2C_RDWR, &ioctl_data);
里面完成一次复合数据传输,先写入3字节数据,之后读取6个字节数据。通过查看ioctl的返回值,可以晓得发送状态。
SMbus传输
SMbus在怎么编撰一个Linux下的I2C设备驱动程序同样有介绍linux操作系统培训,假如I2C适配器支持SMbus合同,这么就可以使用SMbus合同与设备进行通讯。查看I2C适配器关于SMbus支持情况,可以通过ioctl的I2C_FUNCS命令查询。
使用SMbus进行数据传输redhat linux,须要将数据封装到i2c_smbus_ioctl_data中
struct i2c_smbus_ioctl_data {
__u8 read_write;
__u8 command;
__u32 size;
union i2c_smbus_data __user *data;
};
#define I2C_SMBUS_BLOCK_MAX 32 /* As specified in SMBus standard */
union i2c_smbus_data {
__u8 byte;
__u16 word;
__u8 block[I2C_SMBUS_BLOCK_MAX + 2];
/* block[0] is used for length */
/* and one more for user-space compatibility */
};
/* i2c_smbus_xfer read or write markers */
#define I2C_SMBUS_READ 1
#define I2C_SMBUS_WRITE 0
最后,通过ioctl(xxx_fd,I2C_SMBUS,xx)进行数据传输。SMbus的数据传输合同比较复杂,i2ctools中的smbus.c对每位传输合同都进行了封装,我们可以直接借鉴,参考i2ctools一节。
i2ctools
i2ctools就是基于i2c-dev实现的i2c相关的工具集合,其功能非常的强悍,i2cdetect可以实现系统中i2c设备的基本侦测,i2ctransfer可以进行i2c数据传输。在嵌入式开发中,我们可以利用这种工具来对I2C设备进行基本管理和通讯合同测试。
编译
下载i2ctools源码,i2ctools通常应用于嵌入式Linux环境,所以须要交叉编译。
tar zxvf i2c-tools-4.1.tar.gz
cd i2c-tools-4.1
更改Makefile文件编译器相关配置。
CC := arm-linux-gcc
AR := arm-linux-ar
STRIP := arm-linux-strip
编译完成以后,可以到tools目录下获得i2cdetect和i2ctransfer工具,lib和include目录分别是smbus二次开发库,我们可以直接使用。
i2cdetect
i2cdetect可以查看当前系统i2c适配器和设备相关一些配置信息。
使用帮助
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
i2cdetect -F I2CBUS
i2cdetect -l
I2CBUS is an integer or an I2C bus name
If provided, FIRST and LAST limit the probing range.
查询系统i2c适配器列表
root~$ i2cdetect -l
i2c-0 i2c 21a0000.i2c I2C adapter
查询i2cbus的支持的功能root@zpd~$i2cdetect-F21a0000.i2c或则是0Functionalitiesimplementedby/dev/i2c-0:I2CyesSMBusQuickCommandyesSMBusSendByteyesSMBusReceiveByteyesSMBusWriteByteyesSMBusReadByteyesSMBusWriteWordyesSMBusReadWordyesSMBusProcessCallyesSMBusBlockWriteyesSMBusBlockReadnoSMBusBlockProcessCallnoSMBusPECyesI2CBlockWriteyesI2CBlockReadyes
按照这种,可以确定该i2cbus所支持的I2C通讯方法。
查询i2cbus下的设备地址
root@zpd ~$ i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- UU UU -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
可以看见,目前编号为0的i2cbus下有三个设备,地址分别是:0x32,0x48,0x49。
i2ctransfer
i2ctransfer的功能就是基于SMbus合同,完成数据的传输。
基本格式
Usage: i2ctransfer I2CBUS DESC [DATA] [DESC [DATA]]...
I2CBUS is an integer or an I2C bus name
读取实例
我们通过一个实例演示怎么通过,i2ctransfer与i2c设备通讯。rtc-rx8010是一个通过I2C通讯的RTC芯片,我们可以通过i2ctransfer直接读取当前的时间。具体命令如下:
i2ctransfer 0 w1@0x32 0x10 r7
0x28 0x13 0x15 0x02 0x04 0x08 0x20 //2020-08-04 15:13:28 Tue
其中,w1@0x320x10表示首先写入RTC代表的SEC的寄存器地址,r7表示以SEC寄存开始,连续读取7个数据,分别为SECMINHOURWEEKDAYMONTHYEAR
smbus二次开发
i2ctools提供了完整的SMbus通讯方法,假如,我们须要以SMbus形式与设备进行通讯,那我们可以借助将lib库和smbus.h文件加入到我们的工程中完成smbus合同支持。
libi2c.so libi2c.so.0 libi2c.so.0.1.1
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);
/* Returns the number of read bytes */
/* Until kernel 2.6.22, the length is hardcoded to 32 bytes. If you
ask for less than 32 bytes, your code will only work with kernels
2.6.23 and later. */
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);