【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十五章 Linux I2C驱动实验

news2024/9/17 7:06:49

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263


第六十五章 Linux I2C驱动实验

65.1 应用程序与I2C通信

本章内容对应视频讲解链接(在线观看):

应用层实现I2C通信  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=44

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\021-i2c驱动实验”路径下。

我们可以先来体验一下,在Linux上操作I2C是多么的容易,我们可以先来看一下系统里面都有哪些I2C的节点,这里以iMX8MM开发板为例。如下图所示:

Linux有一个非常重要的概念叫一切皆文件,那么我们能不能在应用层通过open这些节点来操作I2C来跟外设I2C通信的芯片进行一个数据交流呢?当然是可以的,我们来一起看一下,这里我们以7寸LVDS屏幕上的触摸芯片FT5X06为例,迅为所有开发板都是支持迅为7寸LVDS屏幕屏的,所有都是可以进行这个实验的。迅为的屏幕除了4.3寸和10.1寸屏外,其他尺寸的屏幕的触摸芯片都是FT5X06,都是可以进行这个实验的。

本次实验我们使用的从机为 FT5X06触摸芯片。 FT5x06 系列 ICs 是单芯片电容式触摸屏控制器 IC,带有一个内置的 8 位微控制器单元(MCU)。采用互电容的方法,在配合的相互的电容式触摸面板,它支持真正的多点触摸功能。FT5x06 具有用户友好的输入的功能,这可以应用在许多便携式设备,例如蜂窝式电话,移动互联网设备,上网本和笔记本个人电脑。FT5x06 系列 IC 包括 FT5206/FT5306/FT5406。FT5x06 可以捕获5个触摸点,编写驱动时,只要去获取这几个点的数据,然后上报就可以了。之后我们的实验也是读取的其中一个寄存器,如下图所示,我们可以在FT5X06的数据手册上查找到。

 

我们打开IMX8MM开发板的底板原理图,我们通过原理图先来确定一下FT5X06使用的是哪个I2C,通过下面的截图我们可以看到在IMX8MM开发板上触摸芯片FT5X06使用的是I2C2。 

我们输入如下图所示命令,查找I2C2对应的设备节点,我们查找如下图所示: 

所以I2C2设备的地址是0038,对应的节点是dev下面的i2c-1。如果我们要在IMX8MM上和触摸芯片FT5X06进行通信,只要操作dev下的i2c-1这个节点就可以了。

那我们怎么在应用层操作I2C呢?应用层操作I2C是以数据包进行交流的,所以我们在应用层就要进行封包的操作。数据包对应的结构体是 i2c_rdwr_ioctl_data,这个结构体定义在include\uapi\linux\i2c-dev.h下面:定义如下:

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data
{
    struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
    __u32 nmsgs;                 /* number of i2c_msgs */
};

第一个结构体成员是我们要发送的数据包的指针,第二个结构体成员是发送数据包的个数。

我们来看一下i2c_msg结构体的定义,这个结构体是定义在include\uapi\linux\i2c.h下面,定义如下:

struct i2c_msg
{
    __u16 addr; /* slave address            */
    __u16 flags;
#define I2C_M_TEN 0x0010          /* this is a ten bit chip address */
#define I2C_M_RD 0x0001           /* read data, from slave to master */
#define I2C_M_STOP 0x8000         /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000      /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000   /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400     /* length will be first received byte */
    __u16 len;                    /* msg length             */
    __u8 *buf;                    /* pointer to msg data            */
};

结构体成员addr是我们从机的地址,flags为读写标志位,如果flags为1,则为读,反之为0,则为写。len为buf的大小,单位是字节。当flags为1是,buf就是我们要接收的数据,当flags为0时,就是我们要发送的数据。

那么我们要怎么设计我们的程序呢?我们来看一下。程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\021-i2c驱动实验\001”路径下。

/*
 * @Author: topeet
 * @Description: 应用程序与I2c通信
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
int fd;
int ret;
/**
 * @description: i2c_read_data i2c读数据
 * @param {unsignedint} slave_addr:从机设备的地址
 * @param {unsignedchar} reg_addr:寄存器的地址
 * @return {*}
 */
