RK3568驱动指南|第十五篇 I2C-第179章在应用程序中使用I2C

news2024/10/6 22:27:40

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十五篇 I2C_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第179章在应用程序中使用I2C

179.1 ioctl控制I2C

ioctl是设备驱动程序中用来控制设备的接口函数,可以在应用程序中通过ioctl控制I2C控制器从而对I2C设备进行读写。RK3568的I2C控制器节点如下所示:

关于Ioctl函数的相关介绍如下所示:

函数原型:

       int ioctl(int fd, unsigned int cmd, unsigned long args);

头文件

       #include <sys/ioctl.h>

函数作用:

用于向设备发送控制和配置命令。

参数含义:

fd :是用户程序打开设备时返回的文件描述符

cmd :是用户程序对设备的控制命令,

args:应用程序向驱动程序下发的参数,如果传递的参数为指针类型,则可以接收驱动向用户空间传递的数据(在下面的实验中会进行使用)

对于I2C控制器的控制命令CMD定义在“include/uapi/linux/i2c-dev.h”文件中,具体内容如下所示:

#define I2C_RETRIES     0x0701  //设置重试次数,即当从设备没有响应时要重新轮询的次数
#define I2C_TIMEOUT     0x0702  //设置超时时间,单位为 10 毫秒
#define I2C_SLAVE       0x0703	//使用此从机地址
#define I2C_SLAVE_FORCE 0x0706	//强制使用此从机地址
#define I2C_TENBIT      0x0704	//0 表示 7 位地址, 非 0 表示 10 位地址
#define I2C_FUNCS       0x0705	//获取适配器功能掩码
#define I2C_RDWR        0x0707  //执行合并读写传输(只有一个 STOP 信号)
#define I2C_PEC         0x0708	//使用 PEC(校验码)进行 SMBus 传输
#define I2C_SMBUS       0x0720  //执行 SMBus 传输

这里提供了9个ioctl 控制命令,在本章节的读写实验中只会用到I2C_RDWR,用来进行读写传输,而第三个参数args为要传输的数据,数据类型为i2c_rdwr_ioctl_data,该结构体具体内容如下所示:

struct i2c_rdwr_ioctl_data {
    /* 指向 i2c_msg 结构体的指针数组 */
    struct i2c_msg __user *msgs;
    /* i2c_msg 结构体的数量 */
    __u32 nmsgs;
};

这个结构体用于在 I2C_RDWR ioctl 调用中传递 I2C 消息。其中:msgs 是一个指向 i2c_msg 结构体数组的指针,用于存储一个或多个 I2C 消息。nmsgs 是 i2c_msg 结构体数组的长度,即 I2C 消息的数量。

所以跟上面章节编写的FT5X06 I2C通信驱动程序相同,在应用程序中也要编写对应的I2C读写函数,编写完成的读函数 ft5x06_read_reg内容如下所示:

/**
 * @brief 从 I2C 设备的寄存器中读取数据
 * @param fd 打开的 I2C 设备文件描述符
 * @param slave_addr I2C 设备的从机地址
 * @param reg_addr 要读取的寄存器地址
 * @return 寄存器的值
 */
int ft5x06_read_reg(int fd, unsigned char slave_addr, unsigned char reg_addr) {
    unsigned char data;
    struct i2c_rdwr_ioctl_data i2c_msgs;
    int ret;

    // 定义两个 i2c_msg 结构体, 第一个用于写入寄存器地址, 第二个用于读取数据
    struct i2c_msg dev_msgs[] = {
        [0] = {
            .addr = slave_addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },
        [1] = {
            .addr = slave_addr,
            .flags = I2C_M_RD, // 设置读取标志
            .len = sizeof(data),
            .buf = &data,
        }
    };

    i2c_msgs.msgs = dev_msgs;
    i2c_msgs.nmsgs = 2; // 两个 i2c_msg 结构体

    // 调用 ioctl 函数执行读取操作
    ret = ioctl(fd, I2C_RDWR, &i2c_msgs);
    if (ret < 0) {
        printf("read error\n");
        return ret;
    }

    return data;
}

编写完成的写函数 ft5x06_write_reg内容如下所示:

