目录

简介:

设备树设置:

驱动代码编写:

注册i2c总线:

读取寄存器值

input使用

注册input

 input上报

delay work使用

sysfs使用


简介:

  • 传感器,ap3216c,红外,光感,距离三合一传感器
  • 平台,正点原子linux imx6ull开发版,linux 4.1.15内核

实现目标:

1. 使能sensor后,定时通过input上报传感器值

2. 上报周期可以通过sysfs 设置,使能和失能也可以通过sysfs设置

具体代码,已经上传gitee,https://gitee.com/king_ocean/linux/tree/master/i2c

设备树设置:

传感器是i2c器件,挂载在i2c1上,器件地址为0x1e。

主要是设置compatiable 属性和 i2c地址,compatiable属性用于和驱动匹配,器件地址用于i2c寻址

查看设备树是否更新成功。

root@ATK-IMX6U:/proc/device-tree/soc/aips-bus@02100000/i2c@021a0000/

查看该目录下是否有生成自己新加的节点,进去目录,可以查看compatible属性,目录里的地址是我的板子的 i2c0 主机地址,不同的板子不一样

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


	ap3216c@1e {
		compatible = "alientek,ap3216c";
		reg = <0x1e>;
	};
};

驱动代码编写:

驱动代码编写分为5个部分,注册i2c总线,通过i2c读取传感器值,使用input上报数据,使用delay work进行定时上报,添加sysfs属性用来控制上报周期和使能失能传感器

先来看下总的数据结构定义。

struct ap3216c_data {
    struct i2c_client *client;
    struct input_dev *input;
    struct delayed_work ap_work; /* delay work用来定时采集传感器数据 */
    int delay;                   /* delay的时间,ms为单位 */
    struct ap3216c_value value;  /* 传感器值 */
    int enable;                  /* 传感器工作状态,1 表示使能,0表示power down */
};

传感器值数据结构定义。

/* ap3216c 数据 */
struct ap3216c_value {
    unsigned short ir;
    unsigned short als;
    unsigned short ps;
};

注册i2c总线:

一般linux驱动,我们都会将其挂载在一种总线下,如i2c总线,spi总线,platfrom总线。

首先需要定义一个 i2c driver,如下代码里几个成员变量是必须要实现的

probe函数,在加载驱动时,如果设备和驱动能匹配上就会执行probe函数,传统方式是通过id_table进行匹配,现在基本都是使用设备树,通过of_match_table匹配,即compatible 属性值和设备树中的必须完全一致,否则probe函数不会执行。

remove函数,卸载驱动时,执行remove函数

driver中的 owner name of_match_table 都需要初始化 

id_table也需要初始化,实测如果id_table不初始化,会导致 probe 不执行,具体原因不清楚

以上初始化完成后,在module_init函数中注册i2c驱动,使用API i2c_add_driver

按照如下配置之后,insmod或者modprobe加载驱动时,就会执行probe函数

/* 传统方式的id匹配 */
static const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0},
    {}
};

/* of match,设备树匹配 */
static const struct of_device_id ap3216c_of_match[] = {
    {.compatible = "alientek,ap3216c"},
    {},
};

/* i2c driver,需要同时定义 id匹配和设备树两种匹配方式 */
static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = AP3216C_NAME,
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,
};

// module_i2c_driver(ap3216c_driver);

static int __init ap3216c_init(void)
{
    int ret = 0;
    KERNEL_LOG("enter");
    ret = i2c_add_driver(&ap3216c_driver);
    return ret;
}

static void __exit ap3216c_exit(void)
{
    KERNEL_LOG("enter");
    i2c_del_driver(&ap3216c_driver);
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);

读取寄存器值

i2c的读写时序如下,具体可以参考 正点原子 IMX6U 嵌入式开发指南。

写时序,先通过i2c发送从机器件地址,最后一位是读写标志位,然后发送要写入的寄存器地址,然后是要写入的数据,如下

 读时序,先通过i2c总线发送从机器件地址,最后一位读写标志位,(注意此处是写,因为要先写入要读取数据的寄存器地址),然后发送要读取的寄存器的地址,然后发送从机器件地址,最后一位是读,接着从机返回数据给主机

 代码中单字节的读取,i2c-core 提供了API接口,如下:

comamnd对应的是寄存器地址,读取到的数据直接通过函数返回值进行返回,写入的值放在参数value中

s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)

s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)

初始化ap3216c
复位,设置工作模式为同时采集ir,als,ps。然后进入power down。

#define AP3216C_CMD_POWER_DOWN 0
#define AP3216C_CMD_RESET 0x04      /* 复位 */
#define AP3216C_CMD_ENABLE_ALL 0x03 /* 打开ALS,IR,PS */

/**
 * @description: ap3216c 硬件初始化,reset,设置功能模式,默认下电状态
 * @param {struct ap3216c_data} *p_ap3216c
 * @return {*}
 */
