[ruby] Créer une classe DHT11 / 22 à partir de zéro en utilisant la fiche technique

Postscript

https://github.com/github0013/rpi-dht J'ai essayé de gemme

$ gem install rpi-dht

problème

J'ai essayé d'utiliser [dht-sensor-ffi] 1, mais lorsque je l'ai utilisé pendant longtemps, j'ai eu une erreur et je n'ai pas pu comprendre ce qui n'allait pas.

Solution

Je n'ai pas d'autre choix que de créer moi-même la bibliothèque (je ne peux pas écrire C). avec rubis

Questions préliminaires

Tout d'abord, DHT est «Communication à bus unique (ONE-WIRE)». Donc je pense que [GPIO4] 4 est ça.

--3.3v / 5v comme un plus pour Raspberry Pi ――De moins au moins de Raspberry Pi --Ligne de données vers [GPIO4] 4 de Raspberry Pi

Comment lire la fiche technique

[Fiche technique DHT11] 2 [Fiche technique DHT22] 3

Flux jusqu'à l'acquisition des données

AM2302_pdf-4.png

AM2302_pdf-2-2.png

Il n'y a pas de différence le 22/11 jusqu'à ce que les données finales soient prises. Le flux est comme ça

  1. Réglez GPIO4 sur ** sortie **
  2. Réglez sur HIGH
  3. Réglez sur LOW (entre 1 et 20 ms)
  4. Réglez GPIO4 sur ** input **
  5. HIGH est retourné une fois environ 20-200us
  6. LOW renvoie environ 80us comme signal de réponse
  7. HIGH renvoie environ 80us comme signal de réponse
  8. ** Les données équivalentes à 40 bits (5 octets) circulent en continu **
  9. LOW coule
  10. Soyez haut tout le temps

HIGH / LOW coule à chaque instant (en microsecondes), et vous devez le lire (donc c'est dur pour ruby ...).

Lecture 40 bits

AM2302_pdf-3.png

Notez que ce n'est pas simplement HIGH = 1 et LOW = 0. Après que LOW dure environ 50us, il est jugé si HIGH dure plus de 50us (= 1) ou non (= 0) (c'est donc strict pour le rubis ...).

Vérifiez si HIGH / LOW continue ??? us

Par exemple, si vous le prenez avec Time.now.to_f lors de l'acquisition de données, ce processus prendra probablement beaucoup de temps et les données circuleront en premier. Pour cette raison, les données ont d'abord été sauvegardées successivement, et le nombre moyen de fois que LOW a continué après cela a été sauvegardé, et il a été jugé si HIGH était supérieur ou inférieur à la moyenne.

Comment utiliser 40 bits

Organiser 40 bits en octets 8 bits,

  1. Morsure supérieure d'humidité
  2. Morsure inférieure d'humidité
  3. Octet haute température
  4. Octet de température inférieure
  5. octet de parité

Diviser en.

Comment utiliser les octets de parité

Octet supérieur d'humidité + Octet inférieur d'humidité + Octet supérieur de température + Octet inférieur de température == parité Vérifier avec.

Octet supérieur d'humidité + octet inférieur d'humidité + octet supérieur de température + octet inférieur de température 00000001 + 00000001 + 00000001 + 00000001 # => 00000100 Si la parité est 00000100, c'est OK

Comment utiliser les octets supérieurs et inférieurs

Pour DHT11

La précision étant faible, l'octet inférieur est toujours «00000000», vous pouvez donc l'ignorer. Simplement

Pour DHT22

L'humidité et la température sont exprimées en 2 octets.

Exemple: 2 octets de «0000001000001111» (527) en supposant une humidité de 52,7%, divisé par 10 à 52,7% 2 octets de 0000000100000111 (263) en supposant que l'humidité est de 26,3 ℃, et en divisant cela par 10, cela donne 26,3 ℃

Par conséquent, la valeur correcte ne peut être obtenue que si l'octet supérieur est décalé de 8 bits et ajouté à l'octet inférieur.

Exemple


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

De plus, seulement dans le cas de la température, le moins peut être exprimé. Cela dépend du fait que le bit de température de poids fort commence par 1. Par conséquent, il est nécessaire de supprimer le premier bit après avoir confirmé si le premier bit est 1.

  is_negative = temp_high & 0b10000000
  temp_high &= 0b01111111

Le dernier calcul est le même que l'exemple d'humidité ci-dessus.

Acquisition réelle des données

La question de savoir si les données circulant en microsecondes peuvent être obtenues avec précision (que le contrôle de parité réussisse ou non) dépend du degré de congestion du traitement à ce moment-là, de sorte que l'on ne sait pas si les données peuvent être obtenues de manière fiable à chaque fois avec un seul appel. .. Par conséquent, il est nécessaire de l'exécuter plusieurs fois jusqu'à ce que les données puissent être obtenues. En fait, [dht-sensor-ffi] 1 vaut également [50 essais] 5 par défaut.

code

Classe DHTBase


require "rpi_gpio"
RPi::GPIO.set_numbering :bcm #Spécifiez la broche par un numéro basé sur bcm

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 #Nombre de fois où des données suffisantes peuvent être obtenues (environ)

  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, ...]
    #Résumer comme
    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
    #Pour cette raison, inverser et vrai du bas/Après avoir créé une fausse paire de tableaux
    #5 octets de données sont préparés pour chaque 8 bits.

    # ture/Fausse paire de tableaux = 0 ou 1 bit
    #C'est 5 octets, 40 bits=80 séquences
    # [false, false, false, ...]
    # [true, true, true, ...]
    # [false, false, false, ...]
    # [true, true, true, ...]
    # [false, false, false, ...]
    # [true, true, true, ...]
    # ...
    # ...
    # ...
    #La fin se termine toujours par une série de faux et une longue série de vrais, et ce ne sont pas des données, alors ne les utilisez pas
    # [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 /fausse paire de tableaux=1 bit
    #1 octet pour 8 éléments
    #   [
    #     [[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 octets au total
    # ]

    #Premier vrai pour le signal de réponse/J'ai un faux tableau mais je n'ai pas besoin de le lire
    #Je prends seulement les 5 derniers
    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) #Aplatir du tableau d'octets en bits dans les octets
    average_false_count = all_falses.sum(&:size) / all_falses.size.to_f

    #Jugez si c'est 1 ou 0 en comparant avec le nombre de vrais éléments en fonction du nombre de fausses consécutives équivalentes à 50us en moyenne.
    @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

Classe DHT11


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

    humidity = humidity_high
    temperature = temp_high #Vous ne devriez pas connaître la température négative
    { humidity: humidity, temperature: temperature }
  end
end

Classe DHT22


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

Exemple d'exécution


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] Créer une classe DHT11 / 22 à partir de zéro en utilisant la fiche technique
Essayez d'utiliser Cocoa de Ruby
Créer une loterie avec Ruby
Examiner les éléments HTML et créer une classe Page (à l'aide de Selenide)
[Note] Créez un environnement Java à partir de zéro avec docker