当前位置:首页 > > 嵌入式大杂烩
[导读]开发板上有AP3216三合一整合型光感测器,看了看出厂SDK包中并未添加相关驱动。本次我们就一起来学习一下。


1、前言

开发板上有AP3216三合一整合型光感测器,看了看出厂SDK包中并未添加相关驱动。本次我们就一起来学习一下。

2、AP3216简介

AP3216C 芯片集成了光强传感器( ALS: Ambient Light Sensor),接近传感器( PS: Proximity Sensor),还有一个红外 LED( IR LED)。

这个芯片设计的用途是给手机之类的使用,比如:返回当前环境光强以便调整屏幕亮度;用户接听电话时,将手机放置在耳边后,自动关闭屏幕避免用户误触碰 。

该芯片通过 I2C 接口与主控制器相连, 如:

3、IIC驱动简介

Linux下IIC有两种驱动方式:一种是按照字符设备驱动方式来驱动IIC;另一种是走Linux下IIC的框架。按照字符设备驱动的方式可以查阅这一篇文章:Linux IIC 字符设备 驱动例子。

这里我们浅浅地(真的很浅~~)了解学习一下第二种方式,因为找到的AP3216的驱动就是基于IIC驱动框架的,哈哈。

整个IIC的驱动框架相关代码在drivers\i2c中,包含的内容有:

IIC驱动框架图如(图片来源于网络,链接见文末参考资料):

IIC驱动框架可大体分为两大部分:

①  I2C 总线驱动:SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。

②  I2C 设备驱动:针对具体的 I2C 设备而编写的驱动。

其中,访问抽象层与I2C核心层数据I2C 总线驱动部分;driver驱动层属于I2C设备驱动部分。

上面框图对应的代码调用层次图如:

下面的AP3216驱动可以对照这张图来看看。

4、AP3216实验

我们使用设备树来描述AP3216设备信息,首先我们没有在设备树中添加AP3216相关节点时,我们系统的I2C设备如:

添加I2C pinctrl,板子上AP3216接的是I2C1:

配置寄存器的值都设为0x4001b8b0,这一段是什么意思我们在什么是Pinctrl子系统及GPIO子系统?这篇笔记中也有写到,就是几个寄存器及其配置。

接下来在i2c1节点下添加ap3216节点:

编译设备树,传到开发板上,重启。此时我们系统的I2C设备有:

可见,新增的AP3216 I2C设备名就是我们设备树里设置的。

下面编写AP3216驱动(以下代码来源于网络):

ap3216.c:

#include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #include "ap3216creg.h" /***************************************************************
文件名  : ap3216c.c
描述     : AP3216C驱动程序
***************************************************************/ #define AP3216C_CNT 1 #define AP3216C_NAME "ap3216c" struct ap3216c_dev { dev_t devid; /* 设备号   */ struct cdev cdev; /* cdev  */ struct class *class; /* 类   */ struct device *device; /* 设备   */ struct device_node *nd; /* 设备节点 */ int major; /* 主设备号 */ void *private_data; /* 私有数据 */ unsigned short ir, als, ps; /* 三个光传感器数据 */ }; static struct ap3216c_dev ap3216cdev; /*
 * @description : 从ap3216c读取多个寄存器数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return   : 操作结果
 */ 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->private_data; /* 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; /* 读取数据缓冲区 */ msg[1].len = len; /* 要读取的数据长度*/ ret = i2c_transfer(client->adapter, msg, 2); if(ret == 2) {
  ret = 0;
 } else {
  printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
  ret = -EREMOTEIO;
 } return ret;
} /*
 * @description : 向ap3216c多个寄存器写入数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return    :   操作结果
 */ 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->private_data; b[0] = reg; /* 寄存器首地址 */ memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */ msg.addr = client->addr; /* ap3216c地址 */ msg.flags = 0; /* 标记为写数据 */ msg.buf = b; /* 要写入的数据缓冲区 */ msg.len = len + 1; /* 要写入的数据长度 */ return i2c_transfer(client->adapter, &msg, 1);
} /*
 * @description : 读取ap3216c指定寄存器值,读取一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器
 * @return    :   读取到的寄存器值
 */ static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg) {
 u8 data = 0;
 
 ap3216c_read_regs(dev, reg, &data, 1); return data; #if 0 struct i2c_client *client = (struct i2c_client *)dev->private_data; return i2c_smbus_read_byte_data(client, reg); #endif } /*
 * @description : 向ap3216c指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */ 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);
} /*
 * @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *    : 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir : ir数据
 * @param - ps  : ps数据
 * @param - ps  : als数据 
 * @return   : 无。
 */ void ap3216c_readdata(struct ap3216c_dev *dev) { unsigned char i =0; unsigned char buf[6]; /* 循环读取所有传感器数据 */ for(i = 0; i < 6; i++) 
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); 
    } if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */ dev->ir = 0; else /* 读取IR传感器的数据     */ dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);    
 
 dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据     */ if(buf[4] & 0x40) /* IR_OF位为1,则数据无效    */ dev->ps = 0; else /* 读取PS传感器的数据    */ dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
} /*
 * @description  : 打开设备
 * @param - inode  : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *        一般在open的时候将private_data指向设备结构体。
 * @return    : 0 成功;其他 失败
 */ static int ap3216c_open(struct inode *inode, struct file *filp) {
 filp->private_data = &ap3216cdev; /* 初始化AP3216C */ ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C    */ mdelay(50); /* AP3216C复位最少10ms  */ ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR   */ return 0;
} /*
 * @description  : 从设备读取数据 
 * @param - filp  : 要打开的设备文件(文件描述符)
 * @param - buf  : 返回给用户空间的数据缓冲区
 * @param - cnt  : 要读取的数据长度
 * @param - offt  : 相对于文件首地址的偏移
 * @return    : 读取的字节数,如果为负值,表示读取失败
 */ static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off) {
 short data[3]; long err = 0; struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data; ap3216c_readdata(dev);
 
 data[0] = dev->ir;
 data[1] = dev->als;
 data[2] = dev->ps;
 err = copy_to_user(buf, data, sizeof(data)); return 0;
} /*
 * @description  : 关闭/释放设备
 * @param - filp  : 要关闭的设备文件(文件描述符)
 * @return    : 0 成功;其他 失败
 */ static int ap3216c_release(struct inode *inode, struct file *filp) { return 0;
} /* AP3216C操作函数 */ static const struct file_operations ap3216c_ops = { .owner = THIS_MODULE,
 .open = ap3216c_open,
 .read = ap3216c_read,
 .release = ap3216c_release,
}; /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */ static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id) { /* 1、构建设备号 */ if (ap3216cdev.major) {
  ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
  register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
 } else {
  alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
  ap3216cdev.major = MAJOR(ap3216cdev.devid);
 } /* 2、注册设备 */ cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
 cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); /* 3、创建类 */ ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME); if (IS_ERR(ap3216cdev.class)) { return PTR_ERR(ap3216cdev.class);
 } /* 4、创建设备 */ ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME); if (IS_ERR(ap3216cdev.device)) { return PTR_ERR(ap3216cdev.device);
 }
 
 ap3216cdev.private_data = client; return 0;
} /*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client  : i2c设备
 * @return          : 0,成功;其他负值,失败
 */ static int ap3216c_remove(struct i2c_client *client) { /* 删除设备 */ cdev_del(&ap3216cdev.cdev);
 unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); /* 注销掉类和设备 */ device_destroy(ap3216cdev.class, ap3216cdev.devid);
 class_destroy(ap3216cdev.class); return 0;
} /* 传统匹配方式ID列表 */ static const struct i2c_device_id ap3216c_id[] = { {"iot,ap3216c", 0},  
 {}
}; /* 设备树匹配列表 */ static const struct of_device_id ap3216c_of_match[] = { { .compatible = "iot,ap3216c" },
 { /* Sentinel */ }
}; /* i2c驱动结构体 */ static struct i2c_driver ap3216c_driver = { .probe = ap3216c_probe,
 .remove = ap3216c_remove,
 .driver = {
   .owner = THIS_MODULE,
      .name = "ap3216c",
      .of_match_table = ap3216c_of_match, 
     },
 .id_table = ap3216c_id,
}; /*
 * @description : 驱动入口函数
 * @param   : 无
 * @return   : 无
 */ static int __init ap3216c_init(void) { int ret = 0;
 
 ret = i2c_add_driver(&ap3216c_driver); return ret;
} /*
 * @description : 驱动出口函数
 * @param   : 无
 * @return   : 无
 */ static void __exit ap3216c_exit(void) {
 i2c_del_driver(&ap3216c_driver);
} /* module_i2c_driver(ap3216c_driver) */ module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("pjw");

