[ruby] Erstellen Sie die DHT11 / 22-Klasse mithilfe des Datenblattes von Grund auf neu

Nachtrag

https://github.com/github0013/rpi-dht Ich habe versucht zu schmücken

$ gem install rpi-dht

Problem

Ich habe versucht, [dht-sensor-ffi] 1 zu verwenden, aber als ich es lange verwendet habe, habe ich einen Fehler erhalten und konnte nicht herausfinden, was falsch war.

Lösungen

Ich habe keine andere Wahl, als die Bibliothek selbst zu erstellen (ich kann kein C schreiben). mit Rubin

Vorbemerkungen

Zuallererst ist DHT "Single-Bus-Kommunikation (ONE-WIRE)". Ich denke, [GPIO4] 4 ist das.

--3,3 V / 5 V als Plus für Raspberry Pi ――Von Minus bis Minus von Raspberry Pi --Datenzeile zu [GPIO4] 4 von Raspberry Pi

So lesen Sie das Datenblatt

[DHT11 Datenblatt] 2 [DHT22 Datenblatt] 3

Fluss bis zur Datenerfassung

AM2302_pdf-4.png

AM2302_pdf-2-2.png

Am 22.11. Gibt es keinen Unterschied, bis die endgültigen Daten erfasst sind. Der Fluss ist so

  1. Stellen Sie GPIO4 auf ** Ausgabe **
  2. Auf HIGH stellen
  3. Auf NIEDRIG einstellen (innerhalb von 1-20 ms)
  4. Stellen Sie GPIO4 auf ** Eingang **
  5. HIGH wird einmal zurückgegeben. Ungefähr 20-200us
  6. LOW gibt ungefähr 80us als Antwortsignal zurück
  7. HIGH gibt ungefähr 80us als Antwortsignal zurück
  8. ** Daten, die 40 Bits (5 Bytes) entsprechen, fließen kontinuierlich **
  9. LOW fließt
  10. Sei die ganze Zeit hoch

HIGH / LOW fließt jeden Moment (in Mikrosekunden) und Sie müssen es lesen (also ist es hart für Rubin ...).

40-Bit-Lesen

AM2302_pdf-3.png

Beachten Sie, dass es nicht einfach HIGH = 1 und LOW = 0 ist. Nachdem LOW ungefähr 50us dauert, wird beurteilt, ob HIGH länger als 50us dauert (= 1) oder nicht (= 0) (also ist es streng für Rubin ...).

Überprüfen Sie, ob HIGH / LOW fortgesetzt wurde

Wenn Sie es beispielsweise beim Erfassen von Daten zusammen mit "Time.now.to_f" verwenden, dauert dieser Vorgang wahrscheinlich lange und die Daten fließen zuerst. Aus diesem Grund wurden die Daten zuerst nacheinander gespeichert, und die durchschnittliche Häufigkeit, mit der LOW danach fortgesetzt wurde, wurde gespeichert, und es wurde beurteilt, ob HIGH über oder unter dem Durchschnitt lag.

Verwendung von 40 Bit

Organisieren Sie 40 Bit in 8-Bit-Bytes.

  1. Oberer Biss der Luftfeuchtigkeit
  2. Luftfeuchtigkeit niedriger Biss
  3. Hochtemperaturbyte
  4. Temperaturbyte senken
  5. Paritätsbyte

Teilen in.

Verwendung von Paritätsbytes

Oberes Feuchtigkeitsbyte + Unteres Feuchtigkeitsbyte + Oberes Temperaturbyte + Unteres Temperaturbyte == Parität Überprüfen Sie mit.

Feuchtigkeitsoberbyte + Feuchtigkeitsunterbyte + Temperaturoberbyte + Temperaturunterbyte 00000001 + 00000001 + 00000001 + 00000001 # => 00000100 Wenn die Parität "00000100" ist, ist es OK

Verwendung von oberen und unteren Bytes

Für DHT11

Da die Genauigkeit gering ist, ist das untere Byte immer "00000000", sodass Sie es ignorieren können. Einfach

Für DHT22

Sowohl Luftfeuchtigkeit als auch Temperatur werden in 2 Bytes ausgedrückt.

