Linux-I2C-AP3216C

AP3216C 介绍

AP3216C 是一款由敦南科技研发的高度集成的三合一环境传感器组件,集成了环境光传感器(ALS——Ambient Light Sensor)、接近传感器(PS——Proximity Sensor)和红外 LED(IR LED——Infrared LED)的三合一数字模块。

  • I2C 接口(工作在 FS 模式,400kHz)
  • 环境光传感器具有 16 位 ADC 输出,确保高精度的环境光检测
  • 接近传感器具有 10 位 ADC 输出(0~1023),用于精确的接近距离测量
  • 设备地址:0x1E,寄存器 8 位,寄存器位宽 8 位

AP3216C 广泛应用于手机和平板电脑等便携式设备中,例如用于自动调节屏幕背光亮度(根据环境光线强度变化调整),以及在打电话时检测脸部接近的距离以实现熄屏、解锁等功能。

典型原理图如下:

image-20240628110159381

涉及到的寄存器:

image-20240628112648480

SYSTEM_CONFIGURATION 寄存器的配置表:

image-20240628112802571

开启三种模式,并且是连续测量的,采集 ALS 需要 100 ms,PS 需要 12.5 ms,因此后续在测试程序的时候需要采集间隔需要延时大于 112.5 ms。

image-20240628112905922

复位,复位后需等待 10 ms 以上。

image-20240628112914332

接近程度判定依据是通过两组 PS 高低阈值寄存器和 PS Data 寄存器 进行比对,PS Data 高于 PS High Threshold 则判定物体远离,PS Data 低于 PS Low Threshold 则判定物体靠近。

驱动代码编写

设备描述信息

无设备树

无设备树的话,则需要使用 i2c_board_info 结构体添加设备名字和设备地址,接着将设备挂载到对应 I2C 总线。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

// 分配一个i2c适配器指针
static struct i2c_adapter *i2c_adap;
// 分配一个i2c_client指针
static struct i2c_client *i2c_client;

static struct i2c_board_info ap3216c_info[] = {
{I2C_BOARD_INFO("ap3216c", 0x1e)},
{},
};

/* 驱动入口函数 */
static int __init ap3216c_i2c_client_init(void)
{
printk("ap3216c_i2c_client_init init\n");

// 原理图是连接到了I2C1
// 传入0表示 i2c1;1表示i2c2 找了半天md
//调用i2c_get_adapter获得一个i2c总线 将ap3216c挂载到i2c1总线上
i2c_adap = i2c_get_adapter(0);

//把i2c client和i2c器件关联起来
i2c_client = i2c_new_device(i2c_adap, ap3216c_info);

// 释放i2c控制器
i2c_put_adapter(i2c_adap);

printk("i2c_new_device ok\n");
return 0;
}

/* 驱动出口函数 */
static void __exit ap3216c_i2c_client_exit(void)
{
// 将前面注册的i2c_driver 也从Linux 内核中注销掉
i2c_unregister_device(i2c_client);

printk("ap3216c_i2c_client_exit exit ok\n");
}


module_init(ap3216c_i2c_client_init);
module_exit(ap3216c_i2c_client_exit);
MODULE_LICENSE("GPL");

设备树

有设备树的话,则需要在设备树中的 I2C 节点中添加设备的信息,接着编译设备树烧录到开发板中。添加信息大致如下:

由原理图可知,该开发板的 AP3216C 是挂载在 i2c1 下的,i2c1 的引脚复用关系已经有了,因此只需要在 i2c1 节点下添加设备信息即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
&iomuxc {
// 省略...
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
// 省略...
}

&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";

ap3216c@1e {
compatible = "ap3216c";
reg = <0x1e>;
status = "okay";
};
};

driver 驱动代码

这一部分主要是注册 i2c_driver 结构体、添加设备节点、编写 file_operation 中的 open、release、read 成员,open 对 AP3216C 进行初始化——复位和设置模式,read 读取相对应数据的寄存器,release 设置休眠模式。

