Linux-I2C-AP3216C
环境
硬件环境
- 开发板型号:100ask_imx6ull_pro 开发板
- 处理器类型:NXP IMX6ULL
- 处理器架构:恩单核 Cortex-A7
- 处理器主频:800 MHZ
- 内存容量:512 MB DDR3
- 存储介质:4GB eMMC
- 本次测试的驱动:AP3216C 传感器芯片
软件环境
- 宿主机
- 宿主机操作系统:Ubuntu 18.04
- 交叉编译器:100ask 提供的工具链 arm-buildroot-linux-gnueabihf- 支持的最低内核版本:4.9.0
- 开发板
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 广泛应用于手机和平板电脑等便携式设备中,例如用于自动调节屏幕背光亮度(根据环境光线强度变化调整),以及在打电话时检测脸部接近的距离以实现熄屏、解锁等功能。
原理图如下:
涉及到的寄存器:
SYSTEM_CONFIGURATION 寄存器的配置表:
开启三种模式,并且是连续测量的,采集 ALS 需要 100 ms,PS 需要 12.5 ms,因此后续在测试程序的时候需要采集间隔需要延时大于 112.5 ms。
复位,复位后需等待 10 ms 以上。
接近程度判定依据是通过两组 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>
static struct i2c_adapter *i2c_adap;
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");
i2c_adap = i2c_get_adapter(0);
i2c_client = i2c_new_device(i2c_adap, ap3216c_info);
i2c_put_adapter(i2c_adap); printk("i2c_new_device ok\n"); return 0; }
static void __exit ap3216c_i2c_client_exit(void) { 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> #include <linux/device.h> #include <linux/string.h> #include <linux/delay.h> #include <linux/uaccess.h>
#define AP3216C_NAME "ap3216c"
#define AP3216C_SYSTEM_CONFIGURATION 0x00 #define AP3216C_IRDATALOW 0x0A #define AP3216C_IRDATAHIGH 0x0B #define AP3216C_ALSDATALOW 0x0C #define AP3216C_ALSDATAHIGH 0X0D #define AP3216C_PSDATALOW 0X0E #define AP3216C_PSDATAHIGH 0X0F
struct ap3216c_dev{ dev_t devid; struct 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 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| static const struct of_device_id of_match_table[] = { {.compatible = "ap3216c"}, {}, };
static const struct i2c_device_id id_table[] = { {"ap3216c"}, {}, };
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, };
static int __init ap3216c_i2c_driver_init(void) { int ret; ret = i2c_add_driver(&ap3216c_i2c_driver); return ret; }
static void __exit ap3216c_i2c_driver_exit(void) { 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
| 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) { 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); }
cdev_init(&ap3216c_dev_t.cdev, &ap3216c_fops);
cdev_add(&ap3216c_dev_t.cdev, ap3216c_dev_t.devid, 1);
ap3216c_dev_t.class = class_create(THIS_MODULE, AP3216C_NAME); if(IS_ERR(ap3216c_dev_t.class)) return PTR_ERR(ap3216c_dev_t.class);
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) { unregister_chrdev_region(ap3216c_dev_t.devid, 1); cdev_del(&ap3216c_dev_t.cdev); device_destroy(ap3216c_dev_t.class, ap3216c_dev_t.devid); class_destroy(ap3216c_dev_t.class);
printk("ap3216c_i2c_driver_remove ok\n"); return 0; }
|
收发数据函数
主要就是填充 i2c_msg 结构体,例如设备地址、写数据/读数据,寄存器的地址、msg 的长度,写入数据的缓冲区和读取数据的缓冲区,然后调用 i2c_transfer 函数进行传输数据。
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
|
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].addr = client->addr; msg[0].flags = 0; msg[0].buf = ® msg[0].len = 1;
msg[1].addr = client->addr; 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 ret = -EREMOTEIO; return ret; }
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; return i2c_transfer(client->adapter, &msg, 1); }
|
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; 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; }
mdelay(20);
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
|
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); }
if(buf[0] & 0x80) { dev->ir = 0; } else { dev->ir = ((uint16_t)buf[1] << 2) | (buf[0] & 0x03); }
dev->als = ((uint16_t)buf[3] << 8) | buf[2];
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; 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]; als = databuf[1]; ps = databuf[2]; printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps); } usleep(200000); } close(fd);
return 0; }
|
打印结果如下:
参考资料
5_2_1_光照信息屏_硬件详解|学习笔记-阿里云开发者社区 (aliyun.com)
https://www.bilibili.com/video/BV1fJ411i7PB/?p=80