Raspberry Pi и Pi4J. Урок 8. Работа с DHT11 и DHT22 из Java и C/C++

В этой статье подключим датчики DHT11 и DHT22 к Raspberry Pi, а точнее к Orange Pi PC, так как эта плата у меня есть. В принципе это не имеет значения, потому что ниже приведённые библиотеки и примеры запустятся и на Banana Pi, Odroid и NanoPi. Это что касается примеров на Java, использующие библиотеки Pi4J. А примеры на C/C++ можно запустить на все платы, на которые можно установить WiringPi.

Я раньше писал о популярных датчиках влажности и температуры DHT11 и DHT22 в статье Датчики температуры и влажности DHT11 и DHT22. Там же и документация по DHT11 и DHT22.

За основу использовалась библиотека от Adafruit — одна из стабильных версий, найденных мною в Интернете. Это Python библиотека для чтения данных с датчиков влажности и температуры DHT серии на Raspberry Pi и Beaglebone Black. Однако для чтения данных непосредственно с датчика используется язык программирования C, чтобы уменьшить вероятность ошибки во время чтения.

Библиотека C/C++

Изначально планировал сделать только на Java, так как это серия уроков по Pi4J, но решил сделать библиотеку и на C/C++, чтобы не писать отдельную статью.

DHTxx.h

#ifndef DHTXX_H_
#define DHTXX_H_

#include <stdint.h>

/*
 * Define errors and return values.
 */
#define DHT_ERROR_TIMEOUT  -1
#define DHT_ERROR_CHECKSUM -2
#define DHT_ERROR_ARGUMENT -3
#define DHT_ERROR_GPIO     -4
#define DHT_SUCCESS         0

/*
 * Define sensor types.
 */
#define DHT11              11
#define DHT22              22
#define AM2302             22

/**
 * This is the only processor specific magic value, the maximum amount of time to
 * spin in a loop before bailing out and considering the read a timeout.  This should
 * be a high value, but if you're running on a much faster platform than a Raspberry
 * Pi or Beaglebone Black then it might need to be increased.
 */
#define DHT_MAXCOUNT    32000

/**
 * Number of bit pulses to expect from the DHT.  Note that this is 41 because
 * the first pulse is a constant 50 microsecond pulse, with 40 pulses to represent
 * the data afterwards.
 */
#define DHT_PULSES         41

class DHTxx {
private:
  int pin;
  int sensor;

public:
  DHTxx(int pin, int sensor);
  virtual ~DHTxx();

  int init();

  /**
   * Read DHT sensor connected to GPIO pin (using BCM numbering).  Humidity and temperature will be
   * returned in the provided parameters. If a successfull reading could be made a value of 0
   * (DHT_SUCCESS) will be returned.  If there was an error reading the sensor a negative value will
   * be returned.  Some errors can be ignored and retried, specifically DHT_ERROR_TIMEOUT or DHT_ERROR_CHECKSUM.
   */
  int read(float* humidity, float* temperature);

  int getPin() const;

  void setPin(int pin);

  int getSensor() const;

  void setSensor(int sensor);

private:
  /**
   * Busy wait delay for most accurate timing, but high CPU usage.
   * Only use this for short periods of time (a few hundred milliseconds at most)!
   */
  void busyWaitMilliseconds(uint32_t millis);

  /**
   * General delay that sleeps so CPU usage is low, but accuracy is potentially bad.
   */
  void sleepMilliseconds(uint32_t millis);

  /**
   * Increase scheduling priority and algorithm to try to get 'real time' results.
   */
  void setMaxPriority(void);

  /**
   * Drop scheduling priority back to normal/default.
   */
  void setDefaultPriority(void);
};

#endif /* DHTXX_H_ */

init()

Инициализация GPIO и WiringPi.

int init();

read(float*, float*)

Считывает данные с датчика DHT, подключенного к выводу GPIO (с использованием нумерации WiringPi). Влажность и температура будут возвращены в заданных параметрах. Если чтение успешное, будет возвращено значение 0 (DHT_SUCCESS). Если при считывании данных произошла ошибка, возвращается отрицательное значение. Некоторые ошибки можно игнорировать и повторять чтение, в частности DHT_ERROR_TIMEOUT или DHT_ERROR_CHECKSUM.

int read(float* humidity, float* temperature);

busyWaitMilliseconds(uint32_t)

Приостанавливает работу потока, в котором она была вызвана, на указанное в аргументе время, загрузка процессора высокая, а точность высокая. Используйте этот метод только в течение короткого периода времени (не более нескольких сотен миллисекунд)!

void busyWaitMilliseconds(uint32_t millis);

sleepMilliseconds(uint32_t)

Приостанавливает работу потока, в котором она была вызвана, на указанное в аргументе время, загрузка процессора низкая, но точность потенциально плохая.

void sleepMilliseconds(uint32_t millis);

setMaxPriority()

Увеличивает приоритет планирования и алгоритм, чтобы попытаться получить результаты «реального времени» (SCHED_FIFO: планировщик FIFO (First In-First Out)).

void setMaxPriority(void);

setDefaultPriority();

Сбрасывает приоритет на нормальный/по умолчанию (SCHED_OTHER: стандартный алгоритм планировщика с разделением времени).

