高端商品网站,网站建设规划书ppt,楚雄州建设局网站,苏州高端网站建设机构Linux之I2C实验是在AP3216C的基础上实现的#xff0c;进一步熟悉修改设备树和编译设备树#xff0c;以及学习如何编写I2C驱动和APP测试程序。
1、AP3216C的原理图
AP3216C集成了一个光强传感器ALS#xff0c;一个接近传感器PS和一个红外LED#xff0c;为三合一的环境传感…Linux之I2C实验是在AP3216C的基础上实现的进一步熟悉修改设备树和编译设备树以及学习如何编写I2C驱动和APP测试程序。
1、AP3216C的原理图
AP3216C集成了一个光强传感器ALS一个接近传感器PS和一个红外LED为三合一的环境传感器。它主要是给手机之类的产品使用比如返回“当前环境的光强”以便调整手机屏幕的亮度当用户接听电话时将手机放置在耳边后它会自动关闭屏幕防止用户错误触碰。 AP3216C用到了I2C5接口其中SCL连接PA11SDA连接到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; ap3216c1e {
/*向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 LOADADDR0XC2000040 -j8回车”执行编译“Image”和“dtbs”并指定装载的起始地址为0XC2000040j8表示指定采用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,IRPS两次数据读取的时间间隔要大于112.5ms
参数ir : ir数据
参数ps : ps数据
参数ps : als数据
返回值: 无。
*/
void ap3216c_readdata(struct ap3216c_dev *dev)
{ unsigned char i 0; unsigned char buf[6]; /*循环读取所有传感器数据*/ //当i0时读取“红外LED的IR数据低字节“ //当i1时读取“红外LED的IR数据高字节“ //当i2时读取“光强传感器ALS数据低字节“ //当i3时读取“光强传感器ALS数据高字节“ //当i4时读取“接近传感器PS数据低字节“ //当i5时读取“接近传感器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保存申请到的设备号 //baseminor0次设备号的起始地址 //countAP3216C_CNT要申请的设备数量 //AP3216C_NAME表示“设备名字” if(ret 0) { pr_err(%s Couldnt 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表示设备号 //countAP3216C_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是设备号; //参数drvdataNULL设备没有使用数据 //AP3216C_NAME是设备名字 //如果设置fmtAP3216C_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为“文件描述符” //fd0表示标准输入流 fd1表示标准输出流fd2表示错误输出流 if(fd 0) { printf(cant 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”
按下“CtrlShiftP”,打开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: gnu14, 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回车”