/**
 * @brief 向 I2C 设备的寄存器写入数据
 * @param fd 打开的 I2C 设备文件描述符
 * @param slave_addr I2C 设备的从机地址
 * @param reg_addr 要写入的寄存器地址
 * @param data 要写入的数据
 * @param len 数据长度
 */
void ft5x06_write_reg(int fd, unsigned char slave_addr, unsigned char reg_addr, unsigned char *data, int len)
{
    unsigned char buff[256];
    struct i2c_rdwr_ioctl_data i2c_msgs;
    int ret;

    // 定义一个 i2c_msg 结构体, 用于写入寄存器地址和数据
    struct i2c_msg dev_msgs[] = {
        [0] = {
            .addr = slave_addr,
            .flags = 0,
            .len = len + 1,
            .buf = buff,
        }
    };

    // 将寄存器地址和数据拷贝到 buff 数组中
    buff[0] = reg_addr;
    memcpy(&buff[1], data, len);

    i2c_msgs.msgs = dev_msgs;
    i2c_msgs.nmsgs = 1;

    // 调用 ioctl 函数执行写入操作
    ret = ioctl(fd, I2C_RDWR, &i2c_msgs);
    if (ret < 0) {
        printf("write error\n");
    }
}

179.2 编写应用测试程序

在上个小节中讲解了如何使用ioctl控制I2C设备,并填充了相应的读函数和写函数,在本小节将编写一个完整的应用程序,用来测试读函数和写函数能否正常工作。

编写好的测试程序存放位置为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\108_ft5x06_06,具体内容如下所示:

#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>

/**
 * @brief 从 I2C 设备的寄存器中读取数据
 * @param fd 打开的 I2C 设备文件描述符
 * @param slave_addr I2C 设备的从机地址
 * @param reg_addr 要读取的寄存器地址
 * @return 寄存器的值
 */
int ft5x06_read_reg(int fd, unsigned char slave_addr, unsigned char reg_addr) {
    unsigned char data;
    struct i2c_rdwr_ioctl_data i2c_msgs;
    int ret;

    // 定义两个 i2c_msg 结构体, 第一个用于写入寄存器地址, 第二个用于读取数据
    struct i2c_msg dev_msgs[] = {
        [0] = {
            .addr = slave_addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },
        [1] = {
            .addr = slave_addr,
            .flags = I2C_M_RD, // 设置读取标志
            .len = sizeof(data),
            .buf = &data,
        }
    };

    i2c_msgs.msgs = dev_msgs;
    i2c_msgs.nmsgs = 2; // 两个 i2c_msg 结构体

    // 调用 ioctl 函数执行读取操作
    ret = ioctl(fd, I2C_RDWR, &i2c_msgs);
    if (ret < 0) {
        printf("read error\n");
        return ret;
    }

    return data;
}

/**
 * @brief 向 I2C 设备的寄存器写入数据
 * @param fd 打开的 I2C 设备文件描述符
 * @param slave_addr I2C 设备的从机地址
 * @param reg_addr 要写入的寄存器地址
 * @param data 要写入的数据
 * @param len 数据长度
 */
void ft5x06_write_reg(int fd, unsigned char slave_addr, unsigned char reg_addr, unsigned char *data, int len)
{
    unsigned char buff[256];
    struct i2c_rdwr_ioctl_data i2c_msgs;
    int ret;

    // 定义一个 i2c_msg 结构体, 用于写入寄存器地址和数据
    struct i2c_msg dev_msgs[] = {
        [0] = {
            .addr = slave_addr,
            .flags = 0,
            .len = len + 1,
            .buf = buff,
        }
    };

    // 将寄存器地址和数据拷贝到 buff 数组中
    buff[0] = reg_addr;
    memcpy(&buff[1], data, len);

    i2c_msgs.msgs = dev_msgs;
    i2c_msgs.nmsgs = 1;

    // 调用 ioctl 函数执行写入操作
    ret = ioctl(fd, I2C_RDWR, &i2c_msgs);
    if (ret < 0) {
        printf("write error\n");
    }
}