Beispiel: 2 Bytes "0000001000001111" (527) bei einer Luftfeuchtigkeit von 52,7%, geteilt durch 10 bis 52,7% 2 Bytes von "0000000100000111" (263) unter der Annahme, dass die Luftfeuchtigkeit 26,3 ° C beträgt, und Teilen durch 10 ergibt 26,3 ° C.

Daher kann der korrekte Wert nur erhalten werden, wenn das obere Byte um 8 Bit verschoben und zum unteren Byte addiert wird.

Beispiel


  humidity = ((humidity_high << 8) + humidity_low) / 10.to_f

Weiterhin kann nur bei Temperatur Minus ausgedrückt werden. Dies hängt davon ab, ob das höherwertige Temperaturbit mit 1 beginnt. Daher muss das erste Bit gelöscht werden, nachdem bestätigt wurde, ob das erste Bit 1 ist.

  is_negative = temp_high & 0b10000000
  temp_high &= 0b01111111

Die spätere Berechnung entspricht dem obigen Feuchtigkeitsbeispiel.

Tatsächliche Datenerfassung

Ob die in Mikrosekunden fließenden Daten genau abgerufen werden können (ob die Paritätsprüfung erfolgreich ist oder nicht), hängt vom Grad der Verarbeitungsstauung zu diesem Zeitpunkt ab. Daher ist nicht bekannt, ob Daten jedes Mal mit einem einzigen Aufruf zuverlässig abgerufen werden können. .. Daher ist es notwendig, es mehrmals wiederholt auszuführen, bis Daten erhalten werden können. Tatsächlich hat [dht-sensor-ffi] 1 standardmäßig auch [50 Versuche] 5.

Code

DHTBase-Klasse


require "rpi_gpio"
RPi::GPIO.set_numbering :bcm #Geben Sie den Pin anhand der bcm-basierten Nummer an

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 #Häufigkeit, mit der ausreichende Daten abgerufen werden können (ungefähr)

  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, ...]
    #Fassen Sie wie zusammen
    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
    #Aus diesem Grund umgekehrt und wahr von unten/Nach dem Erstellen eines falschen Array-Paares
    #Für jeweils 8 Bits werden 5 Datenbytes vorbereitet.

    # ture/Falsches Array-Paar = 0 oder 1 Bit
    #Dies sind 5 Bytes, 40 Bits=80 Sequenzen
    # [false, false, false, ...]
    # [true, true, true, ...]
    # [false, false, false, ...]
    # [true, true, true, ...]
    # [false, false, false, ...]
    # [true, true, true, ...]
    # ...
    # ...
    # ...
    #Das Ende endet immer mit einer Reihe von falschen und einer langen Reihe von wahren, und da es sich nicht um Daten handelt, wird es nicht verwendet.
    # [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 /falsches Array-Paar=1 Bit
    #1 Byte für 8 Elemente
    #   [
    #     [[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
    #   ]

    #   ...
    #Insgesamt 5 Bytes
    # ]

    #Zuerst wahr für Antwortsignal/Ich habe ein falsches Array, muss es aber nicht lesen
    #Ich nehme nur die letzten 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) #Innerhalb von Bytes von Byte zu Bitarray reduzieren
    average_false_count = all_falses.sum(&:size) / all_falses.size.to_f

    #Beurteilen Sie, ob es 1 oder 0 ist, indem Sie die Anzahl der wahren Elemente anhand der Anzahl der falschen aufeinanderfolgenden Elemente vergleichen, die durchschnittlich 50us entsprechen.
    @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-Klasse


class DHT11 < DHTBase
  def convert
    humidity_high, _, temp_high, _, _ = bytes

    humidity = humidity_high
    temperature = temp_high #Sie sollten die negative Temperatur nicht kennen
    { humidity: humidity, temperature: temperature }
  end
end

DHT22 Klasse


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

Ausführungsbeispiel


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

Recommended Posts

[ruby] Erstellen Sie die DHT11 / 22-Klasse mithilfe des Datenblattes von Grund auf neu
Versuchen Sie es mit Cocoa von Ruby
Erstellen Sie eine Lotterie mit Ruby
Untersuchen Sie HTML-Elemente und erstellen Sie eine Seitenklasse (mit Selenide).
[Hinweis] Erstellen Sie mit Docker eine Java-Umgebung von Grund auf neu