文章目录
一、分析电路图
SCL–PF14
SDA–PF15
INT1–PF13
INT1引脚外接上拉电阻,即默认状态为高电平,当产生中断时会产生一个下降沿。
二、AP3216c数据手册
(一)系统模式寄存器
000 掉电模式
001 环境光ALS模式,数据转换时间是100ms;PS数据不工作。
010 仅接近和红外传感器工作,典型的数据转换时间是12.5ms
011 ALS、PS、IR均开启,该模式下转换时间为112.5ms
100 复位 10ms后设备寄存器会恢复默认值,在此期间不要对寄存器操作
(二)中断状态寄存器
ALS INT位寄存器用于指示ALS中断是否被触发(设置为1)或未触发(设置为0)。在读取0x0D寄存器或写入0x01以清除(取决于INT Clear Manner标志)后,该位将被清除。
PS INT位寄存器用于指示PS中断是否被触发(设置为1)或未触发(设置为0)。在读取0x0F寄存器或写入0x02以清除(取决于INT Clear Manner标志)后,该位将被清除
(三)清除中断方式寄存器
CLR_MNR 0 自动清除; 1 手动清除。
(四)IR数据寄存器
红外数据分为10位数据,数据寄存器是只读寄存器。
先读取低字节寄存器,再读取高字节数据(低字节被读取时,高字节数据会存储在一个临时寄存器中)
IR_OF 红外溢出标志位,当其被置1时,表示在强红光环境下,数据无效
(五)ALS环境光数据寄存器
ALS数据寄存器只读,数据为16位。
需要先读取低字节数据,再读取高字节数据。
(六)PS数据寄存器
PS数据寄存器是10位数据,只读寄存器,先读低字节寄存器,在读高字节寄存器
目标状态位(OBJ):OBJ位用于指示目标是否靠近传感器。
当目标远离传感器并超过一定阈值时,OBJ位被重置为0;
当目标靠近传感器时,OBJ位被设置为1。
红外溢出标志(IR_OF):该标志用于指示在强红外光下,光电传感器的数据是否有效。如果IR_OF被设置为1,则表示数据无效。
三、设备树
根据传感器数据手册可知,该从设备的从机地址为0x1E
此外由上述电路图可知,通过I2C1总线进行通讯,中断引脚为PF13
&i2c1{
status = "okay";
//1.工作模式设置为复用, 模式有两种:工作模式(默认default), 休眠模式(sleep),
//第三种挂起模式(idle)不用
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c1_pins_b>;
pinctrl-1 = <&i2c1_sleep_pins_b>;
//2.主机通信速率, 只用设置主机的通信速率, 不需要设置从机的通信速率,
//从机是被动接收的, 主机速率控制其速率,
//但是主机设置速率时需要考虑从机能接收的最大速率
clock-frequency = <400000>;
//3.时钟scl上升沿时间, 下降沿时间, 帮助文档中提供了, 就写上
i2c-scl-rising-time-ns = <185>;
i2c-scl-falling-time-ns = <20>;
ap3216c@1e{
compatible = "zyx,ap3216c";
reg = <0x1e>;
interrupt-parent = <&gpiof>;
interrupts = <13 0>;
};
};
四、驱动代码实现
ap3216c.h
#ifndef __AP3216C_H__
#define __AP3216C_H__
/***
* SCL--PF14
* SDA--PF15
* INT1--PF13
***/
#define CNAME "ap3216c"
#define REG_SYS_CONFIG 0x00
#define REG_INT_S 0x01
#define REG_INT_CM 0x02
#define REG_ALS_CONFIG 0x10
#define REG_IR_DATA_L 0x0A
#define REG_IR_DATA_H 0x0B
#define REG_ALS_DATA_L 0x0C
#define REG_ALS_DATA_H 0x0D
#define REG_PS_DATA_L 0x0E
#define REG_PS_DATA_H 0x0F
#define CMD_RESET 0x04
//配置模式:0x01--ALS 0x02--PS+IR 0x03--PS+IR+ALS
#define CMD_MODE 0x03
#define CMD_CLEAR_MANNER 0x00
#define WAIT_CONVER_TIME 150
typedef struct{
int als;
int ps;
int ir;
}data_t;
#endif
ap3216c.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include "ap3216c.h"
struct cdev *cdev;
dev_t devno;
struct class *cls;
struct device *dev;
struct i2c_client *ap3216c_client; //保存
int ap_irqno; //软中断号
wait_queue_head_t wq; //等待队列头
int condition=0; //阻塞等待条件
struct work_struct mywork; //工作队列
data_t kbuf;
//配置传感器
/*i2c写协议*/
/***
* 功能:向ap3216c的reg寄存器中写一字节的cmd***/
int i2c_write_ap3216c(u8 reg,u8 cmd){
int ret;
u8 data[2] = {reg,cmd};
struct i2c_msg w_msg[]={
[0]={
.addr = ap3216c_client->addr,
.flags = 0,
.len = 2,
.buf = data,
}
};
ret = i2c_transfer(ap3216c_client->adapter,w_msg,ARRAY_SIZE(w_msg));
if(1 != ret){
pr_err("i2c_transfer error\n");
return -EIO;
}
return 0;
}
/*读协议*/
//一次读取一字节数据
u8 i2c_read_ap3216c(u8 reg){
int ret;
u8 data;
//组装数据
struct i2c_msg r_msg[2]={
[0]={
.addr = ap3216c_client->addr,
.flags=0,
.len = 1,
.buf = ®,
},
[1]={
.addr = ap3216c_client->addr,
.flags = 1,
.len = 1,
.buf = &data,
},
};
//发送数据
ret = i2c_transfer(ap3216c_client->adapter,r_msg,ARRAY_SIZE(r_msg));
if(ret != 2){
pr_err("i2c_transfer error\n");
return -EIO;
}
return data;
}
/***中断底半部函数***/
void mywork_bottom_func(struct work_struct *work){
u8 flags = i2c_read_ap3216c(REG_INT_S);
u8 temp1=0,temp2=0;
u8 flag = 0; //只有在产生有效数据时,flag才会置1
mdelay(WAIT_CONVER_TIME); //等待转换
//测试该中断并不会触发,因此此处暂时注释掉中断判断
// if(flags & 0x1){//说明ALS中断触发
temp1 = i2c_read_ap3216c(REG_ALS_DATA_L);
temp2 = i2c_read_ap3216c(REG_ALS_DATA_H);
kbuf.als = temp1|(temp2<<8);
flag = 1;
// }
if(flags & 0x2){//说明PS中断触发
//PS
temp1 = i2c_read_ap3216c(REG_PS_DATA_L);
temp2 = i2c_read_ap3216c(REG_PS_DATA_H);
if((temp1 & 0x80) && (temp2 & 0x80) && !(temp1 & 0x40) && !(temp2 & 0x40)){ //物体接近且红外不溢出
temp1 &= 0x0f;
temp2 &= 0x0f;
kbuf.ps = temp1 | (temp2 << 4);
flag = 1;
}
//IR
temp1 = i2c_read_ap3216c(REG_IR_DATA_L);
temp2 = i2c_read_ap3216c(REG_PS_DATA_H);
if(!(temp1 & 0x80)){
temp1 &= 0x3;
kbuf.ir = temp1 | (temp2 << 2);
flag = 1;
}
}
if(flag) condition = 1;
wake_up_interruptible(&wq);
}
//中断处理函数
irqreturn_t ap3216c_irq_handler(int irqno, void *dev){
schedule_work(&mywork);//开启工作队列
return IRQ_HANDLED;
}
int ap3216c_open(struct inode *inode, struct file *file){
printk("%s:%d:%s\n",__FILE__,__LINE__,__FUNCTION__);
return 0;
}
ssize_t ap3216c_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset){
int ret;
if(file->f_flags & O_NONBLOCK){ //非阻塞io
return -EINVAL;
}else{//阻塞
ret = wait_event_interruptible(wq,condition);
if(ret){
pr_err("interrupt by signal\n");
return ret;
}
if(size>sizeof(kbuf)) size = sizeof(kbuf);
ret = copy_to_user(ubuf,&kbuf,size);
if(ret){
pr_err("copy_to_user error\n");
return ret;
}
}
condition=0;
return size;
}
int ap3216c_close(struct inode *inode, struct file *file){
printk("%s:%d:%s\n",__FILE__,__LINE__,__FUNCTION__);
return 0;
}
struct file_operations fops = {
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_close,
};
int ap3216_init(void){
//复位传感器
i2c_write_ap3216c(REG_SYS_CONFIG,CMD_RESET);
mdelay(10); //延时10ms
//配置寄存器模式为 ALS+PS+IR
i2c_write_ap3216c(REG_SYS_CONFIG,CMD_MODE);
//配置中断清除方式为自动清除
i2c_write_ap3216c(REG_INT_CM,CMD_CLEAR_MANNER);
i2c_write_ap3216c(REG_ALS_CONFIG,0x01);//采样四次产生一个硬件中断
return 0;
}
int ap3216_probe(struct i2c_client *client, const struct i2c_device_id *id){
int ret;
printk("%s:%d:%s\n",__FILE__,__LINE__,__FUNCTION__);
ap3216c_client = client;
/***注册字符设备驱动***/
cdev = cdev_alloc();
if(NULL == cdev){
pr_err("cdev_alloc error\n");
ret = -ENOMEM;
goto err0;
}
cdev_init(cdev,&fops);
ret = alloc_chrdev_region(&devno,0,1,CNAME);
if(ret){
pr_err("alloc_chrdev_region error\n");
goto err0;
}
//注册设备
ret = cdev_add(cdev,devno,1);
if(ret){
pr_err("cdev_add error\n");
goto err1;
}
/***自动创建设备节点***/
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
pr_err("class_create error\n");
goto err2;
}
dev = device_create(cls,NULL,devno,NULL,CNAME);
if(IS_ERR(dev)){
pr_err("device_create error\n");
goto err3;
}
/******中断初始化***/
//获取软中断号
ap_irqno = client->irq;
//申请中断号
ret = request_irq(ap_irqno,ap3216c_irq_handler,IRQF_TRIGGER_FALLING,"ap3216c_irq",NULL);
if(ret){
pr_err("request_irq error\n");
goto err4;
}
/******初始化等待队列头***/
init_waitqueue_head(&wq);
/******中断底半部初始化******/
INIT_WORK(&mywork,mywork_bottom_func);
/******初始化传感器******/
ap3216_init();
return 0;
err4:
device_destroy(cls,devno);
err3:
class_destroy(cls);
err2:
cdev_del(cdev);
err1:
unregister_chrdev_region(devno,1);
err0:
return ret;
}
int ap3216_remove(struct i2c_client *client){
printk("%s:%d:%s\n",__FILE__,__LINE__,__FUNCTION__);
free_irq(ap_irqno,NULL);
device_destroy(cls,devno);
class_destroy(cls);
cdev_del(cdev);
unregister_chrdev_region(devno,1);
return 0;
}
struct of_device_id oftable[] = {
{.compatible = "zyx,ap3216c"},
{},
};
struct i2c_driver ap3216c_driver = {
.probe = ap3216_probe,
.remove = ap3216_remove,
.driver = {
.of_match_table = oftable,
.name = "ap3216",
}
};
module_i2c_driver(ap3216c_driver);
MODULE_LICENSE("GPL");
测试程序:
驱动提供了阻塞IO模型,因此只需要循环读取即可,无需担心会刷屏
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "head.h"
int main(int argc, char const *argv[])
{
int fd = open("/dev/ap3216c",O_RDWR);
if(fd < 0)
{
perror("open error");
return -1;
}
data_t mydata;
while(1){
read(fd,&mydata,sizeof(data_t));
printf("ALS=[%d], PS=[%d], IR=[%d]\n",mydata.als,mydata.ps,mydata.ir);
}
return 0;
}
实验现象:
当接近该传感器时会触发中断,读取数据