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回车

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

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

相关文章

基于单片机的盲人智能水杯系统(论文+源码)

1 总体方案设计 本次基于单片机的盲人智能水杯设计&#xff0c;采用的是DS18B20实现杯中水温的检测&#xff0c;采用HX711及应力片实现杯中水里的检测&#xff0c;采用DS1302实现时钟计时功能&#xff0c;采用TTS语音模块实现语音播报的功能&#xff0c;并结合STC89C52单片机作…

高清种子资源获取指南 | ✈️@seedlinkbot

在如今的数字时代&#xff0c;高清影视、音乐、游戏等资源的获取方式不断丰富。对于追求高质量资源的用户而言&#xff0c;一个高效的资源分享平台至关重要。而 ✈️seedlinkbot 正是这样一个便捷的资源获取工具&#xff0c;为用户提供高质量的种子资源索引和下载信息。 1. ✈️…

Spring Boot项目如何使用MyBatis实现分页查询

写在前面&#xff1a;大家好&#xff01;我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正&#xff0c;感谢大家的不吝赐教。我的唯一博客更新地址是&#xff1a;https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油&#xff0c;冲鸭&#x…

【论文笔记】Fast3R:前向并行muti-view重建方法

众所周知&#xff0c;DUSt3R只适合做稀疏视角重建&#xff0c;与sapnn3r的目的类似&#xff0c;这篇文章以并行的方法&#xff0c;扩展了DUSt3R在多视图重建中的能力。 abstract 多视角三维重建仍然是计算机视觉领域的核心挑战&#xff0c;尤其是在需要跨不同视角实现精确且可…

本地部署DeepSeek教程(Mac版本)

第一步、下载 Ollama 官网地址&#xff1a;Ollama 点击 Download 下载 我这里是 macOS 环境 以 macOS 环境为主 下载完成后是一个压缩包&#xff0c;双击解压之后移到应用程序&#xff1a; 打开后会提示你到命令行中运行一下命令&#xff0c;附上截图&#xff1a; 若遇…

微机原理与接口技术期末大作业——4位抢答器仿真

在微机原理与接口技术的学习旅程中&#xff0c;期末大作业成为了检验知识掌握程度与实践能力的关键环节。本次我选择设计并仿真一个 4 位抢答器系统&#xff0c;通过这个项目&#xff0c;深入探索 8086CPU 及其接口技术的实际应用。附完整压缩包下载。 一、系统设计思路 &…

【Redis】Redis 经典面试题解析:深入理解 Redis 的核心概念与应用

Redis 是一个高性能的键值存储系统&#xff0c;广泛应用于缓存、消息队列、排行榜等场景。在面试中&#xff0c;Redis 是一个高频话题&#xff0c;尤其是其核心概念、数据结构、持久化机制和高可用性方案。 1. Redis 是什么&#xff1f;它的主要特点是什么&#xff1f; 答案&a…

昆仑万维Java开发面试题及参考答案

进程和线程的区别是什么? 进程和线程都是操作系统中非常重要的概念,它们在多个方面存在显著的区别。 从定义上看,进程是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间,包括代码段、数据段、堆栈段等。例如,当你在电脑上同时打开浏览器和音乐播放…

Visual Studio Code应用本地部署的deepseek

1.打开Visual Studio Code&#xff0c;在插件中搜索continue&#xff0c;安装插件。 2.添加新的大语言模型&#xff0c;我们选择ollama. 3.直接点connect&#xff0c;会链接本地下载好的deepseek模型。 参看上篇文章&#xff1a;deepseek本地部署-CSDN博客 4.输入需求生成可用…

SpringBoot 整合 Mybatis:注解版

第一章&#xff1a;注解版 导入配置&#xff1a; <groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version> </dependency> 步骤&#xff1a; 配置数据源见 Druid…

2025年最新在线模型转换工具优化模型ncnn,mnn,tengine,onnx

