Linux I2C(五) I2C字符设备的注册和使用

1,i2c-tools的使用

Android-i2ctools 下载:

https://github.com/skyxiaoyan1/android-i2ctool

编译会生成五个工具:i2cdetect、i2cset、i2cget、i2cdump、i2ctransfer,拷贝到开发板中就可以使用。

i2cdetect:用于扫描 i2c 总线上的设备,并显示地址

i2cset:设置i2c设备某个寄存器的值 

i2cget:读取i2c设备某个寄存器的值 

i2cdump:读取某个i2c设备所有寄存器的值 

i2ctransfer:一次性读写多个字节

Android toybox源码中也有一些i2c tools,但是缺少i2ctransfer工具:

external/toybox/toys/other/i2ctools.c

(1)i2cdetect

用i2cdetect检测有几组i2c总线在系统上,输入:./i2cdetect -l

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.

  y:关闭交互式,不会显示警告信息

  a:扫描总线上所有设备

  q:使用SMBus的"quick write"命令进行检测,不建议使用

  r:使用SMBus的"receive byte"命令进行检测,不建议使用

  i2cbus:指定查询某个总线编号

  first、last:扫描的地址范围

lynkco:/ # i2cdetect -l
i2c-3   i2c             Geni-I2C                                I2C Adapter
i2c-1   i2c             Geni-I2C                                I2C Adapter
i2c-4   i2c             Geni-I2C                                I2C Adapter
i2c-2   i2c             Geni-I2C                                I2C Adapter

用i2cdetect检测挂载在i2c总线上器件,输入 ./i2cdetect -r -y 1(检测i2c-1上的挂载情况)

lynkco:/sys/devices/platform/soc/a94000.i2c/i2c-1/1-0052 # i2cdetect -r -y 10  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- UU -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

两个设备,设备地址0x52和设备地址0x64。

(2)i2cdump

用i2cdump查看器件所有寄存器的值,以总线1上0x52 这个器件为例,输入 i2cdump -f -y 2 0x52

lynkco:/sys/devices/platform/soc/a94000.i2c/i2c-1/1-0052 # i2cdump -f -y 1 0x520  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 09 14 07 08 18 60 22 00 b8 20 20 0e 89 55 40 08    ?????`".?  ??U@?
10: 00 00 10 00 00 81 00 00 02 0d ea 0d d5 00 00 00    ..?..?..?????...
20: 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    4...............
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: a0 07 d0 00 00 00 08 00 00 00 00 00 00 00 00 00    ???...?.........
70: 00 00 00 00 41 00 00 00 00 00 00 00 00 00 00 00    ....A...........
80: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
90: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
a0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
b0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
c0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
d0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
e0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX
f0: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX    XXXXXXXXXXXXXXXX

(3)i2cget

用i2cget 看单个寄存器地址, 以总线1上0x52 0x01这个寄存器为例 i2cget -f -y 1 0x52 0x01

