当前位置: 首页 > news >正文

Linux第104步_基于AP3216C之I2C实验

Linux之I2C实验是在AP3216C的基础上实现的,进一步熟悉修改设备树和编译设备树,以及学习如何编写I2C驱动和APP测试程序。

1、AP3216C的原理图

AP3216C集成一个光强传感器ALS一个接近传感器PS和一个红外LED,为三合一的环境传感器。它主要是给手机之类的产品使用,比如:返回“当前环境的光强”以便调整手机屏幕的亮度;当用户接听电话时,将手机放置在耳边后,它会自动关闭屏幕,防止用户错误触碰。

AP3216C用到了I2C5接口,其中SCL连接PA11,SDA连接到PA12。如果用到AP3216C的中断功能的话,则需要初始化AP_INT,该引脚连接到PE4。本驱动需要使用中断功能因此只需要PA11和PA12这个两个IO复用为AF4功能即可。

2、修改设备树

SOC厂商已经替我们编写好了“I2C适配器驱动”,我们需要做的就是编写具体的设备驱动。

2.1、打开设备树头文件“stm32mp15-pinctrl.dtsi”,找到“i2c5_pins_a”,内容如下:

i2c5_pins_a: i2c5-0 { /*在默认状态下使用*/

pins {

pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL */

<STM32_PINMUX('A', 12, AF4)>; /* I2C5_SDA */

bias-disable;

drive-open-drain;

slew-rate = <0>;

};

};

i2c5_pins_sleep_a: i2c5-1 { /*在睡眠状态下使用*/

pins {

pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */

<STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */

};

};

2.2、打开“stm32mp157d-atk.dts”,添加内容如下(注意:不是在根节点“/”下添加):

&i2c5 {

pinctrl-names = "default", "sleep";

pinctrl-0 = <&i2c5_pins_a>;

pinctrl-1 = <&i2c5_pins_sleep_a>;

status = "okay";

ap3216c@1e {

/*向i2c5添加ap3216c子节点,“@”后面的“1e”就是ap3216c的I2C器件地址*/

compatible = "zgq,ap3216c";/*compatible属性值为"zgq,ap3216c"*/

reg = <0x1e>;/*reg属性是设置ap3216c的器件地址0x1e*/

};

};

2.3、查看PA11和PA12是否被使用

打开设备树头文件“stm32mp15-pinctrl.dtsi”,查看PA11和PA12是否被使用了。

①点击“编辑”,点击“查找”,输入“STM32_PINMUX('A', 11”,然后“回车”,没有发现PA11被复用;

②点击“编辑”,点击“查找”,输入“STM32_PINMUX('A',12”,然后“回车”,发现PA12被复用,屏蔽该语句,见下图:

2.4、编译设备树

在终端,输入“make uImage dtbs LOADADDR=0XC2000040 -j8回车”,执行编译“Image”和“dtbs”,并指定装载的起始地址为0XC2000040,j8表示指定采用8线程执行。make dtbs”,用来指定编译设备树。见下图:

②输入“ls arch/arm/boot/uImage -l

查看是否生成了新的“uImage”文件

③输入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l

查看是否生成了新的“stm32mp157d-atk.dtb”文件

4)、拷贝输出的文件:

①输入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC;

②输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC

③输入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;

④输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;

⑤输入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车”,查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目录下的所有文件和文件夹

⑥输入“ls -l /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹

⑦输入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车

给“stm32mp157d-atk.dtb”文件赋予可执行权限

⑧输入“chmod 777 /home/zgq/linux/tftpboot/uImage回车 ,给“uImage”文件赋予可执行权限

⑨输入“ls /home/zgq/linux/tftpboot/ -l回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹

3、编写AP3216C驱动和APP

3.1、创建“/home/zgq/linux/Linux_Drivers/AP3216C/”目录

1)、打开终端,输入“cd /home/zgq/linux/Linux_Drivers/回车”,切换到“/home/zgq/linux/Linux_Drivers/”目录;

2)、输入“ls回车”,列举“/home/zgq/linux/Linux_Drivers/”目录的所有文件和文件夹;

3)、输入“mkdir AP3216C回车”,创建“/home/zgq/linux/Linux_Drivers/input_key/”目录;

4)、输入“ls回车”,列举“/home/zgq/linux/Linux_Drivers/”目录的所有文件和文件夹;

3.2、编写AP3216C驱动程序之头文件“AP3216C.h

1)、打开虚拟机中的VSCode,点击“文件”,点击“打开文件夹”,然后点击“zgg,linux,Linux_Drivers,AP3216C”,如下图:

