linux温湿度传感器SHTC3驱动开发

开发环境为Ubuntu16.04LTS+vscode,使用了正点原子IMX6ULL开发板,板子上运行的是正点原子的出厂系统
参考了这篇文章(温湿度传感器SHTC3驱动开发(一)小白也能轻松理解_启希的博客-CSDN博客_shtc3

SHTC3简介

SHTC3是一款I2C温湿度传感器,测量的精度为湿度:±2%,温度±0.2℃
一些详细的参数可以在数据手册里找到,这里列几个重要的
设备地址为0x70
在这里插入图片描述

一个测量周期包概括五个步骤:
1、唤醒SHTC3:先发送写入指令(0xE0),再发送唤醒指令高位(0x35),再发送唤醒指令低位(0x17)。
2、等待唤醒:数据手册上写的最大唤醒时间是240us,等待的时间大于这个就行了。
3、发送采集指令:先发送写入指令(0xE0),再发送采集指令的高位和低位。采集指令有多个,根据需要自行选择,见上文。
4、接收数据:发送读取指令(0xE1),连续接收6个字节数据。如果采集的指令是先存温度,那么这6个字节的第1-2个字节就是温度数值,第3个字节是温度校验。第4-5个字节是湿度数值,第6个字节是湿度校验。如果采集的指令是先存湿度,则前3个字节和后3个字节相反。
5、进入睡眠:发送写入指令,再发送睡眠指令进入睡眠。
在这里插入图片描述

不同的采集指令对应不同的采集模式:
在这里插入图片描述
温湿度计算公式:
在这里插入图片描述

驱动程序、测试程序编写

修改设备树

芯片与SHTC3通过I2C交流,首先需要在设备树里添加设备节点
在i2c1节点下添加设备:
在这里插入图片描述
编译设备树,并拷贝到nfs目录或tftp目录:
make dtbs
cp imx6ull-14x14-nand-4.3-800x480-c.dtb imx6ull-14x14-nand-4.3-480x272-c.dtb imx6ull-14x14-nand-7-800x480-c.dtb imx6ull-14x14-nand-7-1024x600-c.dtb imx6ull-14x14-nand-hdmi.dtb imx6ull-14x14-nand-vga.dtb /home/zjz/linux/nfs/ -f
在uboot通过网络测试dtb文件:
tftp 83000000 imx6ull-alientek-nand.dtb
或nfs 83000000 192.168.1.123:/home/zjz/linux/nfs/imx6ull-alientek-nand.dtb (实际使用时出现卡死,下载不动,但是指令应该是没问题的)
更新设备树查看i2c设备:
在这里插入图片描述

驱动程序

linux I2C驱动框架分为两部分,I2C总线驱动和I2C设备驱动。总线部分驱动由厂商提供,设备驱动部分由用户自行编写。
驱动程序框架搭建可以参考文章开头提到的博客,驱动源码放下面:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "shtc3reg.h"
#include <linux/semaphore.h>

#define SHTC3_CNT       1
#define SHTC3_NAME      "shtc3"

struct shtc3_dev{
    dev_t devid;
    struct cdev cdev;
    int major;
    int minor;
    struct class* class;
    struct device* device;

    void * private_data;
    unsigned short T,RH;/*温湿度数据*/

    //设置互斥量,同一时间只能有一个应用在读取温湿度
    struct semaphore sem;
};

struct shtc3_dev shtc3dev;

int shtc3_open(struct inode *inode,struct file *filp)
{
    filp->private_data = &shtc3dev;

    /*上锁*/
    down(&shtc3dev.sem);

    return 0;
}

static ssize_t shtc3_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
    unsigned char data[SHTC1_RESPONSE_LENGTH];
    short real_data[2];
    int ret = 0;
    int val;
    //获取私有数据
    struct shtc3_dev* dev = (struct shtc3_dev *)filp->private_data;

    /*向用户返回SHTC3的原始数据*/
    //发送唤醒指令,等待唤醒成功
    ret = i2c_master_send(dev->private_data, shtc1_cmd_wakeup, SHTC1_CMD_LENGTH);
	if (ret != SHTC1_CMD_LENGTH) {
		printk( "failed to send wakeup command: %d\n", ret);
		return ret < 0 ? ret : -EIO;
	}
    mdelay(1);//延时1ms等待唤醒,大于240us即可

    //发送读取数据指令,选择读取模式,这里的是0x7ca2,长度是2字节
	ret = i2c_master_send(dev->private_data, shtc1_cmd_normalmode_rec_clockstr_enable_Tfirst, 
        SHTC1_CMD_LENGTH);//模式为normal,clock stretching enable,先读取温度数据,后读取湿度数据
	if (ret != SHTC1_CMD_LENGTH) {
		printk("failed to send receive command: %d\n", ret);
		return ret < 0 ? ret : -EIO;
	}

    mdelay(1);//延时1ms,等待数据获取

    //接收数据,保存在data中
	ret = i2c_master_recv(dev->private_data, data, SHTC1_RESPONSE_LENGTH);
	if (ret != SHTC1_RESPONSE_LENGTH) {
		printk("failed to read values: %d\n", ret);
		return ret < 0 ? ret : -EIO;
	}

    /*
	 * From datasheet:
	 * T = -45 + 175 * ST / 2^16
	 * RH = 100 * SRH / 2^16
	 *
	 * Adapted for integer fixed point (3 digit) arithmetic.
	 */
	 //这里温度的数据处理要特别注意,因为Linux内核不支持浮点运算,
	 //所以想要多保留小数点后几位需要特别处理
	 //我们保留小数点后三位,所以放大1000倍,正常来说,根据公式
	 //会这样写((175000 * val) >> 16) - 45000;
	 //但是会有问题,val是一个5位数得值,所以175000*val超过了int数据类型范围了
	 //所以这里直接把175000 >> 3后,再乘以val,然后再 >> 13,减去45000
	 //就得到这个了((21875 * val) >> 13) - 45000
     //湿度保留小数点后两位
	 //先把10000 >> 3后,再做处理
    val = be16_to_cpup((__be16 *)data);
	dev->T = ((21875 * val) >> 13) - 45000;
	val = be16_to_cpup((__be16 *)(data + 3));
	dev->RH = ((1250 * val) >> 13);

    real_data[0] = dev->T;
    real_data[1] = dev->RH;

    ret = -1;
    ret = copy_to_user(buf,real_data,sizeof(real_data));
    if(ret < 0){
        printk("copy to user fail\r\n");
    }

    //printk("kernel message T=%d RH=%d\r\n",dev->T,dev->RH);

    //发送睡眠指令
    ret = i2c_master_send(dev->private_data, shtc1_cmd_sleep, SHTC1_CMD_LENGTH);
	if (ret != SHTC1_CMD_LENGTH) {
		printk("failed to send sleep command: %d\n", ret);
		return ret < 0 ? ret : -EIO;
	}

    return ret;
}