驱动详解可查阅注释及配合上诉的I2C驱动框架的框图及数据手册理解。

ap3216creg.h:

#ifndef AP3216C_H #define AP3216C_H /***************************************************************
文件名  : ap3216creg.h
描述     : AP3216C寄存器地址描述头文件
***************************************************************/ #define AP3216C_ADDR     0X1E /* AP3216C器件地址  */ /* AP3316C寄存器 */ #define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器       */ #define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器   */ #define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器   */ #define AP3216C_IRDATALOW 0x0A /* IR数据低字节     */ #define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节     */ #define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节    */ #define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节    */ #define AP3216C_PSDATALOW 0X0E /* PS数据低字节     */ #define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节     */ #endif 

ap3216应用:

ap3216cApp.c:

#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "sys/ioctl.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" #include  #include  #include  #include  #include  /***************************************************************
文件名  : ap3216cApp.c
描述     : ap3216c设备测试APP。
使用方法  :./ap3216cApp /dev/ap3216c
***************************************************************/ /*
 * @description  : main主程序
 * @param - argc  : argv数组元素个数
 * @param - argv  : 具体参数
 * @return    : 0 成功;其他 失败
 */ int main(int argc, char *argv[]) { int fd; char *filename; unsigned short databuf[3]; unsigned short ir, als, ps; int ret = 0; if (argc != 2) { printf("Error Usage!\r\n"); return -1;
 }
 
 filename = argv[1];
 fd = open(filename, O_RDWR); if(fd < 0) { printf("can't open file %s\r\n", filename); return -1;
 } while (1) {
  ret = read(fd, databuf, sizeof(databuf)); if(ret == 0) { /* 数据读取成功 */ ir =  databuf[0]; /* ir传感器数据 */ als = databuf[1]; /* als传感器数据 */ ps =  databuf[2]; /* ps传感器数据 */ printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
  }
  usleep(200000); /*100ms */ }
 close(fd); /* 关闭文件 */ return 0;
}

编写Makefile,从之前的文章=======拷贝过来修改:

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
 make -C $(KERN_DIR) M=`pwd` modules  $(CROSS_COMPILE)gcc -o ap3216cApp ap3216cApp.c  clean:
 make -C $(KERN_DIR) M=`pwd` modules clean
 rm -rf modules.order
 rm -f ap3216cApp  # 参考内核源码drivers/char/ipmi/Makefile # 要想把a.c, b.c编译成ab.ko, 可以这样指定: # ab-y := a.o b.o # obj-m += ab.o obj-m += ap3216.o
			

编译得到ap3216.ko及ap3216cApp,传到板子上运行:

以上就是本次的实验分享,如果文章对你有帮助,欢迎转发,谢谢!


免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