static int ap3216c_hw_init(struct ap3216c_data *p_ap3216c)
{
    int ret, value;
    ret = i2c_smbus_write_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG, AP3216C_CMD_RESET);
    if (ret < 0) {
        KERNEL_LOG("can't write reg 0x%x : %d",
                   AP3216C_SYSTEMCONG, AP3216C_CMD_RESET);
        return ret;
    }
    mdelay(RESET_TIME);
    ret = i2c_smbus_write_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG, AP3216C_CMD_ENABLE_ALL);
    if (ret < 0) {
        KERNEL_LOG("can't write reg 0x%x : %d",
                   AP3216C_SYSTEMCONG, AP3216C_CMD_ENABLE_ALL);
        return ret;
    }

    value = i2c_smbus_read_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG);
    if (value != AP3216C_CMD_ENABLE_ALL) {
        KERNEL_LOG("config reg set failed, config %d", value);
        return -EINVAL;
    }

    ret = i2c_smbus_write_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG, AP3216C_CMD_POWER_DOWN);
    if (ret < 0) {
        KERNEL_LOG("can't write reg 0x%x : %d",
                   AP3216C_SYSTEMCONG, AP3216C_CMD_POWER_DOWN);
        return ret;
    }

    return ret;
}

读取传感器值

ir,als,ps对应的值都是不超过16位,但是超过8位,所以都是用short类型,如下的转换是根据数据手册来的。

/**
 * @description: 读取ap3216c的 ir,als,ps 到 ap3216c_data
 * @param {struct ap3216c_data} *p_ap3216c  这个驱动的数据
 * @return {*}  0,success
 */
static int ap3216c_read_data(struct ap3216c_data *p_ap3216c)
{
    int i;
    unsigned char buf[6];
    struct ap3216c_value *value;
    KERNEL_LOG("enter");

    for (i = 0; i < 6; i++) {
        // ap3216c_i2c_read_one_reg(p_ap3216c->client, buf+i, AP3216C_IRDATALOW + i);
        buf[i] = i2c_smbus_read_byte_data(p_ap3216c->client, AP3216C_IRDATALOW + i);
        // KERNEL_LOG("%d", buf[i]);
    }

    value = &p_ap3216c->value;
    if (buf[0] & 0x80)
        value->ir = 0;
    else
        value->ir = (buf[1] << 2) | (buf[0] & 0x03);

    value->als = (buf[3] << 8) | buf[2];

    if (buf[4] & 0x40)
        value->ps = 0;
    else
        value->ps = ((buf[5] & 0x3F) << 4) | (buf[4] & 0x0F);

    KERNEL_LOG("ir %d, als %d, ps %d", value->ir, value->als, value->ps);

    return 0;
}

input使用

注册input

注册到input子系统,通过input上报传感器数据,然后上层应用可以通过input子系统获取到传感器值,此处使用abs类型,注意需要将 驱动自定义的数据放入input dev,input_set_drvdata(idev, p_ap3216c);  便于后面通过input dev 获取到 驱动数据

    struct input_dev *idev;

    idev = input_allocate_device();
    if (idev == NULL) {
        KERNEL_LOG("ERROR: alloc input dev failed");
        goto input_alloc_failed;
    }

    p_ap3216c->input = idev;

    idev->name = AP3216C_NAME;
    idev->id.bustype = BUS_I2C;
    idev->dev.parent = &client->dev;

    idev->open = ap3216c_open;
    idev->close = ap3216c_close;

    __set_bit(EV_ABS, idev->evbit);
    input_set_abs_params(idev, ABS_X, AP3216C_IR_MAX, AP3216C_IR_MIN, 0, 0);
    input_set_abs_params(idev, ABS_Y, AP3216C_ALS_MAX, AP3216C_ALS_MIN, 0, 0);
    input_set_abs_params(idev, ABS_Z, AP3216C_PS_MAX, AP3216C_PS_MIN, 0, 0);
    /* 将自定义的数据结构体放到input 设备里 */
    input_set_drvdata(idev, p_ap3216c);

    ret = input_register_device(idev);
    if (ret) {
        KERNEL_LOG("ERROR: register input dev failed");
        goto input_reg_failed;
    }

input注册成功后,会生成input节点,加载驱动时,会有如下打印,则说明input节点注册成功,可以到sys对应的目录下查看具体信息

[  541.566949] input: ap3216c as /devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e/input/input2

 input上报

先读取传感器数据,然后使用 input_report_abs进行上报,注意上报数据后一定要调用input_sync j进行同步

    ap3216c_read_data(p_ap3216c);
    value = &p_ap3216c->value;
    input_report_abs(p_ap3216c->input, ABS_X, value->ir);
    input_report_abs(p_ap3216c->input, ABS_Y, value->als);
    input_report_abs(p_ap3216c->input, ABS_Z, value->ps);

    KERNEL_LOG("ir %d, als %d, ps %d", value->ir, value->als, value->ps);

    input_sync(p_ap3216c->input);