int shtc3_release (struct inode *inode, struct file *filp)
{
    int ret = 0;
    //获取私有数据
    struct shtc3_dev* dev = (struct shtc3_dev *)filp->private_data;
    
    //发送睡眠指令
    ret = i2c_master_send(dev->private_data, shtc1_cmd_sleep, SHTC1_CMD_LENGTH);
	if (ret != SHTC1_CMD_LENGTH) {
		printk("failed to send sleep command: %d\n", ret);
		return ret < 0 ? ret : -EIO;
	}

    /*释放锁*/
    up(&shtc3dev.sem);

    return ret;
}

struct file_operations shtc3_fops={
    .owner = THIS_MODULE,
    .open = shtc3_open,
    .read = shtc3_read,
    .release = shtc3_release,
};

static int shtc3_probe(struct i2c_client *client,
		       const struct i2c_device_id *id)
{
    int ret;
    struct device *dev = &client->dev;
    char id_reg[2];

    sema_init(&shtc3dev.sem,1);

    /*搭建字符设备驱动框架*/
    shtc3dev.major = 0;
    if(shtc3dev.major){//给定设备号
        shtc3dev.devid = MKDEV(shtc3dev.major,0);
        ret = register_chrdev_region(shtc3dev.devid,SHTC3_CNT,SHTC3_NAME);
    }else{//未给定设备号
        ret = alloc_chrdev_region(&shtc3dev.devid,0,SHTC3_CNT,SHTC3_NAME);
        shtc3dev.major = MAJOR(shtc3dev.devid);
        shtc3dev.minor = MINOR(shtc3dev.devid);
    }

    if(ret < 0){
        printk("shtc3dev module fail devid\r\n");
        goto fail_devid;
    }else{
        printk("major:%d minor:%d\r\n",shtc3dev.major,shtc3dev.minor);
    }

    /*注册字符设备*/
    shtc3dev.cdev.owner = THIS_MODULE;
    cdev_init(&shtc3dev.cdev,&shtc3_fops);
    ret = cdev_add(&shtc3dev.cdev,shtc3dev.devid,SHTC3_CNT);

    if(ret < 0){
        printk("ap3216c module fail cdev\r\n");
        goto fail_cdev;
    }

    /*自动添加设备节点*/
    //初始化类
    shtc3dev.class = class_create(THIS_MODULE,SHTC3_NAME);
    if(IS_ERR(shtc3dev.class)){
        ret = PTR_ERR(shtc3dev.class);
        printk("key module failed class\r\n");
        goto fail_class;
    }

    //初始化设备
    shtc3dev.device = device_create(shtc3dev.class,NULL,shtc3dev.devid,NULL,SHTC3_NAME);
    if(IS_ERR(shtc3dev.device)){
        ret = PTR_ERR(shtc3dev.device);
        printk("key module failed device\r\n");
        goto fail_device;
    }

    //将从机地址传入shtc3dev
    shtc3dev.private_data = client;
    
    //shtc3自检
    //唤醒
    ret = i2c_master_send(client, shtc1_cmd_wakeup, SHTC1_CMD_LENGTH);
	if (ret != SHTC1_CMD_LENGTH) {
		dev_err(dev, "could not send wakeup command: %d\n", ret);
		return ret < 0 ? ret : -ENODEV;
	}
    mdelay(1);//延时1ms,等待延时完成
    /* 由数据手册可知,有一个efc8寄存器用于验证传感器是否能正常沟通,所以我们这里读取该寄存器 */
	ret = i2c_master_send(client, shtc1_cmd_read_id_reg, SHTC1_CMD_LENGTH);
	if (ret != SHTC1_CMD_LENGTH) {
		dev_err(dev, "could not send read_id_reg command: %d\n", ret);
		return ret < 0 ? ret : -ENODEV;
	}
	/* 读取完,再接收数据,进行验证 */
	ret = i2c_master_recv(client, id_reg, sizeof(id_reg));
	if (ret != sizeof(id_reg)) {
		dev_err(dev, "could not read ID register: %d\n", ret);
		return -ENODEV;
	}
	if ((id_reg[1] & SHTC1_ID_REG_MASK) != SHTC1_ID) {
		dev_err(dev, "ID register doesn't match\n");
		return -ENODEV;
	}

    printk("shtc3 check ID success\r\n");

    printk("shtc3_probe\r\n");
    return 0;

fail_device:
    device_destroy(shtc3dev.class,shtc3dev.devid);        
fail_class:
    class_destroy(shtc3dev.class);
fail_cdev:
    cdev_del(&shtc3dev.cdev);
fail_devid:
    unregister_chrdev_region(shtc3dev.devid,SHTC3_CNT);
    return ret;
}

