Linux i2c-dev简析

上篇文章简要分析了如何编写一个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传输两种更为通用的数据传输方式,它们支持一次发送、接收多个字节数据,所以,一般选择这种方式与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_FUNCS:指向unsigned long的指针
  • I2C_RDWR:指向i2c_rdwr_ioctl_data结构的指针
  • I2C_SMBUS:指向i2c_smbus_ioctl_data结构的指针
  • 其他的参数都是unsigned long

具体到用户空间的i2c通信,对于这些配置命令的使用方式一般如下:

  1. 调用ioctl(xxx_fd, I2C_SLAVE/I2C_SLAVE_FORCE, xx),设置i2c设备通信地址。
  2. 调用ioctl(xxx_fd, I2C_RETRIES, xx),设置通信未响应时的重试次数
  3. 调用ioctl(xxx_fd, I2C_TIMEOUT, xx),设置通信超时时间
  4. 调用ioctl(xxx_fd, I2C_FUNCS, xx),查询适配支持的功能掩码
  5. 调用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设备驱动程序同样有介绍,如果I2C适配器支持SMbus协议,那么就可以使用SMbus协议与设备进行通信。查看I2C适配器关于SMbus支持情况,可以通过ioctl的I2C_FUNCS命令查询。

使用SMbus进行数据传输,需要将数据封装到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
  • read_write:0表示写,1表示读

  • command:设备寄存器地址

  • i2c_smbus_data:block[0]代表数据的读取和写入长度

  • size:SMbus传输类型,具体定义如下:

      #define I2C_SMBUS_QUICK             0
      #define I2C_SMBUS_BYTE              1
      #define I2C_SMBUS_BYTE_DATA         2
      #define I2C_SMBUS_WORD_DATA         3
      #define I2C_SMBUS_PROC_CALL         4
      #define I2C_SMBUS_BLOCK_DATA        5
      #define I2C_SMBUS_I2C_BLOCK_BROKEN  6
      #define I2C_SMBUS_BLOCK_PROC_CALL   7 /* SMBus 2.0 */
      #define I2C_SMBUS_I2C_BLOCK_DATA    8
    

最后,通过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适配器和设备相关一些配置信息。

  1. 使用帮助

     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.
    
  2. 查询系统i2c适配器列表

     root~$ i2cdetect -l
     i2c-0   i2c             21a0000.i2c                             I2C adapter
    
  3. 查询i2c bus的支持的功能
    root@zpd ~$ i2cdetect -F 21a0000.i2c 或者是 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 no
    SMBus Block Process Call no
    SMBus PEC yes
    I2C Block Write yes
    I2C Block Read yes

    根据这些,可以确定该i2c bus所支持的I2C通信方式。

  4. 查询i2c bus下的设备地址

     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的i2c bus下有三个设备,地址分别是:0x32, 0x48, 0x49。

i2ctransfer

i2ctransfer的功能就是基于SMbus协议,完成数据的传输。

  1. 基本格式

     Usage: i2ctransfer I2CBUS DESC [DATA] [DESC [DATA]]...
      I2CBUS is an integer or an I2C bus name
    
    • 其中,I2CBUS表示i2c bus
    • DESC表示传输格式,{r|w}LENGTH[@address],比如,w2@0x48,表示向地址为0x48的设备写入2个字节,DATA表示字节序列
    • 可以没有DATA字段,比如DESC为r2@0x48,从地址为0x48的设备中,读取2个字节。
    • DESC和DATA可以多次出现,比如,i2ctransfer 0 w1@0x32 0x10 r1,表示,首先向地址为0x32的设备写入一个字节0x10,这个 0x10表示设备一个寄存器地址,然后,从该寄存器读取一个字节数据。
  2. 读取实例

    我们通过一个实例演示如何通过,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@0x32 0x10表示首先写入RTC代表的SEC的寄存器地址,r7表示以SEC寄存开始,连续读取7个数据,分别为SEC MIN HOUR WEEK DAY MONTH YEAR

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);

你可能感兴趣的:(ARM-linux,embedded,development,linux,tools)