int i2c_read_data(unsigned int slave_addr, unsigned char reg_addr)
{
    unsigned char data;
    //定义一个要发送的数据包i2c_read_lcd
    struct i2c_rdwr_ioctl_data i2c_read_lcd;
    //定义初始化i2c_msg结构体
    struct i2c_msg msg[2] = {
        [0] = {
            .addr = slave_addr,       //设置从机额地址
            .flags = 0,               //设置为写
            .buf = &reg_addr,         //设置寄存器的地址
            .len = sizeof(reg_addr)}, //设置寄存器的地址的长度
        [1] = {.addr = slave_addr,    //设置从机额地址
               .flags = 1,            //设置为读
               .buf = &data,          //设置寄存器的地址
               .len = sizeof(data)},  //设置寄存器的地址
    };
    //初始化数据包的数据
    i2c_read_lcd.msgs = msg;
    //初始化数据包的个数
    i2c_read_lcd.nmsgs = 2;
    //操作读写数据包
    ret = ioctl(fd, I2C_RDWR, &i2c_read_lcd);
    if (ret < 0)
    {
        perror("ioctl error ");
        return ret;
    }
    return data;
}
int main(int argc, char *argv[])
{

    int TD_STATUS;
    //打开设备节点
    fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0)
    {
        //打开设备节点失败
        perror("open error \n");
        return fd;
    }
    while (1)
    {
        //i2C读从机地址为0x38,寄存器地址为0x02的数据
        //我们从数据手册中得知TD_STATUS的地址为0x02
        TD_STATUS = i2c_read_data(0x38, 0x02);
        // 打印TD_STATUS的值
        printf("TD_STATUS value is %d \n", TD_STATUS);
        sleep(1);
    }
    close(fd);
    return 0;
}

编译应用程序程序如下图所示:

我们在开发板上运行应用程序,当我们没有触摸屏幕时,如下图所示: 

 

当我们用一根手指触摸时,如下图所示: 

 

当我们用三根手指触摸时,如下图所示: 

 

当我们用五根手指触摸时,如下图所示: 

 

65.2 I2C总线实现client设备

上一章节我们学习了怎么在应用层来操作i2c,本章节我们来学习一下如何写一个i2c驱动。

本章内容对应视频讲解链接(在线观看):

I2C总线实现client设备  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=45

Linux中的I2C也是按照平台总线模型设计的,既然也是按照平台总线模型设计的,是不是也分为一个device和一个driver呢?但是I2C这里的device不叫device,而是叫client。在讲 platform 的时候就说过, platform 是虚拟出来的一条总线,目的是为了实现总线、设备、驱动框架。对于 I2C 而言,不需要虚拟出一条总线,直接使用 I2C总线即可。同样,我们也是先从非设备树开始,先来看一下,在没有设备树之前我们是怎么实现的I2C的device部分,也就是client部分。然后再学习有了设备树之后,我们的client是怎么编写的。

65.2.1 非设备树实现i2c

在没有使用设备树之前,我们使用的是i2c_board_info这个结构体来描述一个I2C设备的,i2c_board_info这个结构体如下:

在这个结构体里面,type 和 addr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,这个名字就是用来进行匹配用的,一个是 I2C 设备的器件地址,也可以使用宏:

#define I2C_BOARD_INFO(dev_typedev_addr\

    .type = dev_type, .addr = (dev_addr)

可以看出, I2C_BOARD_INFO 宏其实就是设置 i2c_board_info 的 type 和 addr 这两个成员变量。

I2C 设备和驱动的匹配过程是由 I2C 核心来完成的,在Linux源码的drivers/i2c/i2c-core.c 就是 I2C 的核心部分, I2C 核心提供了一些与具体硬件无关的 API 函数,如下:

函数

struct i2c_adapter *i2c_get_adapter(int nr);

nr

要获得的那个I2C适配器的编号

返回值

成功返回0;失败返回NULL

功能

获取I2C适配器

函数

void i2c_put_adapter(struct i2c_adapter *adap);

adap

要释放的I2C适配器

返回值

成功返回0;失败返回NULL

功能

释放I2C适配器

