【Linux驱动模块】五、DHT11 温湿度模块
【Linux驱动模块】五、DHT11 温湿度模块
本文最后更新于314 天前,其中的信息可能已经过时,如有错误请发送邮件到273925452@qq.com
Avatar
惨考:
imx6ull裸机开发手册;
博客园:https://www.cnblogs.com/foxclever/p/13836986.html
阿里云:https://developer.aliyun.com/article/1216810
DHT11使用手册:

DHT11 简介

产品描述

传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选择。产品为4针单排引脚封装。

应用领域

►暖通空调►测试及检测设备

►汽车►数据记录器

► 消费品►自动控制

►气象站►家电

►湿度调节器►医疗

►除湿器

量程精度


DHT11 模块硬件设计

引脚

DHT11的供电电压为 3-5.5V。传感器上电后,要等待 1s 以越过不稳定状态在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波。

通讯接口

主机通过一条数据线与 DH11 连接,主机通过这条线发命令给 DHT11,DHT11 再通过这条线把数据发送给主机。建议连接线长度短于20米时用5K上拉电阻,大于20米时根据实际情况使用合适的上拉电阻


DHT11 模块软件设计

DHT11 通信协议:

用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据。从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。:下图为一次完整的传输示例:

其中深黑色信号表示由主机驱动,即主机向 DHT11 发信号,浅灰色信号表示DHT11 驱动,即 DHT11 发向主机发信号。

图:延时时间

图:数字0信号

图:数字1信号

⚫ 当主机没有与 DHT11 通信时,总线处于空闲状态,此时总线电平由于上拉电阻的作用处于高电平。

⚫ 当主机与 DHT11 正在通信时,总线处于通信状态,一次完整的通信过程如下:

a) 主机将对应的 GPIO 管脚配置为输出,准备向 DHT11 发送数据;

b) 主机发送一个开始信号:

开始信号 = 一个低脉冲 + 一个高脉冲。(低脉冲至少持续 18ms,高脉冲持续 20-40us。)

c) 主机将对应的 GPIO 管脚配置为输入,准备接受 DHT11 传来的数据,这时信号由上拉电阻拉高;

d) DHT11 发出响应信号:

响应信号 = 一个低脉冲 + 一个高脉冲。(低脉冲持续 80us,高脉冲持续 80us。)

e) DHT11 发出数据信号:

数据为 0 的一位信号 = 一个低脉冲 + 一个高脉冲。(低脉冲持续50us,高脉冲持续 26~28us。)

数据为 1 的一位信号 = 一个低脉冲 + 一个高脉冲。(低脉冲持续50us,高脉冲持续 70us。)

f) DHT11 发出结束信号

最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,然后释放总线,总线由上拉电阻拉高进入空闲状态。


数据格式:

8bit 湿度整数数据+8bit 湿度小数数据

+8bi 温度整数数据+8bit 温度小数数据

+8bit 校验和。

数据传送正确时,校验和等于“8bit 湿度整数数据+8bit 湿度小数数据+8bi温度整数数据+8bit 温度小数数据”所得结果的末 8 位。

测量分辨率:

测量分辨率分别为 8bit(温度)、8bit(湿度)。


修改设备树:

添加引脚节点

根节点

程序

dht11_drv.c

#include <linux/module.h>
#include <linux/poll.h>
#include "linux/jiffies.h"
#include <linux/delay.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/kthread.h>



/* 主设备号                                                                 */
static int major = 0;
static struct class *dht11_class;    //设备类
static struct gpio_desc *dht11_gpio_desc; // GPIO结构体操作指针
static void dht11_reset(void)
{
    gpiod_direction_output(dht11_gpio_desc, 1);
}

static void dht11_start(void)
{
  // 不能使用GPIOD_OUT_HIGH,有问题,字节用0,1
  mdelay(30);
  gpiod_set_value(dht11_gpio_desc, 0);
  mdelay(20);
  gpiod_set_value(dht11_gpio_desc, 1);
  udelay(40);
  gpiod_direction_input(dht11_gpio_desc);
  udelay(2);
}

