imx6ull dht11驱动

dht11采用单总线通信协议,有vcc,gnd,sda三根引脚。湿度的精度是1,温度精度可以达到小数位。dht11驱动主要涉及到单总线协议,驱动框架采用platform + misc杂项设备驱动。

1、设备树编写

查看手册:

在这里插入图片描述

要求我们使用的gpio必须接有上拉电阻,使得空闲状态的时候是高电平。我们选用gpio1_io2引脚作为sda

  • 在设备树的iomux节点下添加:
/*车载终端 dht11*/
		pinctrl_dht11: dht11grp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO02__GPIO1_IO02           0x000010B0
			>;
		};
  • 添加dht11结点
/*车载终端 dht11*/
	dht11 {
		compatible = "wuyiqiang,dht11";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_dht11>;
		dht11-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;		//单总线待机状态是高电平
		status = "okay";
	};

注意需要检查一下使用的gpio是否被占用

2、dht11复位

在这里插入图片描述

  • 查看手册的:初始化流程

单总线协议需要保证在空闲状态时gpio输出高电平。

①、主机发送起始信号(Tbe + Tgo):主机先将gpio设置为输出模式,默认高电平,拉低20ms,再拉高13us,将gpio设置为输入模式。

②、从机响应(Trel + Treh):从机(dht11)将总线拉低83us之后再拉高87us代表接收到主机的请求。我们需要再驱动程序里判断从机是否响应。

③、从机开始发送数据:单总线协议利用一个波形周期内高电平的占空比不同来区分传输 0 还是 1。

申请引脚,编写功能函数:

//申请gpio
    dht11.gpios = of_get_named_gpio(pdev->dev.of_node, "dht11-gpios", 0);
    if (!gpio_is_valid(dht11.gpios))
    {
        ret = -EINVAL;
        printk("获取gpio失败\n");
        goto failed;
    }
    ret = devm_gpio_request_one(&pdev->dev, dht11.gpios, GPIOF_OUT_INIT_HIGH, "dht11-gpio");//申请gpio并设置输出高,devm会自动释放资源
    if (ret)
    {
        printk("申请gpio失败\n");
        ret = -EINVAL;
        goto failed;
    }

static void dht11_set_output(struct dht11_dev* dev, int val)
{
    if (val)
        gpio_direction_output(dev->gpios, 1);
    else
        gpio_direction_output(dev->gpios, 0);
}

static void dht11_set_input(struct dht11_dev* dev)
{
    gpio_direction_input(dev->gpios);
}

static unsigned char dht11_get_input(struct dht11_dev* dev)
{
    int ret = 0;
    ret = gpio_get_value(dev->gpios);
    return ret;
}

dht11初始化:

//dht11初始化
//成功返回0,失败负数
static int dht11_dev_init(struct dht11_dev* dev)
{
    unsigned char times;
    dht11_set_output(dev, 1);
    udelay(5);
    dht11_set_output(dev, 0);        //主机拉低20ms
    mdelay(20);

    dht11_set_output(dev, 1);        //主机释放13us
    udelay(13);

    dht11_set_input(dev);          //设置输入等待从机响应

    //从机响应低电平时间78~88us
    times = 0;
    while (dht11_get_input(dev) != 0)
    {
        udelay(1);
        times++;
        if (times >= 100)
        {
            printk("dht11 响应超时 -1 \n");
            return -1;
        }
    }
    //从机响应高电平时间80~92us
    times = 0;
    while (dht11_get_input(dev) == 0)
    {
        udelay(1);
        times++;
        if (times >= 100)
        {
            printk("dht11 响应超时 -2 \n");
            return -2;
        }
    }
    times = 0;
    while (dht11_get_input(dev) == 1)
    {
        udelay(1);
        times++;
        if (times >= 100)
        {
            printk("dht11 未开始传输数据 -3\n");
            return -3;
        }
    }
    return 0;
}

最好那里判断从机是否开始传输数据,如果开始传输数据那么一定会有低电平,如果一直是高那么代表从机输出出错了。

3、dht11 读取数据

3、1读取一个字节

在这里插入图片描述

  • 查看手册:dht11的数据由于40bit构成,最后8bit是校验字节,我们可以在读取完数据之后用来校验。、

在这里插入图片描述

可以看到单总线协议通过区分高电平的时间来区分数据