函数

 i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);

adap

I2C适配器

info

i2c_board_info的指针

返回值

成功返回0;失败返回NULL

功能

把I2C适配器和I2C器件关联起来。

函数

void i2c_unregister_device(struct i2c_client *client)

client

i2c client的指针

返回值

成功返回0;失败返回NULL

功能

注销一个client。

65.2.2 设备树实现i2c

在使用了设备树以后,就不用这么复杂了,使用设备树的时候只要在对应的I2C节点下创建相应设备的节点即可,比如我想添加一个触摸芯片FT5X06的设备,我就可以在对应的I2C的节点下这样写,如下所示:

注意:迅为10.1寸屏幕的触摸芯片是 gt911,4.3寸触摸芯片是 tsc2007,其它都是ft5426芯片。

在IMX8MM设备树中,/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk-7.0.dts为7寸LVDS屏幕的设备树文件。

&i2c2 {
	clock-frequency = <400000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";


	typec1_ptn5110: tcpci@50 {
		compatible = "usb,tcpci";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_typec1>;
		reg = <0x50>;
		interrupt-parent = <&gpio2>;
		interrupts = <11 8>;
		src-pdos = <0x380190c8>;
		snk-pdos = <0x380190c8>;
		/* Only can sink 5V for safe */
		max-snk-mv = <5000>;
		max-snk-ma = <3000>;
		op-snk-mw = <10000>;
		max-snk-mw = <15000>;
		port-type = "drp";
		default-role = "sink";
		status = "okay";
	};


#if defined(LCD_TYPE_7_0) || defined(LCD_TYPE_9_7) || defined(LCD_TYPE_MIPI_7_0)
    ft5x06_ts@38 {
        compatible = "edt,edt-ft5x06";
        reg = <0x38>;
        pinctrl-names = "defaults";
        pinctrl-0 = <&pinctrl_ft5x06_int>;
        interrupt-parent = <&gpio1>;
        interrupts = <15 2>;
        status = "okay";
#if defined(LCD_TYPE_7_0)
		lcd_type = <0>;
#elif defined(LCD_TYPE_9_7)
		lcd_type = <1>;
#elif defined(LCD_TYPE_MIPI_7_0)
		lcd_type = <2>;
#endif
	};
  • 更改status为“okay”,使能i2c-2总线;
  • 触摸屏所使用的 FT5x06 芯片节点,挂载 I2C2 节点下;“@”后面的“38”就是edt-ft5x06 的 I2C 器件地址
  • compatible用于和驱动程序的compatible 匹配;
  • reg属性描述ft5x的器件地址为0x38
  • interrupt-parent 属性描述中断 IO 对应的 GPIO 组为 GPIO1;
  • interrupts 属性描述中断 IO 对应的是 GPIO1_C4 

因为我们的开发板默认是设备树的镜像, 我们进入到开发板的/sys/bus/i2c/devices/目录下,因为通过查找原理图发现我们屏幕使用的是i2c2,所以进入到1-0038,查看name为ft5x0x_ts

接下来我们以非设备树的方式写一个client.c,然后加载进去,然后看一下和我们使用设备树的效果是不是一样的呢?有些同学可能会说,现在都是用设备树了,为什么还要用以前的方法呢?因为我们以前的方法也是需要熟悉的,我们只有学会以前的方法,才能够更好的理解现在的这种设备树的方法,而且有些老的版本还是使用低版本的内核,比如说kernel3.0。

65.2.3 修改设备树

因为我们现在使用的是设备树的源码,所以要在设备树文件去掉触摸的设备节点,打开设备树源码/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk-7.0.dts,注释掉如下图所示的内容。如果大家使用的是其他尺寸的屏幕,需要修改屏幕对应的设备树文件。

注释掉框柱的内容,如下图所示:

我们输入make menuconfig,将ft5x06触摸芯片的驱动取消掉,如下图所示:

 

取消选中后退出保存修改。编译完成后,我们将编译好的镜像烧写到开发板,启动开发板后,我们进入到/sys/bus/i2c/devices/目录下,如下图所示,没有i2c设备1-0038了。

65.2.4 编写client.c

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\021-i2c驱动实验\002”路径下。

接下来我们以没有设备树的方法写一下i2c设备,然后注册进去。我们在Ubuntu的/home/topeet/imx8mm/21/002目录下新建client.c,拷贝前面实验的Makefile和build.sh到此目录下,编写client.c代码如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

//分配一个i2c适配器指针
struct i2c_adapter *i2c_ada;

//分配一个i2c_client指针
struct i2c_client *i2c_client;

//支持i2c的设备列表
struct i2c_board_info ft5x06_info[] = {

    //每一项都代表一个i2C设备,这句话的意思是说这个设备的名字是ft5x06_test,器件地址是0x38
    {I2C_BOARD_INFO("ft5x06_test", 0x38)},
    {}};

static int ft5x06_client_init(void)
{
    //调用i2c_get_adapter获得一个i2c总线,因为ft5x06是挂载到了i2c2上,
    // 所以这个参数是1,所以这句代码的意思是把这个触摸芯片挂载到i2c2上
    i2c_ada = i2c_get_adapter(1);
    //把i2c client和i2c器件关联起来
    i2c_client = i2c_new_device(i2c_ada, ft5x06_info);

    i2c_put_adapter(i2c_ada); //释放i2c控制器

    printk("This is ft5x06_client_init \n");
    return 0;
}

static void ft5x06_client_exit(void)
{
    i2c_unregister_device(i2c_client);
    printk("This is ft5x06_client_exit \n");
}

module_init(ft5x06_client_init);
module_exit(ft5x06_client_exit);
MODULE_LICENSE("GPL");

我们将刚刚编写的驱动代码编译为驱动模块,如下图所示: 

我们进入加载驱动模块,如下图所示: 

我们查看到/sys/bus/i2c/devices/目录下生成了1-0038,如下图所示:

如下图所示,我们可以查找到ft5x06,说明我们成功地注册了i2c设备。

 

65.3 I2C总线实现driver驱动

上一章节我们写了client.c,并且我们已经成功地把它加载到内核里面。i2c用非设备树实现,我们需要用i2c_board_info这个结构体来描述我们的i2c设备,如果我们用设备树的方法来实现,我们直接在设备树的节点下面添加创建对应设备的节点就可以了。后面的实验我们都用设备树的,非设备树的方法了解一下就可以了,本章节我们来设计i2c驱动的driver部分。

本章内容对应视频讲解链接(在线观看):

I2C总线实现driver驱动  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=46

我们在Ubuntu的/home/topeet/imx8mm/21/003目录下新建一个driver.c文件,拷贝前面实验的Makefile,build.sh文件,driver.c文件的代码如下所示:

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\021-i2c驱动实验\003”路径下。

/*
 * @Author: topeet
 * @Description: i2c总线实现driver驱动
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

//与设备树的 compatible 匹配
static const struct of_device_id ft5x06_id[] = {
    {.compatible = "edt,edt-ft5306", 0},
    {.compatible = "edt,edt-ft5x06", 0},
    {.compatible = "edt,edt-ft5406", 0},
    {}};
// 无设备树的时候匹配 ID 表
static const struct i2c_device_id ft5x06_id_ts[] = {
    {"xxxxx", 0},
    {}};
/* i2c 驱动的 remove 函数 */
int ft5x06_remove(struct i2c_client *i2c_client)
{
    return 0;
}
/* i2c 驱动的 probe 函数 */
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{
    printk("This is ft5x06_probe\n");
    return 0;
}

//定义一个i2c_driver的结构体
static struct i2c_driver ft5x06_driver = {

    .driver = {
        .owner = THIS_MODULE,
        .name = "ft5x06_test",
        // 采用设备树的时候驱动使用的匹配表
        .of_match_table = ft5x06_id,
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .id_table = ft5x06_id_ts};
/* 驱动入口函数 */
static int ft5x06_driver_init(void)
{
    int ret;
    // 注册 i2c_driver
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0)
    {
        printk(" i2c_add_driver is error \n");
        return ret;
    }
    printk("This is ft5x06_driver_init\n");
    return 0;
}
/* 驱动出口函数 */
static void ft5x06_driver_exit(void)
{
    // 将前面注册的 i2c_driver 也从 Linux 内核中注销掉
    i2c_del_driver(&ft5x06_driver);
    printk("This is ft5x06_driver_exit\n");
}

module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

我们刚刚编写的驱动代码编译为驱动模块,如下图所示:

在加载驱动之前,我们要恢复设备树文件中我们之前章节注释掉的节点,还是取消掉原来的驱动,重新编译设备树内核,然后再烧写镜像。如下图所示 

开发板启动后,我们进入到/sys/bus/i2c/devices/目录下查看是否有生成I2C节点,如下图所示: 

我们进入共享目录并且加载驱动模块,如下图所示:

65.4 I2C驱动程序实现I2C通信

在第65.1章节学习i2c的时候,我们是在应用层操作设备节点对i2c设备进行读写的,那么如果我们在驱动里面对i2c设备进行读写要怎么办呢?本章节我们将来学习。

我们复制第65.3章节的代码,在此基础上进行修改。我们在应用里面对i2c进行读写,最重要的是对我们数据包的封包的操作,封装了一个i2c_rdwr_ioctl_data数据包,才对i2c进行读写,同样在驱动里面,我们也可以使用这种方法。

本章内容对应视频讲解链接(在线观看):

驱动程序实现I2C通信  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=47

我们对某个可读可写的寄存器进行读写操作,我们打开触摸芯片ft5x06的数据手册,打开2.1章节,如下图所示:

 我们以0x08寄存器为例进行读写,大家也可以换其他的可读可写的寄存器进行读写操作。

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\021-i2c驱动实验\004”路径下。

完整的代码如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

static struct i2c_client *ft5x06_client;
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len);
static int ft5x06_read_reg(u8 reg_addr);
//读寄存器函数
static int ft5x06_read_reg(u8 reg_addr)
{
    u8 data;
    struct i2c_msg msgs[] = {
        [0] = {
            .addr = ft5x06_client->addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },

        [1] = {
            .addr = ft5x06_client->addr,
            .flags = 1,
            .len = sizeof(data),
            .buf = &data,
        },
    };
    i2c_transfer(ft5x06_client->adapter, msgs, 2);
    return data;
}
//写寄存器函数
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len)
{
    u8 buff[256];
    struct i2c_msg msgs[] = {
        [0] = {
            .addr = ft5x06_client->addr,
            .flags = 0,
            .len = len + 1,
            .buf = buff,
        },

    };

    buff[0] = reg_addr;
    memcpy(&buff[1], &data, len);
    i2c_transfer(ft5x06_client->adapter, msgs, 1);
}
/* 设备树匹配列表 */
static const struct of_device_id ft5x06_id[] = {
    {.compatible = "edt,edt-ft5x06", 0},
    {.compatible = "edt,edt-ft5206", 0},
    {.compatible = "edt,edt-ft5406", 0},
    {}};
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id ft5x06_id_ts[] = {
    {"", 0},
    {}};
