当前位置: 首页 > article >正文

三、I2C客户端驱动 —— htu21d

前言

和bmp280一样,使用i2c驱动注册。需要注意的是,设备reset需要15ms,可以使用读出来的第三个字节作为CRC8校验码,进行数据校验。另外,温湿度计算公式如果使用小数,可能编译器默认的参数不支持,可以将小数转化为整数进行计算。

另外,htu21d的i2c支持两种模式,hold master模式支持SCL stretching,更加方便我们主机端读取数据,设备会在数据ADC采集完成之前,控制SCL,不让master继续后续操作,再数据完成采集后,再释放SCL,master才进行后续读取操作。另外一个模式,需要我们自己去轮询读取,指导读取到有ACK的字节,才算htu21d数据准备完毕。但是hold master有一个比较严重的问题,如果SCL stretching期间,htu21d掉电的话,master i2c bus会被占用,导致后续都没办法再继续操作了。

CRC-8

需要确定CRC使用的多项式,多项式的计算公式是依据每一项的指数排序,存在指数的项依次用1填充一个字节的一位,没有的项用0填充。比如下面的多项式:

x^8 + x^5 + x^4 + 1

那么x8 应该填充第8位,x5 应该填充第5位,依次类推,最后的+1,应该是 x0 ,最终得到的数是 100110001 ,这时得到一个9位数的值,因为只需要一个字节,因此去掉第8位,得到 00110001 (0x31)。

crc值会有一个默认初始值,htu21d要求的crc初始值是0。将crc值依次与数据的每个字节异或得到新的crc。在异或了一个字节得到新crc后,需要将当时的crc值向左移位,当最高位为1时,则将当前移位后的值与上述计算的多项式得到的值进行异或后赋给crc,如果最高位不为1,则将移位后的值赋给crc。一个字节8位都移位完成后,crc又与下一个字节异或,继续上面的操作,直到最后一个字节完成,就得到了最终的crc值。

/* x^8 + x^5 + x^4 + 1: 100110001 (00110001 -> 0x31) */
static u8 crc8_calcu(u8 *data, size_t len)
{
    /* init value for htu21d: 0x0 */
    u8 crc = 0x0;
    int i, j;

    for (i = 0; i < len; i++) {
        crc ^= data[i];
        for (j = 0; j < 8; j++) {
            crc = (crc & 0x80) ? ((crc << 1) ^ 0x31) : (crc << 1);
        }
    }
    return crc;
}

设备属性

在提供温湿度接口时,我们这里使用最简单的sysfs接口提供温湿度属性值,或者也可以使用字符设备驱动来完成。。一般温湿度传感器是使用iio子系统或者input子系统来提供用户接口。后续做到相应章节再实现iio和input子系统方式的接口。

总线,设备,驱动,类等内核对象都是拥有属性接口的。可以直接属性定义的宏,定义内核某个对象的属性借口,实现属性的读写功能。读属性为show,写属性为store。设备的属性接口使用以下宏进行定义:

#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

static DEVICE_ATTR(temperature, 0644, bmp280_temperature_show,
                    bmp280_temperature_store);

定义属性的名字,sysfs文件节点的权限,读写方法。当属性只读或者只写时,建议不要使用这个接口,可能回到只系统报oops。比如我不支持写,那么我给store传参会是NULL。insmod的时候回报一堆错误警告及堆栈信息出来。
定义完成后,使用 device_create_file 函数创建文件节点。

只读或者只写使用以下宏:

#define DEVICE_ATTR_RO(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
	struct device_attribute dev_attr_##_name = __ATTR_WO(_name)

在用户空间,将生成 temperature 和 humidity 两个属性节点,权限只读。可以使用cat读取设备节点,将分别读取到传感器的温度数据和湿度数据。如果支持写权限的话,可以使用echo命令进行写入。这里两个节点是以字符串的格式进行输出的,如果属性节点是输出二进制数据的,可以使用xxd命令进行读取。

设备树配置

&i2c4 {
    status = "okay";

    htu21d: htu21d@40 {
        status = "okay";
        compatible = "se,htu21d";
        reg = <0x40>;
    };
};

这里使用驱动和设备的匹配方式使用设备树风格的方式,会和驱动中 of_device_id 列表中的 compatible 字段进行匹配。可以尝试修改设备树中的compatible属性,设备树风格匹配将失败,继而使用 i2c_device_id 方式进行匹配,这个时候 i2c_device_id 匹配将匹配设备节点的名称,即 "htu21d"

驱动代码

#include "asm-generic/errno-base.h"
#include "linux/device.h"
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>

