i.MX6ULL驱动开发 | 12 - 基于 Linux I2C 驱动读取AP3216C传感器
Posted Mculover666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了i.MX6ULL驱动开发 | 12 - 基于 Linux I2C 驱动读取AP3216C传感器相关的知识,希望对你有一定的参考价值。
本系列文章驱动源码仓库,欢迎Star~
https://github.com/Mculover666/linux_driver_study。
一、AP3216C
1. 简介
AP3216C集成了一个光照强度(ALS)、接近距离(PS)和红外线强度(IR)这三个传感器为一体。
AP3216C具有以下特性:
- I2C接口,支持400K波特率
- 宽工作温度范围(-30℃ - +80℃)
- 环境光传感器有16位分辨率
- 接近传感器和红外传感器具有10位分辨率
AP3216C内置的接近传感器可以用于检测是否有物体接近,比如手机可以用来检测耳朵是否接触听筒。也可以用环境光传感器检测光照强度,实现自动背光亮度调节。
AP3216C的I2C地址为0x1E,引脚如下图:
2. 功能使用
AP3216C的常用寄存器列表如下:
3. Alpha开发板原理图
AP3216C传感器使用I2C1,引脚连接情况如下:
传感器引脚 | iMX6ULL引脚 |
---|---|
SCL | UART4_TXD |
SDA | UART4_RXD |
二、添加设备树节点
1. 设置I2C1引脚
首先设置I2C1引脚的复用功能和电气属性,找到 pinctrl_i2c1 节点:
这里使用的是UART4_TX和UART4_RX引脚,无需修改。
2. 添加新的i2c设备
找到i2c1子节点的补充描述,因为开发板没有用到 mag3110 和 fxls8471,删除这两个设备节点,添加AP3216C的设备节点:
ap3216c@1e
compatible = "atk,ap3216c";
reg = <0x1e>;
;
3. 重新编译设备树
make dtbs
编译完成后,使用新的设备树启动内核,查看设备树是否有新添加的节点:
查看是否有新添加的总线设备:
三、编写AP3216C设备驱动
1. 先写个模块
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init ap3216c_module_init(void)
return 0;
static void __exit ap3216c_module_exit(void)
module_init(ap3216c_module_init)
module_exit(ap3216c_module_exit)
MODULE_AUTHOR("Mculover666");
MODULE_LICENSE("GPL");
写个Makefile编译一下:
KERNEL_DIR = /home/mculover666/imx6ull/kernel/linux-imx6ull
obj-m = ap3216c.o
build: kernel_module
kernel_module:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
编译没问题,接着写。
2. 再搭个i2c设备驱动框架
包含头文件:
#include <linux/i2c.h>
完成i2c设备驱动基本框架:
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
return 0;
static int ap3216c_remove(struct i2c_client *client)
return 0;
/**
* @brief 设备树匹配列表
*/
static const struct of_device_id ap3216c_of_match[] =
.compatible = "atk,ap3216c" ,
,
;
/**
* @brief 传统id方式匹配列表
*/
static const struct i2c_device_id ap3216c_id[] =
"atk,ap3216c", 0 ,
,
;
/**
* @brief 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,
;
static int __init ap3216c_module_init(void)
int ret;
ret = i2c_add_driver(&ap3216c_driver);
if (ret < 0)
printk("i2c_add_driver fail!\\n");
return -1;
return 0;
static void __exit ap3216c_module_exit(void)
i2c_del_driver(&ap3216c_driver);
3. 再写字符设备驱动框架
引入头文件:
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
添加字符设备驱动框架相关代码:
struct ap3216c_dev
dev_t dev;
struct cdev *cdev;
struct class *class;
struct device *device;
;
static struct ap3216c_dev ap3216c;
static int ap3216c_open(struct inode *node, struct file *fp)
return 0;
static int ap3216c_read(struct file *fp, char __user *buf, size_t size, loff_t *off)
return 0;
static int ap3216c_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)
return 0;
static int ap3216c_release(struct inode *node, struct file *fp)
return 0;
static struct file_operations ap3216c_fops =
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.write = ap3216c_write,
.release = ap3216c_release,
;
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
int ret;
// 申请设备号
ret = alloc_chrdev_region(&ap3216c.dev, 0, 1, "ap3216c");
if (ret != 0)
printk("alloc_chrdev_region fail!");
return -1;
// 创建cdev
ap3216c.cdev = cdev_alloc();
if (!ap3216c.cdev)
printk("cdev_alloc fail!");
return -1;
ap3216c.cdev->owner = THIS_MODULE;
ap3216c.cdev->ops = &ap3216c_fops;
// 注册cdev
cdev_add(ap3216c.cdev, ap3216c.dev, 1);
// 创建设备类
ap3216c.class = class_create(THIS_MODULE, "ap3216c");
if (!ap3216c.class)
printk("class_create fail!");
return -1;
// 创建设备节点
ap3216c.device = device_create(ap3216c.class, NULL, ap3216c.dev, NULL, "ap3216c");
if (IS_ERR(ap3216c.device))
printk("device_create led_dts_device0 fail!");
return -1;
ap3216c.client = client;
return 0;
static int ap3216c_remove(struct i2c_client *client)
// 将设备从内核删除
cdev_del(ap3216c.cdev);
// 释放设备号
unregister_chrdev_region(ap3216c.dev, 1);
// 删除设备节点
device_destroy(ap3216c.class, ap3216c.dev);
// 删除设备类
class_destroy(ap3216c.class);
return 0;
4. 封装I2C操作代码
(1)全局变量中添加 i2c_client 成员:
struct ap3216c_dev
dev_t dev;
struct cdev *cdev;
struct class *class;
struct device *device;
struct i2c_client *client;
;
(2)读取AP3216C寄存器
AP3216C读寄存器的时序如下:
/**
* @brief 读AP3216C寄存器
* @param[in] dev AP3216C设备指针
* @param[in] reg 要读取的寄存器地址
* @param[out] val 读取到的值
* @param[in] len 要读取的数据长度
* @return errcode, 0 is success, -1 is fail
*/
static int ap3216c_read_reg(struct ap3216c_dev *dev, uint8_t reg, uint8_t *val)
struct i2c_client *client = dev->client;
struct i2c_msg msg[2];
int ret;
// 组装数据,要读取的寄存器地址
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 = (void*)val;
msg[1].len = 1;
// 传输数据
ret = i2c_transfer(client->adapter, msg, 2);
if (ret != 2)
printk("ap3216c_read_reg fail, ret is %d, reg is 0x%x\\n", ret, reg);
return -1;
return 0;
(3)AP3216C写入寄存器
AP3216C写寄存器的时序如下:
/**
* @brief 写AP3216C寄存器
* @param[in] dev AP3216C设备指针
* @param[in] reg 要写入的寄存器地址
* @param[in] val 要写入的值
* @param[in] len 要写入的数据长度
* @return errcode, 0 is success, -1 is fail
*/
static int ap3216c_write_reg(struct ap3216c_dev *dev, uint8_t reg, uint8_t val)
struct i2c_client *client = dev->client;
struct i2c_msg msg;
int ret;
uint8_t send_buf[2];
// 组装数据
send_buf[0] = reg;
send_buf[1] = val;
msg.addr = client->addr;
msg.flags = 0; // 标记为发送数据
msg.buf = send_buf;
msg.len = 2;
// 传输数据
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret != 1)
printk("ap3216c_write_reg fail, ret is %d, reg is 0x%x\\n", ret, reg);
return -1;
return 0;
5. AP3216C初始化
要使用mdelay函数,引入头文件:
#include <linux/delay.h>
编写一个AP3216硬件初始化函数
/**
* @brief AP3216C传感器硬件初始化
* @param none
* @retval errcode
*/
static int ap3216c_board_init(void)
int ret;
uint8_t val;
// 设置AP3216C系统模式,软复位
ret = ap3216c_write_reg(&ap3216c, 0x00, 0x04);
if (ret < 0)
printk("ap3216 soft reset fail!\\n");
return -1;
// 软复位后至少等待10ms
mdelay(150);
// 设置AP3216C系统模式,ALS+PS+IR单次模式
ret = ap3216c_write_reg(&ap3216c, 0x00, 0x03);
if (ret < 0)
printk("ap3216 activate fail!\\n");
return -1;
mdelay(150);
// 读模式寄存器,确认模式写入成功
ret = ap3216c_read_reg(&ap3216c, 0x00, &val);
if (ret < 0)
printk("ap3216 read mode fail!\\n");
return -1;
printk("ap3216 mode: %d\\n", val);
return 0;
在驱动加载的时候,进行硬件初始化:
6. AP3216C读取传感器数据
首先封装数据:
struct ap3216c_data
uint16_t ir; /*!< Infrared Radiation, 红外LED */
uint16_t als; /*!< Ambilent Light Sensor, 数字环境光传感器,16位有效线性输出 */
uint16_t ps; /*!< Proximity Sensor, 接近传感器,10位有效线性输出 */
;
作为ap3216c_dev的成员:
编写读取函数:
/**
* @brief AP3216C传感器读取数据
* @param none
* @retval errcode
*/
static int ap3216c_board_read_data(struct ap3216c_dev *dev)
uint8_t low_val, high_val;
// IR
ap3216c_read_reg(dev, 0x0A, &low_val);
ap3216c_read_reg(dev, 0x0B, &high_val);
if (low_val & 0x80)
dev->data.ir = 0;
else
dev->data.ir = ((uint16_t)high_val << 2) | (low_val & 0x03);
// ALS
ap3216c_read_reg(dev, 0x0C, &low_val);
ap3216c_read_reg(dev, 0x0D, &high_val);
dev->data.als = ((uint16_t)high_val << 8) | low_val;
// PS
ap3216c_read_reg(dev, 0x0E, &low_val);
ap3216c_read_reg(dev, 0x0F, &high_val);
if (low_val & 0x40)
dev->data.ps = 0;
else
dev->data.ps = ((uint16_t)(high_val & 0x3F) << 4) | (low_val & 0x0F);
return 0;
7. AP3216C字符设备驱动实现
open
static int ap3216c_open(struct inode *node, struct file *fp)
fp->private_data = &ap3216c;
return 0;
read
使用了copy_to_user
,引入头文件:
#include <asm/uaccess.h>
实现read函数:
static int ap3216c_read(struct file *fp, char __user *buf, size_t size, loff_t *off)
struct ap3216c_dev *dev = (struct ap3216c_dev *)fp->private_data;
uint16_t data[3];
long err;
if (!dev)
printk("ap3216c dev get fail!\\n");
return -1;
ap3216c_board_read_data(dev);
data[0] = dev->data.ir;
data[1] = dev->data.als;
data[2] = dev->data.ps;
err = copy_to_user(buf, data, sizeof(data));
if (err != sizeof(data))
printk("copy to user fail!\\n");
return 0;
return err;
write
static int ap3216c_write(struct file *fp, const char __user *buf, size_t size, loff_t *off)
return 0;
release
static int ap3216c_release(struct inode *node, struct file *fp)
return 0;
至此,驱动终于编写完成。
四、测试驱动模块
1. 加载驱动模块
查看模块列表和设备节点:
2. 编写app测试程序
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
int ap3216_read_data(i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED
i.MX6ULL驱动开发 | 08 -基于pinctrl子系统和gpio子系统点亮LED
i.MX6ULL驱动开发 | 09 -基于Linux自带的LED驱动点亮LED
i.MX6ULL驱动开发 | 34 - 基于SPI框架驱动spi lcd(st7789)