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

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.kler.cn/a/526868.html

相关文章:

  • 愿景:做机器视觉行业的颠覆者
  • 10.3 LangChain实战指南:解锁大模型应用的10大核心场景与架构设计
  • 代码随想录|动态规划 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组
  • python算法和数据结构刷题[2]:链表、队列、栈
  • 策略梯度 (Policy Gradient):直接优化策略的强化学习方法
  • Ubuntu20.04 磁盘空间扩展教程
  • Python学习之旅:进阶阶段(七)数据结构-计数器(collections.Counter)
  • TCP编程
  • 【Linux】日志设计模式与实现
  • DeepSeek Janus-Pro:多模态AI模型的突破与创新
  • 集群部署时的分布式 Session 如何实现?
  • VUE组件如何开发
  • jmap命令详解
  • 一维二维前缀和、差分,c++
  • 二叉树的遍历
  • pytorch实现变分自编码器
  • git 删除子模块 submodule 的步骤
  • AI编程:cursor使用教程
  • stm32硬件实现与w25qxx通信
  • java日志框架详解-Log4j2
  • Workbench 中的热源仿真
  • 01.04、回文排序
  • 常用的 ASCII 码表字符
  • 如何获取Springboot项目运行路径 (idea 启动以及打包为jar均可) 针对无服务器容器新建上传文件路径(适用于win 与 linunix)
  • 【分析某音乐网站】分析一款音乐网站,并实现无限制的下载当前网站里所有的音乐
  • SpringCloud系列教程:微服务的未来(十九)请求限流、线程隔离、Fallback、服务熔断