void setDefaultPriority(void);

DHTxx.cpp

#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <sched.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <wiringPi.h>
#include "DHTxx.h"

DHTxx::DHTxx(int pin, int sensor) {
  this->pin = pin;
  this->sensor = sensor;
}

DHTxx::~DHTxx() {
}

int DHTxx::init() {
  /*
   * Initialize GPIO library.
   */
  if (wiringPiSetup() == -1) {
    return DHT_ERROR_GPIO;
  }
  return DHT_SUCCESS;
}

int DHTxx::read(float* humidity, float* temperature) {
  /* 
   * Validate humidity and temperature arguments and set them to zero.
   */
  if (humidity == NULL || temperature == NULL) {
    return DHT_ERROR_ARGUMENT;
  }
  *temperature = 0.0f;
  *humidity = 0.0f;

  /*
   * Store the count that each DHT bit pulse is low and high.
   * Make sure array is initialized to start at zero.
   */
  uint32_t pulseCounts[DHT_PULSES * 2] = { 0 };
  /*
   * Set pin to output.
   */
  pinMode(pin, OUTPUT);
  /*
   * Bump up process priority and change scheduler to try to try to make process more 'real time'.
   */
  setMaxPriority();
  /*
   * Set pin high for ~500 milliseconds.
   */
  digitalWrite(pin, HIGH);
  sleepMilliseconds(500);
  /*
   * The next calls are timing critical and care should be taken
   * to ensure no unnecssary work is done below.
   */
  /*
   * Set pin low for ~20 milliseconds.
   */
  digitalWrite(pin, LOW);
  busyWaitMilliseconds(20);
  /*
   * Set pin at input.
   */
  pinMode(pin, INPUT);
  /*
   * Need a very short delay before reading pins or else value is sometimes still low.
   */
  for (volatile int i = 0; i < 50; ++i) {
  }
  /*
   * Wait for DHT to pull pin low.
   */
  uint32_t count = 0;
  while (digitalRead(pin)) {
    if (++count >= DHT_MAXCOUNT) {
      /*
       * Timeout waiting for response.
       */
      setDefaultPriority();
      return DHT_ERROR_TIMEOUT;
    }
  }
  /*
   * Record pulse widths for the expected result bits.
   */
  for (int i = 0; i < DHT_PULSES * 2; i += 2) {
    /*
     * Count how long pin is low and store in pulseCounts[i]
     */
    while (!digitalRead(pin)) {
      if (++pulseCounts[i] >= DHT_MAXCOUNT) {
        /*
         * Timeout waiting for response.
         */
        setDefaultPriority();
        return DHT_ERROR_TIMEOUT;
      }
    }
    /*
     * Count how long pin is high and store in pulseCounts[i+1]
     */
    while (digitalRead(pin)) {
      if (++pulseCounts[i + 1] >= DHT_MAXCOUNT) {
        /*
         * Timeout waiting for response.
         */
        setDefaultPriority();
        return DHT_ERROR_TIMEOUT;
      }
    }
  }
  /*
   * Done with timing critical code, now interpret the results.
   */
  /*
   * Drop back to normal priority.
   */
  setDefaultPriority();
  /*
   * Compute the average low pulse width to use as a 50 microsecond reference threshold.
   * Ignore the first two readings because they are a constant 80 microsecond pulse.
   */
  uint32_t threshold = 0;
  for (int i = 2; i < DHT_PULSES * 2; i += 2) {
    threshold += pulseCounts[i];
  }
  threshold /= DHT_PULSES - 1;
  /*
   * Interpret each high pulse as a 0 or 1 by comparing it to the 50us reference.
   * If the count is less than 50us it must be a ~28us 0 pulse, and if it's higher
   * then it must be a ~70us 1 pulse.
   */
  uint8_t data[5] = { 0 };
  for (int i = 3; i < DHT_PULSES * 2; i += 2) {
    int index = (i - 3) / 16;
    data[index] <<= 1;
    if (pulseCounts[i] >= threshold) {
      /*
       * One bit for long pulse.
       */
      data[index] |= 1;
    }
    /*
     * Else zero bit for short pulse.
     */
  }
  /*
   * Verify checksum of received data.
   */
  if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
    if (sensor == DHT11) {
      /*
       * Get humidity and temp for DHT11 sensor.
       */
      *humidity = (float) data[0];
      if (data[1] > 0) {
        if (data[1] <= 9) {
          *humidity += data[1] / 10.0;
        } else {
          *humidity += data[1] / 100.0;
        }
      }
      *temperature = (float) data[2];
      if (data[3] > 0) {
        if (data[3] <= 9) {
          *temperature += data[3] / 10.0;
        } else {
          *temperature += data[3] / 100.0;
        }
      }
    } else if (sensor == DHT22) {
      /*
       * Calculate humidity and temp for DHT22 sensor.
       */
      *humidity = (data[0] * 256 + data[1]) / 10.0f;
      *temperature = ((data[2] & 0x7F) * 256 + data[3]) / 10.0f;
      if (data[2] & 0x80) {
        *temperature *= -1.0f;
      }
    }
    return DHT_SUCCESS;
  } else {
    return DHT_ERROR_CHECKSUM;
  }
}

int DHTxx::getPin() const {
  return pin;
}