2)、点击上图中的确定,然后点击“文件”,点击“新建文件”,点击“文件”,点击“另存为”,输入“AP3216C.h”。

3)、点击“保存”。输入下面的内容:

#ifndef AP3216C_H

#define AP3216C_H

/************************************************

 * 描述 : AP3216C寄存器地址描述头文件

 * **********************************************/

#define AP3216C_ADDR 0X1E /* AP3216C器件地址 */

/* AP3316C寄存器 */

#define AP3216C_SYSTEMCONG  0x00 /* 配置寄存器 */

#define AP3216C_INTSTATUS    0X01 /* 中断状态寄存器 */

#define AP3216C_INTCLEAR     0X02 /* 中断清除寄存器 */

#define AP3216C_IRDATALOW   0x0A /*红外LEDIR数据低字节 */

#define AP3216C_IRDATAHIGH   0x0B /*红外LEDIR数据高字节 */

#define AP3216C_ALSDATALOW  0x0C /*光强传感器ALS数据低字节 */

#define AP3216C_ALSDATAHIGH  0X0D /*光强传感器ALS数据高字节 */

#define AP3216C_PSDATALOW   0X0E /*接近传感器PS数据低字节 */

#define AP3216C_PSDATAHIGH  0X0F /*接近传感器PS数据高字节 */

#endif

3.2、编写AP3216C驱动程序之头文件“AP3216C.c

1)、打开虚拟机中的VSCode,点击“文件”,点击“新建文件”,点击“文件”,点击“另存为”,输入“AP3216C.c”。

2)、点击“保存”。输入下面的内容:

#include <linux/types.h>

//数据类型重命名

//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t

//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t

#include <linux/kernel.h>//必须要包含的头文件

#include <linux/init.h>//必须要包含的头文件

#include <linux/delay.h>

//Linux内核中用到的延时函数

//使能ndelay(),udelay(),mdelay()

#include <linux/ide.h>//使能copy_from_user(),copy_to_user()

#include <linux/module.h>//使能AP3216C_init(),AP3216C_exit()

#include <linux/errno.h>

#include <linux/gpio.h>

//使能gpio_request(),gpio_free(),gpio_direction_input(),

//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()

#include <linux/cdev.h>//使能cdev结构

#include <linux/device.h>//使能class结构和device结构

#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 "AP3216C.h"/*头文件名*/

/*

没有定义一个全局变量,那是因为linux内核不推荐使用;

全局变量要使用内存的就用devm_kzalloc()之类的函数去申请空间。

*/

#define AP3216C_NAME "ap3216c"/*设备名字,APP程序要对它进行操作*/

#define AP3216C_CNT 1 //设备数量

struct ap3216c_dev {

    struct i2c_client *client; /*i2c设备*/

    dev_t devid;               /*设备号*/

    struct cdev cdev;          /*cdev*/

    struct class *class;       /*类*/

    struct device *device;     /*设备*/

    struct device_node *nd;    /*设备节点*/

    unsigned short ir, als, ps;

    /* 三个光传感器数据 */

    /*ir用来存储AP3216C的红外LED的IR数据*/

    /*als用来存储AP3216C的光强传感器ALS数据*/

    /*ps用来存储AP3216C的接近传感器PS数据*/

};

/*

函数功能: 从AP3216C读取多个寄存器数据,注意:AP3216C不支持连续读取多个字节

参数dev : ap3216c设备

参数reg : 要读取的寄存器首地址

参数val : 读取到的数据

参数len : 要读取的数据长度

返回值: 操作结果

*/

static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)

{

    int ret;

    struct i2c_msg msg[2];

    struct i2c_client *client = (struct i2c_client *)dev->client;

    

    /* msg[0]为发送要读取的首地址 */

    msg[0].addr = client->addr; /*AP3216C地址*/

    msg[0].flags = 0;           /*标记为发送数据*/

    msg[0].buf = ®          /*读取的寄存器首地址*/

    msg[0].len = 1;             /*reg长度*/

    

    /* msg[1]读取数据 */

    msg[1].addr = client->addr; /*AP3216C地址*/

    msg[1].flags = I2C_M_RD;    /*标记为读取数据*/

    msg[1].buf = val;           /*读取数据缓冲区,pointer to msg data*/

    msg[1].len = len;           /*要读取的数据长度,msg length*/

    

    ret = i2c_transfer(client->adapter, msg, 2);

    /*先发送“AP3216C地址“和发送“读取的寄存器首地址“,接着读取“该寄存器的数据“*/

    /*因为是先写后读,因此消息有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多个寄存器写入数据,注意:AP3216C不支持连续写多个字节

参数dev: ap3216c设备

参数reg: 要写入的寄存器首地址

参数val: 要写入的数据缓冲区

参数len: 要写入的数据长度

返回值: 操作结果

*/

static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)