/* i2c 驱动的 remove 函数 */
int ft5x06_remove(struct i2c_client *i2c_client)
{
    return 0;
}
/* i2c 驱动的 probe 函数 */
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{
    int ret;
    printk("This is ft5x06_probe\n");
    //因为我们要在别的函数里面使用client,所以我们要把他复制出来
    ft5x06_client = i2c_client;
    //往地址为0x80的寄存器里面写入数据0x4b
    ft5x06_write_reg(0x80, 0x4b, 1);
    //读出0x80寄存器的值
    ret = ft5x06_read_reg(0x80);
    //打印0x80寄存器的值
    printk("ret is %#x\n", ret);
    return 0;
}

//定义一个i2c_driver的结构体
static struct i2c_driver ft5x06_driver = {

    .driver = {
        .owner = THIS_MODULE,
        .name = "ft5x06_test",
        // 采用设备树的时候驱动使用的匹配表
        .of_match_table = ft5x06_id,
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .id_table = ft5x06_id_ts};
/* 驱动入口函数 */
static int ft5x06_driver_init(void)
{
    int ret;
    //注册 i2c_driver
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0)
    {
        printk(" i2c_add_driver is error \n");
        return ret;
    }
        return ret;

    printk("This is ft5x06_driver_init\n");
    return 0;
}
/* 驱动出口函数 */
static void ft5x06_driver_exit(void)
{
    i2c_del_driver(&ft5x06_driver);
    printk("This is ft5x06_driver_exit\n");
}