void DHTxx::setPin(int pin) {
  this->pin = pin;
}

int DHTxx::getSensor() const {
  return sensor;
}

void DHTxx::setSensor(int sensor) {
  this->sensor = sensor;
}

void DHTxx::busyWaitMilliseconds(uint32_t millis) {
  /*
   * Set delay time period.
   */
  struct timeval deltatime;
  deltatime.tv_sec = millis / 1000;
  deltatime.tv_usec = (millis % 1000) * 1000;
  struct timeval walltime;
  /*
   * Get current time and add delay to find end time.
   */
  gettimeofday(&walltime, NULL);
  struct timeval endtime;
  timeradd(&walltime, &deltatime, &endtime);
  /*
   * Tight loop to waste time (and CPU) until enough time as elapsed.
   */
  while (timercmp(&walltime, &endtime, <)) {
    gettimeofday(&walltime, NULL);
  }
}

void DHTxx::sleepMilliseconds(uint32_t millis) {
  struct timespec sleep;
  sleep.tv_sec = millis / 1000;
  sleep.tv_nsec = (millis % 1000) * 1000000L;
  while (clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep, &sleep) && errno == EINTR)
    ;
}

void DHTxx::setMaxPriority(void) {
  struct sched_param sched;
  memset(&sched, 0, sizeof(sched));
  /*
   * Use FIFO scheduler with highest priority for the lowest chance of the kernel context switching.
   */
  sched.sched_priority = sched_get_priority_max(SCHED_FIFO);
  sched_setscheduler(0, SCHED_FIFO, &sched);
}

void DHTxx::setDefaultPriority(void) {
  struct sched_param sched;
  memset(&sched, 0, sizeof(sched));
  /*
   * Go back to default scheduler with default 0 priority.
   */
  sched.sched_priority = 0;
  sched_setscheduler(0, SCHED_OTHER, &sched);
}

Подключение DHT11 к Orange Pi One

Чтобы проверить работоспособность приведённого выше года, я подключил DHT11 к Orange Pi PC, скомпилировал и запустил ниже приведённый пример. На схеме Orange Pi One, потому что не нашёл компонент Orange Pi PC в fritzing.

Схема подключения DHT11 к Orange Pi One

Лучше использовать подтягивающий резистор на 10 кОм, при подключении резистора номинала 4.7 к — датчик не заработал.

Схема подключения DHT11 к Orange Pi One

Пример программы на C/C++

main.cpp

#include <iostream>
#include <unistd.h>
#include "DHTxx.h"

using namespace std;

int main() {
  float humidity;
  float temperature;
  int pin = 7;

  DHTxx dht11(pin, DHT11);

  if (dht11.init() == DHT_SUCCESS) {
    for (int i = 0; i < 10; ++i) {
      int result = dht11.read(&humidity, &temperature);
      switch (result) {
      case DHT_ERROR_TIMEOUT:
        cout << "DHT_ERROR_TIMEOUT" << endl;
        break;
      case DHT_ERROR_CHECKSUM:
        cout << "DHT_ERROR_CHECKSUM" << endl;
        break;
      case DHT_ERROR_ARGUMENT:
        cout << "DHT_ERROR_ARGUMENT" << endl;
        break;
      case DHT_ERROR_GPIO:
        cout << "DHT_ERROR_GPIO" << endl;
        break;
      case DHT_SUCCESS:
        cout << "humidity = " << humidity << " %" << endl;
        cout << "temperature = " << temperature << " °C" << endl;
        break;
      default:
        break;
      }
      sleep(3);
    }
  }
  return 0;
}

Принцип работы примера

  1. Включаем файл DHTxx.h
    #include "DHTxx.h"
  2. Объявляем переменные humidity и temperature для сохранения данных температуры и относительной влажности
    float humidity;
    float temperature;
  3. Создаём объект класса DHTxx. Первый параметр — это WiringPi пин, а второй — тип датчика
    DHTxx dht11(pin, DHT11);
  4. Инициализируем датчик и продолжаем читать данные, если возвращаемый результат метода init() равен DHT_SUCCESS
    if (dht11.init() == DHT_SUCCESS) {/*...*/}
  5. Создаём цикл и читаем данные при помощи метода int result = dht11.read(&humidity, &temperature);. Если возвращаемый результат метода read() в переменную result равен DHT_SUCCESS, тогда выводим температуру и влажность на экран. А если во время чтения возникла какая-то ошибка, тогда выводим на экран код ошибки.
    for (int i = 0; i < 10; ++i) {
      int result = dht11.read(&humidity, &temperature);
      switch (result) {
      case DHT_ERROR_TIMEOUT:
        cout << "DHT_ERROR_TIMEOUT" << endl;
        break;
      case DHT_ERROR_CHECKSUM:
        cout << "DHT_ERROR_CHECKSUM" << endl;
        break;
      case DHT_ERROR_ARGUMENT:
        cout << "DHT_ERROR_ARGUMENT" << endl;
        break;
      case DHT_ERROR_GPIO:
        cout << "DHT_ERROR_GPIO" << endl;
        break;
      case DHT_SUCCESS:
        cout << "humidity = " << humidity << " %" << endl;
        cout << "temperature = " << temperature << " °C" << endl;
        break;
      default:
        break;
      }
      sleep(3);
    }
  6. Минимальный интервал очередного чтения около двух секунд, я поставил 3.
    sleep(3);