{

    u8 b[256];

    struct i2c_msg msg;

    struct i2c_client *client = (struct i2c_client *)dev->client;

    

    b[0] = reg;            /*要写入数据的寄存器首地址*/

    memcpy(&b[1],buf,len);

    /*将首地址为buf中的数据拷贝到首地址为&b[1]的存储区中,字节数量为len*/

    

    msg.addr = client->addr; /*AP3216C地址*/

    msg.flags = 0; /*标记为写数据*/

    

    msg.buf = b;       /*要写入的数据缓冲区,pointer to msg data*/

    msg.len = len + 1; /*要写入的数据长度,因为reg占1个字节,所以这里要加1*/

    

    return i2c_transfer(client->adapter, &msg, 1);

    /*发送“AP3216C地址“,发送“要写入数据的寄存器首地址“,接着写入“该寄存器的数据“*/

    /*因为只有“一条写消息“,因此消息数量为1*/

}

/*

函数功能: 读取AP3216C指定寄存器值,读取一个寄存器,注意:AP3216C不支持连续读取多个字节

参数dev: ap3216c设备

参数reg: 要读取的寄存器

返回值: 读取到的寄存器值

*/

static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)

{

    u8 data = 0;

    

    ap3216c_read_regs(dev, reg, &data, 1);

    /*从AP3216C读取多个寄存器数据,注意:AP3216C不支持连续读取多个字节*/

    return data;

}

/*

函数功能: 向ap3216c指定寄存器写入指定的值,写一个寄存器,注意:AP3216C不支持连续写多个字节

参数dev: ap3216c设备

参数reg: 要写的寄存器

参数data: 要写入的值

返回值: 无

*/

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多个寄存器写入数据,由于AP3216C不支持连续写多个字节,因此这里只写入1个字节*/

}

/*

函数功能: 读取AP3216C的原始数据值,包括光强传感器ALS,接近传感器PS和红外LED的IR

注意!如果同时打开ALS,IR+PS两次数据读取的时间间隔要大于112.5ms

参数ir : ir数据

参数ps : ps数据

参数ps : als数据

返回值: 无。

*/

void ap3216c_readdata(struct ap3216c_dev *dev)

{

    unsigned char i =0;

    unsigned char buf[6];

    

    /*循环读取所有传感器数据*/

    //当i=0时,读取“红外LED的IR数据低字节“

    //当i=1时,读取“红外LED的IR数据高字节“

    //当i=2时,读取“光强传感器ALS数据低字节“

    //当i=3时,读取“光强传感器ALS数据高字节“

    //当i=4时,读取“接近传感器PS数据低字节“

    //当i=5时,读取“接近传感器PS数据高字节“

    for(i = 0; i < 6; i++)

    {

        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);

        /*读取AP3216C指定寄存器值,读取一个寄存器*/

    }

    

    if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */

      dev->ir = 0;

    else 

    dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);

    /*保存"红外LED的IR传感器的数据"*/

    

    dev->als = ((unsigned short)buf[3] << 8) | buf[2];

    /*保存光强传感器ALS数据*/

    

    if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */

      dev->ps = 0;

    else 

      dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);

      /*保存"PS传感器的数据"*/

}

/*

函数功能: 打开设备

参数inode : 传递给驱动的inode

参数filp : 设备文件,file结构体有个叫做private_data的成员变量

* 一般在open的时候将private_data指向设备结构体。

返回值: 0 成功;其他 失败

*/

static int ap3216c_open(struct inode *inode, struct file *filp)

{

    /* 从file结构体获取cdev指针,再根据cdev获取ap3216c_dev首地址 */

    struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;

    struct ap3216c_dev *ap3216cdev = container_of(cdev, struct ap3216c_dev, cdev);

    /*根据ap3216c_dev结构中的cdev成员和这个成员的指针值cdev,计算出ap3216c_dev型结构变量的首地址*/   

    /* 初始化AP3216C */

ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0x04);

//将0x04写入AP3216C的配置寄存器

    mdelay(50);

ap3216c_write_reg(ap3216cdev, AP3216C_SYSTEMCONG, 0X03);

//将0x03写入AP3216C的配置寄存器

    return 0;

}

    