static int shtc3_remove(struct i2c_client* client)
{
    //发送睡眠指令
    int ret = i2c_master_send(shtc3dev.private_data, shtc1_cmd_sleep, SHTC1_CMD_LENGTH);
	if (ret != SHTC1_CMD_LENGTH) {
		printk("failed to send sleep command: %d\n", ret);
		return ret < 0 ? ret : -EIO;
	}

   //注销cdev
    cdev_del(&shtc3dev.cdev);
    //注销设备号
    unregister_chrdev_region(shtc3dev.devid,SHTC3_CNT);
    //摧毁设备
    device_destroy(shtc3dev.class,shtc3dev.devid);
    //摧毁类
    class_destroy(shtc3dev.class);

    printk("shtc3 remove\r\n");
    return 0;
}

//传统匹配方法
static const struct i2c_device_id shtc3_id[]={
    {"shtc3",0},
    { }
};

//设备数匹配方法
static struct of_device_id shtc3_dt_id[]={
    {.compatible = "fzu,shtc3"},
    { }
};

static struct i2c_driver shtc3_i2c_driver={
    .probe = shtc3_probe,
    .remove = shtc3_remove,
    .id_table = shtc3_id,
    .driver = {
        .name = "shtc3",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(shtc3_dt_id),
    },
};

static int __init shtc3_init(void)
{
    printk("i2c module init\r\n");
    return i2c_add_driver(&shtc3_i2c_driver);
}

static void __exit shtc3_exit(void)
{
    i2c_del_driver(&shtc3_i2c_driver);
    printk("i2c module exit\r\n");
}


module_init(shtc3_init);
module_exit(shtc3_exit);

测试程序

编写一个读取温湿度的测试程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>

/*
*argc:应用程序参数个数
*argv[]:参数内容字符串
*./shtc3 /dev/shtc3 读取数据
*/
int main(int argc,char *argv[])
{
    int fd;
    unsigned short data[2];
    float T,RH;
    int ret = -1;

    if(argc!=2){
        printf("usage error\r\n");
        return -1;
    }

    char *filename;

    filename = argv[1];

    fd = open(filename,O_RDONLY);
    if(fd < 0){
        printf("open file %s failed\r\n",filename);
        return -1;
    }

    while(1){
        ret = read(fd,data,sizeof(data));
        //ret = fread(data, sizeof(unsigned short), 2, (FILE*)fdopen(fd, "r"));
        printf("reading shtc3:%d\r\n",ret);
        printf("sizeof data:%d\r\n",sizeof(data));
       if(ret >= 0){        
           T = (float)data[0]/1000 ;
           RH = (float)data[1]/1000;
            printf("T:%.2f RH:%.2f \r\n",T,RH);
       }
        usleep(500000);
    }

    close(fd);

    return ret;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值