lynkco:/ # i2cget -f -y 1 0x52 0x01
0x14

Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]

  I2CBUS is an integer or an I2C bus name

  ADDRESS is an integer (0x03 - 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

    

    f:强制访问

    y:关闭交互模式,不会提示警告信息

    i2cbus:总线编号

    chip-address:i2c设备地址

    data-address:i2c寄存器地址

    mode:指定读取的大小,b字节,w字,s是SMBus块,i是i2c块

(4)i2cset

i2cset:向i2c设备某个寄存器写入值

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 (0x03 - 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

    

    f:强制访问

    y:指令执行自动yes,否则会提示确认执行Continue? [Y/n] Y,不加参数y会有很多执行提示,可以帮助判断

    r:写入后立即回读寄存器的值,并将结果与写入的值进行比较

    i2cbus:总线编号

    chip-address:i2c设备地址

    data-address:i2c寄存器地址

    value 要写入的值

    mode:指定读取的大小,b字节,w字,s是SMBus块,i是i2c块

设置i2c-1上0x20器件的0x77寄存器值为0x3f

./i2cset -f -y 1 0x20 0x77 0x3f

2,i2c-dev注册

i2c-dev.c文件完全可以被看作是一个i2c设备驱动,不过,它实现的i2c_client是虚拟的,临时的,主要是为了便于从用户空间操作i2c外设。

i2c-dev.c针对每个i2c适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,因此i2c-dev.c的主体是"i2c_driver成员函数 + 字符设备驱动"。

2.1 i2c-dev注册代码流程

static int __init i2c_dev_init(void)
{int res;printk(KERN_INFO "i2c /dev entries driver\n");res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c"); //注册设备编号,起始主设备号89, 起始次设备号为0if (res)goto out;i2c_dev_class = class_create(THIS_MODULE, "i2c-dev"); //创建i2c-dev的class,为在linux文件系统中创建字符设备做准备if (IS_ERR(i2c_dev_class)) {res = PTR_ERR(i2c_dev_class);goto out_unreg_chrdev;}i2c_dev_class->dev_groups = i2c_groups;/* Keep track of adapters which will be added or removed later */res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); //监听i2c adapter设备的增加或移除事件,添加或移除adapter对应的 cdev和deviceif (res)goto out_unreg_class;/* Bind to already existing adapters right away */i2c_for_each_dev(NULL, i2cdev_attach_adapter);//通过函数i2c_for_each_dev遍历已经绑定的adapter,有多少个adapter就调用i2cdev_attach_adapter函数几次return 0;out_unreg_class:class_destroy(i2c_dev_class);
out_unreg_chrdev:unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
out:printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);return res;
}

i2cdev_attach_adapter:

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{struct i2c_adapter *adap;struct i2c_dev *i2c_dev;int res;if (dev->type != &i2c_adapter_type) //device type为adapter才会被创建字符设备return 0;adap = to_i2c_adapter(dev); //通过dev获取对应的i2c adapteri2c_dev = get_free_i2c_dev(adap); //给i2c_dev分配内存空间if (IS_ERR(i2c_dev))return PTR_ERR(i2c_dev);cdev_init(&i2c_dev->cdev, &i2cdev_fops); //初始化一个字符设备结构体,并初始化device的fops,cdev->ops = fops;i2c_dev->cdev.owner = THIS_MODULE;device_initialize(&i2c_dev->dev);i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr); // i2c_dev->dev.devti2c_dev->dev.class = i2c_dev_class; //classi2c_dev->dev.parent = &adap->dev; //parenti2c_dev->dev.release = i2cdev_dev_release;dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr); //设置device name为i2c-x,也就是我们在字符设备创建成功后看到的/dev/i2c-x设备res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev); //添加设备到系统,并且创建对应的字符设备到用户空间,cdev_add(cdev, dev->devt, 1); and device_add(dev);if (res) {put_i2c_dev(i2c_dev, false);return res;}pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",adap->name, adap->nr);return 0;
}

2.2 cdev_device_add函数实现

/**
* cdev_device_add() - add a char device and it's corresponding
*    struct device, linkink
* @dev: the device structure
* @cdev: the cdev structure
*
* cdev_device_add() adds the char device represented by @cdev to the system,
* just as cdev_add does. It then adds @dev to the system using device_add
* The dev_t for the char device will be taken from the struct device which
* needs to be initialized first. This helper function correctly takes a
* reference to the parent device so the parent will not get released until
* all references to the cdev are released.
*
* This helper uses dev->devt for the device number. If it is not set
* it will not add the cdev and it will be equivalent to device_add.
*
* This function should be used whenever the struct cdev and the
* struct device are members of the same structure whose lifetime is
* managed by the struct device.
*
* NOTE: Callers must assume that userspace was able to open the cdev and
* can call cdev fops callbacks at any time, even if this function fails.
*/
int cdev_device_add(struct cdev *cdev, struct device *dev)
{int rc = 0;if (dev->devt) {cdev_set_parent(cdev, &dev->kobj);rc = cdev_add(cdev, dev->devt, 1); //注册字符设备if (rc)return rc;}rc = device_add(dev); // 创建/dev/xxx,device_create()会调用到device_addif (rc)cdev_del(cdev);return rc;
}

2.3 i2cdev_fops

2.3.1 i2c-dev的字符设备操作集
static int i2cdev_open(struct inode *inode, struct file *file)
{unsigned int minor = iminor(inode);struct i2c_client *client;struct i2c_adapter *adap;adap = i2c_get_adapter(minor);if (!adap)return -ENODEV;/* This creates an anonymous i2c_client, which may later be* pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.** This client is ** NEVER REGISTERED ** with the driver model* or I2C core code!!  It just holds private copies of addressing* information and maybe a PEC flag.*/client = kzalloc(sizeof(*client), GFP_KERNEL);if (!client) {i2c_put_adapter(adap);return -ENOMEM;}snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);client->adapter = adap;file->private_data = client; //创建一个临时的i2c_client,不注册进i2c corereturn 0;
}static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,loff_t *offset)
{char *tmp;int ret;struct i2c_client *client = file->private_data;if (count > 8192)count = 8192;tmp = kzalloc(count, GFP_KERNEL);if (tmp == NULL)return -ENOMEM;pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",iminor(file_inode(file)), count);ret = i2c_master_recv(client, tmp, count);if (ret >= 0)if (copy_to_user(buf, tmp, ret))ret = -EFAULT;kfree(tmp);return ret;
}static const struct file_operations i2cdev_fops = {.owner        = THIS_MODULE,.llseek        = no_llseek,.read        = i2cdev_read,.write        = i2cdev_write,
/* 如果是64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的APP运行在32位的kernel上,调用的也是unlocked_ioctl。*/.unlocked_ioctl    = i2cdev_ioctl,
/* compat_ioctl:支持64bit的driver必须要实现ioctl,当有32bit的userspace application call 64bit kernel的IOCTL的时候,这个callback会被调用到。如果没有实现compat_ioctl,那么32位的用户程序在64位的kernel上执行ioctl时会返回错误:Not a typewriter */.compat_ioctl    = compat_i2cdev_ioctl,.open        = i2cdev_open,.release    = i2cdev_release,
};

    I2c-dev.c提供的i2cdev_read()、i2cdev_write()函数对应于用户空间要使用的read()和write()文件操作接口,这两个部分分别调用I2C核心的i2c_master_recv() 和 i2c_master_send()函数来构造一条I2C消息并引发适配器Algorithm通信函数的调用, 以完成消息的传输。

    但是,大多数稍微复杂一点的I2C设备的读写流程并不对应于一条消息,往往需要两条甚至多条消息来进行一次读写周期,在这种情况下,在应用层仍然调用read()、write()文件API来读写I2C设备,将不能正确读写。

    鉴于上述原因,i2c-dev.c中的i2cdev_read() 和 i2cdev_write()函数不具备太强的通用性,只能适用于非RepStart模式的情况。对于由两条以上消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C_RDWR_IOCTL命令。

