树莓派之DHT11传感器
硬件DHT11
主要看其原理,具体的详细原理大家就搜索引擎都能搜到。
也是为了好奇心,入手一台便宜的示波器,看看具体波形,下面是我所测:
这张显示首次触发,主机至少下拉18ms。图中可以看到主机下拉快近30ms,其实代码实现中我也是用的是18ms。具体原因为什么会测出是快30ms下面谈到代码实现再解释。
这张图显示的是主机拉高20~40us,但实际代码中并不需要主动拉高。
这张图显示的是DHT11开始响应的信号80us低电平80us高电平,表示下面即将开始40位的数据输出。
这张图显示的是一位数据0的表示即:50us的低电平26~28us的高电平。
这张图显示的是一位数据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。网上查询了下,说是比较不稳定,遇到系统采样就读取错误。
下面是我读出的各种错误:
这是湿度读取,有读取错误,也有读到0,反正大多数情况是读取错误,很少读取到0,没有一次读取到正确湿度。
这是温度读取,有读到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。