int main(int argc, char *argv[])
{
    int fd;
    int ID_G_THGROUP;

    // 打开 I2C 设备文件
    fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0) {
        printf("open error\n");
        return fd;
    }

    // 向 0x38 地址的寄存器 0x80 写入 0x55
    unsigned char data = 0x55;
    ft5x06_write_reg(fd, 0x38, 0x80, &data, 1);

    // 从 0x38 地址的寄存器 0x80 读取数据
    ID_G_THGROUP = ft5x06_read_reg(fd, 0x38, 0x80);
    printf("ID_G_THGROUP is 0x%02X\n", ID_G_THGROUP);

    return 0;
}

179.3.2 编译应用程序

首先进行应用程序的编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图所示:

 aarch64-linux-gnu-gcc app.c -o app

然后将编译完成的可执行程序拷贝到开发板上.

179.3.3 运行测试

由于本章节是使用应用程序来控制I2C设备,所以并不需要加载对应的驱动,系统启动之后,使用以下命令运行上个小节编译完成的可执行程序,具体如下图所示:

 

可以看到读取到的ID_G_THGROUP变量值为0x55,表示编写的写函数和读函数都正常工作,至此,关于在应用程序中使用I2C实验就完成了。

179.4 通用I2C驱动讲解

在前面的小节中,我们学习了如何使用ioctl函数来控制具体的I2C设备,那在驱动程序中是如何实现该功能的呢?这就需要对Linux源码中的通用I2C驱动程序进行分析了。

通用I2C驱动文件为drivers/i2c/i2c-dev.c,它为 I2C 外设提供了统一的驱动框架,分为 I2C 客户端 (I2C client) 和 I2C 驱动 (I2C driver)。它为上层应用程序提供了通用的设备节点 /dev/i2c-X(X 代表 I2C 总线号)。应用程序可以直接通过打开这个设备节点 /dev/i2c-X,并使用标准的 I/O 操作如 open()、ioctl()、read()、write() 等来与 I2C 从设备进行通信。

该驱动程序一般情况下都是默认使能的,具体路径如下所示,如果进入系统之后发现在/dev目录下没有i2c-X相应的节点,就需要修改内核配置文件了。

然后来对该驱动程序进行分析,首先来看它的入口函数i2c_dev_init,具体内容如下所示:

static int __init i2c_dev_init(void)
{
    int res;

    // 打印内核日志,表示 i2c /dev 条目驱动已经初始化
    printk(KERN_INFO "i2c /dev entries driver\n");

    // 注册字符设备驱动,主设备号为 I2C_MAJOR,次设备号范围为 0 到 I2C_MINORS-1,设备名为"i2c"
    res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
    if (res)
        goto out;

    // 创建一个 class 对象,名称为 "i2c-dev",用于在用户空间创建设备节点
    i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
    if (IS_ERR(i2c_dev_class)) {
        res = PTR_ERR(i2c_dev_class);
        goto out_unreg_chrdev;
    }
    // 将 i2c_groups 数组设置为该 class 的 dev_groups 属性
    i2c_dev_class->dev_groups = i2c_groups;

    // 注册一个总线通知函数 i2cdev_notifier,用于追踪 i2c 总线上新添加或删除的适配器
    res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
    if (res)
        goto out_unreg_class;

    // 立即绑定已经存在的 i2c 适配器到 i2c 设备
    i2c_for_each_dev(NULL, i2cdev_attach_adapter);

    return 0;

out_unreg_class:
    // 销毁创建的 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;
}

第9行:注册字符设备驱动,为 i2c 设备创建设备节点。

第13-20行:创建一个 class 对象,用于在用户空间创建设备节点。

第23-25行:调用bus_register_notifier函数注册一个总线通知函数,用于追踪 i2c 总线上新添加或删除的适配器。

第28行:循环遍历I2C适配器并调用i2cdev_attach_adapter函数绑定已经存在的 i2c 适配器到 i2c 设备。

i2cdev_attach_adapter函数的具体内容如下所示:

static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
    struct i2c_adapter *adap;
    struct i2c_dev *i2c_dev;
    int res;

    // 检查设备类型是否为 i2c_adapter_type,如果不是则返回
    if (dev->type != &i2c_adapter_type)
        return 0;
    adap = to_i2c_adapter(dev);

    // 从 i2c_dev_list 中获取一个空闲的 i2c_dev 结构体
    i2c_dev = get_free_i2c_dev(adap);
    if (IS_ERR(i2c_dev))
        return PTR_ERR(i2c_dev);

    // 初始化 i2c_dev 结构体中的 cdev 字段,设置文件操作函数为 i2cdev_fops
    cdev_init(&i2c_dev->cdev, &i2cdev_fops);
    i2c_dev->cdev.owner = THIS_MODULE;

    // 初始化设备对象 i2c_dev->dev
    device_initialize(&i2c_dev->dev);
    // 设置设备号为主设备号 I2C_MAJOR 和次设备号 adap->nr
    i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr);
    i2c_dev->dev.class = i2c_dev_class;
    i2c_dev->dev.parent = &adap->dev;
    i2c_dev->dev.release = i2cdev_dev_release;
    // 设置设备名称为 "i2c-{adap->nr}"
    dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr);

    // 将 i2c_dev 设备添加到设备树中
    res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev);
    if (res) {
        // 如果添加失败,则释放 i2c_dev 结构体
        put_i2c_dev(i2c_dev, false);
        return res;
    }

    // 打印调试信息,表示适配器 [adap->name] 已注册为次设备号 adap->nr
    pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
             adap->name, adap->nr);
    return 0;
}

这个函数的作用是在系统总线上发现新的 i2c 适配器时,为其创建对应的字符设备节点。

第8-10行:检查设备类型是否为 i2c_adapter_type,如果不是则直接返回

第13-15行:从 i2c_dev_list 中获取一个空闲的 i2c_dev 结构体。

第18-19行:初始化 i2c_dev 结构体中的 cdev 字段,设置文件操作函数为 i2cdev_fops。

第22-29行:初始化设备对象 i2c_dev->dev,设置设备号、class、父设备、设备名称等属性。

第32-37行:调用cdev_device_add() 函数,将字符设备与对应的物理设备进行关联,并将 i2c_dev设备添加到设备树中。

i2c_dev结构体中的cdev字段指定的文件操作集结构体为i2cdev_fops,具体内容如下所示:

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,
};

分别实现了常用的open、read、write和ioctl,接下来对上述函数的实现进行讲解,首先来看i2cdev_open函数,函数的具体内容如下所示:

static int i2cdev_open(struct inode *inode, struct file *file)
{
    // 获取次设备号
    unsigned int minor = iminor(inode);
    // 声明 i2c_client 和 i2c_adapter 结构体指针
    struct i2c_client *client;
    struct i2c_adapter *adap;

    // 根据次设备号获取对应的 i2c_adapter
    adap = i2c_get_adapter(minor);
    // 如果没有找到对应的 i2c_adapter,返回 -ENODEV 错误
    if (!adap)
        return -ENODEV;

    /* 
     * 创建一个匿名的 i2c_client 结构体实例
     * 该 i2c_client 实例稍后可以通过 I2C_SLAVE 或 I2C_SLAVE_FORCE 命令设置从设备地址
     * 但是这个 i2c_client 实例永远不会被注册到设备模型或 I2C 核心代码中
     * 它只是持有私有的地址信息和可能的 PEC 标志
     */
    client = kzalloc(sizeof(*client), GFP_KERNEL);
    // 如果内存分配失败,释放 i2c_adapter 并返回 -ENOMEM 错误
    if (!client) {
        i2c_put_adapter(adap);
        return -ENOMEM;
    }
    // 设置 i2c_client 的名称
    snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);

    // 将 i2c_adapter 赋值给 i2c_client 的 adapter 字段
    client->adapter = adap;
    // 将 i2c_client 指针保存到 file 的 private_data 字段
    file->private_data = client;

    // 返回成功
    return 0;
}

第4-13行:获取次设备号,根据次设备号获取对应的i2c_adapter实例。

第21-28行:创建一个匿名的i2c_client实例,并设置它的一些基本信息。

第31行:i2c_adapter 赋值给i2c_client的adapter字段。

第33行:将 i2c_client 指针保存到file的私有数据private_data字段。

然后来看I2C读函数i2cdev_read,该函数的具体内容如下所示:

static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,
        loff_t *offset)
{
    // 声明一个临时缓冲区指针
    char *tmp;
    // 保存 i2c_master_recv 的返回值
    int ret;

    // 从 file 的 private_data 字段获取 i2c_client 指针
    struct i2c_client *client = file->private_data;

    // 限制最大读取字节数为 8192
    if (count > 8192)
        count = 8192;

    // 分配临时缓冲区
    tmp = kzalloc(count, GFP_KERNEL);
    // 如果内存分配失败,返回 -ENOMEM 错误
    if (tmp == NULL)
        return -ENOMEM;

    // 打印调试信息
    pr_debug("i2c-dev: i2c-%d reading %zu bytes.\n",
        iminor(file_inode(file)), count);

    // 使用 i2c_master_recv 函数从 i2c_client 设备读取数据
    ret = i2c_master_recv(client, tmp, count);
    // 如果读取成功
    if (ret >= 0)
        // 将读取的数据拷贝到用户空间缓冲区
        if (copy_to_user(buf, tmp, ret))
            // 如果拷贝失败,返回 -EFAULT 错误 
            ret = -EFAULT;
    // 释放临时缓冲区
    kfree(tmp);
    // 返回实际读取的字节数,或者错误码
    return ret;
}

在第27行调用了i2c_master_recv函数,该函数在前面I2C子系统框架以及编写I2C通信驱动的章节中讲解过,最后会调用i2c_transfer函数进行I2C数据的读取,最后在第31行调用copy_to_user将读取到的数据拷贝到用户空间。

然后来看I2C写函数i2cdev_write,该函数的具体内容如下所示:

static ssize_t i2cdev_write(struct file *file, const char __user *buf,
        size_t count, loff_t *offset)
{
    // 保存 i2c_master_send 的返回值
    int ret;
    // 声明一个临时缓冲区指针
    char *tmp;
    // 从 file 的 private_data 字段获取 i2c_client 指针
    struct i2c_client *client = file->private_data;

    // 限制最大写入字节数为 8192
    if (count > 8192)
        count = 8192;

    // 分配一个临时缓冲区,并从用户空间拷贝数据到该缓冲区
    tmp = memdup_user(buf, count);
    // 如果内存拷贝失败,返回错误码
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    // 打印调试信息
    pr_debug("i2c-dev: i2c-%d writing %zu bytes.\n",
        iminor(file_inode(file)), count);

    // 使用 i2c_master_send 函数将数据写入 i2c_client 设备
    ret = i2c_master_send(client, tmp, count);
    // 释放临时缓冲区
    kfree(tmp);
    // 返回实际写入的字节数,或者错误码
    return ret;
}

在第26行调用了i2c_master_send函数,该函数和i2c_master_recv函数一样,最后都会调用i2c_transfer函数,只是参数不同这里是将I2C数据写入I2C设备。

最后来看i2cdev_ioctl函数,正是因为i2cdev_ioctl函数,才能够在应用程序中调用ioctl控制I2C设备,该函数的具体内容如下所示:

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    // 从 file 的 private_data 字段获取 i2c_client 指针
    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);

    // 根据不同的 ioctl 命令进行处理
    switch (cmd) {
    case I2C_SLAVE:
    case I2C_SLAVE_FORCE:
        // 检查从设备地址是否合法
        if ((arg > 0x3ff) ||
            (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
            return -EINVAL;
        // 如果是 I2C_SLAVE 命令,检查地址是否被占用
        if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
            return -EBUSY;
        // 设置从设备地址
        client->addr = arg;
        return 0;
    case I2C_TENBIT:
        // 设置 10 位地址模式
        if (arg)
            client->flags |= I2C_M_TEN;
        else
            client->flags &= ~I2C_M_TEN;
        return 0;
    case I2C_PEC:
        // 设置 PEC 标志
        if (arg)
            client->flags |= I2C_CLIENT_PEC;
        else
            client->flags &= ~I2C_CLIENT_PEC;
        return 0;
    case I2C_FUNCS:
        // 获取 i2c 适配器的功能
        funcs = i2c_get_functionality(client->adapter);
        // 将结果写入用户空间的地址
        return put_user(funcs, (unsigned long __user *)arg);
    case I2C_RDWR: {
        // 处理 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;

        // 限制最大消息数为 I2C_RDWR_IOCTL_MAX_MSGS
        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);

        // 调用 i2cdev_ioctl_rdwr 函数进行 i2c 读写操作
        return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
    }
    case I2C_SMBUS: {
        // 处理 I2C_SMBUS 命令
        struct i2c_smbus_ioctl_data data_arg;
        // 从用户空间拷贝参数结构体
        if (copy_from_user(&data_arg,
                (struct i2c_smbus_ioctl_data __user *) arg,
                sizeof(struct i2c_smbus_ioctl_data)))
            return -EFAULT;
        // 调用 i2cdev_ioctl_smbus 函数进行 SMBus 读写操作
        return i2cdev_ioctl_smbus(client, data_arg.read_write,
                data_arg.command,
                data_arg.size,
                data_arg.data);
    }
    case I2C_RETRIES:
        // 设置 i2c 适配器的重试次数
        if (arg > INT_MAX)
            return -EINVAL;
        client->adapter->retries = arg;
        break;
    case I2C_TIMEOUT:
        // 设置 i2c 适配器的超时时间
        if (arg > INT_MAX)
            return -EINVAL;
        // 用户空间设置的单位是 10 ms
        client->adapter->timeout = msecs_to_jiffies(arg * 10);
        break;
    default:
        // 不支持的 ioctl 命令
        return -ENOTTY;
    }
    return 0;
}