寄存器宏定义和 ap3216c_dev 结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h> /*注册设备节点的文件结构体*/
#include <linux/cdev.h> // 对字符设备结构cdev 以及一系列的操作函数的定义。包含了cdev 结构及相关函数的定义。
#include <linux/device.h> //包含了device、class 等结构的定义
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/uaccess.h> //包含了copy_to_user、copy_from_user 等内核访问用户进程内存地址的函数定义。

/* 字符设备 */
#define AP3216C_NAME "ap3216c"

/* AP3216C寄存器 */
#define AP3216C_SYSTEM_CONFIGURATION 0x00 /* 配置寄存器 */
#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数据高字节 */

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 ap3216c_dev_t;

注册/注销 i2c_driver结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 驱动入口函数 */
static int __init ap3216c_i2c_driver_init(void)
{
int ret;
// 注册 i2c 驱动
ret = i2c_add_driver(&ap3216c_i2c_driver);
return ret;
}

/* 驱动出口函数 */
static void __exit ap3216c_i2c_driver_exit(void)
{
// 将前面注册的i2c_driver也从Linux内核中注销掉
i2c_del_driver(&ap3216c_i2c_driver);
}
module_init(ap3216c_i2c_driver_init);
module_exit(ap3216c_i2c_driver_exit);
MODULE_LICENSE("GPL");

添加设备节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
static const struct file_operations ap3216c_fops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.release = ap3216c_close,
.read = ap3216c_read,
};

static int ap3216c_i2c_driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// 1.分配设备号
if(ap3216c_dev_t.major)
{
ap3216c_dev_t.devid = MKDEV(ap3216c_dev_t.major, 0);
register_chrdev_region(ap3216c_dev_t.devid, 1, AP3216C_NAME);
}
else
{
alloc_chrdev_region(&ap3216c_dev_t.devid, 0, 1, AP3216C_NAME);
ap3216c_dev_t.major = MAJOR(ap3216c_dev_t.devid);
}

// 2. 初始化cdev
cdev_init(&ap3216c_dev_t.cdev, &ap3216c_fops);

// 3.添加设备号到cdev
cdev_add(&ap3216c_dev_t.cdev, ap3216c_dev_t.devid, 1);

// 4. 创建类
ap3216c_dev_t.class = class_create(THIS_MODULE, AP3216C_NAME);
if(IS_ERR(ap3216c_dev_t.class))
return PTR_ERR(ap3216c_dev_t.class);

// 5. 类下创建设备
ap3216c_dev_t.device = device_create(ap3216c_dev_t.class, NULL, ap3216c_dev_t.devid, NULL, AP3216C_NAME);
if (IS_ERR(ap3216c_dev_t.device))
return PTR_ERR(ap3216c_dev_t.device);

ap3216c_dev_t.private_data = client;

printk("ap3216c_i2c_driver_probe ok\n");
return 0;
}
static int ap3216c_i2c_driver_remove(struct i2c_client *client)
{
// 1.注销设备号
unregister_chrdev_region(ap3216c_dev_t.devid, 1);
// 2.删除设备
cdev_del(&ap3216c_dev_t.cdev);
// 3.注销设备节点
device_destroy(ap3216c_dev_t.class, ap3216c_dev_t.devid);
// 4.删除类
class_destroy(ap3216c_dev_t.class);

printk("ap3216c_i2c_driver_remove ok\n");
return 0;
}

/* 定义i2c总线设备结构体 */
static struct i2c_driver ap3216c_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c",
.of_match_table = of_match_table,
},
.probe = ap3216c_i2c_driver_probe,
.remove = ap3216c_i2c_driver_remove,
.id_table = id_table,
};

收发数据函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
* @description : 读取I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int ap3216c_read_reg(struct ap3216c_dev *dev, uint8_t reg, uint8_t *val, uint16_t 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; // 设备地址
msg[0].flags = 0; // 写数据
msg[0].buf = &reg; // 寄存器地址
msg[0].len = 1; // msg长度:寄存器地址长度

/* msg[1],第二条读消息,读取寄存器数据*/
msg[1].addr = client->addr; // 设备地址
msg[1].flags = I2C_M_RD; // 读数据
msg[1].buf = val; // 读取的数据
msg[1].len = len; // msg长度:读取数据的长度

ret = i2c_transfer(client->adapter, msg, 2); // 2个msg
if(ret == 2)
ret = 0;
else
ret = -EREMOTEIO;

return ret;
}