/*

函数功能: 从设备读取数据

参数filp : 要打开的设备文件(文件描述符)

参数buf : 返回给用户空间的数据缓冲区

参数cnt : 要读取的数据长度

参数offt : 相对于文件首地址的偏移

返回值: 读取的字节数,如果为负值,表示读取失败

*/

/* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */

//file结构指针变量flip表示要打开的设备文件

//buf表示用户数据块的首地址

//cnt表示用户数据的长度,单位为字节

//loff_t结构指针变量off表示“相对于文件首地址的偏移”

static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)

{

    short data[3];

    long err = 0;

    

    struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;

    struct ap3216c_dev *dev = container_of(cdev, struct ap3216c_dev, cdev);

    /*根据ap3216c_dev结构中的cdev成员和这个成员的指针值cdev,计算出ap3216c_dev型结构变量的首地址*/

    

    ap3216c_readdata(dev);/*读取AP3216C的原始数据值*/

    

    data[0] = dev->ir;//保存“红外LED的IR数据“

    data[1] = dev->als;//保存“光强传感器ALS的数据“

    data[2] = dev->ps;//保存“接近传感器PS的的数据“

    err = copy_to_user(buf, data, sizeof(data));

    /*将data[]中数据拷贝到buf[]中*/

    return 0;

}

/*

函数功能: 关闭/释放设备

参数filp : 要关闭的设备文件(文件描述符)

返回值: 0 成功;其他 失败

*/

static int ap3216c_release(struct inode *inode, struct file *filp)

{

    return 0;

}

/* AP3216C操作函数 */

/*声明file_operations结构变量ap3216c_ops*/

/*它是指向设备的操作函数集合变量*/

static const struct file_operations ap3216c_ops = {

    .owner = THIS_MODULE,

    /*表示该文件的操作结构体所属的模块是当前的模块,即这个模块属于内核*/

    .open = ap3216c_open,

    .read = ap3216c_read,

    .release = ap3216c_release,

};

/*

函数功能: i2c驱动的probe函数,当驱动与设备匹配以后,此函数就会执行

参数client : i2c设备

参数id : i2c设备ID

返回值: 0,成功;其他负值,失败

*/

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、创建设备号 */

    ret = alloc_chrdev_region(&ap3216cdev->devid, 0, AP3216C_CNT, AP3216C_NAME);

    //注册字符设备驱动

    //ap3216cdev->devid:保存申请到的设备号

    //baseminor=0:次设备号的起始地址

    //count=AP3216C_CNT:要申请的设备数量;

    //AP3216C_NAME:表示“设备名字”

    if(ret < 0)

    {

        pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", AP3216C_NAME, ret);

        return -ENOMEM;

    }

    

    /* 2、初始化cdev */

    ap3216cdev->cdev.owner = THIS_MODULE;

    /*使用THIS_MODULE将owner指针指向当前这个模块*/

    cdev_init(&ap3216cdev->cdev, &ap3216c_ops);

    //初始化字符设备

    //ap3216cdev->cdev是等待初始化的结构体变量

    //ap3216c_ops就是字符设备文件操作函数集合,就是AP3216C操作函数

   

    /* 3、添加一个cdev */

    ret = cdev_add(&ap3216cdev->cdev, ap3216cdev->devid, AP3216C_CNT);

    //添加字符设备

    //&ap3216cdev->cdev表示指向要添加的字符设备,即字符设备结构cdev变量

    //ap3216cdev->devid表示设备号

    //count=AP3216C_CNT表示需要添加的设备数量

    if(ret < 0) {

        goto del_unregister;

    }

    

    /* 4、创建类 */

    ap3216cdev->class = class_create(THIS_MODULE, AP3216C_NAME);

    //创建类

    /*使用THIS_MODULE将owner指针指向当前这个模块*/

    //使用AP3216C_NAME作为“类名字“

    //返回值是指向结构体class的指针,也就是创建的类

    if (IS_ERR(ap3216cdev->class)) {

        goto del_cdev;

    }

    

    /* 5、创建设备 */

    ap3216cdev->device = device_create(ap3216cdev->class, NULL, ap3216cdev->devid, NULL, AP3216C_NAME);

    //创建设备

    //设备要创建在ap3216cdev->class类下面

    //NULL表示没有父设备

    //ap3216cdev->devid是设备号;

    //参数drvdata=NULL,设备没有使用数据

    //AP3216C_NAME是设备名字

    //如果设置fmt=AP3216C_NAMEE的话,就会生成/dev/AP3216C_NAME设备文件。

    //返回值就是创建好的设备。

    if (IS_ERR(ap3216cdev->device)) {

        goto destroy_class;

    }

    ap3216cdev->client = client;

    /*保存ap3216cdev结构体*/

    i2c_set_clientdata(client,ap3216cdev);

    /*将ap3216cdev变量的地址绑定client*/

    /*就可以通过i2c_get_clientdata(client)获取ap3216cdev变量指针*/

    

    return 0;

    destroy_class:

    device_destroy(ap3216cdev->class, ap3216cdev->devid);

    /*注销设备,删除创建的设备*/

    /*参数ap3216cdev->class是设备所处的类,ap3216cdev->devid是设备号*/

    del_cdev:

    cdev_del(&ap3216cdev->cdev);

    //删除字符设备

    /*&ap3216cdev->cdev表示指向需要删除的字符设备,即字符设备结构ap3216cdev->cdev变量*/

    del_unregister:

    unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);

   /*注销设备号,释放设备号 */

   //ap3216cdev->devid:需要释放的设备号

   //AP3216C_CNT:需要释放的次设备号数量;

    return -EIO;

}