module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

我们将刚刚编写的驱动代码编译为驱动模块,如下图所示:

我们进入共享目录并且加载驱动模块,如下图所示: 

如上图所示,我们可以看到读写函数是没问题的,可以对寄存器进行正常的读写操作。 

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

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

相关文章

Qt编写自定义控件:跑马灯文本控件

#ifndef RUNNINGTEXTWIDGET_H #define RUNNINGTEXTWIDGET_H#include <QWidget>enum Direction {North 0, //上South, //下West, //左East //右 };class RunningTextWidget : public QWidget {Q_OBJECT public:explicit RunningTextWidget(QWidget *parent nullptr);…

第二期:集成电路(IC)——智能世界的微观建筑大师

嘿&#xff0c;小伙伴们&#xff01;&#x1f44b; 我是你们的老朋友小竹笋&#xff0c;一名热爱创作和技术的工程师。上一期我们聊了聊AI芯片&#xff0c;这次我们要深入到更微观的层面&#xff0c;来探究集成电路&#xff08;IC&#xff09;的世界。准备好一起探索了吗&#…

50+受高度近视屈光参差与白内障阻碍,巫雷院长一场手术“均衡”双眼

周女士双眼近视度数一直差异很大&#xff0c;这么多年从未看清。“这次”是因为发现视力逐渐下降检查得知并发性白内障&#xff0c;以屈光性白内障手术得以一次性治疗多个问题。 周女士小时候就近视了&#xff0c;那时家里不重视&#xff0c;且自己觉得戴眼镜“不好”&#xf…

