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 = &reg;
    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(char *filename)
{
    
    
    int fd;
    int ret;
    unsigned char data_buf[3];

    if (!filename) {
    
    
        return -1;
    }

    // 打开设备文件
    fd = open(filename, O_RDWR);
    if (fd < 0) {
    
    
        printf("open %s error!\n", filename);
        return 0;
    }

    // 读文件
    ret = read(fd, data_buf, 3);
    if (ret != 0) {
    
    
        printf("read fail!\n");
    } else {
    
    
        printf("ir:%d als: %d ps: %d\n", data_buf[0], data_buf[1], data_buf[2]);
    }

    // 关闭文件
    close(fd);

    return 0;
}

int main(int argc, char *argv[])
{
    
    
    uint32_t interval;

    // 检查参数
    if (argc != 3) {
    
    
        printf("usage: ./test_ap3216c [device] [read interval(s)]\n");
        return -1;
    }

    interval = atoi(argv[2]);
    if (interval < 1) {
    
    
        interval = 1;
    }

    while (1) {
    
    
        ap3216_read_data(argv[1]);
        sleep(interval);
    }
}

3. 测试

读出来数据一直是0,逻辑分析仪抓了一下也都是0:

看样子硬件不吐数据哇,再抓一下初始化的波形:

卧槽,找到问题所在了,写寄存器的时候分了两次msg写入,果然出问题了,修复ap3216c_write_reg函数,搞定。

猜你喜欢

转载自blog.csdn.net/Mculover666/article/details/124024664