在前面应用程序的编写中只调用了I2C_RDWR用来进行I2C数据的读写,所以只需要关注45-72行即可,而这部分的重点在最后一行调用i2cdev_ioctl_rdwr函数进行i2c读写操作,i2cdev_ioctl_rdwr函数最终也是调用了i2c_transfer函数进行的读写,至此对于通用I2C驱动就讲解完成了。

根据本小节的分析,无论是i2cdev_ioctl函数、i2cdev_read函数还是i2cdev_write函数,最终都是调用的i2c_transfer函数进行的读写,所以在下个小节中将会使用i2cdev_read函数和i2cdev_write函数在应用程序中控制I2C。

179.5 编写应用测试程序

在前面个小节中讲解了如何使用ioctl控制I2C设备,而在上个小节中讲解了i2cdev_ioctl函数、i2cdev_read函数和i2cdev_write函数的实现,发现每个函数最终都是由i2c_transfer函数实现的,所以在应用程序中直接使用read函数和write函数也可以在应用程序中使用I2C,本小节将编写对应的测试应用程序。

编写好的测试程序存放位置为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\109_ft5x06_07,具体内容如下所示:

#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <unistd.h>

/*
 * 从 I2C 设备读取寄存器值
 * @param fd: I2C 设备文件句柄
 * @param reg_addr: 要读取的寄存器地址
 */
void ft5x06_read_reg(int fd, unsigned char reg_addr) {
    unsigned char rd_data[1];
    rd_data[0] = reg_addr;
    write(fd, rd_data, 1);
    read(fd, rd_data, 1);
    printf("reg value is %x\n", rd_data[0]);
}

/*
 * 向 I2C 设备写入寄存器值
 * @param fd: I2C 设备文件句柄
 * @param reg_addr: 要写入的寄存器地址
 * @param data: 要写入的数据
 */
void ft5x06_write_reg(int fd, unsigned char reg_addr, unsigned char data) {
    unsigned char wr_data[2];
    wr_data[0] = reg_addr;
    wr_data[1] = data;
    write(fd, wr_data, 2);
}

int main(int argc, char *argv[]) {
    int fd;

    // 打开 I2C 设备文件
    fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0) {
        printf("open error\n");
        return fd;
    }

    // 设置从设备地址为 0x38
    ioctl(fd, I2C_SLAVE_FORCE, 0x38);

    // 向寄存器 0x80 写入数据 0x66
    ft5x06_write_reg(fd, 0x80, 0x66);

    // 读取寄存器 0x80 的值
    ft5x06_read_reg(fd, 0x80);

    return 0;
}

179.3 运行测试

179.3.2 编译应用程序

首先进行应用程序的编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图所示:

 aarch64-linux-gnu-gcc app.c -o app

然后将编译完成的可执行程序拷贝到开发板上.

179.3.3 运行测试

