22.2Linux的I2C驱动实验(编程)_csdn
我尽量讲的更详细,为了关注我的粉丝!!!
这里我们用到的是stm32mp157的板子,所以我们看一下I2C用到的引脚。
1、硬件原理图分析
可以看到在这块板子上面用的SDA和SCL总线是PA11,PA12。所以要修改设备树和镜像文件!
2、修改镜像
打开 stm32mp15-pinctrl.dtsi文件。
镜像这里不用改!
3、修改设备树
打开 stm32mp157d-atk.dts 文件。
通过节点内容追加的方式,向 i2c5 节点中添加“ap3216c@1e”子节点。
原本在内核源码 arch/arm/boot/dts/stm32mp151.dtsi 设备树文件中就可以找到STM32MP1 的 I2C5控制器节点,但是不够,因为在实验过程中有不同的从设备,所以要在其他地方添加从设备的地址信息。我们在 stm32mp157d-atk.dts 文件添加上面的节点信息!
编译:
make dtbs
复制到开发板中:
sudo cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/chensir/linux/tftpboot/ -f
“0-001e”就是 ap3216c 的设备目录。
1e”就是 ap3216c 器件地址。进入0-001e 目录,可以看到“name”文件, name 文件保存着此设备名字,在这里就是“ap3216c”。
这里就可以说明系统扫描到了这个从设备,并将它配置在devices目录下。
4、AP3216C 驱动编写
之前的博客也是跟大家按照肌肉记忆来编写程序!一步一步按照思路来编写!
总代码会放在最后。
为了让大家更能明白,可以先对着总代码,进行对我的写代码流程更加详细得当!
先在 ap3216creg.h 中定义好 AP3216C 的寄存器,ap3216creg.h 没什么好讲的,就是一些寄存器宏定义。
输入如下内容:
这个不就跟裸机代码keil一样啦!所以这里没什么好讲的!
接下来就是讲编写驱动代码了!
4.1、头文件
可以看到下面的头文件比以前的多了i2c.h和ap3216creg.h。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"
4.2、驱动注册和注销
对于 I2C 驱动而言,没有像 misc
子系统、input子系统那样直接提供完全自动创建设备号、cdev
、device
、class
的机制。
但是我们可以像之前的文章中module_platform_driver类似写module_i2c_driver来实现init和exit一体化。也就是不需要自己编写init和exit了。
这里的ap3216c_driver是定义为i2c_driver的结构体,类似platform_driver。
举例:(这里并不要我们写!)
把i2c_add_driver和i2c_del_driver都封装了!
4.2.1、编写I2C_driver驱动结构体
其中流程是:
27行代码:设备树中的compatible值会与ap3216c_of_match下的compatible相匹配。
如下图ap3216c_of_match的代码:
其中MODULE_DEVICE_TABLE是声明一下而已!
26行代码:会在driver目录下生成ap3216c。这个是驱动开发者自己编写的!
和设备树中的compatible没有关系。
25行代码:一般将其设置为 THIS_MODULE
,这表明该驱动属于当前的内核模块。
29行代码:id_table
是一个设备 ID 表,用于传统的设备匹配方式(不使用设备树时)。ap3216c_id
是一个预先定义好的 ID 表。
22~23行代码:compatible值一旦匹配成功,就会执行probe和remove。(这些其实之前的驱动程序讲解已经讲过很多遍了)
4.2.2、编写probe和remove函数
这里就用于之前的设备注册和注销,它并不能像MISC和INPUT子系统一样自动注册和注销设备号、cdev、class、device。
这里可以很明显发现和platform驱动不同;
static int miscbeep_probe(struct platform_device *pdev)static int miscbeep_remove(struct platform_device *dev)
但是道理都一样,传递进去的数据是设备,区别就是数据分别是I2C设备和platform设备。
4.2.3、注册和注销字符驱动设备
在这之前需要定义相关的设备结构体,为了统一用到的数据。
1、定义设备结构体
这里我们不用:
struct ap3216c_dev ap3216cdev;
因为如果有多个从设备的话,难以处理多个设备实例的情况;会增加代码复杂度。
用上面这个传递会使系统会在栈上创建该结构体的一个副本。这样的拷贝操作会带来额外的性能开销。
==当然如果驱动代码全文没有用struct ap3216c_dev ap3216cdev这种形式的,就一定要为它分配内存!==这种形式已经是全局的 struct ap3216c_dev
结构体实例,指针 dev
直接指向它,无需额外分配内存。
后面可以通过:
例如:
struct ap3216c_dev *dev
通过指针变量来修改设备结构体的子类内容。传递指针(即结构体的地址),只需要传递一个固定大小(通常是 4 字节或 8 字节,取决于系统的架构)的地址值,避免了大量数据的拷贝,提高了程序的运行效率。
这里对于结构体指针不熟练的同学尤为重要的是:
例如struct ap3216c_dev *dev=&my_device;
其中dev存储的是my_device变量的地址,当使用printf展示dev和&my_device时地址是一样的,但是&dev是存储的dev变量自身的地址!
分配内存:
2、申请设备号
同理注销设备号:
3、配置cdev
配置设备结构体:
初始化cdev:
这些都是之前的文章讲遍了的!
THIS_MODULE:让dev在本模块执行。(cdev本身就是执行到dev)
&ap3216c_ops:字符操作集,与用户空间信息交互。
举例:(这里了解一下即可)
对字符操作集进行操作:
添加cdev:
同理注销cdev:
4、配置class和device
配置相关设备结构体:
创建class和device:
这里之前也讲过:作用就是在dev目录下生成ap3216c。
同理注销class和device:
之前的代码。比如按键,led都需要经过获取设备节点、设备树下GPIO的相关属性然后配置GPIO,I2C协议的SCL\SDA总线都需要连接GPIO,为什么驱动I2C就不用获取设备节点?
虽然 I2C 的 SCL 和 SDA 线在物理上连接到 GPIO 引脚,但由于 I2C 控制器和内核 I2C 子系统的存在,使得 I2C 驱动开发中不需要像操作普通 GPIO 那样去获取和配置其属性,而是通过专门的 I2C 接口函数来实现通信功能。
- I2C 控制器的功能:在大多数芯片中,I2C 功能是由专门的 I2C 控制器来实现的。I2C 控制器负责产生时钟信号、控制数据的传输、处理数据的起始和停止条件等复杂的操作。它会将 SCL 和 SDA 引脚配置为特定的 I2C 功能模式,而不是简单地作为普通 GPIO 使用。例如,当 I2C 控制器初始化时,它会自动将对应的 GPIO 引脚设置为开漏输出模式(这是 I2C 通信所要求的电气特性),并配置内部的时钟发生器和数据移位寄存器等硬件模块,以实现 I2C 协议规定的通信功能。
- 内核 I2C 子系统的封装:Linux 内核的 I2C 子系统对底层的 I2C 控制器进行了抽象和封装。驱动开发者通过 I2C 子系统提供的接口函数来与 I2C 设备进行通信,而无需关心底层 SCL 和 SDA 引脚的具体配置细节。这些接口函数会在内部调用硬件相关的底层驱动代码,来完成对 I2C 控制器的操作,进而实现对 SCL 和 SDA 引脚的控制。例如,驱动开发者使用
i2c_transfer
函数来发送和接收 I2C 数据帧时,I2C 子系统会根据具体的硬件平台和 I2C 控制器的特性,自动配置 SCL 和 SDA 引脚的电平变化、数据采样时机等,以确保数据的正确传输。
按键和 LED 驱动
- 内核里有专门的 GPIO 子系统,提供了一系列函数用于配置和操作 GPIO 引脚。按键和 LED 驱动通常会利用这些函数来获取设备树里 GPIO 的属性,然后配置相应的 GPIO 引脚。例如,使用
of_get_named_gpio
函数从设备树中获取 GPIO 编号,再使用gpio_direction_input
或gpio_direction_output
函数配置引脚方向。
I2C 驱动
- 内核有独立的 I2C 子系统,该子系统对 I2C 通信的底层细节进行了封装。I2C 驱动主要关注如何与 I2C 设备进行通信,也就是如何发送和接收 I2C 数据帧。通过 I2C 子系统提供的函数,如
i2c_transfer
,可以方便地实现与 I2C 设备的通信,而无需直接操作 GPIO 引脚。
这样就不用在驱动代码里面写获取设备节点了!除非遇到要开启中断的就要了!
这里是没有用到中断和DMA!
4.3、配置字符操作集
在完成写完字符操作集之前,我们先来编写I2C协议,发送和接收单个字节或多个字节,并且进阶完成ap3216c中3个传感器数据的收集!
因为我们有3个传感器的收集,所以我们要对一个从设备的3个寄存器进行读取和发送数据!
4.4、从ap3216c读取多个寄存器数据
因为之前说了系统扫描一个从设备就会有一个client。client很重要!里面有从设备的相关数据,所以要在设备结构体中配置!其中client也有adapter适配器!可以看到上面的代码也有client。
配置结构体:
写读取寄存器数据代码:
图片中的代码读取的数据是那个val。这个数据是由从设备那边提供的!
其中在 struct ap3216c_dev 中定义 struct i2c_client *client;是为了关联设备与 i2c_client 结构体。而在函数中重新定义 struct i2c_client *client
`一般是作为局部变量,用于在函数作用域内方便地操作 i2c_client 相关功能,避免直接操作结构体成员带来的混淆,同时也便于参数传递、错误处理和临时存储等,增强代码的可读性与可维护性。
4.5、向 ap3216c 多个寄存器写入数据
4.6、读取 ap3216c 指定寄存器值,读取一个寄存器
4.7、向 ap3216c 指定寄存器写入指定的值,写一个寄存器
可以看到上面已经把读取数据和写入数据已经封装好了!
5、编写AP3216C的三路合一传感器数据
我们这里只编写读取数据,观察传感器数据!
我们先配置相关的设备结构体:
5.1、读取 AP3216C 的数据,包括 ALS,PS 和 IR
注意!如果同时打开 ALS,IR+PS 两次数据读取的时间间隔要大于 112.5ms!
因为这里我们要读取6个数据。如上一个文章所说:
这些都是AP3216C上3个寄存器的不同地址。
在之前ap3216creg.h编写时就可以发现:
地址是+1的,所以可以利用数组来读取从设备发过来的数据,读取6个数据!
这样就可以循环读取6次了!目前读取的数据在数组buf内!
根据上面的条件发现,传递过来的数据可能是无效的,所以要加判断!
我们可以一一分析:
1、IR数据低8位中的第7位为1时则无效。其中IR&PS 数据有效的意思是当第7位为0时,则IR和PS传感器都是有效的。而图片中并未提到IR寄存器中低8位的2-6位,显然2-6位都是不重要的,0X0A
寄存器里只有第 1 - 0 位存储的是 IR
的有效数据。所以后面编程时要把无关数据置0。
2、ALS传感器都是有效的,只要将数据进行合并即可。
3、PS数据低8位第7位为1时,物体接近,为0时,物体远离;第6位为1时,PS和IR数据都无效,反之有效;其中PS寄存器低8位里面有效数据就是第0-3位存储的数据。PS数据高8位第7位为1时,物体接近,为0时,物体远离;第6位为1时,PS和IR数据都无效,反之有效;其中PS寄存器低8位里面有效数据就是第0-5位存储的数据。所以后面编程时要把无关数据置0。
原理讲了这么多,现在我们就开始写代码!
代码的解释都在上面写了噢!
6、编写字符操作集
我们现在其实已经完成了关于I2C的读取和写入!但是为了可以通过执行用户程序得到传感器数据,所以得编写字符操作集,完成传感器数据的信息交互。
6.1、编写open函数
驱动开发者通常会将设备的私有数据封装在自定义的设备结构体中,而 struct cdev
是内核可见的公共部分。通过 cdev
来间接访问设备结构体,可以避免在通用的字符设备操作流程中直接暴露设备的私有数据,提高了代码的安全性和可维护性。
这种方式使得驱动的各个部分可以更好地进行模块化设计。例如,内核的字符设备管理模块只需要处理 cdev
相关的操作==,而驱动开发者可以在自定义设备结构体中添加任意的私有数据和状态,两者之间通过 cdev
进行关联,互不干扰==。
在设备驱动的生命周期中,设备结构体可能是动态分配和释放的。内核只需要管理 cdev
的注册和注销,而具体的设备结构体可以由驱动自行管理。通过 cdev
来关联设备结构体,使得设备的动态管理更加灵活。
这里我们希望用户执行用户程序,一执行就初始化ap3216c:
6.2、编写read函数
这里就要将3个传感器数据读取了!
6.2、编写release函数
最后我们会发现我们并没有给client指明结构体实例:
1、在 struct ap3216c_dev
结构体中定义 struct i2c_client *client
只是声明了一个指针类型的成员变量,它的作用是用来保存指向 i2c_client
结构体的指针。不过,这个指针在初始时是未被赋值的(除非有显式初始化),它不指向任何有效的 i2c_client
结构体实例。
2、在 ap3216c_probe
函数里,struct i2c_client *client
作为参数传入,这个 client
指针指向一个有效的 i2c_client
结构体实例,该实例代表了当前连接的 I2C 设备。通过 ap3216cdev->client = client;
这行代码,把传入的 client
指针赋值给 ap3216c_dev
结构体中的 client
成员,这样 ap3216c_dev
结构体就能够保存当前 I2C 设备的相关信息。
缺失赋值语句带来的影响:
如果不进行 ap3216cdev->client = client;
这个赋值操作,ap3216c_dev
结构体中的 client
成员依旧是未初始化的,也就是它可能指向一个无效的内存地址,或者是一个随机值。在后续的驱动函数(如 ap3216c_read_regs
、ap3216c_write_regs
等)里,当尝试通过 ap3216cdev->client
去访问 i2c_client
结构体的成员时,就会出现问题。
也就是:
实际就是关联起来!
当然也可以不在驱动代码里面的设备结构体写client,直接引用i2c_client是另一种可行的方案,但是后果是:
- 若不把 i2c_client
指针保存到 ap3216c_dev
结构体中,那么在每个需要使用 i2c_client
的函数里都得显式传递该指针。这样会让函数的参数列表变长,代码变得冗余。例如,在多个操作函数中都要传递 i2c_client
指针,当驱动代码规模增大时,维护和修改都会变得困难。
所以我们在probe添加赋值操作:
同样我们要在remove编写(这里不是解除关联):
从 i2c_client
结构体中获取之前存储的 ap3216cdev
指针。这样我们就可以通过 ap3216cdev
访问到设备的各种信息,从而正确地释放资源。
补充错误类型:
驱动代码这里就全部讲完了!
下面我们开始写APP.c代码!
7、编写测试 APP
这里其实也简单,就是传递3个数据!
核心代码:
8、效果
正点原子的STM32MP157的板子,ap3216c芯片的位置并没有设置丝印,所以并不好找!
如下图:
当放手指上去时:
当放手电筒时:
9、总代码:
ap3216c.c:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"/*ap3216c设备结构体*/
struct ap3216c_dev{dev_t devid;int major;int minor;struct cdev cdev;struct class *class; /* 类 */struct device *device; /* 设备 */struct i2c_client *client;//描述设备unsigned short ir, als, ps; /* 16位,三个光传感器数据 */
};/*从ap3216c读取多个寄存器数据*/
static int ap3216c_read_regs(struct ap3216c_dev *dev,u8 reg,void *val, int len)
{int ret;struct i2c_msg msg[2];//真正传输的数据// i2c_client 用于描述 I2C 总线下的设备/*在 struct ap3216c_dev 中定义 struct i2c_client *client; 是为了关联设备与 i2c_client 结构体。而在函数中重新定义 struct i2c_client *client 一般是作为局部变量,用于在函数作用域内方便地操作 i2c_client 相关功能,避免直接操作结构体成员带来的混淆,同时也便于参数传递、错误处理和临时存储等,增强代码的可读性与可维护性。*/struct i2c_client *client = (struct i2c_client *)dev->client;/*这里意味着从设备的地址始终在 i2c_client结构体的addr子类中所以还是要访问 i2c_client结构体*//* msg[0]为发送要读取的首地址 */msg[0].addr = client->addr; /* ap3216c 地址,一般为7位地址 */msg[0].flags = 0; /* 标记为发送数据 ,其实就是0*/msg[0].buf = ® /* 读取的首地址 */msg[0].len = 1; /* reg 长度 *//* msg[1],第二条读消息,读取寄存器数据 */msg[1].addr = client->addr; /* I2C 器件地址,跟上面的地址一样,一个client一个从设备*/msg[1].flags = I2C_M_RD; /* 标记为读取数据,其实就是1*/msg[1].buf = val; /* 读取数据缓冲区 */msg[1].len = len; /* 要读取的数据长度 *//*上面的就是驱动代码读数据的时序:先发送然后读取*/ret = i2c_transfer(client->adapter, msg, 2);//发送这两条数据if(ret == 2) {ret = 0;} else {printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);ret = -EREMOTEIO;}return ret;
}/*向ap3216c多个寄存器写入数据*/
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg,u8 *buf, u8 len)
{u8 b[256];struct i2c_msg msg;//真正传输的数据/*在 struct ap3216c_dev 中定义 struct i2c_client *client; 是为了关联设备与 i2c_client 结构体。而在函数中重新定义 struct i2c_client *client 一般是作为局部变量,用于在函数作用域内方便地操作 i2c_client 相关功能,避免直接操作结构体成员带来的混淆,同时也便于参数传递、错误处理和临时存储等,增强代码的可读性与可维护性。*/struct i2c_client *client = (struct i2c_client *)dev->client;// i2c_client 用于描述 I2C 总线下的设备b[0] = reg; /* 寄存器首地址 ,将寄存器先赋值给b数组*/memcpy(&b[1],buf,len); /*将要写入的数据拷贝到数组b里面,从位置1开始赋值,位置0已经给寄存器了*/msg.addr = client->addr; /*ap3216c地址,一般为7个地址*/msg.flags = 0; /*标记为写数据,其实就是0*/msg.buf = b; /* 要写入的数据缓冲区 */msg.len = len + 1; /*要写入的数据长度,这里加1是因为前面多加了个reg*/return i2c_transfer(client->adapter,&msg,1);//传输的数据
}/*读取ap3216c指定寄存器值读取一个寄存器*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev,u8 reg)
{u8 data = 0;ap3216c_read_regs(dev, reg, &data, 1);return data;
}/*向ap3216c指定寄存器写入指定的值写一个寄存器*/
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg,u8 data)
{u8 buf = 0;buf = data;ap3216c_write_regs(dev, reg, &buf, 1);
}/*读取 AP3216C 的数据,包括 ALS,PS 和 IR, 注意!如果同时
打开 ALS,IR+PS 两次数据读取的时间间隔要大于 112.5ms*/
void ap3216c_readdata(struct ap3216c_dev *dev)
{unsigned char i =0;unsigned char buf[6];//8位/* 循环读取所有传感器数据 */for(i = 0; i < 6; i++) {buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);}/*判断IR寄存器中低8位的第7位是否有效,0:有效;1:无效*/if(buf[0] & 0X80) {/* IR_OF 位为 1,则数据无效 */dev->ir = 0;}else {/* 读取 IR 传感器的数据 *//*只要IR的高8位数据和低8位中的第0-1位数据,无效数据都置0,如低8位的2-7位数据*/dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);}/*ALS寄存器高8位与低8位合并*/dev->als = ((unsigned short)buf[3] << 8) | buf[2];/*判断PS寄存器中低8位的第6位是否有效,0:有效;1:无效*/if(buf[4] & 0x40) {/* IR_OF 位为 1,则数据无效 */dev->ps = 0;}else{/* 读取 PS 传感器的数据 *//*其中也要判断PS寄存器中高8位的第6位是否有效,0:有效;1:无效*//**只要高8位的5:0位和低8位的3:0为数据合并,无效数据都置0*/dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] &0X0F);}
}static int ap3216c_open(struct inode *inode, struct file *filp)
{/* 从 file 结构体获取 cdev 指针, 再根据 cdev 获取 ap3216c_dev 首地址 *//**open 函数中,通过 filp->f_path.dentry->d_inode->i_cdev 获取到字符设备的 struct cdev 指针*通过Linux 内核采用分层架构设计,各个子系统和模块之间需要保持一定的独立性和抽象性在字符设备操作函数(如 open、read、write 等)中,内核传递给驱动的参数是通用的,比如 struct inode 和 struct file 指针。这些参数中只包含了与文件系统和字符设备通用管理相关的信息,并不直接包含驱动自定义设备结构体的指针。内核通过 inode->i_cdev 提供了一个统一的接口,让驱动可以获取到字符设备的核心管理结构,然后再由驱动自己去关联到具体的设备数据*内核是分层架构,用 cdev 统一管理字符设备,操作函数参数通用,只能通过 cdev 关联具体设备数据。*/struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;/*通过cdev获取ap3216c设备结构体的首地址,保证内核操作的独立性*/struct ap3216c_dev *ap3216cdev = container_of(cdev,struct ap3216c_dev, cdev);/*初始化ap3216c*//*配置寄存器 软复位*/ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);mdelay(50);//延时50ms/*配置寄存器 使能PS+ALS+IR*/ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03);return 0;
}static ssize_t ap3216c_read(struct file *filp, char __user *buf,size_t cnt, loff_t *off)
{short data[3];//3个16位long err = 0;//用于存储错误码,初始化为 0 表示无错误/* 从 file 结构体获取 cdev 指针, 再根据 cdev 获取 ap3216c_dev 首地址 *//**open 函数中,通过 filp->f_path.dentry->d_inode->i_cdev 获取到字符设备的 struct cdev 指针*通过Linux 内核采用分层架构设计,各个子系统和模块之间需要保持一定的独立性和抽象性在字符设备操作函数(如 open、read、write 等)中,内核传递给驱动的参数是通用的,比如 struct inode 和 struct file 指针。这些参数中只包含了与文件系统和字符设备通用管理相关的信息,并不直接包含驱动自定义设备结构体的指针。内核通过 inode->i_cdev 提供了一个统一的接口,让驱动可以获取到字符设备的核心管理结构,然后再由驱动自己去关联到具体的设备数据*内核是分层架构,用 cdev 统一管理字符设备,操作函数参数通用,只能通过 cdev 关联具体设备数据。*/struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;/*通过cdev获取ap3216c设备结构体的首地址,保证内核操作的独立性*/struct ap3216c_dev *dev = container_of(cdev,struct ap3216c_dev, cdev);ap3216c_readdata(dev);data[0] = dev->ir;data[1] = dev->als;data[2] = dev->ps;err = copy_to_user(buf, data, sizeof(data));return 0;
}static int ap3216c_release(struct inode *inode, struct file *filp)
{struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;/*通过cdev获取ap3216c设备结构体的首地址,保证内核操作的独立性*/struct ap3216c_dev *ap3216cdev = container_of(cdev,struct ap3216c_dev, cdev);/*配置寄存器 掉电*/ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x00);return 0;
}/* AP3216C 操作函数 */
static const struct file_operations ap3216c_ops = {.owner = THIS_MODULE,.open = ap3216c_open,.read = ap3216c_read,.release = ap3216c_release,
};/*i2c 驱动的 probe 函数*/
static int ap3216c_probe(struct i2c_client *client,const struct i2c_device_id *id)
{int ret;/*分配内存*/struct ap3216c_dev *ap3216cdev;ap3216cdev = devm_kzalloc(&client->dev, sizeof(*ap3216cdev),GFP_KERNEL);if(!ap3216cdev){return -ENOMEM;}/* 注册字符设备驱动 *//*1->申请设备号*/ap3216cdev->major=0;if(ap3216cdev->major){//若给定主设备号ap3216cdev->devid=MKDEV(ap3216cdev->major,0);ret=register_chrdev_region(ap3216cdev->devid,AP3216C_CNT,AP3216C_NAME);}else {//若未给定主主设备号ret=alloc_chrdev_region(&ap3216cdev->devid,0,AP3216C_CNT,AP3216C_NAME);ap3216cdev->major=MAJOR(ap3216cdev->devid);ap3216cdev->minor=MINOR(ap3216cdev->devid);}if(ret < 0){//注册字符设备驱动失败goto fail_devid;}printk("major = %d,minor = %d,CNT = %d,NAME = %s\r\n",ap3216cdev->major,ap3216cdev->minor,AP3216C_CNT,AP3216C_NAME);/*2->初始化cdev*/ap3216cdev->cdev.owner=THIS_MODULE;cdev_init(&ap3216cdev->cdev,&ap3216c_ops);/*3->添加cdev*/ret=cdev_add(&ap3216cdev->cdev,ap3216cdev->devid,AP3216C_CNT);if(ret < 0) {goto fail_cdev;}/*4->创建类class*/ap3216cdev->class = class_create(THIS_MODULE,AP3216C_NAME);//创建/dev/ap3216cif(IS_ERR(ap3216cdev->class)){ret = PTR_ERR(ap3216cdev->class);goto fail_class;}/*5->创建设备节点device*/ap3216cdev->device = device_create(ap3216cdev->class,NULL,ap3216cdev->devid,NULL,AP3216C_NAME);//创建/dev/ap3216cif(IS_ERR(ap3216cdev->device)){ret = PTR_ERR(ap3216cdev->device);goto fail_device;}ap3216cdev->client = client;/* 保存 ap3216cdev 结构体 *//*实现了ap3216c_dev设备结构体与 i2c_client 之间的关联*/i2c_set_clientdata(client,ap3216cdev);return 0;
fail_device:class_destroy(ap3216cdev->class);
fail_class:cdev_del(&ap3216cdev->cdev);
fail_cdev:unregister_chrdev_region(ap3216cdev->devid,AP3216C_CNT);
fail_devid:return ret;
}
/*i2c 驱动的 remove 函数*/
static int ap3216c_remove(struct i2c_client *client)
{/*获取与 i2c_client 关联的自定义数据指针*/struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);/*注销字符设备驱动*//*注销设备device*/device_destroy(ap3216cdev->class,ap3216cdev->devid);/*注销类class*/class_destroy(ap3216cdev->class);/*注销字符设备对象*/cdev_del(&ap3216cdev->cdev);/*注销设备号*/unregister_chrdev_region(ap3216cdev->devid,AP3216C_CNT);return 0;
}/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id ap3216c_id[] = {{"alientek,ap3216c", 0},{}
};/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of,ap3216c_of_match);/* i2c 驱动结构体 */
static struct i2c_driver ap3216c_driver = {.probe = ap3216c_probe,.remove = ap3216c_remove,.driver = {.owner = THIS_MODULE,.name = "ap3216c",.of_match_table = ap3216c_of_match,},.id_table = ap3216c_id,
};/*驱动注册和注销一体化*/
module_i2c_driver(ap3216c_driver);MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree, "Y");
ap3216cApp.c:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int fd;char *filename;unsigned short databuf[3];unsigned short ir, als, ps;int ret = 0;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0) {printf("can't open file %s\r\n", filename);return -1;}while (1) {ret = read(fd, databuf, sizeof(databuf));if(ret == 0) { /* 数据读取成功 */ir = databuf[0]; /* ir 传感器数据 */als = databuf[1]; /* als 传感器数据 */ps = databuf[2]; /* ps 传感器数据 */printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);}usleep(200000); /*100ms */}close(fd); /* 关闭文件 */return 0;
}
makefile:
KERNELDIR := /home/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd)
obj-m := ap3216c.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
相关文章:
22.2Linux的I2C驱动实验(编程)_csdn
我尽量讲的更详细,为了关注我的粉丝!!! 这里我们用到的是stm32mp157的板子,所以我们看一下I2C用到的引脚。 1、硬件原理图分析 可以看到在这块板子上面用的SDA和SCL总线是PA11,PA12。所以要修改设备树和镜像文件&…...
socket-IO复用技术
五个I/O模型 1、阻塞I/O 2、非阻塞I/O 3、I/O复用(select和poll) 4、信号驱动I/O 5、异步I/O I/O复用 是一种在单线程或单进程环境下,同时监听多个 I/O 事件的技术。它允许程序高效地处理多个输入输出流(如网络套接字、文件描…...
上位机知识篇---二进制操作
文章目录 前言接收数据示例:0xAA 0x12 0x34 0x55合并高/低字节数据RGB565颜色值:0xF800(红色)Python中负数右移接收帧:01 03 02 12 34 CRC前言 本文简单对单片机、上位机中的映射(Mapping)和位移操作符(Bit Shifting)等相关知识进行了简单介绍. 一、单片机与上位机中…...
openEuler 22.03 安装 Mysql 5.7,TAR离线安装
目录 一、检查系统是否安装其他版本Mariadb数据库二、环境检查2.1 必要环境检查2.2 在线安装(有网络)2.3 离线安装(无网络) 二、下载Mysql2.1 在线下载2.2 离线下载 三、安装Mysql四、配置Mysql五、开放防火墙端口六、数据备份七、…...
《排序算法总结》
引言: 编程学到现在,我们已经接触了很多种排序算法,这篇文章我就对常见的几种排序算法进行一个小结。 一: 排序算法分类: 二: 插入排序: 直接插入排序: 1. 概念: 直…...
【Java学习笔记】递归
递归(recursion) 思想:把一个复杂的问题拆分成一个简单问题和子问题,子问题又是更小规模的复杂问题,循环往复 本质:栈的使用 递归的注意事项 (1)需要有递归出口,否者就…...
体系学习1:C语言与指针1——预定义、进制打印、传参为数组
1、不对一段代码进行编译 #if 0 statement #endif2、输出地址 int d[3]{1,2,3}; printf("%p",(void*)d);//p期待的是void*类型的数据3、不同进制的打印 int data 1200; char hed[9];//为\0预留位置!!! sprintf(hed,"%08X&…...
使用Java正则表达式进行分组与匹配文本提取
在Java开发中,正则表达式(Regex)是处理字符串的强大工具,广泛应用于数据验证、文本解析和格式转换等场景。通过正则表达式的分组功能,开发者可以精确地提取匹配模式的子部分,而不仅仅是整个匹配内容。Java的…...
RAGFlow上传3M是excel表格到知识库,提示上传的文件总大小过大
环境: Ragflowv0.17.2 问题描述: RAGFlow上传3M是excel表格到知识库,提示上传的文件总大小过大 解决方案: 定位问题: 1.查询Nginx 日志 Nginx 日志 检查 Nginx 配置中日志路径是否正确,确保日志文件有…...
2025年4月文章一览
2025年4月编程人总共更新了30篇文章: 1.2025年3月文章一览 2.《Operating System Concepts》阅读笔记:p528-p544 3.《Operating System Concepts》阅读笔记:p545-p551 4.《Operating System Concepts》阅读笔记:p552-p579 5.…...
2025大模型微调视频课程全套(附下载)
2025大模型微调视频课程全套,共10课。主要内容如下: 1、大模型的发展 2、Transformer & LLMs 3、大模型微调预览&Lora微调&Alpaca模型微调 4、Alpaca&AdaLoRA&QLoRA模型微调 5、Efficient Fine-tuning&Efficient Inference&…...
【Python Web开发】04-Cookie和Session
文章目录 1. Cookie1.1 定义1.2 工作原理1.3 用途1.4 优缺点 2. Session2.1 定义2.2 工作原理2.3 用途2.4 优缺点 3. Cookie 与 Session 的关系4. 安全性考量5. Python 中使用 Cookie 和 Session 在 HTTP 协议里,Cookie 和 Session 是用于管理客户端与服务器之间会话…...
从股指到期指,哪些因素影响基差?
当我们谈论股指期货(简称“期指”)与股票现货指数(简称“股指”)的基差时,其实是在探讨期货价格与现货价格之间的“差价”。这个差价受多种因素影响,时而扩大,时而缩小,甚至可能“翻…...
n8n 中文系列教程_15. 【工具篇】n8n中文版与汉化指南:从原理到实践
n8n 作为一款强大的开源自动化工具,目前尚未推出官方中文版,但社区提供了汉化方案。不过,对于技术用户,我们更推荐使用英文原版,以便更好地查阅文档和解决问题。如果你仍希望尝试汉化,本文将详细介绍如何通…...
3D版同步帧游戏
以下是实现一个3D版同步帧游戏的详细步骤与完整代码示例。我们将以第一人称射击游戏(FPS)为原型,重点讲解3D空间中的同步机制优化。 项目升级:3D版核心改动 1. 3D坐标系与消息结构 // common/messages.go type Vector3 struct {X float32 `json:"x"`Y float32 `…...
C语言中数字转化为字符串的方法
C语言中数字转化为字符串的方法 1. 使用 sprintf 函数 这是 stdio.h 头文件中的标准库函数 ,功能类似于 printf ,但不是输出到控制台,而是将格式化后的内容输出到字符数组(字符串)中。 示例代码: c #inc…...
使用MGeo模型高精度实现文本中地址识别
一、功能与安装 1、模型地址 模型是阿里开发的门址高精度识别模型。 https://modelscope.cn/models/iic/mgeo_geographic_elements_tagging_chinese_base/summary 注意:不能自己安装包,没法解决依赖问题,直接按照官方要求安装下面的包&am…...
OpenGL-ES 学习(15) ----纹理
目录 纹理简介纹理映射纹理映射流程示例代码:纹理的环绕和过滤方式纹理的过滤方式 纹理简介 现实生活中,纹理(Texture) 类似于游戏中皮肤的概念,最通常的作用是装饰 3D 物体,它像贴纸一样贴在物体的表面,丰富物体的表…...
类成员函数编译链接的过程
1.静态成员函数和普通成员函数 源文件编译成目标文件,静态成员函数和普通成员函数在目标文件代码段,函数添加进了符号表,地址是在代码段的相对地址,这个地址只是一个临时地址因为后面链接时还要合并代码段,函数地址还…...
PostgreSQL:pgAdmin 4 使用教程
pgAdmin 4 是一个用于管理和维护 PostgreSQL 数据库的强大工具。它提供了一个图形化界面,使用户能够轻松地连接到数据库、创建表、运行 SQL 语句以及执行其他数据库管理任务。 安装和使用 安装 pgAdmin 4 安装 pgAdmin 4 非常简单。下载并运行安装程序࿰…...
*(解引用运算符)与 ++(自增运算符)的优先级
在 C 和 C 等编程语言里,*(解引用运算符)与 (自增运算符)的执行优先级高低,要依据 是前缀形式还是后缀形式来确定。下面为你详细分析: 1. 后缀 运算符 后缀 运算符的优先级比 *(…...
二叉搜索树中的搜索(递归解决)
700. 二叉搜索树中的搜索 - 力扣(LeetCode) 二叉搜索树(BST):以任意节点为根节点的数值大于其左子树所有节点的值,小于右子树所有节点的值。 查找二叉搜索树中的值,要利用节点之间的大小关系。…...
idea安装
1.卸载 2.安装 3.ssh...
在ASP.NET MVC中使用Repeater指南
虽然ASP.NET MVC框架本身不包含Web Forms中的Repeater控件,但您可以通过几种方式实现类似的功能。以下是几种在MVC中实现Repeater效果的方法: 1. 使用foreach循环 最简单的方法是直接在视图中使用Razor的foreach循环: csharp model IEnumer…...
【C语言常用字符串解析】
总结一下在 C 语言中用于字符串解析(特别是从文件中读取行并提取数据)的常用函数、 核心任务: 通常是从文件中读取一行文本(一个字符串),然后从这个字符串中提取出需要的数据(比如数字、单词等…...
基于深度学习农作物叶部病害实时检测系统研究(源码+定制+开发)
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...
『MCP』初体验
『MCP』初体验 介绍 MCP 其实就是 Function Calling 的一个统一接口协议,网上介绍会有很多,所以这里不就重复介绍,这里主要是想记录说明一下 MCP 使用体验,可以帮助新人入门一下 安装 VSCode 以及 MCP client VSCode 自行安装…...
前端面试宝典---webpack原理解析,并有简化版源码
前言 先看一下webpack打包后的bundle.js,前边的直接扫一眼就过,可以发现这个立即执行函数的形参就是一个,key为引入文件路径,value为该模块代码的函数。 所以比较重要的就是通过webpack的配置文件中的entry的入口文件,…...
负载均衡深度实践:基于Nginx+Keepalived的高可用方案与Zabbix监控设计
目录 综合实践-部署负载均衡 1 环境准备 2 zabbix监控nginx和keeplive 2.1 nginx安装 2.2 安装keepalived 2.3 部署vue 2.4 安装agent 2.5 zabbix监控nginx配置 2.6 zabbix监控keeplived 3 zabbix监控jar 3.1 安装agent 3.2 安装jdk 3.3 部署jar包 3.4 配置web 4…...
深度学习基础--目标检测入门简介
博主简介:努力学习的22级本科生一枚 🌟 博客主页:羊小猪~~-CSDN博客 内容简介:探索AI算法,C,go语言的世界;在迷茫中寻找光芒🌸 往期回顾:yolov5基础–一步一步教…...
Redis ⑧-RESP | 渐进式遍历 | 数据库管理
Redis data-types 除了之前学习的 string、hash、list、set、Zset 五种数据结构之外,Redis 还提供了 bitmap、bitfield、 hyperloglog、geospatial、stream 等数据结构。 另外的一些数据结构,都是在某些特定环境下才会使用,使用频率不高&…...
【Android】四大组件之ContentProvider
目录 一、什么是 ContentProvider 二、创建和使用 ContentProvider 三、跨应用权限控制 四、数据变更通知 五、多表关联与视图 六、异步处理 你手机里的通讯录,存储了所有联系人的信息。如果你想把这些联系人信息分享给其他App,就可以通过ContentP…...
Qwen3 发布:优化编码与代理能力,强化 MCP 支持引领 AI 新潮流
人工智能领域的每一次重大突破都如同璀璨星辰,照亮了人类前行的道路。2025 年 4 月 29 日凌晨,阿里巴巴旗下的 Qwen 官方团队正式发布了最新一代大语言模型 ——Qwen3,犹如一颗重磅炸弹,在 AI 领域掀起了惊涛骇浪。此次发布&#…...
LEETERS题解
【题目描述】 给出一个rowcolrowcol的大写字母矩阵,一开始的位置为左上角,你可以向上下左右四个方向移动,并且不能移向曾经经过的字母。问最多可以经过几个字母。 【输入】 第一行,输入字母矩阵行数RR和列数SS,1≤R,S≤…...
图像加密算法概述
版本: 1.0 日期: 2025年5月1日 目录 引言 1.1 什么是图像加密?1.2 为什么需要图像加密?1.3 图像数据的特点与加密挑战加密基础概念 2.1 明文与密文2.2 加密与解密2.3 密钥2.4 对称加密与非对称加密为什么传统文本加密算法不完全适用于图像? 3.1 数据量巨大3.2 高度冗余性…...
loads、dumps、jsonpath使用场景
在处理JSON数据时,loads、dumps 和 jsonpath 是三个非常有用的工具或概念。它们各自在不同的场景下发挥作用,让我们一一来看: 1. loads loads 函数是 Python 中 json 模块的一部分,用于将 JSON 格式的字符串解析成 Python 的数据…...
Winform(7.序列化方式整理)
今天我又对序列化方式进行了整理,可以与上一篇序列化方式一起看 一.序列化方式(四种) 1.二进制序列化 //定义 Person 类,需要标记为可序列化 [Serializable] public class Person { public string Name{get;set;} public int Age{get;set;} } 在进行二进制序列化…...
通过AI的联网功能提升搜索检索能力
以百度ai搜索(百度AI搜索 - 办公学习一站解决)为例,ai会自动根据问题搜集现有互联网文章,避免人工通过传统检索引擎的结果逐个去查找,这种方式文章的相关性会更高。 tip:快速查看每篇文档,仅关…...
Spring IoC容器的设计与实现
Spring整体架构与模块划分 核心容器(Core Container) spring-core 基础工具类:如资源加载(Resource接口)、反射工具(ReflectionUtils)、类型转换(ConversionService)。…...
使用vue的插值表达式渲染变量,格式均正确,但无法渲染
如图,作者遇到的问题为,输入以下代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…...
数据库 AI 助手测评:Chat2DB、SQLFlow 等工具如何提升开发效率?
一、引言:数据库开发的 “效率革命” 正在发生 在某互联网金融公司的凌晨故障现场,资深 DBA 正满头大汗地排查一条执行超时的 SQL—— 该语句涉及 7 张核心业务表的复杂关联,因索引缺失导致全表扫描,最终引发交易系统阻塞。这类场景在传统数据库开发中屡见不鲜:据 Gartne…...
21.1Linux中的LCD驱动实验(知识)_csdn
1、LCD 和 LTDC 简介 1.1、LCD 简介 1.1.1、分辨率 1.1.2、像素格式 可以看到红、绿、蓝每个8位,还有一位是A7~A0就是透明通道,32位ARG8888。 1.1.3、LCD 屏幕接口 1.1.4、LCD 时间参数 如果将 LCD 显示一帧图像的过程想象成绘画,那么…...
Angular教程前言:历史、安装与用途
Angular 是一个强大且流行的开源前端 Web 应用程序框架,由 Google 开发并维护 1。它在现代 Web 开发中占据着重要的地位,尤其在构建动态、高效且可扩展的 Web 应用程序方面表现出色,特别适用于单页应用程序 (SPA) 和复杂的用户界面 1。本教程…...
node.js模块化步骤(各标准区别)CommonJS规范、AMD规范、UMD规范、ES Modules (ESM)
前后端建议统一使用ESM 文章目录 Node.js模块化发展历程与标准对比一、模块化的意义1.1 解决的核心问题1.2 没有模块化的问题 二、CommonJS规范2.1 核心特征2.2 实现示例 三、AMD (Asynchronous Module Definition)3.1 特点3.2 代码示例 四、UMD (Universal Module Definition)…...
Unity图片导入设置
🏆 个人愚见,没事写写笔记 🏆《博客内容》:Unity3D开发内容 🏆🎉欢迎 👍点赞✍评论⭐收藏 🔎Unity支持的图片格式 ☀️BMP:是Windows操作系统的标准图像文件格式,特点是…...
MySQL与分布式架构的碰撞
目录 一、分布式架构的核心挑战与MySQL的应对策略 1.1 高并发与扩展性 1.3 高可用与容灾 二、MySQL分布式架构的核心技术实现 2.1 读写分离与主从复制(扩展) 2.2 数据分片与分布式存储(扩展) 2.3 MySQL Cluster与NDB引擎&am…...
python-MySQL鏈接
python鏈接MySQL,主要利用庫 pip install mysql-connector-pythonimport mysql.connector# 配置连接参数 config {"user": "your_username","password": "your_password","host": "localhost", # 或…...
cv::remap() 和 cv::undistortion() 的区别
在 OpenCV 中,cv::remap 和 cv::undistort 都用于处理图像畸变校正,但它们的实现方式和应用场景有显著区别。以下是详细对比: 1. cv::undistort:直接畸变校正 功能 输入:原始畸变图像 相机内参矩阵 (cameraMatrix) …...
【AI提示词】决策树专家
提示说明 一位熟悉决策树算法的机器学习专家,擅长用树状图量化不同选择的结果概率。 提示词 # Role: 决策树专家## Profile - language: 中文 - description: 一位熟悉决策树算法的机器学习专家,擅长用树状图量化不同选择的结果概率 - background: 决…...
【中间件】bthread_数据结构_学习笔记
bthread数据结构 bthread_数据结构_学习笔记1 pthread_cond_t1.1 definition1.2 解释1.3 设计动机1.4 使用示例1.5 注意事项1.6 进一步延伸:pthread_cond_s 2 pthread_mutex_t bthread_数据结构_学习笔记 1 pthread_cond_t POSIX线程库 /usr/include/x86_64-linux…...