Результат

humidity = 53 %
temperature = 29.1 °C
DHT_ERROR_CHECKSUM
humidity = 53 %
temperature = 29 °C
humidity = 53 %
temperature = 29 °C
humidity = 53 %
temperature = 29 °C
humidity = 53 %
temperature = 29 °C
humidity = 53 %
temperature = 29 °C
humidity = 53 %
temperature = 29 °C
humidity = 53 %
temperature = 29 °C
humidity = 52 %
temperature = 28.9 °C

Подключение DHT22 к Orange Pi One

Всё вышесказанное про DHT11 подойдёт и для DHT22. Единственное отличие только в том, что, при инициализации объекта класса DHTxx, второй параметр — это DHT22.

DHTxx dht22(pin, DHT22);

Схема подключения DHT22 к Orange Pi One

Схема подключения DHT22 к Orange Pi One

Пример программы на C/C++

main.cpp

#include <iostream>
#include <unistd.h>
#include "DHTxx.h"

using namespace std;

int main() {
  float humidity;
  float temperature;
  int pin = 7;

  DHTxx dht22(pin, DHT22);

  if (dht22.init() == 0) {
    for (int i = 0; i < 10; ++i) {
      int result = dht22.read(&humidity, &temperature);
      switch (result) {
      case DHT_ERROR_TIMEOUT:
        cout << "DHT_ERROR_TIMEOUT" << endl;
        break;
      case DHT_ERROR_CHECKSUM:
        cout << "DHT_ERROR_CHECKSUM" << endl;
        break;
      case DHT_ERROR_ARGUMENT:
        cout << "DHT_ERROR_ARGUMENT" << endl;
        break;
      case DHT_ERROR_GPIO:
        cout << "DHT_ERROR_GPIO" << endl;
        break;
      case DHT_SUCCESS:
        cout << "humidity = " << humidity << " %" << endl;
        cout << "temperature = " << temperature << " °C" << endl;
        break;
      default:
        break;
      }
      sleep(3);
    }
  }
  return 0;
}

Результат

humidity = 49 %
temperature = 28.8 °C
humidity = 49.1 %
temperature = 28.8 °C
humidity = 49.1 %
temperature = 28.8 °C
humidity = 49.1 %
temperature = 28.8 °C
humidity = 49.1 %
temperature = 28.8 °C
humidity = 49 %
temperature = 28.8 °C
humidity = 49 %
temperature = 28.8 °C
humidity = 49 %
temperature = 28.8 °C
humidity = 49 %
temperature = 28.8 °C
DHT_ERROR_TIMEOUT

Библиотека Java

С помощью инструмента Pi4J я перевёл C/C++ код на Java. Проблема Джавы в том, что при чтении данных с датчика очень часто возникают ошибки. Проблема может быть решена двумя костылями: первое — это использовать нативные методы и вызывать C/C++ функции с помощью JNI, что является более правильным решением; второе и самое простое — это запросить данные с датчика пока не получим правильный результат.

Если ещё не установили Pi4J на вашу плату перейдите на страницу Установка Pi4J на Raspberry Pi и Orange Pi, Banana Pi.

DhtData.java

Класс DhtData содержит информацию о влажности и температуре.

package ru.micropi.pi4j.dhtxx;

public class DhtData {
  private double temperature;
  private double humidity;

  public DhtData() {
    super();
  }

  public DhtData(double temperature, double humidity) {
    super();
    this.temperature = temperature;
    this.humidity = humidity;
  }

  public double getTemperature() {
    return temperature;
  }

  public void setTemperature(double temperature) {
    this.temperature = temperature;
  }

  public double getHumidity() {
    return humidity;
  }

  public void setHumidity(double humidity) {
    this.humidity = humidity;
  }

  @Override
  public String toString() {
    return "Temperature: " + temperature + "°C\nHumidity: " + humidity + "%";
  }
}

DHTxx.java

Интерфейс DHTxx — это абстракция устройств DHT. Он позволяет инициализировать GPIO/WiringPi и читать данные с DHT11/DHT22.

package ru.micropi.pi4j.dhtxx;

import com.pi4j.io.gpio.Pin;

public interface DHTxx {
  public void init() throws Exception;

  public Pin getPin();

  public void setPin(Pin pin);

  public DhtData getData() throws Exception;
}

init()

Метод инициализирует GPIO/WiringPi, выбрасывает исключение, если это не удалось сделать.

public void init() throws Exception;

getData()

Метод считывает и проверяет данные с датчика, а после возвращает эти данные в виде объекта класса DhtData. В случае ошибки выбрасывает исключение.

public DhtData getData() throws Exception;

DHTxxBase.java

В абстрактном классе DHTxxBase реализованы методы: getPin(), setPin() и init(). Также добавлен метод getRawData(), именно здесь происходит чтение и преобразование данных с датчиков DHT11/DHT22. Эти методы являются общими для обоих датчиков.

package ru.micropi.pi4j.dhtxx;

import com.pi4j.io.gpio.Pin;
import com.pi4j.wiringpi.Gpio;

