这是我参与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) */

linux iic驱动_I2C设备用户空间通信方法_Linux I2C i2c-dev框架使用教程

#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设备用户空间通信方法_linux iic驱动_Linux I2C i2c-dev框架使用教程

下边是使用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中

I2C设备用户空间通信方法_Linux I2C i2c-dev框架使用教程_linux iic驱动

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

linux iic驱动_Linux I2C i2c-dev框架使用教程_I2C设备用户空间通信方法

其中,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);

Tagged:
Author

这篇优质的内容由TA贡献而来

刘遄

《Linux就该这么学》书籍作者,RHCA认证架构师,教育学(计算机专业硕士)。

发表回复