2.3.2 i2cdev_ioctl()函数框架
static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct i2c_client *client = file->private_data;unsigned long funcs;dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",cmd, arg);switch (cmd) {case I2C_SLAVE:case I2C_SLAVE_FORCE:if ((arg > 0x3ff) ||(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))return -EINVAL;if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))return -EBUSY;/* REVISIT: address could become busy later */client->addr = arg;return 0;case I2C_TENBIT:if (arg)client->flags |= I2C_M_TEN;elseclient->flags &= ~I2C_M_TEN;return 0;case I2C_PEC:return 0;case I2C_FUNCS:funcs = i2c_get_functionality(client->adapter);return put_user(funcs, (unsigned long __user *)arg);case I2C_RDWR: {struct i2c_rdwr_ioctl_data rdwr_arg;struct i2c_msg *rdwr_pa;return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);}case I2C_SMBUS: {return i2cdev_ioctl_smbus(client, data_arg.read_write,data_arg.command,data_arg.size,data_arg.data);}case I2C_RETRIES:if (arg > INT_MAX)return -EINVAL;client->adapter->retries = arg;break;case I2C_TIMEOUT:if (arg > INT_MAX)return -EINVAL;/* For historical reasons, user-space sets the timeout* value in units of 10 ms.*/client->adapter->timeout = msecs_to_jiffies(arg * 10);break;default:/* NOTE:  returning a fault code here could cause trouble* in buggy userspace code.  Some old kernel bugs returned* zero in this case, and userspace code might accidentally* have depended on that bug.*/return -ENOTTY;}return 0;
}
2.3.3 ioctl支持的功能

kernel\msm_kernel\include\uapi\linux\i2c-dev.h

/* /dev/i2c-X ioctl commands.  The ioctl's parameter is always an

* unsigned long, except for:

*    - I2C_FUNCS, takes pointer to an unsigned long

*    - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data

*    - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data

*/

#define I2C_RETRIES    0x0701    /* number of times a device address should

                   be polled when not acknowledging */

#define I2C_TIMEOUT    0x0702    /* set timeout in units of 10 ms */

/* NOTE: Slave address is 7 or 10 bits, but 10-bit addresses

* are NOT supported! (due to code brokenness)

*/

#define I2C_SLAVE    0x0703    /* Use this slave address */

#define I2C_SLAVE_FORCE    0x0706    /* Use this slave address, even if it

                   is already in use by a driver! */