public abstract class DHTxxBase implements DHTxx {
  private static final int DHT_MAXCOUNT = 32000;
  private static final int DHT_PULSES = 41;

  private Pin pin;

  public DHTxxBase(Pin pin) {
    this.pin = pin;
  }

  public Pin getPin() {
    return pin;
  }

  public void setPin(Pin pin) {
    this.pin = pin;
  }

  @Override
  public void init() throws Exception {
    /*
     * Initialize GPIO library.
     */
    if (Gpio.wiringPiSetup() == -1) {
      throw new Exception("DHT_ERROR_GPIO");
    }
  }

  protected int[] getRawData() throws Exception {
    /*
     * Store the count that each DHT bit pulse is low and high. Make sure array
     * is initialized to start at zero.
     */
    int pulseCounts[] = new int[DHT_PULSES * 2];

    /*
     * Set pin to output.
     */
    Gpio.pinMode(pin.getAddress(), Gpio.OUTPUT);

    /*
     * Bump up process priority and change scheduler to try to try to make
     * process more 'real time'.
     */
    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

    /*
     * Set pin high for ~500 milliseconds.
     */
    Gpio.digitalWrite(pin.getAddress(), Gpio.HIGH);
    Gpio.delay(500);

    /*
     * The next calls are timing critical and care should be taken to ensure no
     * unnecessary work is done below.
     */

    /*
     * Set pin low for ~20 milliseconds.
     */
    Gpio.digitalWrite(pin.getAddress(), Gpio.LOW);
    Gpio.delay(20);

    /*
     * Set pin at input.
     */
    Gpio.pinMode(pin.getAddress(), Gpio.INPUT);

    /*
     * Need a very short delay before reading pins or else value is sometimes
     * still low.
     */
    /*
     * for (int i = 0; i < 50; ++i) {}
     */

    /*
     * Wait for DHT to pull pin low.
     */
    long count = 0;
    while (Gpio.digitalRead(pin.getAddress()) == 1) {
      if (++count >= DHT_MAXCOUNT) {
        /*
         * Timeout waiting for response.
         */
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
        throw new Exception("DHT_ERROR_TIMEOUT");
      }
    }

    // Record pulse widths for the expected result bits.
    for (int i = 0; i < DHT_PULSES * 2; i += 2) {
      /*
       * Count how long pin is low and store in pulseCounts[i]
       */
      while (Gpio.digitalRead(pin.getAddress()) == 0) {
        if (++pulseCounts[i] >= DHT_MAXCOUNT) {
          /*
           * Timeout waiting for response.
           */
          Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
          throw new Exception("DHT_ERROR_TIMEOUT: " + pulseCounts[i] + " pulses, " + i);
        }
      }
      /*
       * Count how long pin is high and store in pulseCounts[i+1]
       */
      while (Gpio.digitalRead(pin.getAddress()) == 1) {
        if (++pulseCounts[i + 1] >= DHT_MAXCOUNT) {
          /*
           * Timeout waiting for response.
           */
          Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
          throw new Exception("DHT_ERROR_TIMEOUT: " + pulseCounts[i + 1] + " pulses, " + i);
        }
      }
    }

    /*
     * Done with timing critical code, now interpret the results.
     */

    /*
     * Drop back to normal priority.
     */
    Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

    /*
     * Compute the average low pulse width to use as a 50 microsecond reference
     * threshold. Ignore the first two readings because they are a constant 80
     * microsecond pulse.
     */
    long threshold = 0;
    for (int i = 2; i < DHT_PULSES * 2; i += 2) {
      threshold += pulseCounts[i];
    }
    threshold /= DHT_PULSES - 1;

    /*
     * Interpret each high pulse as a 0 or 1 by comparing it to the 50us
     * reference. If the count is less than 50us it must be a ~28us 0 pulse, and
     * if it's higher then it must be a ~70us 1 pulse.
     */
    int data[] = new int[5];
    for (int i = 3; i < DHT_PULSES * 2; i += 2) {
      int index = (i - 3) / 16;
      data[index] <<= 1;
      if (pulseCounts[i] >= threshold) {
        /*
         * One bit for long pulse.
         */
        data[index] |= 1;
      }
      /*
       * Else zero bit for short pulse.
       */
    }
    return data;
  }

}

DHTxxBase(Pin)

Конструктор класса DHTxxBase, как параметр получает номер пина по WiringPi, к которому подключён DHT датчик.

public DHTxxBase(Pin pin)

getRawData()

Метод считывает и преобразовывает данные с датчиков DHT11/DHT22. Возвращает массив из 5 байт, содержащий сведения о влажности, температуре и контрольной сумме. Выбрасывает исключение, если во время чтения возникла ошибка.

protected int[] getRawData() throws Exception

DHT11.java

package ru.micropi.pi4j.dhtxx;

import com.pi4j.io.gpio.Pin;
import com.pi4j.wiringpi.Gpio;

public class DHT11 extends DHTxxBase {

  private static final int DHT_WAIT_INTERVAL = 2000;

  public DHT11(Pin pin) {
    super(pin);
  }