/*dht11 读取一个字节*/
/*成功0 失败 1*/
static unsigned char dht11_read_byte(struct dht11_dev* dev)
{
    unsigned char i, times = 0, data = 0;
    for (i = 0; i < 8; i++)
    {
        times = 0;
        while (dht11_get_input(dev) == 0)      //判断数据传输是否出错,没错的一定会拉高
        {
            times++;
            udelay(1);
            if (times > 100)
                return 1;
        }
        udelay(45);     //拉高时间 23~27:0, 68~74:1
        if (dht11_get_input(dev) == 1)
        {
            data = data | 0x01 << (7 - i);      //该bit为1
            times = 0;
            while (dht11_get_input(dev) == 1)      //判断数据传输是否出错,没错的一定会拉低
            {
                times++;
                udelay(1);
                if (times > 100)
                    return 1;
            }
        }

    }
    return data;
}

3、2读取数据

在这里插入图片描述

可以看到我们每次读取完40bit的数据之后都需要调用一下 dht11_init 来发送起始信号唤醒dht11才能再次读取数据。并且读取一次之后需要间隔 2s 才能再次读取。

static int dht11_read_data(struct dht11_dev* dev, unsigned char* data)
{
    int ret, retry = 3;
    mutex_lock(&dev->lock);
    while (retry--)
    {
        ret = dht11_dev_init(dev);
        if(ret)
            continue;
        data[0] = dht11_read_byte(dev);
        data[1] = dht11_read_byte(dev);
        data[2] = dht11_read_byte(dev);
        data[3] = dht11_read_byte(dev);
        data[4] = dht11_read_byte(dev);

        //校验
        if (data[4] != data[0] + data[1] + data[2] + data[3])
        {
            continue;
        } else {
            mutex_unlock(&dev->lock);
            return 0;
        }
    }
    mutex_unlock(&dev->lock);
    return ret;
}

驱动框架完善

下面就是platform + misc字符驱动框架的编写了。本驱动可以采用3中方式来获取温湿度。分别是 通过cat /sys/class/misc/dht11/value,系统调用read,和ioctl。

完整程序如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#define DHT11_NAME      "dht11"
#define DHT11_MINOR       1

#define DHT11_GET_VALUE     _IOR(0xEF, 0, unsigned char[5])
struct dht11_dev {
    struct miscdevice mdev;
    int gpios;
    struct mutex lock;
};

static struct dht11_dev dht11;

static void dht11_set_output(struct dht11_dev* dev, int val)
{
    if (val)
        gpio_direction_output(dev->gpios, 1);
    else
        gpio_direction_output(dev->gpios, 0);
}

static void dht11_set_input(struct dht11_dev* dev)
{
    gpio_direction_input(dev->gpios);
}

static unsigned char dht11_get_input(struct dht11_dev* dev)
{
    int ret = 0;
    ret = gpio_get_value(dev->gpios);
    return ret;
}

//dht11初始化
//成功返回0,失败负数
static int dht11_dev_init(struct dht11_dev* dev)
{
    unsigned char times;
    dht11_set_output(dev, 1);
    udelay(5);
    dht11_set_output(dev, 0);        //主机拉低20ms
    mdelay(20);

    dht11_set_output(dev, 1);        //主机释放13us
    udelay(13);

    dht11_set_input(dev);          //设置输入等待从机传数据

    //从机响应低电平时间78~88us
    times = 0;
    while (dht11_get_input(dev) != 0)
    {
        udelay(1);
        times++;
        if (times >= 100)
        {
            printk("dht11 等待超时 -1 \n");
            return -1;
        }
    }
    //从机响应高电平时间80~92us
    times = 0;
    while (dht11_get_input(dev) == 0)
    {
        udelay(1);
        times++;
        if (times >= 100)
        {
            printk("dht11 等待超时 -2 \n");
            return -2;
        }
    }
    times = 0;
    while (dht11_get_input(dev) == 1)
    {
        udelay(1);
        times++;
        if (times >= 100)
        {
            printk("dht11 未开始传输数据 -3\n");
            return -3;
        }
    }
    return 0;
}
/*dht11 读取一个字节*/
/*成功0 失败 1*/
static unsigned char dht11_read_byte(struct dht11_dev* dev)
{
    unsigned char i, times = 0, data = 0;
    for (i = 0; i < 8; i++)
    {
        times = 0;
        while (dht11_get_input(dev) == 0)      //判断数据传输是否出错,没错的一定会拉高
        {
            times++;
            udelay(1);
            if (times > 100)
                return 1;
        }
        udelay(45);     //拉高时间 23~27:0, 68~74:1
        if (dht11_get_input(dev) == 1)
        {
            data = data | 0x01 << (7 - i);      //该bit为1
            times = 0;
            while (dht11_get_input(dev) == 1)      //判断数据传输是否出错,没错的一定会拉低
            {
                times++;
                udelay(1);
                if (times > 100)
                    return 1;
            }
        }

    }
    return data;
}