#define I2C_TENBIT    0x0704    /* 0 for 7 bit addrs, != 0 for 10 bit */

#define I2C_FUNCS    0x0705    /* Get the adapter functionality mask */

#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 */

 

2.3.4 ioctl实例

设置从设备地址

ioctl(fd, I2C_SLAVE, SLAVE_ADDR);
ioctl(fd, I2C_SLAVE_FORCE, SLAVE_ADDR);

设置超时时间

ioctl(fd, I2C_TIMEOUT, 1);

超时时间单位:10ms

设置重试次数

ioctl(fd, I2C_RETRIES, 1);

I2C读写操作

struct i2c_rdwr_ioctl_data data;
ioctl(fd, I2C_RDWR, (unsigned long)&data);

i2c-dev的读写操作是通过ioctl系统调用的I2C_RDWR命令完成,将struct i2c_rdwr_ioctl_data结构体的参数传递给内核态;

// include/uapi/linux/i2c-dev.h
struct i2c_rdwr_ioctl_data {struct i2c_msg __user *msgs;    /* pointers to i2c_msgs */__u32 nmsgs;            /* number of i2c_msgs */
};

i2c_rdwr_ioctl_data结构体包含了指向i2c_msg结构体的消息指针msgs,和i2c_msg消息个数的nmsgs;

I2C传输数据是以字节为单位的,具体到i2c_msg结构体,buf表示要传输的数据,len表示传输的数据字节数;

I2C读取,需要两个i2c_msg组成的数组;第一个i2c_msg的buf,保存master向slave发出目标寄存器地址,len表示寄存器地址字节长度;第二个i2c_msg的buf,用来接收slave向master返回的数据,len表示期望读到的数据字节长度;

I2C写入,仅由一个i2c_msg组成;i2c_msg的buf,保存从slave的目标寄存器地址和要写入的数据,len表示期望写入的数据字节长度;

i2c_msg消息以数组格式定义,是为了访问连续,因为数组是连续内存存储的;

2.3.5 i2cdev_ioctl_rdwr函数实现

i2cdev_ioctl_rdwr()函数,处理通过ioctl()系统调用I2C_RDWR命令的操作,即对从设备读写的操作。

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct i2c_client *client = file->private_data;unsigned long funcs;dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",cmd, arg);switch (cmd) {... ... ...case I2C_RDWR: {struct i2c_rdwr_ioctl_data rdwr_arg;struct i2c_msg *rdwr_pa;if (copy_from_user(&rdwr_arg,(struct i2c_rdwr_ioctl_data __user *)arg,sizeof(rdwr_arg)))return -EFAULT;if (!rdwr_arg.msgs || rdwr_arg.nmsgs == 0)return -EINVAL;/** Put an arbitrary limit on the number of messages that can* be sent at once*/if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)return -EINVAL;rdwr_pa = memdup_user(rdwr_arg.msgs,rdwr_arg.nmsgs * sizeof(struct i2c_msg));if (IS_ERR(rdwr_pa))return PTR_ERR(rdwr_pa);return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);}... ... ...}return 0;
}static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,unsigned nmsgs, struct i2c_msg *msgs)
{u8 __user **data_ptrs;int i, res;data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL);if (data_ptrs == NULL) {kfree(msgs);return -ENOMEM;}res = 0;for (i = 0; i < nmsgs; i++) {/* Limit the size of the message to a sane amount */if (msgs[i].len > 8192) {res = -EINVAL;break;}data_ptrs[i] = (u8 __user *)msgs[i].buf;msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);if (IS_ERR(msgs[i].buf)) {res = PTR_ERR(msgs[i].buf);break;}/* memdup_user allocates with GFP_KERNEL, so DMA is ok */msgs[i].flags |= I2C_M_DMA_SAFE;... ... ...//通过i2c_transfer进行i2c消息的收发res = i2c_transfer(client->adapter, msgs, nmsgs);while (i-- > 0) {if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {if (copy_to_user(data_ptrs[i], msgs[i].buf,msgs[i].len))res = -EFAULT;}kfree(msgs[i].buf);}kfree(data_ptrs);kfree(msgs);return res;
}

i2cdev_ioctl_rdwr()函数,完成了消息的收发操作,具体操作:

将i2c_rdwr_ioctl_data数据从用户空间拷贝到内核空间

将i2c_rdwr_ioctl_data.msgs消息数组从用户空间拷贝到内核空间

