树莓派从DHT11读取温湿度

树莓派之DHT11传感器

硬件DHT11

主要看其原理,具体的详细原理大家就搜索引擎都能搜到。

也是为了好奇心,入手一台便宜的示波器,看看具体波形,下面是我所测:

树莓派从DHT11读取温湿度

这张显示首次触发,主机至少下拉18ms。图中可以看到主机下拉快近30ms,其实代码实现中我也是用的是18ms。具体原因为什么会测出是快30ms下面谈到代码实现再解释。

树莓派从DHT11读取温湿度

这张图显示的是主机拉高20~40us,但实际代码中并不需要主动拉高。

树莓派从DHT11读取温湿度

这张图显示的是DHT11开始响应的信号80us低电平80us高电平,表示下面即将开始40位的数据输出。

树莓派从DHT11读取温湿度

这张图显示的是一位数据0的表示即:50us的低电平26~28us的高电平。

树莓派从DHT11读取温湿度

这张图显示的是一位数据1的表示:50us低电平70us高电平。

代码实现

之前有看到一个博客说是要修改/boot/config.txt,于是查看/boot/overlays/README的确有这么一段:

Name:   dht11
Info:   Overlay for the DHT11/DHT21/DHT22 humidity/temperature sensors
        Also sometimes found with the part number(s) AM230x.
Load:   dtoverlay=dht11,<param>=<val>
Params: gpiopin                 GPIO connected to the sensor's DATA output.
                                (default 4)

但我实际测试时,开启和不开启都能测出同样的温湿度。我不知道这是什么原因,我的树莓派装的是64位的。如果有对这个了解的请给我科普一下。我觉得官方既然有这段话,肯定是有原因的。

现在补充下,开启dtoverlay=dht11就可以直接读取温湿度,不需要自己实现了:

$ sudo cat /sys/devices/platform/dht11@0/iio\:device0/in_temp_input             #读取温度
$ sudo cat /sys/devices/platform/dht11@0/iio\:device0/in_humidityrelative_input #读取湿度

但我实际使用过程中发现很多时候是读不出来,读出来的也都是0。网上查询了下,说是比较不稳定,遇到系统采样就读取错误。

下面是我读出的各种错误:

树莓派从DHT11读取温湿度

这是湿度读取,有读取错误,也有读到0,反正大多数情况是读取错误,很少读取到0,没有一次读取到正确湿度。

树莓派从DHT11读取温湿度

这是温度读取,有读到0,有读到错误,更有连接超时,也是大多数情况读取错误,很少情况读出0和连接超时,也是没有一次读到正确温度。

也在网上找了很多博客以及github上面对读DHT11数据的实现,发现很多都是使用计数器加死循环来读取数据的。感觉这样读是很不准确的,因为对树莓派我使用的都是远程连接库,这涉及到网络的延迟,毕竟你使用的计数器在本机并不在树莓派机器上。所以我采用了pigpio库的示例稍做修改,但也并不能完全排除网络延迟(触发时)。下面可以创建dht.py文件,将下面的代码复制进去:

import time
import pigpio

# 有效状态
DHT_GOOD = 0
# 校验不一致
DHT_BAD_CHECKSUM = 1
# 超出量程
DHT_BAD_DATA = 2
# 超时
DHT_TIMEOUT = 3