  @Override
  public DhtData getData() throws Exception {
    int atempts = 0;
    while (true) {
      try {
        int[] data = getRawData();

        /*
         * Verify checksum of received data.
         */
        if (data[4] != ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
          throw new Exception("DHT_ERROR_CHECKSUM");
        }
        /*
         * Get humidity and temp for DHT11 sensor.
         */
        double humidity = data[0];
        if (data[1] > 0) {
          if (data[1] <= 9) {
            humidity += data[1] / 10.0;
          } else {
            humidity += data[1] / 100.0;
          }
        }
        double temperature = data[2];
        if (data[3] > 0) {
          if (data[3] <= 9) {
            temperature += data[3] / 10.0;
          } else {
            temperature += data[3] / 100.0;
          }
        }
        return new DhtData(temperature, humidity);
      } catch (Exception e) {
        atempts++;
        if (atempts <= 3) {
          Gpio.delay(DHT_WAIT_INTERVAL);
          continue;
        }
        throw new Exception("Atempts " + atempts, e);
      }
    }
  }

  @Override
  public String toString() {
    return "DHT11, pin: " + getPin();
  }
}

DHT22.java

package ru.micropi.pi4j.dhtxx;

import com.pi4j.io.gpio.Pin;
import com.pi4j.wiringpi.Gpio;

public class DHT22 extends DHTxxBase {

  private static final int DHT_WAIT_INTERVAL = 2000;

  public DHT22(Pin pin) {
    super(pin);
  }

  @Override
  public DhtData getData() throws Exception {
    int atempts = 0;
    while (true) {
      try {
        int[] data = getRawData();

        /*
         * Verify checksum of received data.
         */
        if (data[4] != ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
          throw new Exception("DHT_ERROR_CHECKSUM");
        }
        /*
         * Calculate humidity and temp for DHT22 sensor.
         */
        double humidity = (data[0] * 256 + data[1]) / 10.0;
        double temperature = ((data[2] & 0x7F) * 256 + data[3]) / 10.0;
        if ((data[2] & 0x80) != 0) {
          temperature *= -1.0f;
        }

        return new DhtData(temperature, humidity);
      } catch (Exception e) {
        atempts++;
        if (atempts <= 3) {
          Gpio.delay(DHT_WAIT_INTERVAL);
          continue;
        }
        throw new Exception("Atempts " + atempts, e);
      }
    }
  }

  @Override
  public String toString() {
    return "DHT22, pin: " + getPin();
  }
}

Классы DHT11 и DHT22 расширяют класс DHTxxBase, переопределён конструктор класса и реализован метод getData().

getData()

Метод getData() классов DHT11 и DHT22 почти одинаковые, отличаются лишь тем, как преобразовываются данные в градусах цельсия и процентах. Полученные данные возвращаются в виде объекта класс DhtData.

public DhtData getData() throws Exception {}

Из-за того, что очень часто при чтении возникают ошибки, функция пытается считывать данные из датчика до первой удачной попытки. Количество попыток ограничено 3.

if (atempts <= 3) {
  Gpio.delay(DHT_WAIT_INTERVAL);
  continue;
}

Если не удалось получить результат, тогда выбрасывается исключение.

Проблема в том, Raspberry Pi/Orange Pi не работают в режиме реального времени, то есть, программные задержки не всегда точны и это приводит к ошибкам при считывании данных.

Работа с DHT11 из Java

Ниже приведён пример программы, которая 10 запрашивает данные с интервалом в 2 секунд и выводит результат в терминале. Для DHT11 нужно создать экземпляр класса DHT11.

DHTxx dht11 = new DHT11(OrangePiPin.GPIO_07);

Схема подключения DHT11 к Orange Pi One

Raspberry Pi и Pi4J. Урок 8. Схема подключения DHT11 к Orange Pi One

Пример программы на Java, DHT11Test.java

package ru.micropi.pi4j.dhtxx.tests;

import ru.micropi.pi4j.dhtxx.DHT11;
import ru.micropi.pi4j.dhtxx.DHTxx;

import com.pi4j.io.gpio.OrangePiPin;

public class DHT11Test {

  private static final int DHT_WAIT_INTERVAL = 2000;