将i2c_rdwr_ioctl_data.msgs.buf数组从用户空间拷贝到内核空间

通过i2c_transfer()函数,以i2c_msg消息格式数组和从设备通信

2.4 监听i2c adapter设备的增加或移除事件,添加或移除adapter对应的 cdev和device

static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,void *data)
{struct device *dev = data;switch (action) {case BUS_NOTIFY_ADD_DEVICE:return i2cdev_attach_adapter(dev, NULL);case BUS_NOTIFY_DEL_DEVICE:return i2cdev_detach_adapter(dev, NULL);}return 0;
}static struct notifier_block i2cdev_notifier = {.notifier_call = i2cdev_notifier_call,
};

BUS_NOTIFY_ADD_DEVICE notifier event的发出:

device_add(&control->dev);/* Notify clients of device addition.  This call must come* after dpm_sysfs_add() and before kobject_uevent().*/if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);

2.5 i2c cdev和device的创建结果示例

char device:

lynkco:/ # ls -l /dev/i2c-*

crw-rw---- 1 system system 89,   1 1970-11-20 21:04 /dev/i2c-1

crw-rw---- 1 system system 89,   2 1970-11-20 21:04 /dev/i2c-2

crw-rw---- 1 system system 89,   3 1970-11-20 21:04 /dev/i2c-3

crw-rw---- 1 system system 89,   4 1970-11-20 21:04 /dev/i2c-4

i2c-dev clas:

127|lynkco:/sys/class/i2c-dev # ls -l

total 0

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-1 -> ../../devices/platform/soc/a94000.i2c/i2c-1/i2c-dev/i2c-1

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-2 -> ../../devices/platform/soc/984000.i2c/i2c-2/i2c-dev/i2c-2

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-3 -> ../../devices/platform/soc/988000.i2c/i2c-3/i2c-dev/i2c-3

lrwxrwxrwx 1 root root 0 2023-12-05 08:56 i2c-4 -> ../../devices/platform/soc/a84000.i2c/i2c-4/i2c-dev/i2c-4

sysfs device:

lynkco:/sys/devices/platform/soc/a94000.i2c/i2c-1/i2c-dev/i2c-1 # ls -l

total 0

-r--r--r-- 1 root root 4096 2023-12-05 08:56 dev

lrwxrwxrwx 1 root root    0 2023-12-05 08:56 device -> ../../../i2c-1

-r--r--r-- 1 root root 4096 2023-12-05 08:56 name

drwxr-xr-x 2 root root    0 2023-12-05 08:56 power

lrwxrwxrwx 1 root root    0 2023-12-05 08:56 subsystem -> ../../../../../../../class/i2c-dev

-rw-r--r-- 1 root root 4096 2023-12-05 08:56 uevent

3,在用户空间读写I2C设备

3.1 open

用户态使用open函数打开对应的I2C设备节点/dev/i2c-X,如:/dev/i2c-2;

int fd = -1;
fd = open("/dev/i2c-2", O_RDWR);

i2c-dev在open时,为设备节点建立一个i2c_client,但是这个i2c_client并不加添加到i2c_adapter的client链表中,而是在用户关闭设备节点时,自动释放i2c_client。

3.2 read/write实现

使用操作普通文件的接口read()和write()。这两个函数间接调用了i2c_master_recv和 i2c_master_send。

但是在使用之前需要使用I2C_SLAVE设置从机地址,设置可能失败,需要检查返回值。这种通信过程进行I2C层的通信,一次只能进行一个方向的传输。

1)发送

int i2c_write_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{unsigned char *data_wr = NULL;int ret = -1;data_wr = malloc(len + 2);if (!data_wr) {printf("%s, malloc failed!\n", __func__);return -1;}data_wr[0] = addr / 0xff;data_wr[1] = addr % 0xff;memcpy(&data_wr[2], data, len);ioctl(fd, I2C_SLAVE, SLAVE_ADDR);ioctl(fd, I2C_TIMEOUT, 1);ioctl(fd, I2C_RETRIES, 1);ret = write(fd, data_wr, len+2);if (ret < 0) {printf("%s, write failed, ret: 0x%x\n", __func__, ret);return ret;}printf("%s, write ok, num: %d\n", __func__, ret);if (data_wr != NULL) {free(data_wr);data_wr = NULL;}return ret;
}

2)接收