struct htu21d_dev {
    struct i2c_client *client;

    s32 temperature;
    u32 humidity;
};

/* x^8 + x^5 + x^4 + 1: 100110001 (00110001 -> 0x31) */
static u8 crc8_calcu(u8 *data, size_t len)
{
    /* init value for htu21d: 0x0 */
    u8 crc = 0x0;
    int i, j;

    for (i = 0; i < len; i++) {
        crc ^= data[i];
        for (j = 0; j < 8; j++) {
            crc = (crc & 0x80) ? ((crc << 1) ^ 0x31) : (crc << 1);
        }
    }
    return crc;
}

static int htu21d_i2c_read_bytes(struct i2c_client *client, u16 chip, u8 reg,
                                u8 *data, u8 size)
{
    struct i2c_msg msg[] = {
        {
            .flags = 0,
            .addr = client->addr,
            .buf = &reg,
            .len = 1,
        },
        {
            .flags = I2C_M_RD | I2C_M_STOP,
            .addr = client->addr,
            .buf = data,
            .len = size,
        },
    };

    if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) != ARRAY_SIZE(msg)) {
        dev_err(&client->dev, "i2c read error, chip=0x%x, reg=0x%x\n",
                client->addr, reg);
        return -EREMOTEIO;
    }

    return 0;
}

static int htu21d_reset(struct htu21d_dev *htu21d)
{
    struct i2c_client *client = htu21d->client;
    u8 val = 0xFE;
    struct i2c_msg msg[] = {
        {
            .flags = I2C_M_STOP,
            .addr = client->addr,
            .buf = &val,
            .len = 1,
        },
    };
    int ret;

    ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
    if (ret != ARRAY_SIZE(msg)) {
        dev_err(&client->dev, "i2c write reg error, chip=0x%02x, reg=0x%02x, ret=%d\n",
                client->addr, val, ret);
        return -EREMOTEIO;
    }

    /* soft reset take at least 15ms */
    msleep(15);

    return 0;
}

static int htu21d_config(struct htu21d_dev *htu21d)
{
    struct i2c_client *client = htu21d->client;
    u8 data[] = {0xE6, 0x01};
    struct i2c_msg msg[] = {
        {
            .flags = I2C_M_STOP,
            .addr = client->addr,
            .buf = (unsigned char *)&data,
            .len = ARRAY_SIZE(data),
        },
    };
    int ret = 0;

    ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
    if (ret != ARRAY_SIZE(msg)) {
        dev_err(&client->dev, "i2c write reg error, chip=0x%02x, reg=0x%02x, ret=%d\n",
                client->addr, data[0], ret);
        return -EREMOTEIO;
    }

    return 0;
}

/* actual value T = temp / 100 */
static int htu21d_read_temperature(struct htu21d_dev *htu21d, s32 *temp)
{
    u8 data[3] = {0};
    struct i2c_client *client = htu21d->client;
    int ret = 0;
    u32 s_temp;
    u8 crc;

    *temp = 0;

    /* hold master */
    ret = htu21d_i2c_read_bytes(client, client->addr, 0xE3, data, ARRAY_SIZE(data));
    if (ret) {
        return ret;
    }

    crc = crc8_calcu(data, 2);
    if (crc != data[2]) {
        dev_err(&client->dev, "temp data crc error\n");
        return -EINVAL;
    }

    s_temp = (data[0] << 8) | data[1];
    *temp = -4685 + 17572 * s_temp / (1 << 16);

    return 0;
}

/* actual value H = hum / 100 */
static int htu21d_read_humidity(struct htu21d_dev *htu21d, u32 *hum)
{
    u8 data[3] = {0};
    struct i2c_client *client = htu21d->client;
    int ret = 0;
    u32 s_rh;
    u8 crc;

    *hum = 0;

    /* hold master */
    ret = htu21d_i2c_read_bytes(client, client->addr, 0xE5, data, ARRAY_SIZE(data));
    if (ret) {
        return ret;
    }

    crc = crc8_calcu(data, 2);
    if (crc != data[2]) {
        dev_err(&client->dev, "humi data crc error\n");
        return -EINVAL;
    }

    s_rh = (data[0] << 8)| data[1];
    *hum = -600 + 12500 * s_rh / (1 << 16);

    return 0;
}

static ssize_t temperature_show(struct device *dev, struct device_attribute *attr,
                                char *buf)
{
    struct i2c_client *client = container_of(dev, struct i2c_client, dev);
    struct htu21d_dev *htu21d = i2c_get_clientdata(client);

    htu21d_read_temperature(htu21d, &htu21d->temperature);
    return sprintf(buf, "%d\n", htu21d->temperature);
}
static DEVICE_ATTR_RO(temperature);

