https://github.com/github0013/rpi-dht I tried to gem
$ gem install rpi-dht
I tried using [dht-sensor-ffi] 1, but when I used it for a long time, I got an error and couldn't figure out what was wrong.
I have no choice but to make the library myself (I can't write C). with ruby
First of all, DHT is Single-bus communication (ONE-WIRE)
. So I think [GPIO4] 4 is that.
--3.3v / 5v as a plus for Raspberry Pi --Minus to Raspberry Pi minus --Data line to [GPIO4] 4 of Raspberry Pi
[DHT11 data sheet] 2 [DHT22 data sheet] 3
There is no difference on 11/22 until the final data is taken. The flow is like this
HIGH / LOW is flowing every moment (in microseconds), and you have to read it (so it's tough for ruby ...).
Note that it is not simply HIGH = 1 and LOW = 0. After LOW lasts about 50us, it is judged whether HIGH lasts longer than 50us (= 1) or not (= 0) (so it is strict for ruby ...).
For example, if you take Time.now.to_f
together when acquiring data, this process will probably take a long time and the data will flow first. For this reason, the data was saved in succession first, and the average number of times that LOW continued after that was saved, and it was judged whether HIGH was above or below the average.
Organize 40 bits into 8-bit bytes,
Divide into.
Humidity upper byte + Humidity lower byte + Temperature upper byte + Temperature lower byte == parity
Check with.
Humidity upper byte + Humidity lower byte + Temperature upper byte + Temperature lower byte
00000001 + 00000001 + 00000001 + 00000001 # => 00000100
If the parity is 00000100
, it's OK
Since the precision is low, the lower byte is always 00000000
, so you can ignore it. Simply
--Humidity upper byte = Humidity% --Temperature upper byte = Temperature ℃
Both humidity and temperature are expressed in 2 bytes.
Example:
2 bytes of 0000001000001111
(527) assuming that the humidity is 52.7%, which is 52.7% when divided by 10.
2 bytes of 0000000100000111
(263) assuming that the humidity is 26.3 ℃, and dividing this by 10 gives 26.3 ℃
Therefore, the correct value cannot be obtained unless the upper byte is shifted by 8 bits and added to the lower byte.
Example
humidity = ((humidity_high << 8) + humidity_low) / 10.to_f
Furthermore, only in the case of temperature, minus can be expressed. This depends on whether the high-order bit of temperature starts with 1. Therefore, it is necessary to drop the first bit after confirming whether the first bit is 1.
is_negative = temp_high & 0b10000000
temp_high &= 0b01111111
The later calculation is the same as the humidity example above.
Whether or not the data flowing in microseconds can be accurately obtained (whether or not the parity check passes) depends on the degree of processing congestion at that time, so it is not known whether or not data can be reliably obtained each time with one call. .. Therefore, it is necessary to repeatedly execute it multiple times until data can be obtained. In fact, [dht-sensor-ffi] 1 also has [50 tries] 5 by default.
DHTBase class
require "rpi_gpio"
RPi::GPIO.set_numbering :bcm #Specify a pin with a bcm-based number
class DHTBase
CLEAR_SIGNALS = 500 / 1000.to_f # ms
START_SIGNAL = 1 / 1000.to_f # ms
VALID_BYTE_SIZE = 5 # humidity_high, humidity_low, temp_high, temp_low, parity
BITS_IN_BYTE = 8
HUMIDITY_PRECISION = 10.to_f
TEMPERATURE_PRECISION = 10.to_f
ENOUGH_TO_CAPTURE_COUNT = 1000 #Number of times sufficient data can be obtained (about)
class << self
def read(pin)
dht = new(pin)
dht.send_start_signal
dht.collect_response_bits
dht.convert
end
end
def initialize(pin)
@pin = pin
end
def send_start_signal
RPi::GPIO.setup pin, as: :output
RPi::GPIO.set_high pin
sleep(CLEAR_SIGNALS)
RPi::GPIO.set_low pin
sleep(START_SIGNAL)
end
def collect_response_bits
RPi::GPIO.setup pin, as: :input, pull: :up
@bits = ENOUGH_TO_CAPTURE_COUNT.times.collect { RPi::GPIO.high?(pin) }
release
break_into_byte_strings
check_parity!
end
private
attr_reader :pin, :bits, :byte_strings
def release
RPi::GPIO.clean_up pin
end
def break_by_high_or_low
# HIGH = true
# LOW = false
# [false, false, false, ...]
# [true, true, true, ...]
#Summarize like
last_value = :not_yet
bits.slice_before do |value|
(last_value != value).tap { |not_same| last_value = value if not_same }
end.to_a
end
def break_into_byte_strings
#For this reason, reverse and true from the bottom/After creating a false array pair
#Data for 5 bytes is prepared for each 8 bits.
# ture/False array pair = 0 or 1 bit
#This is 5 bytes, 40 bits=80 array
# [false, false, false, ...]
# [true, true, true, ...]
# [false, false, false, ...]
# [true, true, true, ...]
# [false, false, false, ...]
# [true, true, true, ...]
# ...
# ...
# ...
#The end always ends with a continuity of false and a long-term continuity of true, and since it is not data, it is not used.
# [false, false, false, ...]
# [true, true, true, true, true, true, true, true, true, ...]
end_part, *low_high_pairs = break_by_high_or_low.reverse.each_slice(2).to_a
# low_high_pairs = [
# ture /false array pair=1 bit
#1 byte for 8 elements
# [
# [[true, true ...], [false, false ...]], 1
# [[true, true ...], [false, false ...]], 2
# [[true, true ...], [false, false ...]], 3
# [[true, true ...], [false, false ...]], 4
# [[true, true ...], [false, false ...]], 5
# [[true, true ...], [false, false ...]], 6
# [[true, true ...], [false, false ...]], 7
# [[true, true ...], [false, false ...]], 8
# ]
# ...
#5 bytes in total
# ]
#First true for Response signal/I have a false array but I don't need to read it
#I'm taking only the last 5
valid_bytes =
low_high_pairs.reverse.each_slice(8).to_a.last(VALID_BYTE_SIZE).select do |pair|
pair.all? { |x| x.is_a?(Array) }
end
unless valid_bytes.size == VALID_BYTE_SIZE
raise "not valid byte set (#{valid_bytes.size}bytes, should be #{
VALID_BYTE_SIZE
}bytes)"
end
valid_bytes.each do |byte|
unless byte.size == BITS_IN_BYTE
raise "not a byte (#{byte.size}bits, should be #{BITS_IN_BYTE}bits)"
end
end
all_falses = valid_bytes.collect { |byte| byte.collect(&:last) }.flatten(1) #From byte to bitwise array in bytes with flatten
average_false_count = all_falses.sum(&:size) / all_falses.size.to_f
#Judge whether it is 1 or 0 by comparing with the number of true elements based on the average number of false consecutives equivalent to 50us.
@byte_strings =
valid_bytes.collect do |byte|
byte.collect { |trues, _| average_false_count <= trues.size ? 1 : 0 }.join
end
end
def bytes
byte_strings.collect { |x| x.to_i(2) }
end
def check_parity!
humidity_high, humidity_low, temp_high, temp_low, parity = bytes
unless (humidity_high + humidity_low + temp_high + temp_low) == parity
raise "parity check failed"
end
end
end
DHT11 class
class DHT11 < DHTBase
def convert
humidity_high, _, temp_high, _, _ = bytes
humidity = humidity_high
temperature = temp_high #You should not know the negative temperature
{ humidity: humidity, temperature: temperature }
end
end
DHT22 class
class DHT22 < DHTBase
def convert
humidity_high, humidity_low, temp_high, temp_low, _ = bytes
is_negative = 0 < (temp_high & 0b10000000)
temp_high &= 0b01111111
humidity = ((humidity_high << 8) + humidity_low) / HUMIDITY_PRECISION
temperature = ((temp_high << 8) + temp_low) / TEMPERATURE_PRECISION
temperature *= -1 if is_negative
{ humidity: humidity, temperature: temperature }
end
end
Execution example
100.times do
begin
p DHT22.read(4)
break
rescue => exception
p exception
puts exception.backtrace.first(10).join("\n")
sleep 0.1
end
end