int i2c_read_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{unsigned char addr_slave[2] = { 0 };int ret = -1;ioctl(fd, I2C_SLAVE, SLAVE_ADDR);ioctl(fd, I2C_TIMEOUT, 1);ioctl(fd, I2C_RETRIES, 1);addr_slave[0] = addr / 0xff;addr_slave[1] = addr % 0xff;ret = write(fd, addr_slave, 2);if (ret < 0) {printf("%s, write failed, ret: 0x%x\n", __func__, ret);return ret;}ret = read(fd, data, len);if (ret < 0) {printf("%s, read failed, ret: 0x%x\n", __func__, ret);return ret;}printf("%s, read ok, num: %d\n", __func__, ret);return ret;
}

3.3  ioctl实现

可以更为灵活的read或者write数据,包括i2c_transfer。使用该方法可以以struct i2c_msg为参数,一次读取、或者写入、或者读取加写入,一定数量的数据。

1)发送

int i2c_write_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{struct i2c_rdwr_ioctl_data data_wr;int ret = -1;data_wr.nmsgs = 1;data_wr.msgs = malloc(sizeof(struct i2c_msg) * data_wr.nmsgs);if (!data_wr.msgs) {printf("%s, msgs malloc failed!\n", __func__);return -1;}data_wr.msgs[0].addr = SLAVE_ADDR;data_wr.msgs[0].flags = 0;data_wr.msgs[0].len = len + 2;data_wr.msgs[0].buf = malloc(data_wr.msgs[0].len + 2);if (!data_wr.msgs[0].buf) {printf("%s, msgs buf malloc failed!\n", __func__);return -1;}data_wr.msgs[0].buf[0] = addr / 0xff;data_wr.msgs[0].buf[1] = addr % 0xff;memcpy(&data_wr.msgs[0].buf[2], data, len);ret = ioctl(fd, I2C_RDWR, (unsigned long)&data_wr);if (ret < 0) {printf("%s, ioctl failed, ret: 0x%x\n", __func__, ret);return ret;}if (data_wr.msgs[0].buf != NULL) {free(data_wr.msgs[0].buf);data_wr.msgs[0].buf = NULL;}if (data_wr.msgs != NULL) {free(data_wr.msgs);data_wr.msgs = NULL;}return ret;
}

2)接收

int i2c_read_bytes(int fd, unsigned short addr, unsigned char *data, int len)
{struct i2c_rdwr_ioctl_data data_rd;int ret = -1;int i = 0;data_rd.nmsgs = 2;data_rd.msgs = malloc(sizeof(struct i2c_msg) * data_rd.nmsgs);if (!data_rd.msgs) {printf("%s, msgs malloc failed!\n", __func__);return -1;}data_rd.msgs[0].addr = SLAVE_ADDR;data_rd.msgs[0].flags = 0;data_rd.msgs[0].len = 2;data_rd.msgs[0].buf = malloc(data_rd.msgs[0].len);if (!data_rd.msgs[0].buf) {printf("%s, msgs buf malloc failed!\n", __func__);return -1;}data_rd.msgs[0].buf[0] = addr / 0xff;data_rd.msgs[0].buf[1] = addr % 0xff;data_rd.msgs[1].addr = SLAVE_ADDR;data_rd.msgs[1].flags = I2C_M_RD;data_rd.msgs[1].len = len;data_rd.msgs[1].buf = malloc(data_rd.msgs[1].len);if (!data_rd.msgs[0].buf) {printf("%s, msgs buf malloc failed!\n", __func__);return -1;}memset(data_rd.msgs[1].buf, 0, data_rd.msgs[1].len);ret = ioctl(fd, I2C_RDWR, (unsigned long)&data_rd);if (ret < 0) {printf("%s, ioctl failed, ret: 0x%x\n", __func__, ret);return ret;}memcpy(data, data_rd.msgs[1].buf, len);printf("%s, read ok, num: %d\n", __func__, ret);if (data_rd.msgs[0].buf != NULL) {free(data_rd.msgs[0].buf);data_rd.msgs[0].buf = NULL;}if (data_rd.msgs[1].buf != NULL) {free(data_rd.msgs[1].buf);data_rd.msgs[1].buf = NULL;}if (data_rd.msgs != NULL) {free(data_rd.msgs);data_rd.msgs = NULL;}return ret;
}