强制重新启动 iPhone

官网&#xff1a;https://support.apple.com/zh-cn/guide/iphone/iph8903c3ee6/ios 按住调高音量按钮&#xff0c;然后快速松开。按住调低音量按钮&#xff0c;然后快速松开。按住侧边按钮。当 Apple 标志出现时&#xff0c;松开侧边按钮。

【初阶数据结构题目】1.返回倒数第k个节点

文章目录 题目描述代码 题目描述 返回倒数第k个节点 代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; int kthToLast(struct ListNode* head, int k){ListNode* t hea…

保姆级教程!!教你通过【Pycharm远程】连接服务器运行项目代码

小罗碎碎念 这篇文章主要解决一个问题——我有服务器&#xff0c;但是不知道怎么拿来写代码&#xff0c;跑深度学习项目。确实&#xff0c;玩深度学习的成本比较高&#xff0c;无论是前期的学习成本&#xff0c;还是你需要具备的硬件成本&#xff0c;都是拦路虎。小罗没有办法…

「12月·长沙」人工智能与网络安全国际学术会议(ISAICS 2024)

人工智能与网络安全国际学术会议(ISAICS 2024)将于2024年12月20日-2024年12月22日在湖南长沙召开。会议中发表的文章将会被收录,并于见刊后提交EI核心索引。会议旨在在为国内与国际学者搭建交流平台,推进不同学科领域的融合发展&#xff0c;就当今人工智能与网络安全范畴内各学…

史上最全的Seata教学并且连接springcloudAlibaba进行使用

来都来了点个赞收藏一下在走呗~~&#x1f339;&#x1f339;玫瑰 一、Seata是什么 Seata&#xff08;Simple Extensible Autonomous Transaction Architecture&#xff0c;简单可扩展自治事务框架&#xff09;是一种分布式事务解决方案&#xff0c;旨在解决分布式系统中的事务…

【iOS】通知的底层原理(实现)

通知 一. 通知的基本使用 1. 基本概念 NSNotification 是iOS中一个调度消息通知的类,采用单例模式设计,在程序中实现传值、回调等地方应用很广。在iOS中&#xff0c;NSNotification & NSNotificationCenter是使用观察者模式来实现的用于跨层传递消息。 概要&#xff1a…

C++ 关键字与库函数 学习总结

sizeof与strlen 含义 sizeof&#xff1a;是一个操作符&#xff0c;用于计算数据类型或变量的大小&#xff08;以字节为单位&#xff09;。在编译时求值strlen&#xff1a; 是一个函数&#xff0c;用于计算字符串的长度&#xff08;不包括终止符 \0&#xff09;。在运行时求值不…

QT基础教程(QEvent事件和事件过滤器)

文章目录 前言一、具体介绍二、具体案例1.鼠标事件2.键盘事件3.窗口事件 三、事件过滤器事件过滤器的工作原理 总结 前言 本篇文章将带大家来学习QT中的QEvent事件&#xff0c;QEvent 是 Qt 框架中的一个核心类&#xff0c;用于处理各种事件。在 Qt 的事件处理系统中&#xff…

framebuffer(帧缓冲)

framebuffer 在Linux系统中&#xff0c;Framebuffer通常是指Framebuffer设备&#xff0c;它是一种特殊的字符设备&#xff0c;在Linux系统中&#xff0c;Framebuffer设备使得程序员可以通过其设定的函数接口直接访问硬件&#xff0c;而不需要通过CPU。 framebuffer的一般操作流…

江协科技51单片机学习- p29 DS18B20温度传感器

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

2-49 基于matlab的表面缺陷的自动分割

基于matlab的表面缺陷的自动分割。基于梯度图操作&#xff0c;对得到的梯度图进行开运算去噪&#xff0c;二值化后经过一定的形态学处理得到缺陷轮廓。通过在两个尺度上同时操作&#xff0c;高尺度的图精细&#xff0c;噪点多&#xff1b;低尺度的图粗糙&#xff0c;但包含的噪…

【人工智能】人工智能概述(二)人工智能的关键技术

文章目录 一. 机器学习与深度学习1. 机器学习2. 深度学习 二. 计算机视觉1. 基本概念和分类2. 未来计算机视觉面临的主要挑战 三. 自然语言处理1. 基本概念与分类2. 自然语言处理面临的四大挑战 四. 知识图谱1. 基本概念2. 应用场景 五. SLAM技术1. 基本概念2. 主要分类 六. 人…

Halcon 感兴趣区域

一 感兴趣区域 机器视觉中感兴趣区域是必不可少的&#xff0c;尤其是Halcon。其目的是将集中处理图像中的特定部分。此方法将区域信息与图像矩阵相结合&#xff0c;只与图像中的某些区域保持关联&#xff0c;减少图像处理的像素。使用ROI的优势&#xff1a;第一&#xff0c;减…

Redis学习[1] ——基本概念和数据类型

Redis学习[1] ——基本概念和数据类型 一、Redis基础概念 1.1 Redis是什么&#xff0c;有什么特点&#xff1f; Redis是一个基于**内存的数据库&#xff0c;因此读写速度非常快**&#xff0c;常用作缓存、消息队列、分布式锁和键值存储数据库。支持多种数据结构&#xff1a;…

网络协议二 : 使用Cisco Packet Traceer工具模拟网络环境,集线器,网桥,交换机,路由器,IP,同一网段

1. 安装 Cisco Packet Tracer baidu 网盘地址&#xff0c;感谢大神分享 安装&#xff0c;破解&#xff0c;中文化&#xff0c;都有说明&#xff0c;建议使用7.x的那个版本&#xff0c;感觉比8.x的翻译要完整一点 https://pan.baidu.com/s/18iWBOfhJJRhqgQqdNQcfMQ?pwddcch#…

【C++】实验七

题目&#xff1a; 1、自己找规律利用数组完成下列数据的输出&#xff1a; 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 思路&#xff1a;数据是斐波那契数列的前20项。该数列特点是除第一第二项以…

公交车客流统计产品介绍

在当今智能科技与交通运输融合的背景下&#xff0c;一款新型公交车客流统计产品应运而生。该系统采用先进的双目客流统计算法&#xff0c;实现多通道视频的客流数据统计&#xff0c;以其高实时性和98%的准确性在复杂环境下准确统计人数。 产品特点 双目客流统计算法 该公交车客…