  public static void main(String[] args) {
    DHTxx dht11 = new DHT11(OrangePiPin.GPIO_07);
    System.out.println(dht11);
    try {
      dht11.init();
      for (int i = 0; i < 10; i++) {
        try {
          System.out.println(dht11.getData());
          Thread.sleep(DHT_WAIT_INTERVAL);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    } catch (Exception e1) {
      e1.printStackTrace();
    }
  }
}

Результат

DHT11, pin: GPIO 7
Temperature: 28.6°C
Humidity: 54.0%
Temperature: 28.6°C
Humidity: 53.0%
Temperature: 28.6°C
Humidity: 53.0%
Temperature: 28.6°C
Humidity: 52.0%
Temperature: 28.6°C
Humidity: 52.0%
Temperature: 28.6°C
Humidity: 52.0%
Temperature: 28.6°C
Humidity: 52.0%
Temperature: 28.6°C
Humidity: 52.0%
Temperature: 28.6°C
Humidity: 51.0%
Temperature: 28.6°C
Humidity: 51.0%

Работа с DHT22 из Java

Для работы с DHT22 создаём экземпляр класса DHT22, в остальном всё то же самое как и с DHT11.

DHTxx dht22 = new DHT22(OrangePiPin.GPIO_07);

Схема подключения DHT22 к Orange Pi One

Raspberry Pi и Pi4J. Урок 8. Схема подключения DHT22 к Orange Pi One

Пример программы на Java, DHT22Test.java

package ru.micropi.pi4j.dhtxx.tests;

import ru.micropi.pi4j.dhtxx.DHT22;
import ru.micropi.pi4j.dhtxx.DHTxx;

import com.pi4j.io.gpio.OrangePiPin;

public class DHT22Test {

  private static final int DHT_WAIT_INTERVAL = 2000;

  public static void main(String[] args) {
    DHTxx dht22 = new DHT22(OrangePiPin.GPIO_07);
    System.out.println(dht22);
    try {
      dht22.init();
      for (int i = 0; i < 10; i++) {
        try {
          System.out.println(dht22.getData());
          Thread.sleep(DHT_WAIT_INTERVAL);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    } catch (Exception e1) {
      e1.printStackTrace();
    }
  }
}

Результат

DHT22, pin: GPIO 7
Temperature: 28.3°C
Humidity: 48.7%
Temperature: 28.3°C
Humidity: 48.6%
Temperature: 28.3°C
Humidity: 48.9%
Temperature: 28.3°C
Humidity: 48.5%
Temperature: 28.3°C
Humidity: 48.5%
Temperature: 28.3°C
Humidity: 48.5%
Temperature: 28.2°C
Humidity: 48.4%
Temperature: 28.3°C
Humidity: 48.5%
Temperature: 28.2°C
Humidity: 48.4%
Temperature: 28.3°C
Humidity: 48.5%

Проекты Eclipse

DHT11 — Eclipse C++
DHTxx — Eclipse Java

Материалы

Adafruit Python DHT Sensor Library
Датчики температуры и влажности DHT11 и DHT22
Проект OpenNet: MAN sched_setscheduler (2) Системные вызовы (FreeBSD и Linux)

Похожие записи

Комментарии 15

  • Отличная статья!Жаль нет пошаговой инструкции для чайников.Будем разбираться 🙂

    • Благодарю, а если возникают вопросы, то можете их задавать, для этого и есть комментарии. Я всегда стараюсь держать баланс, если написать в деталях — возьмёт слишком много времени, а на оборот — многие не поймут.

  • Здравствуйте, спасибо за ваше руководство. Когда я запускаю программу, время от времени я получаю следующую ошибку. Мой метод использует аннотацию @Scheduled (fixedRate = 10000). Как ее решить?

    java.lang.Exception: Atempts 4

    Caused by: java.lang.Exception: DHT_ERROR_TIMEOUT: 32000 pulses, 70
    at ru.micropi.pi4j.dhtxx.DHTxxBase.getRawData(DHTxxBase.java:119)
    at ru.micropi.pi4j.dhtxx.DHT22.getData(DHT22.java:19)

    • Здравствуйте,
      проблема в том, что это «нормальная» ошибка, а всё дело в ОС Linux, ибо нельзя просто так взять и остановить работу системы и дать выполняться только приложению.
      Чтобы уменьшить количество ошибок, я установил максимальный приоритет во время чтения данных с датчика:

      Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
      

      Вы только можете увеличить количество повторений в методе

      public DhtData getData() throws Exception{}
      

      я поставил 3 раза:

              atempts++;
              if (atempts < = 3) {
                Gpio.delay(DHT_WAIT_INTERVAL);
                continue;
              }
              throw new Exception("Atempts " + atempts, e);
      

      но вы можете установить, к примеру, 10. Таким образом ошибка должна появляться реже.

  • Здравствуйте. Что-то не получается скомпилировать C++

    /tmp/ccRZ5e8m.o: In function `main’:
    /dht22/cpp/dht.cpp:12: undefined reference to `DHTxx::DHTxx(int, int)’
    /dht22/cpp/dht.cpp:14: undefined reference to `DHTxx::init()’
    /dht22/cpp/dht.cpp:16: undefined reference to `DHTxx::read(float*, float*)’
    /dht22/cpp/dht.cpp:12: undefined reference to `DHTxx::~DHTxx()’
    /dht22/cpp/dht.cpp:12: undefined reference to `DHTxx::~DHTxx()’

  • Добрый день!
    при компиляции получаю ошибку
    DHTxx.cpp:256:1: error: stray ‘\177’ in program
    датчик dht22

    • ))) сам дурак, все поправил.
      Подскажите еще такой момент, чем теперь удобнее собирать данные в файл или еще лучше прикрутить график для удаленного мониторинга?
      Насколько я понимаю это уже в perl или python, что лучше выбрать?
      Ранее программированием не занимался.

  • Ошибка компиляции 🙁
    sudo g++ -o app main.cpp DHTxx.cpp
    /usr/bin/ld: /tmp/cccirMw9.o: in function `DHTxx::init()’:
    DHTxx.cpp:(.text+0x88): undefined reference to `wiringPiSetup’
    /usr/bin/ld: /tmp/cccirMw9.o: in function `DHTxx::read(float*, float*)’:
    DHTxx.cpp:(.text+0x110): undefined reference to `pinMode’
    /usr/bin/ld: DHTxx.cpp:(.text+0x12a): undefined reference to `digitalWrite’
    /usr/bin/ld: DHTxx.cpp:(.text+0x148): undefined reference to `digitalWrite’
    /usr/bin/ld: DHTxx.cpp:(.text+0x164): undefined reference to `pinMode’
    /usr/bin/ld: DHTxx.cpp:(.text+0x1a4): undefined reference to `digitalRead’
    /usr/bin/ld: DHTxx.cpp:(.text+0x200): undefined reference to `digitalRead’
    /usr/bin/ld: DHTxx.cpp:(.text+0x266): undefined reference to `digitalRead’