3.4 main函数

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>#define SLAVE_ADDR 0x51int arr_show(unsigned char *data, int len)
{int i = 0;for (i = 0; i < len; i++) {printf("data[%d]: 0x%x\n", i, data[i]);}return 0;
}void usage(void)
{printf("xxx -r addr len\n");printf("xxx -w addr data1 data2 ...\n");
}int main(int argc, char *argv[])
{int opt;int fd = -1;unsigned short addr;unsigned char buf[256] = { 0 };int len = 0;int i = 0;if (argc < 4) {usage();return -1;}fd = open("/dev/i2c-2", O_RDWR);if (fd < 0) {printf("%s, open failed!\n", __func__);return -1;}while ((opt = getopt(argc, argv, "w:r:")) != -1) {printf("optarg: %s\n", optarg);printf("optind: %d\n", optind);printf("argc: %d\n", argc);printf("argv[optind]: %s\n", argv[optind]);addr = (unsigned short)strtol(optarg, NULL, 0);printf("addr: %d\n", addr);switch(opt) {case 'w':for (len = 0; optind < argc; optind++, len++) {buf[len] = (unsigned char)strtol(argv[optind], NULL, 0);}printf("len: %d\n", len);i2c_write_bytes(fd, addr, buf, len);break;case 'r':len = (unsigned int)strtol(argv[optind], NULL, 0);printf("len: %d\n", len);i2c_read_bytes(fd, addr, buf, len);arr_show(buf, len);break;default:printf("Invalid parameter!\n");usage;break;}}close(fd);return 0;
}

参考链接:

【I2C】通用驱动i2c-dev分析_i2c-dev.c-CSDN博客

Linux-kernel中的i2c-dev驱动 | Mshrimp blog

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/827626.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

sklearn 笔记 metrics

1 分类 1.1 accuracy_score 分类准确率得分 在多标签分类中&#xff0c;此函数计算子集准确率&#xff1a;y_pred的标签集必须与 y_true 中的相应标签集完全匹配。 1.1.1 参数 y_true真实&#xff08;正确&#xff09;标签y_pred由分类器返回的预测标签normalize 默认为 Tr…

LLama的激活函数SwiGLU 解释

目录 Swish激活函数 1. Swish函数公式 LLaMA模型中的激活函数 1. SwiGLU激活函数 2. SwiGLU激活函数的表达式 3. SwiGLU激活函数的优势 Swish激活函数 Swish是一种激活函数&#xff0c;其计算公式如下&#xff1a; 1. Swish函数公式 Swish(x) x * sigmoid(x) 其中&am…

概率图模型在机器学习中的应用:贝叶斯网络与马尔可夫随机场

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

【重要】Heygen订阅指南和用法详解!让照片学说话?一张照片变演讲?Heygen订阅值得吗?

常见问题 Q&#xff1a;Heygen是什么&#xff1f;Heygen是什么玩意&#xff1f; A&#xff1a;Heygen是一款由AI视频工具,创作者只需要上传视频并选择要翻译的语言&#xff0c;该工具可实现自动翻译、调整音色、匹配嘴型。为了方便理解&#xff0c;笔者利用Heygen制作了一个AI视…

裤裤带你一起学C语言内存函数啦!

目录 1.memcpy的使用和模拟实现 2.memmove使用和模拟实现 3.memset函数的使用 4.memcmp函数的使用 内存函数在<string.h>库中&#xff0c;我们使用内存函数必须先引入<string.h>头文件 1.memcpy的使用和模拟实现 memcpy的函数原型如下&#xff1a; void * m…

Vue--》深入了解 VueUse 功能性工具集

今天博主为大家介绍一款实用性的插件名字叫做 VueUse &#xff0c;它是专门为 Vue.js 生态系统设计的功能性工具集合。其提供了许多可重用的功能函数&#xff0c;可以帮助开发者更轻松地构建 Vue.js 应用程序。其提供了大量的功能&#xff0c;包括状态管理、副作用管理、组合式…

【刷题】前缀和入门

送给大家一句话&#xff1a; 既然已经做出了选择&#xff0c;最好还是先假定自己是对的。焦虑未来和后悔过去&#xff0c;只经历一个就够了。 – 张寒寺 《不正常人类症候群》 ☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆ ☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆ ☆ミヾ(∇≦((ヾ…

react引入iconfont的svg图标

react引入iconfont的svg图标 本文目录 react引入iconfont的svg图标普通图标通过link引入css组件内引入css使用 svg图标通过script引入js组件内引入js使用 通过封装组件自定义封装组件中调用 通过antd封装使用 普通图标 通过link引入css <link rel"stylesheet" h…

基于springboot实现在线考试系统设计【项目源码+论文说明】计算机毕业设计