delay work使用

使用delay work实现定时上报

初始化 dealy work,调用如下函数,第二个参数是回调函数,在此回调函数里做具体上报工作。

INIT_DELAYED_WORK(&p_ap3216c->ap_work, work_func);

调用 schedule_delayed_work(&p_ap3216c->ap_work, delay); 开始执行,delay时间到后,回调work_func函数

struct ap3216c_data {
    struct i2c_client *client;
    struct input_dev *input;
    struct delayed_work ap_work; /* delay work用来定时采集传感器数据 */
    int delay;                   /* delay的时间,ms为单位 */
    struct ap3216c_value value;  /* 传感器值 */
    int enable;                  /* 传感器工作状态,1 表示使能,0表示power down */
};


INIT_DELAYED_WORK(&p_ap3216c->ap_work, work_func);
    
    delay = msecs_to_jiffies(p_ap3216c->delay); //延时时间

    ret = schedule_delayed_work(&p_ap3216c->ap_work, delay);

sysfs使用

创建sysfs ,可以直接通过echo,cat形式访问 驱动代码。我们创建两个属性,delay,实现通过sysfs节点读取和设置 上报周期效果。ebable,实现通过sysfs节点读取 传感器工作状态,设置传感器使能,关闭。

通过DEVICE_ATTR 设置属性的访问权限,对应的show,store函数。

定义属性 static struct attribute *ap3216c_attributes[] = {};

定义属性 组 static struct attribute_group ap3216c_attribute_group

static DEVICE_ATTR(delay, S_IRUGO | S_IWUSR | S_IWGRP,
                   ap3216c_delay_show, ap3216c_delay_store);

static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
                   ap3216c_enable_show, ap3216c_enable_store);

static struct attribute *ap3216c_attributes[] = {
    &dev_attr_delay.attr,
    &dev_attr_enable.attr,
    NULL
};

static struct attribute_group ap3216c_attribute_group = {
    .attrs = ap3216c_attributes
};

 定义对应的show,store函数

ssize_t ap3216c_delay_store(struct device *dev, struct attribute *attr, const char *buf, size_t count)
{
    struct ap3216c_data *p_ap3216c;
    int tmp_delay;
    struct i2c_client *client;
    KERNEL_LOG("buf: %s", buf);

    client = to_i2c_client(dev);
    p_ap3216c = i2c_get_clientdata(client);

    kstrtoint(buf, 10, &tmp_delay);
    KERNEL_LOG("tmp_delay %d", tmp_delay);
    if (tmp_delay > 0 && tmp_delay < AP3216C_DELAY_MAC) {
        p_ap3216c->delay = tmp_delay;
    } else {
        KERNEL_LOG("ERROR: the delay value %d is unvalid", tmp_delay);
    }

    return count;
}

static ssize_t ap3216c_enable_show(struct device *dev, struct attribute *attr, char *buf)
{
    struct ap3216c_data *p_ap3216c;
    int count;
    struct i2c_client *client;
    KERNEL_LOG("enter");

    client = to_i2c_client(dev);
    p_ap3216c = i2c_get_clientdata(client);

    snprintf(buf, 8, "%d\n", p_ap3216c->enable);
    count = strlen(buf);
    return count;
    return 0;
}

ssize_t ap3216c_enable_store(struct device *dev, struct attribute *attr, const char *buf, size_t count)
{
    struct ap3216c_data *p_ap3216c;
    int tmp_enable;
    struct i2c_client *client;
    KERNEL_LOG("buf: %s", buf);

    client = to_i2c_client(dev);
    p_ap3216c = i2c_get_clientdata(client);

    kstrtoint(buf, 10, &tmp_enable);
    KERNEL_LOG("tmp_enable %d", tmp_enable);
    if (tmp_enable == 0) {
        p_ap3216c->enable = 0;
        ap3216c_disable(p_ap3216c);
    } else {
        p_ap3216c->enable = 1;
        ap3216c_enable(p_ap3216c);
    }

    return count;
}
static DEVICE_ATTR(delay, S_IRUGO | S_IWUSR | S_IWGRP,
                   ap3216c_delay_show, ap3216c_delay_store);

static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
                   ap3216c_enable_show, ap3216c_enable_store);

static struct attribute *ap3216c_attributes[] = {
    &dev_attr_delay.attr,
    &dev_attr_enable.attr,
    NULL
};

static struct attribute_group ap3216c_attribute_group = {
    .attrs = ap3216c_attributes
};

在probe函数中注册sysfs group

ret = sysfs_create_group(&p_ap3216c->client->dev.kobj, &ap3216c_attribute_group);

通过如下方法操作sysfs节点

 

Logo

更多推荐