由于本章节是使用应用程序来控制I2C设备,所以并不需要加载对应的驱动,系统启动之后,使用以下命令运行上个小节编译完成的可执行程序,具体如下图所示:

可以看到读取到的变量值为0x66,表示编写的写函数和读函数都正常工作,至此,关于在应用程序中使用wirte和read对I2C设备进行读写的实验就完成了。 

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

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

相关文章

WPDRRC信息安全体系架构模型

构建信息安全保障体系框架应包括技术体系、组织机构体系和管理体系等三部分&#xff0c;也就是说&#xff1a;人、管理和技术手段是信息安全架构设计的三大要素&#xff0c;而构成动态的信息与网络安全保障体系框架是实现系统的安全保障。 1.WPDRRC信息安全模型的定义 WPDRRC…

Oracle - 数据库打补丁实践

原文&#xff1a;https://www.cnblogs.com/ddzj01/p/12097467.html 一、概述 本文将介绍如何给oracle数据库打最新补丁&#xff0c;数据库版本为11.2.0.4单实例&#xff0c;操作系统为redhat6.5 二、下载相关升级包 1. 登录MOS&#xff0c;查阅(ID 2118136.2)&#xff0c;下载…

电脑录音方法:电脑怎么录音?5招轻松搞定录音!

想要从麦克风或系统音频录制电脑声音吗&#xff1f;这是一项简单的任务。本文将为您介绍5种最佳且最简单的方法&#xff0c;包括使用Windows系统自带的录音工具来录制电脑音频&#xff0c;在线音频录音软件和专业的第三方电脑录音软件。这些工具都能够很好地帮助您完成电脑怎么…

MySQL数据库中文乱码处理

出现中文乱码之后处理方式 1、执行下面语句查看一下关于编码方式 show variables like %char%结果展示&#xff1a;【你应该和我的不一样】 2、如果你的和我查询结果不一致请设置成一致语句&#xff0c;根据自己需要复制语句 如下&#xff1a;【除了最后一条记录哈】 SET G…

llm-universe | 五. 系统评估与优化

系统评估与优化 一.LLM应用评估思路1.人工评估准则一 量化评估准则二 多维评估 2.自动评估方法一. 构造客观题方法二. 计算答案相似度 3.使用大模型评估4.混合评估 二.评估并优化生成部分1. 提升直观回答质量2.标明知识来源&#xff0c;提高可信度3. 构造思维链4.增加一个指令解…

独立开发者系列(10)——fastadmin后台框架的认识

软件开发项目涉及到的东西非常多&#xff0c;作为独立开发者&#xff0c;普遍性的面对的是中小项目。而其中接单的情况下&#xff0c;以WEB方向的居多。其中主要有以下这么些类的:搭建官网cms 就是常见的资讯发布平台&#xff0c;发布一些企业新闻/活动宣传&#xff0c;纯粹是…

Docker部署ETCD 3.5.14(保姆级图文教程)

系列文章目录 Docker部署Nginx 1.21.5&#xff08;保姆级图文教程&#xff09; Docker部署MySQL 8.3.0&#xff08;保姆级图文教程&#xff09; Docker部署ETCD 3.5.14&#xff08;保姆级图文教程&#xff09; 文章目录 一、环境二、拉取镜像2.1 查找 Docker Hub 上的 ETCD 镜像…

【区块链+基础设施】国家健康医疗大数据科创平台 | FISCO BCOS应用案例

在医疗领域&#xff0c;疾病数据合法合规共享是亟待解决的难题。一方面&#xff0c;当一家医院对患者实施治疗后&#xff0c;若患者转到其 他医院就医&#xff0c;该医院就无法判断诊疗手段是否有效。另一方面&#xff0c;医疗数据属于个人敏感数据&#xff0c;一旦被泄露或被恶…

网络配线架的隐藏功能

网络布线是确保现代信息社会高效运转的关键技术之一。在这一领域&#xff0c;网络配线架扮演着至关重要 的角色。它不仅仅是一个简单的物理连接点&#xff0c;更拥有许多隐藏功能&#xff0c;这些功能极大地提升了网络的 效率、稳定性和可管理性。 1、集中管理 网络配线架提…

springboot图书馆座位预约系统-计算机毕业设计源码85670