基于springboot实现在线考试管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于JavaWeb技术的在线考试系统设计与实现的开发全过程。通过分析基于Java Web技术的在线考试系统设计与实现管理的不…

http 3.0 有哪些新特性

HTTP/3 是超文本传输协议&#xff08;HTTP&#xff09;的最新主要版本&#xff0c;其显著特点是放弃了传统的TCP作为传输层协议&#xff0c;转而采用基于UDP的QUIC&#xff08;Quick UDP Internet Connections&#xff09;协议。以下是HTTP/3利用QUIC实现高性能传输的关键特性&…

sketchup{su}安装错误1402

错误如图 解决方法如下 打开autoremove&#xff0c;点击扩展&#xff0c;输入1402&#xff0c;点击搜索 等待修复成功既可尝试重新安装su 软件每周六选择其他方式登录免费使用

微软github技术公开课(web开发、生成式AI、ML、数据科学、物联网)

一些微软在github上公开的课程整理&#xff1a; web开发基础入门 面向初学者的数据数据科学课程 https://microsoft.github.io/Data-Science-For-Beginners/#/ 面向初学者的AI入门课程 https://github.com/microsoft/ai-for-beginners 面向初学者的生成式AI课程 https://…

WordPress自动采集发布AutoPostPro汉化版插件

WP-AutoPostPro 是一款极为出色的WordPress自动采集发布插件&#xff0c;其显著优势在于能够从任何网站抓取内容并自动将其发布到你的WordPress网站上。它实现了对任何网页内容的自动采集和发布&#xff0c;整个采集过程完全自动化&#xff0c;无需手动操作。 项 目 地 址 &…

网络 (基础概念, OSI 七层模型, TCP/IP 五层模型)

网络互连 网络互连: 将多台计算机连接在一起, 完成数据共享 数据共享的本质是网络数据传输, 即计算机之间通过网络来传输数, 也叫做网络通信 根据网络互连的规模不同, 将网络划分为局域网和广域网 注意: 局域网和广域网是相对的概念 局域网LAN 又称内网, 局域网和局域网之间在没…

生成式AI在B端产品的应用分析

AI产品发展到现在&#xff0c;消费端的产品应用还受到比较大的限制&#xff1b;但是在B端&#xff0c;已经有了不错的表现。作者总结了AI产品在B端的几款应用&#xff0c;一起来看看表现如何。 生成式AI在B端产品的应用分析© 由 ZAKER 提供 随着今年生成式AI应用的大范围…

Python基础06-日期和时间的操作方法

在Python中处理日期和时间是编程中常见的需求&#xff0c;无论是安排任务、记录日志还是分析数据。本文将介绍如何在Python中获取当前日期和时间、创建特定日期和时间、格式化日期和时间、解析字符串中的日期和时间、使用时间差、比较日期和时间、从日期/时间中提取组件、处理时…

2024年Java接单平台强力推荐!各个都知名!

Java这几年真卷飞了&#xff0c;一点都不夸张。因此&#xff0c;很多程序员朋友都已经试着转方向、换赛道了。试着接单、找私活就是一大途径。当然&#xff0c;特别是有技术、又有相对空闲时间的朋友&#xff0c;更值得一试。既是增加收入的捷径&#xff0c;又可以提升自我——…

在Milk-v Duo上部署YOLOV8模型

建议自己编译images固件&#xff0c;我使用官方给的固件在部署中出现了一些问题&#xff0c;请参考: 编译Milkv-duo固件-CSDN博客 下载YOLOv8 git clone https://github.com/ultralytics/ultralytics.git 下载yolo_export.zip 下载链接&#xff1a;链接&#xff1a;百度网盘…

mysql常见语法操作笔记

1. 数据库的基本操作 1.1. MYSQL登录与退出 D:\phpstudy_pro\Extensions\MySQL5.7.26\bin 输入 mysql -uroot -proot -h127.0.0.1 退出的三种方法 mysql > exit; mysql > quit; mysql > \q; 1.2. MYSQL数据库的一些解释 注意&#xff1a;数据库就相当于文件夹 …

Nacos的简介及安装和使用

Nacos的简介及安装和使用 1. Nacos简介1.1 核心特性1.2 常见的注册中心1.3 Nacos结构图 2. 如何安装和配置Nacos&#xff1f;2.1 Nacos的安装2.2 如何使用Nacos&#xff1f; 1. Nacos简介 ​ Nacos是一个开源的动态服务发现、配置和服务管理平台&#xff0c;由阿里巴巴开发和维…