static int dht11_wait_ready(void)
{
    int timeout_us = 20000;

    /*  等待低电平 - 或者直到超时*/
    timeout_us = 200;
    while (gpiod_get_value(dht11_gpio_desc) && --timeout_us)
    {
        udelay(1);
    }
    // 超时
    if (!timeout_us)
    {
        printk("----debug1-----\n");
        return -1;
    }

    /* 等待高电平 - 或者直到超时 */
    timeout_us = 200;
    while (!gpiod_get_value(dht11_gpio_desc) && --timeout_us)
    {
        udelay(1);
    }
    if (!timeout_us)
    {
        printk("----debug2-----\n");
        return -1;
    }

    /*  等待低电平 */
    timeout_us = 200;
    while (gpiod_get_value(dht11_gpio_desc) && --timeout_us)
    {
        udelay(1);
    }
    // 超时
    if (!timeout_us)
    {
        printk("----debug3-----\n");
        return -1;
    }

    return 0;
}

static int dht11_read_byte(unsigned char *data)
{
    int i = 0;
    unsigned char buffer = 0; //0-255
    int timeout_us = 400;
    for (i = 0; i < 8; i++)
    {

        /* 等待高电平  */
        timeout_us = 400;
        while (!gpiod_get_value(dht11_gpio_desc) && --timeout_us)
        {
            udelay(1);
        }
        if (!timeout_us)
        {
            return -1;
        }

        udelay(40);

        /* 延时在26-28us是0,70us是1,直接延时40us后,读到高电平则是1*/
        if (gpiod_get_value(dht11_gpio_desc))
        {
            buffer = (buffer << 1) | 1; //初始:0左移1位还是0,|1则为1

            /*  等待高电平结束 */
            timeout_us = 400;
            while (gpiod_get_value(dht11_gpio_desc) && --timeout_us)
            {
                udelay(1);
            }
            if (!timeout_us)
            {
                return -1;
            }
        }
        else
        {
            buffer = (buffer << 1) | 0;
        }
    }

    *data = buffer;
    return 0;
}

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
/**
 * 从DHT11传感器读取数据
 * 
 * 此函数用于从DHT11湿度和温度传感器读取数据。它首先发送一个高脉冲来启动DHT11,
 * 然后等待DHT11准备好。接着,它读取5个字节的数据,并根据校验码验证数据的完整性。
 * 最后,将数据返回给用户空间。
 * 
 * @param file 文件结构指针,通常为NULL
 * @param buf 用户空间的缓冲区指针,用于接收数据
 * @param size 要读取的数据大小
 * @param offset 当前文件位置指针,通常未使用
 * 
 * @return 成功时返回读取的字节数,失败时返回负错误码
 */
static ssize_t dht11_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
    int i = 0;
    unsigned char kernel_buf[5];    // 存储从DHT11读取的数据
    unsigned long flags;            // 保存中断状态
    u64 pre, last;

    // 检查输入的大小是否正确,DHT11返回的数据应为4字节
    if(size != 4)
        return -EINVAL;

    printk("==== %s ====\n", __FUNCTION__);

    
    local_irq_save(flags);  //保存并禁用本地中断。

    pre = ktime_get_ns();
    udelay(40);
    last = ktime_get_ns();

    printk("udelay 40 times use ns: %lld\n", last - pre);

    // 发送高脉冲启动DHT11
    dht11_reset();
    dht11_start();

    // 等待DHT11就绪
    if (0 != dht11_wait_ready()) 
    {
        printk("设备未就绪\n");
        local_irq_restore(flags); // 恢复中断状态
        return -1;
    }

    // 读取5个字节
    for (i = 0; i < 5; i++)
    {
        if (dht11_read_byte(&kernel_buf[i]) != 0) //返回其他数据则错误
        {
            local_irq_restore(flags); // 恢复中断状态
            return -1;
        }
    }

    dht11_reset();            // 复位

    local_irq_restore(flags);  // 恢复中断状态

    // 根据校验码验证数据
    if (kernel_buf[4] != kernel_buf[0] + kernel_buf[1] + kernel_buf[2] + kernel_buf[3]) {
        printk("验证错误\n");
        local_irq_restore(flags);  // 恢复中断状态
        return -1;
    }

    // 将数据返回给用户空间
    size = copy_to_user(buf, kernel_buf, size);

    return size;
}