class sensor:
    """
    A class to read the DHT11 temperature/humidity sensors.
    """

    def __init__(self, pi, gpio, callback=None):
        """
        Instantiate with the Pi and the GPIO connected to the
        DHT temperature and humidity sensor.

        Optionally a callback may be specified.  If specified the
        callback will be called whenever a new reading is available.

        The callback receives a dictionary of GPIO, status,
        temperature, and humidity.

        The status will be one of:
        0 DHT_GOOD (a good reading)
        1 DHT_BAD_CHECKSUM (receieved data failed checksum check)
        2 DHT_BAD_DATA (data receieved had one or more invalid values)
        3 DHT_TIMEOUT (no response from sensor)
        """
        self._pi = pi
        self._gpio = gpio
        self._callback = callback

        # 标记数据是否解码完成
        self._new_data = False
        self._in_code = False

        # DHT11计数读取的bit位
        self._bits = 0
        # DHT11读取的数据
        self._code = 0

        # 标记数据的状态
        self._status = DHT_TIMEOUT
        self._temperature = 0.0
        self._humidity = 0.0

        pi.set_mode(gpio, pigpio.INPUT)
        self._last_edge_tick = pi.get_current_tick() - 10000
        # 上升沿触发,这是读取DHT11数据的方法,也是这段代码的核心
        self._cb_id = pi.callback(gpio, pigpio.RISING_EDGE, self._rising_edge)

    def _datum(self):
        return {'gpio': self._gpio, 'status': self._status,
                 'temperature': self._temperature, 'humidity': self._humidity}

    def _validate_DHT11(self, b1, b2, b3, b4):
        t = b2
        h = b4
        # 判断读取的温湿度是否超过DHT11的量程
        # 只要有一项在量程范围内即认为数据有效
        if ((t >= 0) and (t <= 50)) or ((h >= 20) and (h <= 90)):
            valid = True
        else:
            valid = False
        return (valid, t + b1 / 100, h + b3 / 100)

    def _decode_dht11(self):
        """
              +-------+
              | DHT11 |
              +-------+
        Temp C| 0-50  |
              +-------+
        RH%   | 20-80 |
              +-------+

                 0      1      2      3      4
              +------+------+------+------+------+
        DHT11 |check-| temp | temp | RH%  | RH%  |
              |sum   |      |      |      |      |
              +------+------+------+------+------+
        """
        b0 = self._code & 0xff
        b1 = (self._code >> 8) & 0xff
        b2 = (self._code >> 16) & 0xff
        b3 = (self._code >> 24) & 0xff
        b4 = (self._code >> 32) & 0xff

        chksum = (b1 + b2 + b3 + b4) & 0xFF

        if chksum == b0:
            valid, t, h = self._validate_DHT11(b1, b2, b3, b4)
            if valid:
                self._temperature = t
                self._humidity = h
                self._status = DHT_GOOD
            else:
                self._status = DHT_BAD_DATA
        else:
            self._status = DHT_BAD_CHECKSUM
        self._new_data = True

    def _rising_edge(self, gpio, level, tick):
        edge_len = pigpio.tickDiff(self._last_edge_tick, tick)
        self._last_edge_tick = tick
        if edge_len > 10000:
            self._in_code = True
            self._bits = -2
            self._code = 0
        elif self._in_code:
            self._bits += 1
            if self._bits >= 1:
                self._code <<= 1
                if (edge_len >= 60) and (edge_len <= 150):
                    if edge_len > 100:
                        # 1 bit
                        self._code += 1
                else:
                    # invalid bit
                    self._in_code = False
            if self._in_code:
                if self._bits == 40:
                    self._decode_dht11()
                    self._in_code = False

    def _trigger(self, ajust):
        self._new_data = False
        self._status = DHT_TIMEOUT
        self._pi.write(self._gpio, 0)
        time.sleep((18 + ajust) / 1000)
        self._pi.set_mode(self._gpio, pigpio.INPUT)

    def cancel(self):
        """
        """
        if self._cb_id is not None:
            self._cb_id.cancel()
            self._cb_id = None

    # 参数ajust用于远程连接时的微调可以为负数
    def read(self, ajust=0):
        """
        This triggers a read of the sensor.

        The returned data is a dictionary of GPIO, status,
        temperature, and humidity.

        The status will be one of:
        0 DHT_GOOD (a good reading)
        1 DHT_BAD_CHECKSUM (receieved data failed checksum check)
        2 DHT_BAD_DATA (data receieved had one or more invalid values)
        3 DHT_TIMEOUT (no response from sensor)
        """
        self._trigger(ajust)
        for i in range(5):  # timeout after 0.25 seconds.
            time.sleep(0.05)
            if self._new_data:
                break
        datum = self._datum()
        if self._callback is not None:
            self._callback(datum)
        return datum

这里使用的是上升沿触发,并且调用树莓派的tick这样避免了在本机使用计数器来读取数据的不准确性。但其中触发DHT11时使用的是本机的time.sleep,还记得上面图片主机下拉接近30ms吗?这就是因为调用的是本机线程的sleep。

上一篇:分布式下载方式(一)原理分析


下一篇:基于vue3.0的element-plus省市区选择器