/*

函数功能: i2c驱动的remove函数,移除i2c驱动的时候此函数会执行

参数client : i2c设备

返回值: 0,成功;其他负值,失败

*/

static int ap3216c_remove(struct i2c_client *client)

{

    struct ap3216c_dev *ap3216cdev = i2c_get_clientdata(client);

    /*通过i2c_get_clientdata(client)获取ap3216cdev变量指针*/

    /* 注销字符设备驱动 */

    /* 1、删除cdev */

    cdev_del(&ap3216cdev->cdev);

    //删除字符设备

    /*&ap3216cdev->cdev表示指向需要删除的字符设备,即字符设备结构ap3216cdev->cdev变量*/

    /* 2、注销设备号 */

    unregister_chrdev_region(ap3216cdev->devid, AP3216C_CNT);

    /*注销设备号,释放设备号 */

    /*ap3216cdev->devid:需要释放的设备号*/

    /*AP3216C_CNT:需要释放的次设备号数量*/

    /* 3、注销设备 */

    device_destroy(ap3216cdev->class, ap3216cdev->devid);

    /*注销设备,删除创建的设备*/

    /*参数ap3216cdev->class是设备所处的类,ap3216cdev->devid是设备号*/

    /* 4、注销类 */

    class_destroy(ap3216cdev->class);

    /*删除类,ap3216cdev->class就是要删除的类*/

    return 0;

}

/*传统匹配方式ID列表*/

static const struct i2c_device_id ap3216c_id[] = {

    {"zgq,ap3216c", 0},

    {}

};

/*设备树匹配列表*/

static const struct of_device_id ap3216c_of_match[] = {

    { .compatible = "zgq,ap3216c" },

    /*在stm32mp157d-atk.dts设备树文件中,定义“compatible = "zgq,ap3216c”*/

    { /*这是一个空元素,在编写of_device_id时最后一个元素一定要为空*/

      /* Sentinel */ 

    }

};

/*初始化i2c_driver结构变量ap3216c_driver,i2c驱动结构体 */

static struct i2c_driver ap3216c_driver = {

    .probe = ap3216c_probe,

    /*platform的probe函数为ap3216c_probe()*/

    .remove = ap3216c_remove,

    /*platform的remove函数为ap3216c_remove()*/

    .driver = {

        .owner = THIS_MODULE,

        /*表示该文件的操作结构体所属的模块是当前的模块,即这个模块属于I2C内核*/

        .name = "ap3216c",/* 驱动名字,用于和设备匹配 */

        .of_match_table = ap3216c_of_match,/*设备树匹配表*/

    },

    .id_table = ap3216c_id,/*传统匹配方式ID列表*/

};

//函数功能:驱动入口函数初始化

static int __init ap3216c_init(void)

{

    int ret = 0;

    

    ret = i2c_add_driver(&ap3216c_driver);

    //根据i2c_driver结构变量ap3216c_driver,向Linux内核注册一个platform驱动

    return ret;

}

//函数功能:驱动出口函数初始化

static void __exit ap3216c_exit(void)

{

    i2c_del_driver(&ap3216c_driver);

    //根据i2c_driver结构变量ap3216c_driver,卸载一个platform驱动

}

module_init(ap3216c_init);//声明ap3216c_init()为驱动入口函数

module_exit(ap3216c_exit);//声明ap3216c_exit()为驱动出口函数

MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

MODULE_AUTHOR("Zhanggong");//添加作者名字

MODULE_INFO(intree, "Y");//去除显示“loading out-of-tree module taints kernel.”

3.2、编写AP3216C驱动程序之头文件“AP3216C_APP.c