/* 定义自己的file_operations结构体                                              */
static struct file_operations dht11_drv = {
    .owner     = THIS_MODULE,
    .read    = dht11_read,
};

static int dht11_probe(struct platform_device *pdev)
{

    printk("====%s====\n", __FUNCTION__);

    // 获得硬件信息
    dht11_gpio_desc = gpiod_get(&pdev->dev, "dht11", GPIOD_OUT_HIGH); 
    if (IS_ERR(dht11_gpio_desc))
    {
        dev_err(&pdev->dev, "Failed to get GPIO for dht11\n");
        return PTR_ERR(dht11_gpio_desc);
    }
    /* 注册file_operations     */
    major = register_chrdev(0, "dht11_chrdev", &dht11_drv); /* /dev/gpio_desc */

    dht11_class = class_create(THIS_MODULE, "dht11_class");

    device_create(dht11_class, NULL, MKDEV(major, 0), NULL, "dht11"); /* /dev/mydht11 */

    dev_info(&pdev->dev, "dht11 initialized successfully\n");
    return 0;
}

static int dht11_remove(struct platform_device *pdev)
{

    printk("======%s=======\n", __FUNCTION__);

    device_destroy(dht11_class, MKDEV(major, 0));
    class_destroy(dht11_class);
    unregister_chrdev(major, "dht11_chrdev");
    gpiod_put(dht11_gpio_desc);         // 释放 GPIO

    return 0;
}

/* 定义设备树匹配表,用于识别和支持特定的LED驱动器 */
static const struct of_device_id dht11_match_table[] = {
    /* 匹配字符串 "fire,dht11" 用于标识 */
    {.compatible = "fire,dht11"},
    /* 空项作为匹配表的结束标志 */
    {},
};



/*  定义platform_driver */
static struct platform_driver dht11_driver = {
    .probe = dht11_probe,   // 设置探测函数,当设备被探测到时调用
    .remove = dht11_remove, // 设置移除函数,当设备被移除时调用

    /* 设置<驱动程序的名称>和<设备树匹配表> */
    .driver = {
        .name = "dht11",                     // 字符设备名
        .of_match_table = dht11_match_table, // 设置设备树匹配表,用于设备的匹配
    },
};

/* 在入口函数 */
static int __init dht11_platform_driver_init(void)
{
    int ret = 0;
    printk("====%s====\n", __FUNCTION__);

    ret = platform_driver_register(&dht11_driver); // 注册驱动程序

    return ret;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit dht11_platform_driver_exit(void)
{
    printk("====%s====\n", __FUNCTION__);

     platform_driver_unregister(&dht11_driver); // 销毁设备信息

}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(dht11_platform_driver_init);
module_exit(dht11_platform_driver_exit);

MODULE_LICENSE("GPL");


dht11_test.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>


/*
 * ./dht11_test /dev/dht11
 *
 */
int main(int argc, char **argv)
{
    int fd;
    int ret = 0;
    unsigned char data[4];

    /* 1. 判断参数 */
    if (argc != 2)
    {
        printf("Usage: %s /dev/xxx\n", argv[0]);
        return -1;
    }

    /* 2. 打开文件 */
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("can not open file %s\n", argv[1]);
        return -1;
    }

    while (1)
    {

        ret = read(fd, data, 4);
        if (ret < 0)
        {
            perror("read error");
            return -1;
        }
        printf("temperature: %d.%d\t", data[0], data[1]);
        printf("humidity: %d.%d\n", data[2], data[3]);

        sleep(1);
    }
    // close(fd);

    return 0;
}

Makefile


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

KERN_DIR =  /home/book/100ask_imx6ull-sdk/Linux-4.9.88 # 板子所用内核源码的目录

all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o dht11_test dht11_test.c
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order  dht11_test

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o



obj-m += dht11_drv.o


模块测试

💡商业转载请联系作者获得授权,非商业转载请注明出处。
协议(License):署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)。
使用这些免费资源的时候应遵守版权意识,切勿非法利用,售卖,尊重原创内容知识产权。未经允许严禁转载。

评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