Linux i2c-dev简析

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
简介: 上篇文章简要分析了如何编写一个Linux下的I2C设备驱动程序。编写驱动程序毕竟有一定的门槛,需要熟悉内核各种相关的开发规范,有时为了快速的测试一款I2C设备的功能,临时编写驱动程序可能会使得工期比较紧张;并且有时I2C设备十分的简单,为此编写一个单独的驱动程序未免有点“兴师动众”。

上篇文章简要分析了如何编写一个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设备通信地址。


  1. 调用ioctl(xxx_fd, I2C_RETRIES, xx),设置通信未响应时的重试次数


  1. 调用ioctl(xxx_fd, I2C_TIMEOUT, xx),设置通信超时时间


  1. 调用ioctl(xxx_fd, I2C_FUNCS, xx),查询适配支持的功能掩码


  1. 调用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适配器和设备相关一些配置信息。


使用帮助


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


查询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通信方式。


查询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协议,完成数据的传输。


基本格式


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表示设备一个寄存器地址,然后,从该寄存器读取一个字节数据。


读取实例


我们通过一个实例演示如何通过,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);


相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
相关文章
|
9月前
|
设计模式 缓存 网络协议
Linux网络编程 --- 以太网帧格式简析
Linux网络编程 --- 以太网帧格式简析
99 0
|
存储 缓存 Linux
Linux 目录结构学习与简析 Part2
Linux 目录结构学习与简析 Part2
81 0
|
存储 机器学习/深度学习 网络协议
Linux 目录结构学习与简析 Part1
Linux 目录结构学习与简析 Part1
58 0
|
存储 IDE Linux
Linux 磁盘分区方案简析
Linux 磁盘分区方案简析
114 0
|
Linux
linux开发各种I/O操作简析,以及select、poll、epoll机制的对比
linux开发各种I/O操作简析,以及select、poll、epoll机制的对比
237 1
linux开发各种I/O操作简析,以及select、poll、epoll机制的对比
|
Shell Linux 数据安全/隐私保护
|
存储 监控 安全