/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – val : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static int ap3216c_write_reg(struct ap3216c_dev *dev, uint8_t reg, uint8_t *buf, uint16_t len)
{
uint8_t tmp[256];
struct i2c_msg msg; // 存放待写入的寄存器地址和数据等信息
struct i2c_client *client = (struct i2c_client *)dev->private_data;

tmp[0] = reg; // 寄存器地址
memcpy(&tmp[1], buf, len); // 写入的数据

msg.addr = client->addr; // 设备地址
msg.flags = 0; // 写数据
msg.buf = tmp; // 发送的数据缓冲区
msg.len = len + 1; // msg长度:写入的数据长度 + 寄存器地址长度 (单位:字节)

return i2c_transfer(client->adapter, &msg, 1); // 1个msg
}

open 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static int ap3216c_open (struct inode *node, struct file *file)
{
uint8_t value;
int ret;

// 1.复位
file->private_data = &ap3216c_dev_t;
value = 0x04;
ret = ap3216c_write_reg(&ap3216c_dev_t, AP3216C_SYSTEM_CONFIGURATION, &value, 1);
if(ret < 0)
{
printk("%s, %s, %d error\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}

// 2.复位完需要等待10ms以上
mdelay(20);

// 3.设置模式为ALS+PS+IR
value = 0x03;
ret = ap3216c_write_reg(&ap3216c_dev_t, AP3216C_SYSTEM_CONFIGURATION, &value, 1);
if(ret < 0)
{
printk("%s, %s, %d error\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}

return 0;
}

read 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* @description : 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 142
* : 同时打开ALS,IR+PS的话两次数据读取的间隔要大于112.5ms
* @param - dev : ap3216c_dev结构体,存放传感器数据:ir、als、ps
* @return : 操作结果
*/
static void ap3216c_read_data(struct ap3216c_dev *dev)
{
int i;
uint8_t buf[6];

for(i = 0; i < 6; i++)
{
ap3216c_read_reg(dev, AP3216C_IRDATALOW + i, &buf[i], 1);
}

/* 读取 IR 传感器的数据 */
if(buf[0] & 0x80)
{
dev->ir = 0;
}
else
{ // 需要转16位再移位,否则会丢失数据
dev->ir = ((uint16_t)buf[1] << 2) | (buf[0] & 0x03);
}

/* 读取 ALS 数据 */
dev->als = ((uint16_t)buf[3] << 8) | buf[2];

/* 读取 PS 传感器的数据 */
if(buf[4] & 0x40)
{
dev->ps = 0;
}
else
{
dev->ps = (((uint16_t)buf[5] & 0x3F) << 4) | (buf[4] & 0x0F);
}
}

static ssize_t ap3216c_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
short kbuf[3];

struct ap3216c_dev *dev = file->private_data;

ap3216c_read_data(dev);
kbuf[0] = dev->ir;
kbuf[1] = dev->als;
kbuf[2] = dev->ps;

if(copy_to_user(ubuf, kbuf, sizeof(kbuf)) != 0)
{
printk("%s, %s, %d error\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
return 0;
}

release 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int ap3216c_close (struct inode *node, struct file *file)
{
uint8_t value;
int ret;

// 1.休眠
file->private_data = &ap3216c_dev_t;
value = 0x00;
ret = ap3216c_write_reg(&ap3216c_dev_t, AP3216C_SYSTEM_CONFIGURATION, &value, 1);
if(ret < 0)
{
printk("%s, %s, %d error\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
return 0;
}

应用测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

int main(int argc, char **argv)
{
int ret;
unsigned short databuf[3];
unsigned short ir, als, ps;
// 打开设备节点
int fd = open("/dev/ap3216c", O_RDWR);
if (fd < 0)
{
printf("open ap3216c failed\n");
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);/* 间隔200ms 确保数据已经采集完成 */
}

close(fd);

return 0;
}

打印结果如下:

image-20240628133137808

参考资料

5_2_1_光照信息屏_硬件详解|学习笔记-阿里云开发者社区 (aliyun.com)

https://www.bilibili.com/video/BV1fJ411i7PB/?p=80