1)、打开虚拟机中的VSCode,点击“文件”,点击“新建文件”,点击“文件”,点击“另存为”,输入“AP3216C_APP.c”。见下图:

2)、点击“保存”。输入下面的内容:

#include "stdio.h"

#include "unistd.h"

//Linux系统编程下用到的延时函数

//使能usleep(),sleep()

//#include <delay.h>

//Linux内核中用到的延时函数

//使能ndelay(),udelay(),mdelay()

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

//APP运行命令: ./AP3216C_APP /dev/AP3216C

//argv[]是指向输入参数./AP3216C_APP /dev/AP3216C

/*

参数argc: argc[]数组元素个数

参数argv[]:是一个指针数组

返回值: 0 成功;其他 失败

*/

int main(int argc, char *argv[])

{

  int fd;

  char *filename;

  unsigned short data[3];

  unsigned short ir, als, ps;

  int ret = 0;

  if (argc != 2)

  {

    printf("Error Usage!\r\n");

    return -1;

  }

    

    filename = argv[1];//argv[1]指向字符串“/dev/AP3216C"

    fd = open(filename, O_RDWR);

    //打开AP3216C驱动

    //如果打开“/dev/ap3216c”文件成功,则fd为“文件描述符”

    //fd=0表示标准输入流; fd=1表示标准输出流;fd=2表示错误输出流;

    if(fd < 0)

    {

        printf("can't open file %s\r\n", filename);

        return -1;

    }

    

    while (1)

    {

        ret = read(fd, data, sizeof(data));/* 读取数据 */

        if(ret == 0)

        { /* 数据读取成功 */

          ir = data[0]; /* 红外LED的ir传感器数据 */

          als = data[1]; /* 光强传感器als传感器数据 */

          ps = data[2]; /* 接近传感器ps传感器数据 */

          printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);

        }

          usleep(200000);

          //延时200000微秒,即200毫秒,不会占用cpu资源

    }

    close(fd); /* 关闭文件,关闭设备 */

    //fd表示要关闭的“文件描述符”

    //返回值等于0表示关闭成功

    //返回值小于0表示关闭失败

    return 0;

}

3.3、新建Makefile

1)、打开虚拟机中的VSCode,点击“文件”,点击“新建文件”,点击“文件”,点击“另存为”,输入“Makefile”。

2)、点击“保存”。输入下面的内容:

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

#使用“:=”将其后面的字符串赋值给KERNELDIR

CURRENT_PATH := $(shell pwd)

#采用“shell pwd”获取当前打开的路径

#使用“$(变量名)”引用“变量的值”

MyAPP := AP3216C_APP

AP3216C_drv-objs = AP3216C.o

obj-m := AP3216C_drv.o

CC := arm-none-linux-gnueabihf-gcc

drv:

$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

app:

$(CC)  $(MyAPP).c  -o $(MyAPP)

clean:

$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

rm $(MyAPP)

install:

sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f

3.4、添加“c_cpp_properties.json

按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件。

修改c_cpp_properties.json内容如下所示:

{

    "configurations": [

        {

            "name": "Linux",

            "includePath": [

                "${workspaceFolder}/**",

                "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",

                "/home/zgq/linux/Linux_Drivers/AP3216C",

                "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",

                "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",

                "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"
            ],

            "defines": [],

            "compilerPath": "/usr/bin/gcc",

            "cStandard": "gnu11",

            "cppStandard": "gnu++14",

            "intelliSenseMode": "gcc-x64"

        }

    ],

    "version": 4

}

3.5、编译设备驱动和APP

输入“make clean回车

输入“make drv回车

输入“make app回车

输入“make install回车

输入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车”查看是存在“AP3216C_APP和AP3216C_drv.ko

3.6通电测试

1)、查看/sys/bus/i2c/devices目录下存放着所有I2C设备,如果设备树修改正确的话,会在/sys/bus/i2c/devices目录下看到一个名为“0-001e”的子目录

①用新的umage和stm32mpl57d-atk.dtb启动开发板。

输入“root回车”。

③输入“cd /sys/bus/i2c/devices/回车”切换/sys/bus/i2c/devices/目录。

④输入“ls回车

⑤输入“cd 0-001e回车”切换/sys/bus/i2c/devices/0-001e/目录。

⑥输入“ls回车

⑦输入“cat name” 查看“name”文件,这个name文件保存着此设备名字ap3216c

2)、测试

启动开发板,从网络下载程序

②输入“root

③输入“cd /lib/modules/5.4.31/

在nfs挂载中,切换到“/lib/modules/5.4.31/”目录,

注意:“lib/modules/5.4.31/在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中