static int dht11_read_data(struct dht11_dev* dev, unsigned char* data)
{
    int ret, retry = 3;
    mutex_lock(&dev->lock);
    while (retry--)
    {
        ret = dht11_dev_init(dev);
        if(ret)
            continue;
        data[0] = dht11_read_byte(dev);
        data[1] = dht11_read_byte(dev);
        data[2] = dht11_read_byte(dev);
        data[3] = dht11_read_byte(dev);
        data[4] = dht11_read_byte(dev);

        //校验
        if (data[4] != data[0] + data[1] + data[2] + data[3])
        {
            continue;
        } else {
            mutex_unlock(&dev->lock);
            return 0;
        }
    }
    mutex_unlock(&dev->lock);
    return ret;
}

static int dht11_open(struct inode* inode, struct file* filp)
{
    filp->private_data = &dht11;
    return 0;
}

static ssize_t dht11_read(struct file* filp, char* __user buf, size_t cnt, loff_t* offt)
{
    int ret = 0;
    unsigned char data[5];
    struct dht11_dev* dev = (struct dht11_dev*)filp->private_data;

    ret = dht11_read_data(dev, data);
    if (ret)
        return ret;
    ret = copy_to_user(buf, data, sizeof(data));       //返回未拷贝的字节数
    if (ret)
        return -EFAULT;
    return 5;
}

static int dht11_release(struct inode* inode, struct file* filp)
{
    return 0;
}
static long dht11_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
    struct dht11_dev* dev = (struct dht11_dev*)filp->private_data;
    unsigned char data[5];
    int ret = 0;
    switch(cmd)
    {
        case DHT11_GET_VALUE:
            ret = dht11_read_data(dev, data);
            break;
        default:
            ret = -1;
            break;
    }
    
    if (copy_to_user((void*) arg, data, sizeof(data)))
        return -1;
    return ret;

}
static struct file_operations dht11_fops = {
    .owner = THIS_MODULE,
    .open = dht11_open,
    .read = dht11_read,
    .release = dht11_release,
    .unlocked_ioctl = dht11_ioctl,
};

/* sysfs 属性文件回调:直接输出温湿度 */
static ssize_t show_rh_temp(struct device *dev,
                            struct device_attribute *attr, char *buf)
{
    unsigned char data[5];
    int ret;

    ret = dht11_read_data(&dht11, data);
    if (ret) {
        return sprintf(buf, "Error: DHT11 read failed, ret=%d\n", ret);
    }

    /* 格式:湿度,温度 (DHT11小数位恒为0) */
    return sprintf(buf, "%d.%d,%d.%d\n",
                   data[0], data[1], data[2], data[3]);
}
static DEVICE_ATTR(value, S_IRUGO, show_rh_temp, NULL);
static struct attribute *dht11_attrs[] = {
	&dev_attr_value.attr,
	NULL
};

static const struct attribute_group dht11_attrs_group = {
	.attrs = dht11_attrs,
};

static const struct attribute_group *dht11_attr_groups[] = {
	&dht11_attrs_group,
	NULL
};