目 录 摘要 1 绪论 1.1 选题背景与意义 1.2开发现状 1.3论文结构与章节安排 2 开发环境及相关技术介绍 2.1 MySQL数据库 2.2 Tomcat服务器 2.3 Java语言 2.4 SpringBoot框架介绍 3 图书馆座位预约系统系统分析 3.1 可行性分析 3.1.1 技术可行性分析 3.1.2 经济可…

MySQL 代理层:ProxySQL

文章目录 说明安装部署1.1 yum 安装1.2 启停管理1.3 查询版本1.4 Admin 管理接口 入门体验功能介绍3.1 多层次配置系统 读写分离将实例接入到代理服务定义主机组之间的复制关系配置路由规则事务读的配置延迟阈值和请求转发 ProxySQL 核心表mysql_usersmysql_serversmysql_repli…

达梦数据库修改日期时间格式和语言

1、问题 重装了达梦数据库后&#xff0c;通过达梦管理工具查询&#xff0c;表字段timestamp类型变成了中文 对于2023-11-01 01:55:33格式时间插入报错&#xff0c;非法的时间日期类型数据 2、查询配置 通过DM8系统管理员手册查到相关配置 通过命令进行查询显示&#xff0c;语…

《工业微生物》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《工业微生物》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。 问&#xff1a;《工业微生物》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国轻工业联合会 主办单位&#xff…

多个tomcat同时使用 不设置CATALINA_HOME环境变量

通常一台服务器只使用一个tomcat&#xff0c;设置一个CATALINA_HOME的环境变量。但有些时候需要一台服务器启动多个tomcat&#xff0c;那就不能设置CATALINA_HOME了&#xff01;因为会串~ 我们可以在对应tomcat的startup.bat启动脚本中&#xff0c;加入对应的CATALINA_HOME。 …

南京林业大学点云相关团队论文

【1】Chen Dong, Wan Lincheng, Hu Fan, Li Jing, Chen Yanming, Shen Yueqian*, Peethambaran Jiju, 2024. Semantic-aware room-level indoor modeling from point clouds, International Journal of Applied Earth Observation and Geoinformation, 2024, 127, 103685. 语义…

Python入门 2024/7/2

目录 格式化的精度控制 字符串格式化 对表达式进行格式化 小练习&#xff08;股票计算小程序&#xff09; 数据输入 布尔类型和比较运算符 if语句 小练习&#xff08;成人判断&#xff09; if-else语句 if-elif-else语句 练习&#xff1a;猜猜心里数字 嵌套语句 猜…

TransMIL:基于Transformer的多实例学习

MIL是弱监督分类问题的有力工具。然而&#xff0c;目前的MIL方法通常基于iid假设&#xff0c;忽略了不同实例之间的相关性。为了解决这个问题&#xff0c;作者提出了一个新的框架&#xff0c;称为相关性MIL&#xff0c;并提供了收敛性的证明。基于此框架&#xff0c;还设计了一…

imagesc函数 纵坐标翻转 colormap “set” “jet“

纵坐标默认上小下大 翻转&#xff1a; 在Matlab中&#xff0c;使用imagesc函数绘制的图像的纵坐标默认是从上到下递增的&#xff0c;即下面的值较大&#xff0c;上面的值较小。如果想要翻转纵坐标&#xff0c;使之从上到下递增&#xff0c;可以通过设置坐标轴的YDir属性为’n…

【Python机器学习】模型评估与改进——带交叉验证的网格搜索

虽然将数据划分为训练集、验证集、测试集的方法是可行的&#xff0c;也相对常用&#xff0c;但这种方法对数据的划分相当敏感&#xff0c;为了得到对泛化性能的更好估计&#xff0c;我们可以使用交叉验证来评估每种参数组合的性能&#xff0c;而不是仅将数据单次划分为训练集与…

Git入门 本地仓库 远端仓库 多分支

Git入门 Git入门本地git初始化git仓库初始化 创建远端仓库githubgitee 指定远端仓库推送至远端多分支将feature分支合并至dev分支 其他开发者 Git入门 本地git初始化 git仓库初始化 mkdir myrepo # 创建仓库文件夹 cd myrepo/ # 进入目录 git init # 初始化git仓库 (创建.g…