④输入“ls -l

⑤输入“depmod”,驱动在第一次执行时,需要运行“depmod”

输入“lsmod”查看有哪些驱动在工作;

⑦输入“modprobe AP3216C_drv.ko”,加载“AP3216C_drv.ko”模块

输入“lsmod”查看有哪些驱动在工作;

输入“cd /dev回车”切换到“dev”目录;

输入“ls”查看是否有“ap3216c

⑨输入“cd /lib/modules/5.4.31/

⑩输入“./AP3216C_APP /dev/ap3216c回车

相关文章:

Linux第104步_基于AP3216C之I2C实验

Linux之I2C实验是在AP3216C的基础上实现的&#xff0c;进一步熟悉修改设备树和编译设备树&#xff0c;以及学习如何编写I2C驱动和APP测试程序。 1、AP3216C的原理图 AP3216C集成了一个光强传感器ALS&#xff0c;一个接近传感器PS和一个红外LED&#xff0c;为三合一的环境传感…...

常用Android模拟器(雷电 MuMu 夜神 Genymotion 蓝叠) - 20250131

常用Android模拟器(雷电 MuMu 夜神 Genymotion 蓝叠) - 20250131 Android模拟器概述 Android 模拟器是一种软件工具&#xff0c;允许用户在 Windows、Linux 或 macOS 电脑上运行 Android 操作系统&#xff0c;以模拟 Android 设备的行为。它广泛用于 开发测试、应用运行、游戏…...

算法题(53):对称二叉树

审题&#xff1a; 需要我们判断二叉树是否满足对称结构&#xff0c;并返回判断结果 思路&#xff1a; 方法一&#xff1a;递归 其实是否对称分成两部分判断 第一部分&#xff1a;根节点是否相等 第二部分&#xff1a;根节点一的左子树和根节点二的右子树是否相等&#xff0c;根…...

Golang 并发机制-2:Golang Goroutine 和竞争条件

在今天的软件开发中&#xff0c;我们正在使用并发的概念&#xff0c;它允许一次执行多个任务。在Go编程中&#xff0c;理解Go例程是至关重要的。本文试图详细解释什么是例程&#xff0c;它们有多轻&#xff0c;通过简单地使用“go”关键字创建它们&#xff0c;以及可能出现的竞…...

深入剖析 CSRF 漏洞:原理、危害案例与防护

目录 前言 漏洞介绍 漏洞原理 产生条件 产生的危害 靶场练习 post 请求csrf案例 防御措施 验证请求来源 设置 SameSite 属性 双重提交 Cookie 结语 前言 在网络安全领域&#xff0c;各类漏洞层出不穷&#xff0c;时刻威胁着用户的隐私与数据安全。跨站请求伪造&…...

C++和Python实现SQL Server数据库导出数据到S3并导入Redshift数据仓库

用C实现高性能数据处理&#xff0c;Python实现操作Redshift导入数据文件。 在Visual Studio 2022中用C和ODBC API导出SQL Server数据库中张表中的所有表的数据为CSV文件格式的数据流&#xff0c;用逗号作为分隔符&#xff0c;用双引号包裹每个数据&#xff0c;字符串类型的数据…...

AI大模型开发原理篇-5:循环神经网络RNN

神经概率语言模型NPLM也存在一些明显的不足之处:模型结构简单&#xff0c;窗口大小固定&#xff0c;缺乏长距离依赖捕捉&#xff0c;训练效率低&#xff0c;词汇表固定等。为了解决这些问题&#xff0c;研究人员提出了一些更先进的神经网络语言模型&#xff0c;如循环神经网络、…...

4-图像梯度计算

文章目录 4.图像梯度计算(1)Sobel算子(2)梯度计算方法(3)Scharr与Laplacian算子4.图像梯度计算 (1)Sobel算子 图像梯度-Sobel算子 Sobel算子是一种经典的图像边缘检测算子,广泛应用于图像处理和计算机视觉领域。以下是关于Sobel算子的详细介绍: 基本原理 Sobel算子…...

数据结构与算法 —— 常用算法模版

数据结构与算法 —— 常用算法模版 二分查找素数筛最大公约数与最小公倍数 二分查找 人间若有天堂&#xff0c;大马士革必在其中&#xff1b;天堂若在天空&#xff0c;大马士革必与之齐名。 —— 阿拉伯谚语 算法若有排序&#xff0c;二分查找必在其中&#xff1b;排序若要使用…...

DDD - 领域事件_解耦微服务的关键