static int dht11_probe(struct platform_device* pdev)
{
    int ret = 0;
    struct miscdevice *mdev = &dht11.mdev;
    mdev->name = DHT11_NAME;       //设备名
    mdev->minor = MISC_DYNAMIC_MINOR;      //杂项设备次设备号
    mdev->fops = &dht11_fops;
    mdev->groups = dht11_attr_groups;

    mutex_init(&dht11.lock);
    //申请gpio
    dht11.gpios = of_get_named_gpio(pdev->dev.of_node, "dht11-gpios", 0);
    if (!gpio_is_valid(dht11.gpios))
    {
        ret = -EINVAL;
        printk("获取gpio失败\n");
        goto failed;
    }
    ret = devm_gpio_request_one(&pdev->dev, dht11.gpios, GPIOF_OUT_INIT_HIGH, "dht11-gpio");//申请gpio并设置输出高,devm会自动释放资源
    if (ret)
    {
        printk("申请gpio失败\n");
        ret = -EINVAL;
        goto failed;
    }
    ret = misc_register(mdev);        //注册misc设备
    if (ret)
    {
        printk("注册miscdev失败\n");
        ret = -EINVAL;
        goto failed;
    }
    printk("dht11 probe success\n");
failed:
    return ret;
}

static int dht11_remove(struct platform_device* pdev)
{
    misc_deregister(&dht11.mdev);
    printk("dht11 remove success\n");
    return 0;
}
static struct of_device_id dht11_of_match[] = {
    {.compatible = "wuyiqiang,dht11"},
    {}
};
static struct platform_driver dht11_driver = {
    .driver = {
        .name = "dht11",
        .owner = THIS_MODULE,
        .of_match_table = dht11_of_match,
    },
    .probe = dht11_probe,
    .remove = dht11_remove,
    
};
static int __init dht11_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&dht11_driver);
    if (ret)
    {
        printk("dht11驱动注册失败\n");
    } else {
        printk("dht11驱动注册成功\n");
    }
    return ret;
}

static void __exit dht11_exit(void)
{
    platform_driver_unregister(&dht11_driver);
    printk("dht11驱动卸载成功\n");
}
module_init(dht11_init);
module_exit(dht11_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wuyiqiang");

编写测试app

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

// ioctl 命令码(和驱动一致)
#define DHT11_GET_VALUE     _IOR(0xEF, 0, unsigned char[5])
// sysfs 属性文件固定路径
#define SYSFS_VALUE_PATH    "/sys/class/misc/dht11/value"

int main(int argc, char* argv[])
{
    char *filename;
    unsigned char data[5];  // 存储read/ioctl数据
    char buf[32];           // 存储sysfs读取的文本
    int fd, ret;
    int rh_int, rh_dec, temp_int, temp_dec; // 解析sysfs用

    // 1. 参数检查
    if (argc != 2)
    {
        printf("用法: %s /dev/dht11\n", argv[0]);
        return -1;
    }
    filename = argv[1];

    // 2. 打开设备节点(/dev/dht11)
    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        perror("open 设备失败");
        return -1;
    }
    printf("=== 成功打开 DHT11 设备 ===\n\n");

// ====================== 1. 测试 read 接口 ======================
    ret = read(fd, data, sizeof(data));
    if (ret != sizeof(data))
    {
        printf("read 读取错误!\n");
    } else {
        printf("---------- read 读取 ----------\n");
        printf("湿度:%d.%d  温度:%d.%d\n", data[0], data[1], data[2], data[3]);
    }
    sleep(2);

// ====================== 2. 测试 ioctl 接口 ======================
    ret = ioctl(fd, DHT11_GET_VALUE, data);
    if (ret < 0)
    {
        printf("ioctl 读取错误!\n");
    } else {
        printf("---------- ioctl 读取 ----------\n");
        printf("湿度:%d.%d  温度:%d.%d\n", data[0], data[1], data[2], data[3]);
    }
    sleep(2);

// ====================== 3. 新增:读取 sysfs 文件 ======================
    printf("---------- sysfs 读取 ----------\n");
    FILE *sysfs_fp = fopen(SYSFS_VALUE_PATH, "r");
    if (!sysfs_fp)
    {
        perror("打开 sysfs 文件失败");
    }
    else
    {
        // 读取文本内容(格式:25.0,26.5)
        fgets(buf, sizeof(buf), sysfs_fp);
        // 解析文本,提取4个整数
        sscanf(buf, "%d.%d,%d.%d", &rh_int, &rh_dec, &temp_int, &temp_dec);
        printf("sysfs 数据:%s", buf);
        printf("解析结果:湿度:%d.%d  温度:%d.%d\n", rh_int, rh_dec, temp_int, temp_dec);
        
        fclose(sysfs_fp);
    }

    // 关闭设备
    close(fd);
    printf("\n=== 三种接口测试完成 ===\n");
    return 0;
}

结果测试

在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