static ssize_t humidity_show(struct device *dev, struct device_attribute *attr,
                                char *buf)
{
    struct i2c_client *client = container_of(dev, struct i2c_client, dev);
    struct htu21d_dev *htu21d = i2c_get_clientdata(client);

    htu21d_read_humidity(htu21d, &htu21d->humidity);
    return sprintf(buf, "%u\n", htu21d->humidity);
}
static DEVICE_ATTR_RO(humidity);


static int htu21d_init(struct htu21d_dev *htu21d)
{
    int ret = 0;

    ret = htu21d_reset(htu21d);
    if (ret) {
        return ret;
    }

    ret = htu21d_config(htu21d);
    if (ret) {
        return ret;
    }

    return 0;
}

static int htu21d_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct htu21d_dev *htu21d;
    int ret = 0;

    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        dev_err(&client->dev, "i2c_check_functionality not support I2C_FUNC_I2C\n");
        return -EIO;
    }

    htu21d = devm_kzalloc(&client->dev, sizeof(*htu21d), GFP_KERNEL);
    if (IS_ERR(htu21d)) {
        dev_err(&client->dev, "kzalloc error: %ld\n", -PTR_ERR(htu21d));
        return PTR_ERR(htu21d);
    }

    i2c_set_clientdata(client, htu21d);
    htu21d->client = client;

    ret = htu21d_init(htu21d);
    if (ret) {
        dev_err(&client->dev, "htu21d init error: %d\n", -ret);
        return ret;
    }

    device_create_file(&client->dev, &dev_attr_temperature);
    device_create_file(&client->dev, &dev_attr_humidity);

    dev_info(&client->dev, "htu21d probe success\n");
    return 0;
}

static int htu21d_remove(struct i2c_client *client)
{
    i2c_set_clientdata(client, NULL);
    device_remove_file(&client->dev, &dev_attr_temperature);
    device_remove_file(&client->dev, &dev_attr_humidity);

    dev_info(&client->dev, "htu21d remove\n");
    return 0;
}

static struct i2c_device_id htu21d_id_table[] = {
    {"htu21d", 0},
    { },
};
MODULE_DEVICE_TABLE(i2c, htu21d_id_table);

static struct of_device_id htu21d_of_match[] = {
    {.compatible = "se,htu21d"},
    { },
};
MODULE_DEVICE_TABLE(of, htu21d_of_match);

static struct i2c_driver htu21d_driver = {
    .probe = htu21d_probe,
    .remove = htu21d_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "htu21d_drv",
        .of_match_table = of_match_ptr(htu21d_of_match),
    },
    .id_table = htu21d_id_table,
};

module_i2c_driver(htu21d_driver);

MODULE_AUTHOR("duapple <duapple2@gmail.com>");
MODULE_LICENSE("GPL");

http://www.kler.cn/a/513061.html

相关文章:

  • 51c大模型~合集105
  • 如何使用CRM数据分析优化销售和客户关系?
  • 2025年PHP面试宝典,技术总结。
  • C++《AVL树》
  • PyTorch使用教程(13)-一文搞定模型的可视化和训练过程监控
  • 单片机基础模块学习——定时器
  • uboot剖析之命令行延时
  • C++ 学习:深入理解 Linux 系统中的冯诺依曼架构
  • python爬虫入门(实践)
  • 基于Springboot+Redis秒杀系统 demo
  • 【2024年华为OD机试】 (JavaScriptJava PythonC/C++)
  • 网络安全态势感知技术综述
  • Apache Hive 聚合函数与 OVER 窗口函数:从基础到高级应用
  • Oracle审计
  • SecureUtil.aes数据加密工具类
  • 通义万相:阿里巴巴 AI 生成式多模态平台解析与实战应用
  • 细说STM32F407单片机电源低功耗StandbyMode待机模式及应用示例
  • AI编程工具使用技巧:在Visual Studio Code中高效利用阿里云通义灵码
  • 如何提升IP地址查询数据服务的安全?
  • controlnet 多 condition 融合
  • 网安篇(一)日志分析——从给的登录日志中找出攻击IP和使用的用户名
  • 数据结构学习记录-树和二叉树
  • 堆的实现(C语言详解版)
  • yolo系列模型为什么坚持使用CNN网络?
  • LeetCode:37. 解数独
  • [Easy] leetcode-500 键盘行