  • Что еще можно сделать, куда копать ?

    • Я вижу два варианта, если вы указали правильный номер ножки:
      1. Отсутствие резистора, хотя это маловероятно;
      2. Не правильная версия WiringPi
      Для второго можете ещё раз установить используя эту инструкцию:

      BPI-M2

      git clone https://github.com/BPI-SINOVOIP/BPI-WiringPi.git -b BPI_M2

      https://micro-pi.ru/%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%be%d0%b2%d0%ba%d0%b0-%d0%bd%d0%b0%d1%81%d1%82%d1%80%d0%be%d0%b9%d0%ba%d0%b0-wiringpi-%d0%bd%d0%b0-banana-pi/

      Просто такая ошибка появляется, если невозможно считывать данные, а причины могут быть следующие: неправильно указана ножка (нужно по столбцу wPi), отсутствует резистор, WiringPi не тот.

      И еще одна банальная причина — это не с того конца считаете распиновку, я имею ввиду гребёнку на 40 пин, самый первый должно быть питание 3,3 Вольта

    • Вот мой результат по быстрому:

        ___  ____  _   ____   ____
       / _ \|  _ \(_) |  _ \ / ___|
      | | | | |_) | | | |_) | |
      | |_| |  __/| | |  __/| |___
       \___/|_|   |_| |_|    \____|
      
      Welcome to Armbian 21.08.3 Buster with Linux 5.10.60-sunxi
      
      System load:   3%               Up time:       92 days 20:50
      Memory usage:  19% of 999M      IP:            192.168.0.102
      CPU temp:      36°C             Usage of /:    6% of 55G
      
      root@orangepipc:~# mkdir DHT11
      root@orangepipc:~# cd DHT11/
      root@orangepipc:~/DHT11# ls -la
      total 8
      drwxr-xr-x  2 root root 4096 Feb 10 07:14 .
      drwx------ 11 root root 4096 Feb 10 07:14 ..
      root@orangepipc:~/DHT11# nano DHTxx.h
      root@orangepipc:~/DHT11# nano DHTxx.cpp
      root@orangepipc:~/DHT11# nano main.cpp
      root@orangepipc:~/DHT11# la -la
      total 24
      drwxr-xr-x  2 root root 4096 Feb 10 07:15 .
      drwx------ 11 root root 4096 Feb 10 07:14 ..
      -rw-r--r--  1 root root 6152 Feb 10 07:15 DHTxx.cpp
      -rw-r--r--  1 root root 2257 Feb 10 07:14 DHTxx.h
      -rw-r--r--  1 root root  931 Feb 10 07:15 main.cpp
      root@orangepipc:~/DHT11# sudo g++ main.cpp -o dht11 -lwiringPi -lpthread
      /usr/bin/ld: /tmp/ccroxAtQ.o: in function `main':
      main.cpp:(.text+0x16): undefined reference to `DHTxx::DHTxx(int, int)'
      /usr/bin/ld: main.cpp:(.text+0x1e): undefined reference to `DHTxx::init()'
      /usr/bin/ld: main.cpp:(.text+0x4c): undefined reference to `DHTxx::read(float*, float*)'
      /usr/bin/ld: main.cpp:(.text+0x18c): undefined reference to `DHTxx::~DHTxx()'
      /usr/bin/ld: main.cpp:(.text+0x198): undefined reference to `DHTxx::~DHTxx()'
      collect2: error: ld returned 1 exit status
      root@orangepipc:~/DHT11# sudo g++ main.cpp DHTxx.cpp  -o dht11 -lwiringPi -lpthread
      root@orangepipc:~/DHT11# ./dht11
      DHT_ERROR_CHECKSUM
      humidity = 52 %
      temperature = 22.1 °C
      humidity = 52 %
      temperature = 22 °C
      humidity = 52 %
      temperature = 22.1 °C
      humidity = 52 %
      temperature = 22.1 °C
      DHT_ERROR_TIMEOUT
      humidity = 52 %
      temperature = 22 °C
      humidity = 52 %
      temperature = 22.1 °C
      humidity = 52 %
      temperature = 22.1 °C
      humidity = 52 %
      temperature = 22.1 °C
      root@orangepipc:~/DHT11#
      



  • Судя по комментариям на гите, библиотеку под BPI-M2 делали с BPI-M3, а позже платы стали отличаться распайкой пинов, и поддежку моей платы выпилили.
    По указанной Вами ссылке ставил:
    home@banana:/mnt/lan/cc/BPI-WiringPi$ gpio readall
    Unable to determine hardware version. I see: Hardware : Allwinner sun8i Family
    ,
    — expecting BCM2708 or BCM2709. Please report this to projects@drogon.net

    Спасибо большое за участие но пожалуй вернусь к спайке Banan PiArduino

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *