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

avatar
作者
猴君
阅读量:0

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

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

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

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

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!