文章目录 引言最新网址地点一、模型转换1. 框架转换全景图2. 安全的模型转换3. 网站全景图 二、转换说明三、模型转换流程图四、感谢 引言 在yolov5&#xff0c;yolov8&#xff0c;yolov11等等模型转换的领域中&#xff0c;时间成本常常是开发者头疼的问题。最近发现一个超棒的…

理解知识蒸馏中的散度损失函数(KLDivergence/kldivloss )-以DeepSeek为例

1. 知识蒸馏简介 什么是知识蒸馏&#xff1f; 知识蒸馏&#xff08;Knowledge Distillation&#xff09;是一种模型压缩技术&#xff0c;目标是让一个较小的模型&#xff08;学生模型&#xff0c;Student Model&#xff09;学习一个较大、性能更优的模型&#xff08;教师模型…

Electron使用WebAassembly实现CRC-8 MAXIM校验

Electron使用WebAssembly实现CRC-8 MAXIM校验 将C/C语言代码&#xff0c;经由WebAssembly编译为库函数&#xff0c;可以在JS语言环境进行调用。这里介绍在Electron工具环境使用WebAssembly调用CRC-8 MAXIM格式校验的方式。 CRC-8 MAXIM校验函数WebAssebly源文件 C语言实现CR…

Vue3.0实战:大数据平台可视化

文章目录 创建vue3.0项目项目初始化项目分辨率响应式设置项目顶部信息条创建页面主体创建全局引入echarts和axios后台接口创建express销售总量图实现完整项目下载项目任何问题都可在评论区,或者直接私信即可。 创建vue3.0项目 创建项目: vue create vueecharts选择第三项:…

vector容器(详解)

本文最后是模拟实现全部讲解&#xff0c;文章穿插有彩色字体&#xff0c;是我总结的技巧和关键 1.vector的介绍及使用 1.1 vector的介绍 https://cplusplus.com/reference/vector/vector/&#xff08;vector的介绍&#xff09; 了解 1. vector是表示可变大小数组的序列容器。…

Airflow:深入理解Apache Airflow Task

Apache Airflow是一个开源工作流管理平台&#xff0c;支持以编程方式编写、调度和监控工作流。由于其灵活性、可扩展性和强大的社区支持&#xff0c;它已迅速成为编排复杂数据管道的首选工具。在这篇博文中&#xff0c;我们将深入研究Apache Airflow 中的任务概念&#xff0c;探…

开发环境搭建-4:WSL 配置 docker 运行环境

在 WSL 环境中构建&#xff1a;WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 基本概念说明 容器技术 利用 Linux 系统的 文件系统&#xff08;UnionFS&#xff09;、命名空间&#xff08;namespace&#xff09;、权限管理&#xff08;cgroup&#xff09;&#xff0c;虚拟出一…

JavaScript 基础 - 7

关于JS函数部分的学习和一个案例的练习 1 函数封装 抽取相同部分代码封装 优点 提高代码复用性&#xff1a;封装好的函数可以在多个地方被重复调用&#xff0c;避免了重复编写相同的代码。例如&#xff0c;编写一个计算两个数之和的函数&#xff0c;在多个不同的计算场景中都…

详解u3d之AssetBundle

一.AssetBundle的概念 “AssetBundle”可以指两种不同但相关的东西。 1.1 AssetBundle指的是u3d在磁盘上生成的存放资源的目录 目录包含两种类型文件(下文简称AB包)&#xff1a; 一个序列化文件&#xff0c;其中包含分解为各个对象并写入此单个文件的资源。资源文件&#x…

微信登录模块封装

文章目录 1.资质申请2.combinations-wx-login-starter1.目录结构2.pom.xml 引入okhttp依赖3.WxLoginProperties.java 属性配置4.WxLoginUtil.java 后端通过 code 获取 access_token的工具类5.WxLoginAutoConfiguration.java 自动配置类6.spring.factories 激活自动配置类 3.com…