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 /*红外LED的IR数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /*红外LED的IR数据高字节 */
#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回车”