Linux驱动 --- AP3216C三合一环境光传感器驱动

发布于:2024-10-17 ⋅ 阅读:(11) ⋅ 点赞:(0)

一、分析电路图

在这里插入图片描述

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 = &reg,
        },
        [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;
}

实验现象:
当接近该传感器时会触发中断,读取数据
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到