文章目录 Pre领域事件的核心概念领域事件的作用领域事件的识别领域事件的技术实现领域事件的运行机制案例领域事件驱动的优势 Pre DDD - 微服务设计与领域驱动设计实战(中)_ 解决微服务拆分难题 EDA - Spring Boot构建基于事件驱动的消息系统 领域事件的核心概念 领域事件&a…...

芯片AI深度实战:实战篇之vim chat

利用vim-ollama这个vim插件&#xff0c;可以在vim内和本地大模型聊天。 系列文章&#xff1a; 芯片AI深度实战&#xff1a;基础篇之Ollama-CSDN博客 芯片AI深度实战&#xff1a;基础篇之langchain-CSDN博客 芯片AI深度实战&#xff1a;实战篇之vim chat-CSDN博客 芯片AI深度…...

【产品经理学习案例——AI翻译棒出海业务】

前言&#xff1a; 本文主要讲述了硬件产品在出海过程中&#xff0c;翻译质量、翻译速度和本地化落地策略是硬件产品规划需要考虑的核心因素。针对不同国家&#xff0c;需要优化翻译质量和算法&#xff0c;关注市场需求和文化差异&#xff0c;以便更好地满足当地用户的需求。同…...

解决运行npm时报错

在运行一个Vue项目时报错&#xff0c;产生下面问题 D:\node\npm.cmd run dev npm WARN logfile could not be created: Error: EPERM: operation not permitted, open D:\node\node_cache\_logs\2025-01-31T01_01_58_076Z-debug-0.log npm WARN logfile could not be created:…...

【07-编译工程与导入网表】

这里写自定义目录标题 一丶编译原理图编译默认属性一丶编译项目二丶输出BOM材料报告优化EXCEL-BOM清单 三丶输出PDF原理图给维修人员看 四丶导入网格表查看是否有错误常见错误 其他问题什么是位号(C1)?EXCEL添加序号列和居中显示?位号(序号)与单位(型号)EXCEL设置自动换行 编…...

FireFox | Google Chrome | Microsoft Edge 禁用更新 final版

之前的方式要么失效&#xff0c;要么对设备有要求&#xff0c;这次梳理一下对设备、环境几乎没有要求的通用方式&#xff0c;universal & final 版。 1.Firefox 方式 FireFox火狐浏览器企业策略禁止更新_火狐浏览器禁止更新-CSDN博客 这应该是目前最好用的方式。火狐也…...

conda配置channel

你收到 CondaKeyError: channels: value https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main not present in config 错误是因为该镜像源&#xff08;https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main&#xff09;可能没有被正确添加到 Conda 的配置文件中&…...

【MQ】探索 Kafka

基本概念 主题&#xff1a;Topic。主题是承载消息的逻辑容器&#xff0c;在实际使用中多用来区分具体的业务。 分区&#xff1a;Partition。一个有序不变的消息序列。每个主题下可以有多个分区。消息位移&#xff1a;Offset。表示分区中每条消息的位置信息&#xff0c;是一个…...

Workbench 中的热源仿真

探索使用自定义工具对移动热源进行建模及其在不同行业中的应用。 了解热源动力学 对移动热源进行建模为各种工业过程和应用提供了有价值的见解。激光加热和材料加工使用许多激光束来加热、焊接或切割材料。尽管在某些情况下&#xff0c;热源 &#xff08;q&#xff09; 不是通…...

计算机网络 笔记 网络层 3

IPv6 IPv6 是互联网协议第 6 版&#xff08;Internet Protocol Version 6&#xff09;的缩写&#xff0c;它是下一代互联网协议&#xff0c;旨在解决 IPv4 面临的一些问题&#xff0c;以下是关于 IPv6 的详细介绍&#xff1a; 产生背景&#xff1a; 随着互联网的迅速发展&…...

翼星求生服务器搭建【Icarus Dedicated Server For Linux】

一、前言 本次搭建的服务器为Steam平台一款名为Icarus的沙盒、生存、建造游戏,由于官方只提供了Windows版本服务器导致很多热爱Linux的小伙伴无法释怀,众所周知Linux才是专业服务器的唯一准则。虽然Github上已经有大佬制作了容器版本但是容终究不够完美,毕竟容器无法与原生L…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

《信号与系统》第 6 章 信号与系统的时域和频域特性

目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...

Qt的学习(一)

1.什么是Qt Qt特指用来进行桌面应用开发&#xff08;电脑上写的程序&#xff09;涉及到的一套技术Qt无法开发网页前端&#xff0c;也不能开发移动应用。 客户端开发的重要任务&#xff1a;编写和用户交互的界面。一般来说和用户交互的界面&#xff0c;有两种典型风格&…...