完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》 关注官方微信号公众号,获取更多资料:正点原子 61.3.2 I2C设备数据收发处理流程在61.1.2小节已经说过了,I2C设备驱动首先要做的就是初始化i2c_driver并向Linux内核注册。当设备和驱动匹配以后i2c_driver里面的probe函数就会执行,probe函数里面所做的就是字符设备驱动那一套了。一般需要在probe函数里面初始化I2C设备,要初始化I2C设备就必须能够对I2C设备寄存器进行读写操作,这里就要用到i2c_transfer函数了。i2c_transfer函数最终会调用I2C适配器中i2c_algorithm里面的master_xfer函数,对于I.MX6U而言就是i2c_imx_xfer这个函数。i2c_transfer函数原型如下: int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 函数参数和返回值含义如下: adap:所使用的I2C适配器,i2c_client会保存其对应的i2c_adapter。 msgs:I2C要发送的一个或多个消息。 num:消息数量,也就是msgs的数量。 返回值:负值,失败,其他非负值,发送的msgs数量。 我们重点来看一下msgs这个参数,这是一个i2c_msg类型的指针参数,I2C进行数据收发说白了就是消息的传递,Linux内核使用i2c_msg结构体来描述一个消息。i2c_msg结构体定义在include/uapi/linux/i2c.h文件中,结构体内容如下: 示例代码61.3.2.1 i2c_msg结构体 68struct i2c_msg { 69 __u16 addr; /* 从机地址 */ 70 __u16 flags; /* 标志 */ 71 #define I2C_M_TEN 0x0010 72 #define I2C_M_RD 0x0001 73 #define I2C_M_STOP 0x8000 74 #define I2C_M_NOSTART 0x4000 75 #define I2C_M_REV_DIR_ADDR 0x2000 76 #define I2C_M_IGNORE_NAK 0x1000 77 #define I2C_M_NO_RD_ACK 0x0800 78 #define I2C_M_RECV_LEN 0x0400 79 __u16 len; /* 消息(本msg)长度 */ 80 __u8 *buf; /* 消息数据 */ 81}; 使用i2c_transfer函数发送数据之前要先构建好i2c_msg,使用i2c_transfer进行I2C数据收发的示例代码如下: 示例代码61.3.2.2 I2C设备多寄存器数据读写 1 /* 设备结构体 */ 2struct xxx_dev { 3 ...... 4 void*private_data;/* 私有数据,一般会设置为i2c_client */ 5}; 6 7/* 8 * @description : 读取I2C设备多个寄存器数据 9 * @param – dev : I2C设备 10 * @param – reg : 要读取的寄存器首地址 11 * @param – val : 读取到的数据 12 * @param – len : 要读取的数据长度 13 * @Return : 操作结果 14 */ 15staticint xxx_read_regs(struct xxx_dev *dev, u8 reg,void*val, int len) 16{ 17 int ret; 18 struct i2c_msg msg[2]; 19 struct i2c_client *client =(struct i2c_client *) dev->private_data; 20 21 /* msg[0],第一条写消息,发送要读取的寄存器首地址 */ 22 msg[0].addr = client->addr; /* I2C器件地址 */ 23 msg[0].flags =0; /* 标记为发送数据 */ 24 msg[0].buf =® /* 读取的首地址 */ 25 msg[0].len =1; /* reg长度 */ 26 27 /* msg[1],第二条读消息,读取寄存器数据 */ 28 msg[1].addr = client->addr; /* I2C器件地址 */ 29 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */ 30 msg[1].buf = val; /* 读取数据缓冲区 */ 31 msg[1].len = len; /* 要读取的数据长度 */ 32 33 ret = i2c_transfer(client->adapter, msg,2); 34 if(ret ==2){ 35 ret =0; 36 }else{ 37 ret =-EREMOTEIO; 38 } 39 return ret; 40} 41 42/* 43 * @description : 向I2C设备多个寄存器写入数据 44 * @param – dev : 要写入的设备结构体 45 * @param – reg : 要写入的寄存器首地址 46 * @param – val : 要写入的数据缓冲区 47 * @param – len : 要写入的数据长度 48 * @return : 操作结果 49 */ 50static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len) 51{ 52 u8 b[256]; 53 struct i2c_msg msg; 54 struct i2c_client *client =(struct i2c_client *) dev->private_data; 55 56 b[0]= reg; /* 寄存器首地址 */ 57 memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组b里面 */ 58 59 msg.addr = client->addr; /* I2C器件地址 */ 60 msg.flags =0; /* 标记为写数据 */ 61 62 msg.buf = b; /* 要发送的数据缓冲区 */ 63 msg.len = len +1; /* 要发送的数据长度 */ 64 65 return i2c_transfer(client->adapter,&msg,1); 66} 第2~5行,设备结构体,在设备结构体里面添加一个执行void的指针成员变量private_data,此成员变量用于保存设备的私有数据。在I2C设备驱动中我们一般将其指向I2C设备对应的i2c_client。 第15~40行,xxx_read_regs函数用于读取I2C设备多个寄存器数据。第18行定义了一个i2c_msg数组,2个数组元素,因为I2C读取数据的时候要先发送要读取的寄存器地址,然后再读取数据,所以需要准备两个i2c_msg。一个用于发送寄存器地址,一个用于读取寄存器值。对于msg[0],将flags设置为0,表示写数据。msg[0]的addr是I2C设备的器件地址,msg[0]的buf成员变量就是要读取的寄存器地址。对于msg[1],将flags设置为I2C_M_RD,表示读取数据。msg[1]的buf成员变量用于保存读取到的数据,len成员变量就是要读取的数据长度。调用i2c_transfer函数完成I2C数据读操作。 第50~66行,xxx_write_regs函数用于向I2C设备多个寄存器写数据,I2C写操作要比读操作简单一点,因此一个i2c_msg即可。数组b用于存放寄存器首地址和要发送的数据,第59行设置msg的addr为I2C器件地址。第60行设置msg的flags为0,也就是写数据。第62行设置要发送的数据,也就是数组b。第63行设置msg的len为len+1,因为要加上一个字节的寄存器地址。最后通过i2c_transfer函数完成向I2C设备的写操作。 另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。首先来看一下I2C数据发送函数i2c_master_send,函数原型如下: int i2c_master_send(const struct i2c_client *client, const char *buf, int count) 函数参数和返回值含义如下: client:I2C设备对应的i2c_client。 buf:要发送的数据。 count:要发送的数据字节数,要小于64KB,以为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据。 返回值:负值,失败,其他非负值,发送的字节数。 I2C数据接收函数为i2c_master_recv,函数原型如下: int i2c_master_recv(const struct i2c_client *client, char *buf, int count) 函数参数和返回值含义如下: client:I2C设备对应的i2c_client。 buf:要接收的数据。 count:要接收的数据字节数,要小于64KB,以为i2c_msg的len成员变量是一个u16(无符号16位)类型的数据。 返回值:负值,失败,其他非负值,发送的字节数。 关于Linux下I2C设备驱动的编写流程就讲解到这里,重点就是i2c_msg的构建和i2c_transfer函数的调用,接下来我们就编写AP3216C这个I2C设备的Linux驱动。 61.4 硬件原理图分析本章实验硬件原理图参考26.2小节即可。 61.5 实验程序编写本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->21_iic。 61.5.1 修改设备树1、IO修改或添加 首先肯定是要修改IO,AP3216C用到了I2C1接口,I.MX6U-ALPHA开发板上的I2C1接口使用到了UART4_TXD和UART4_RXD,因此肯定要在设备树里面设置这两个IO。如果要用到AP3216C的中断功能的话还需要初始化AP_INT对应的GIO1_IO01这个IO,本章实验我们不使用中断功能。因此只需要设置UART4_TXD和UART4_RXD这两个IO,NXP其实已经将他这两个IO设置好了,打开imx6ull-alientek-emmc.dts,然后找到如下内容: 示例代码61.5.1.1 pinctrl_i2c1子节点 1 pinctrl_i2c1: i2c1grp { 2 fsl,pins =< 3 MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 4 MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 5>; 6}; pinctrl_i2c1就是I2C1的IO节点,这里将UART4_TXD和UART4_RXD这两个IO分别复用为I2C1_SCL和I2C1_SDA,电气属性都设置为0x4001b8b0。 2、在i2c1节点追加ap3216c子节点 AP3216C是连接到I2C1上的,因此需要在i2c1节点下添加ap3216c的设备子节点,在imx6ull-alientek-emmc.dts文件中找到i2c1节点,此节点默认内容如下: 示例代码61.5.1.2 i2c1子节点默认内容 1&i2c1 { 2 clock-frequency =<100000>; 3 pinctrl-names ="default"; 4 pinctrl-0=<&pinctrl_i2c1>; 5 status ="okay"; 6 7 mag3110@0e { 8 compatible ="fsl,mag3110"; 9 reg =<0x0e>; 10 position =<2>; 11}; 12 13 fxls8471@1e { 14 compatible ="fsl,fxls8471"; 15 reg =<0x1e>; 16 position =<0>; 17 interrupt-parent =<&gpio5>; 18 interrupts =<08>; 19}; 20}; 第2行,clock-frequency属性为I2C频率,这里设置为100KHz。 第4行,pinctrl-0属性指定I2C所使用的IO为示例代码61.5.1.1中的pinctrl_i2c1子节点。 第7~11行,mag3110是个磁力计,NXP官方的EVK开发板上接了mag3110,因此NXP在i2c1节点下添加了mag3110这个子节点。正点原子的I.MX6U-ALPHA开发板上没有用到mag3110,因此需要将此节点删除掉。 第13~19行,NXP官方EVK开发板也接了一个fxls8471,正点原子的I.MX6U-ALPHA开发板同样没有此器件,所以也要将其删除掉。 将i2c1节点里面原有的mag3110和fxls8471这两个I2C子节点删除,然后添加ap3216c子节点信息,完成以后的i2c1节点内容如下所示: 示例代码61.5.1.3 添加ap3216c子节点以后的i2c1节点 1&i2c1 { 2 clock-frequency =<100000>; 3 pinctrl-names ="default"; 4 pinctrl-0=<&pinctrl_i2c1>; 5 status ="okay"; 6 7 ap3216c@1e { 8 compatible ="alientek,ap3216c"; 9 reg =<0x1e>; 10}; 11}; 第7行,ap3216c子节点,@后面的"1e"是ap3216c的器件地址。 第8行,设置compatible值为"alientek,ap3216c"。 第9行,reg属性也是设置ap3216c器件地址的,因此reg设置为0x1e。 设备树修改完成以后使用"makedtbs"重新编译一下,然后使用新的设备树启动Linux内核。/sys/bus/i2c/devices目录下存放着所有I2C设备,如果设备树修改正确的话,会在/sys/bus/i2c/devices目录下看到一个名为"0-001e"的子目录,如图61.5.1.1所示: 图61.5.1.1 当前系统I2C设备 图61.5.1.1中的"0-001e"就是ap3216c的设备目录,"1e"就是ap3216c器件地址。进入0-001e目录,可以看到"name"文件,name问价就保存着此设备名字,在这里就是"ap3216c",如图61.5.1.2所示: 图61.5.1.2 ap3216c器件名字 61.5.2 AP3216C驱动编写新建名为"21_iic"的文件夹,然后在21_iic文件夹里面创建vscode工程,工作区命名为"iic"。工程创建好以后新建ap3216c.c和ap3216creg.h这两个文件,ap3216c.c为AP3216C的驱动代码,ap3216creg.h是AP3216C寄存器头文件。先在ap3216creg.h中定义好AP3216C的寄存器,输入如下内容, 示例代码61.5.2.1 ap3216creg.h文件代码段 1 #ifndef AP3216C_H 2 #define AP3216C_H 3/*************************************************************** 4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 5文件名 : ap3216creg.h 6作者 : 左忠凯 7版本 : V1.0 8描述 : AP3216C寄存器地址描述头文件 9其他 : 无 10论坛 : www.openedv.com 11日志 : 初版V1.0 2019/9/2 左忠凯创建 12 ***************************************************************/ 13/* AP3316C寄存器 */ 14 #define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */ 15 #define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */ 16 #define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */ 17 #define AP3216C_IRDATALOW 0x0A /* IR数据低字节 */ 18 #define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节 */ 19 #define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节 */ 20 #define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节 */ 21 #define AP3216C_PSDATALOW 0X0E /* PS数据低字节 */ 22 #define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节 */ 23 24 #endif ap3216creg.h没什么好讲的,就是一些寄存器宏定义。然后在ap3216c.c输入如下内容: 示例代码61.5.2.2 ap3216c.c文件代码段 1 #include <linux/types.h> 2 #include <linux/kernel.h> 3 #include <linux/delay.h> 4 #include <linux/ide.h> 5 #include <linux/init.h> 6 #include <linux/module.h> 7 #include <linux/errno.h> 8 #include <linux/gpio.h> 9 #include <linux/cdev.h> 10 #include <linux/device.h> 11 #include <linux/of_gpio.h> 12 #include <linux/semaphore.h> 13 #include <linux/timer.h> 14 #include <linux/i2c.h> 15 #include <asm/mach/map.h> 16 #include <asm/uaccess.h> 17 #include <asm/io.h> 18 #include "ap3216creg.h" 19/*************************************************************** 20 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 21文件名 : ap3216c.c 22作者 : 左忠凯 23版本 : V1.0 24描述 : AP3216C驱动程序 25其他 : 无 26论坛 : www.openedv.com 27日志 : 初版V1.0 2019/9/2 左忠凯创建 28 ***************************************************************/ 29 #define AP3216C_CNT 1 30 #define AP3216C_NAME "ap3216c" 31 32struct ap3216c_dev { 33 dev_t devid; /* 设备号 */ 34struct cdev cdev; /* cdev */ 35struct class *class; /* 类 */ 36struct device *device; /* 设备 */ 37struct device_node *nd; /* 设备节点 */ 38int major; /* 主设备号 */ 39void*private_data; /* 私有数据 */ 40unsignedshort ir, als, ps; /* 三个光传感器数据 */ 41}; 42 43staticstruct ap3216c_dev ap3216cdev; 44 45/* 46 * @description : 从ap3216c读取多个寄存器数据 47 * @param – dev : ap3216c设备 48 * @param – reg : 要读取的寄存器首地址 49 * @param – val : 读取到的数据 50 * @param – len : 要读取的数据长度 51 * @return : 操作结果 52 */ 53staticint ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void*val,int len) 54{ 55int ret; 56struct i2c_msg msg[2]; 57struct i2c_client *client =(struct i2c_client *) dev->private_data; 58 59/* msg[0]为发送要读取的首地址 */ 60 msg[0].addr = client->addr; /* ap3216c地址 */ 61 msg[0].flags =0; /* 标记为发送数据 */ 62 msg[0].buf =® /* 读取的首地址 */ 63 msg[0].len =1; /* reg长度 */ 64 65/* msg[1]读取数据 */ 66 msg[1].addr = client->addr; /* ap3216c地址 */ 67 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */ 68 msg[1].buf = val; /* 读取数据缓冲区 */ 69 msg[1].len = len; /* 要读取的数据长度 */ 70 71 ret = i2c_transfer(client->adapter, msg,2); 72if(ret ==2){ 73 ret =0; 74}else{ 75 printk("i2c rd failed=%d reg=%06x len=%dn",ret, reg, len); 76 ret =-EREMOTEIO; 77} 78return ret; 79} 80 81/* 82 * @description : 向ap3216c多个寄存器写入数据 83 * @param – dev : ap3216c设备 84 * @param – reg : 要写入的寄存器首地址 85 * @param – val : 要写入的数据缓冲区 86 * @param – len : 要写入的数据长度 87 * @return : 操作结果 88 */ 89static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len) 90{ 91 u8 b[256]; 92struct i2c_msg msg; 93struct i2c_client *client =(struct i2c_client *) dev->private_data; 94 95 b[0]= reg; /* 寄存器首地址 */ 96 memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */ 97 98 msg.addr = client->addr; /* ap3216c地址 */ 99 msg.flags =0; /* 标记为写数据 */ 100 101 msg.buf = b; /* 要写入的数据缓冲区 */ 102 msg.len = len +1; /* 要写入的数据长度 */ 103 104return i2c_transfer(client->adapter,&msg,1); 105} 106 107/* 108 * @description : 读取ap3216c指定寄存器值,读取一个寄存器 109 * @param – dev : ap3216c设备 110 * @param – reg : 要读取的寄存器 111 * @return : 读取到的寄存器值 112 */ 113staticunsignedchar ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg) 114{ 115 u8 data =0; 116 117 ap3216c_read_regs(dev, reg,&data,1); 118return data; 119 120 #if0 121struct i2c_client *client =(struct i2c_client *) dev->private_data; 122return i2c_smbus_read_byte_data(client, reg); 123 #endif 124} 125 126/* 127 * @description : 向ap3216c指定寄存器写入指定的值,写一个寄存器 128 * @param – dev : ap3216c设备 129 * @param – reg : 要写的寄存器 130 * @param – data : 要写入的值 131 * @return : 无 132 */ 133staticvoid ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data) 134{ 135 u8 buf =0; 136 buf = data; 137 ap3216c_write_regs(dev, reg,&buf,1); 138} 139 140/* 141 * @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 142 * :同时打开ALS,IR+PS的话两次数据读取的间隔要大于112.5ms 143 * @param - ir : ir数据 144 * @param - ps : ps数据 145 * @param - ps : als数据 146 * @return : 无。 147 */ 148void ap3216c_readdata(struct ap3216c_dev *dev) 149{ 150unsignedchar i =0; 151unsignedchar buf[6]; 152 153/* 循环读取所有传感器数据 */ 154for(i =0; i <6; i++) 155{ 156 buf[i]= ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); 157} 158 159if(buf[0]&0X80) /* IR_OF位为1,则数据无效 */ 160 dev->ir =0; 161else /* 读取IR传感器的数据 */ 162 dev->ir =((unsignedshort)buf[1]<<2)|(buf[0]&0X03); 163 164 dev->als =((unsignedshort)buf[3]<<8)| buf[2];/* ALS数据*/ 165 166if(buf[4]&0x40) /* IR_OF位为1,则数据无效 */ 167 dev->ps =0; 168else /* 读取PS传感器的数据 */ 169 dev->ps =((unsignedshort)(buf[5]&0X3F)<<4)| (buf[4]&0X0F); 170} 171 172/* 173 * @description : 打开设备 174 * @param – inode : 传递给驱动的inode 175 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 176 * 一般在open的时候将private_data指向设备结构体。 177 * @return : 0 成功;其他失败 178 */ 179staticint ap3216c_open(struct inode *inode,struct file *filp) 180{ 181 filp->private_data =&ap3216cdev; 182 183/* 初始化AP3216C */ 184 ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG,0x04); 185 mdelay(50); /* AP3216C复位最少10ms */ 186 ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG,0X03); 187return0; 188} 189 190/* 191 * @description : 从设备读取数据 192 * @param – filp : 要打开的设备文件(文件描述符) 193 * @param - buf : 返回给用户空间的数据缓冲区 194 * @param - cnt : 要读取的数据长度 195 * @param – offt : 相对于文件首地址的偏移 196 * @return : 读取的字节数,如果为负值,表示读取失败 197 */ 198static ssize_t ap3216c_read(struct file *filp,char __user *buf, size_t cnt, loff_t *off) 199{ 200short data[3]; 201long err =0; 202 203struct ap3216c_dev *dev =(struct ap3216c_dev *) filp->private_data; 204 205 ap3216c_readdata(dev); 206 207 data[0]= dev->ir; 208 data[1]= dev->als; 209 data[2]= dev->ps; 210 err = copy_to_user(buf, data,sizeof(data)); 211return0; 212} 213 214/* 215 * @description : 关闭/释放设备 216 * @param - filp : 要关闭的设备文件(文件描述符) 217 * @return : 0 成功;其他失败 218 */ 219staticint ap3216c_release(struct inode *inode,struct file *filp) 220{ 221return0; 222} 223 224/* AP3216C操作函数 */ 225staticconststruct file_operations ap3216c_ops ={ 226.owner = THIS_MODULE, 227.open = ap3216c_open, 228.read = ap3216c_read, 229.release = ap3216c_release, 230}; 231 232/* 233 * @description : i2c驱动的probe函数,当驱动与 234 * 设备匹配以后此函数就会执行 235 * @param - client : i2c设备 236 * @param - id : i2c设备ID 237 * @return : 0,成功;其他负值,失败 238 */ 239staticint ap3216c_probe(struct i2c_client *client, conststruct i2c_device_id *id) 240{ 241/* 1、构建设备号 */ 242if(ap3216cdev.major){ 243 ap3216cdev.devid = MKDEV(ap3216cdev.major,0); 244 register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME); 245}else{ 246 alloc_chrdev_region(&ap3216cdev.devid,0, AP3216C_CNT, AP3216C_NAME); 247 ap3216cdev.major = MAJOR(ap3216cdev.devid); 248} 249 250/* 2、注册设备 */ 251 cdev_init(&ap3216cdev.cdev,&ap3216c_ops); 252 cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); 253 254/* 3、创建类 */ 255 ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME); 256if(IS_ERR(ap3216cdev.class)){ 257return PTR_ERR(ap3216cdev.class); 258} 259 260/* 4、创建设备 */ 261 ap3216cdev.device = device_create(ap3216cdev.class,NULL, ap3216cdev.devid,NULL, AP3216C_NAME); 262if(IS_ERR(ap3216cdev.device)){ 263return PTR_ERR(ap3216cdev.device); 264} 265 266 ap3216cdev.private_data = client; 267 268return0; 269} 270 271/* 272 * @description : i2c驱动的remove函数,移除i2c驱动此函数会执行 273 * @param – client : i2c设备 274 * @return : 0,成功;其他负值,失败 275 */ 276staticint ap3216c_remove(struct i2c_client *client) 277{ 278/* 删除设备 */ 279 cdev_del(&ap3216cdev.cdev); 280 unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); 281 282/* 注销掉类和设备 */ 283 device_destroy(ap3216cdev.class, ap3216cdev.devid); 284 class_destroy(ap3216cdev.class); 285return0; 286} 287 288/* 传统匹配方式ID列表 */ 289staticconststruct i2c_device_id ap3216c_id[]={ 290{"alientek,ap3216c",0}, 291{} 292}; 293 294/* 设备树匹配列表 */ 295staticconststruct of_device_id ap3216c_of_match[]={ 296{.compatible ="alientek,ap3216c"}, 297{/* Sentinel */} 298}; 299 300/* i2c驱动结构体 */ 301staticstruct i2c_driver ap3216c_driver ={ 302.probe = ap3216c_probe, 303.remove = ap3216c_remove, 304.driver ={ 305.owner = THIS_MODULE, 306.name ="ap3216c", 307.of_match_table = ap3216c_of_match, 308}, 309.id_table = ap3216c_id, 310}; 311 312/* 313 * @description : 驱动入口函数 314 * @param : 无 315 * @return : 无 316 */ 317staticint __init ap3216c_init(void) 318{ 319int ret =0; 320 321 ret = i2c_add_driver(&ap3216c_driver); 322return ret; 323} 324 325/* 326 * @description : 驱动出口函数 327 * @param : 无 328 * @return : 无 329 */ 330staticvoid __exit ap3216c_exit(void) 331{ 332 i2c_del_driver(&ap3216c_driver); 333} 334 335/* module_i2c_driver(ap3216c_driver) */ 336 337 module_init(ap3216c_init); 338 module_exit(ap3216c_exit); 339 MODULE_LICENSE("GPL"); 340 MODULE_AUTHOR("zuozhongkai"); 第32~41行,ap3216c设备结构体,第39行的private_data成员变量用于存放ap3216c对应的i2c_client。第40行的ir、als和ps分别存储AP3216C的IR、ALS和PS数据。 第43行,定义一个ap3216c_dev类型的设备结构体变量ap3216cdev。 第53~79行,ap3216c_read_regs函数实现多字节读取,但是AP3216C好像不支持连续多字节读取,此函数在测试其他I2C设备的时候可以实现多给字节连续读取,但是在AP3216C上不能连续读取多个字节。不过读取一个字节没有问题的。 第89~105行,ap3216c_write_regs函数实现连续多字节写操作。 第113~124行,ap3216c_read_reg函数用于读取AP3216C的指定寄存器数据,用于一个寄存器的数据读取。 第133~138行,ap3216c_write_reg函数用于向AP3216C的指定寄存器写入数据,用于一个寄存器的数据写操作。 第148~170行,读取AP3216C的PS、ALS和IR等传感器原始数据值。 第179~230行,标准的支付设备驱动框架。 第239~269行,ap3216c_probe函数,当I2C设备和驱动匹配成功以后此函数就会执行,和platform驱动框架一样。此函数前面都是标准的字符设备注册代码,最后面会将此函数的第一个参数client传递给ap3216cdev的private_data成员变量。 第289~292行,ap3216c_id匹配表,i2c_device_id类型。用于传统的设备和驱动匹配,也就是没有使用设备树的时候。 第295~298行,ap3216c_of_match匹配表,of_device_id类型,用于设备树设备和驱动匹配。这里只写了一个compatible属性,值为"alientek,ap3216c"。 第301~310行,ap3216c_driver结构体变量,i2c_driver类型。 第317~323行,驱动入口函数ap3216c_init,此函数通过调用i2c_add_driver来向Linux内核注册i2c_driver,也就是ap3216c_driver。 第330~333行,驱动出口函数ap3216c_exit,此函数通过调用i2c_del_driver来注销掉前面注册的ap3216c_driver。 61.5.3 编写测试APP新建ap3216cApp.c文件,然后在里面输入如下所示内容: 示例代码61.5.3.1 ap3216cApp.c文件代码段 1 #include "stdio.h" 2 #include "unistd.h" 3 #include "sys/types.h" 4 #include "sys/stat.h" 5 #include "sys/ioctl.h" 6 #include "fcntl.h" 7 #include "stdlib.h" 8 #include "string.h" 9 #include <poll.h> 10 #include <sys/select.h> 11 #include <sys/time.h> 12 #include <signal.h> 13 #include <fcntl.h> 14/*************************************************************** 15 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 16文件名 : ap3216cApp.c 17作者 : 左忠凯 18版本 : V1.0 19描述 : ap3216c设备测试APP。 20其他 : 无 21使用方法 :./ap3216cApp /dev/ap3216c 22论坛 : www.openedv.com 23日志 : 初版V1.0 2019/9/20 左忠凯创建 24 ***************************************************************/ 25 26/* 27 * @description : main主程序 28 * @param - argc : argv数组元素个数 29 * @param - argv : 具体参数 30 * @return : 0 成功;其他失败 31 */ 32int main(int argc,char*argv[]) 33{ 34 int fd; 35 char*filename; 36 unsignedshort databuf[3]; 37 unsignedshort ir, als, ps; 38 int ret =0; 39 40 if(argc !=2){ 41 printf("Error Usage!rn"); 42 return-1; 43 } 44 45 filename = argv[1]; 46 fd = open(filename, O_RDWR); 47 if(fd <0){ 48 printf("can't open file %srn", filename); 49 return-1; 50 } 51 52 while(1){ 53 ret = read(fd, databuf,sizeof(databuf)); 54 if(ret ==0){ /* 数据读取成功 */ 55 ir = databuf[0]; /* ir传感器数据 */ 56 als = databuf[1]; /* als传感器数据 */ 57 ps = databuf[2]; /* ps传感器数据 */ 58 printf("ir = %d, als = %d, ps = %drn", ir, als, ps); 59 } 60 usleep(200000); /*100ms */ 61 } 62 close(fd); /* 关闭文件 */ 63 return0; 64} ap3216cApp.c文件内容很简单,就是在while循环中不断的读取AP3216C的设备文件,从而得到ir、als和ps这三个数据值,然后将其输出到终端上。 61.6 运行测试61.6.1 编译驱动程序和测试APP1、编译驱动程序 编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为"ap3216c.o",Makefile内容如下所示: 示例代码61.6.1.1 Makefile文件 1 KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek ...... 4 obj-m := ap3216c.o ...... 11 clean: 12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean 第4行,设置obj-m变量的值为"ap3216c.o"。 输入如下命令编译出驱动模块文件: make-j32 编译成功以后就会生成一个名为"ap3216c.ko"的驱动模块文件。 2、编译测试APP 输入如下命令编译ap3216cApp.c这个测试程序: arm-linux-gnueabihf-gccap3216cApp.c -o ap3216cApp 编译成功以后就会生成ap3216cApp这个应用程序。 61.6.2 运行测试将上一小节编译出来ap3216c.ko和ap3216cApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中。输入如下命令加载ap3216c.ko这个驱动模块。 depmod //第一次加载驱动的时候需要运行此命令 modprobe ap3216c.ko //加载驱动模块 当驱动模块加载成功以后使用ap3216cApp来测试,输入如下命令: ./ap3216cApp /dev/ap3216c 测试APP会不断的从AP3216C中读取数据,然后输出到终端上,如图61.6.2.1所示: 图61.6.2.1 获取到的AP3216C数据 大家可以用手电筒照一下AP3216C,或者手指靠近AP3216C来观察传感器数据有没有变化。 |
|
相关推荐
|
|
590 浏览 0 评论
AI模型部署边缘设备的奇妙之旅:如何在边缘端部署OpenCV
2241 浏览 0 评论
tms320280021 adc采样波形,为什么adc采样频率上来波形就不好了?
1233 浏览 0 评论
1788 浏览 0 评论
1464 浏览 0 评论
74840 浏览 21 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 09:23 , Processed in 0.605369 second(s), Total 62, Slave 44 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号