Пользователь
0,0
рейтинг
29 января 2013 в 03:25

Raspberry Pi: измеряем влажность и температуру с помощью DHT11/DHT22

На Хабре уже публиковалась статья о подключении датчика температуры DS18B20 к Raspberry Pi. В нашем новом проекте, который строится на Raspberry Pi, понадобилось измерять не только температуру, но и влажность. Я расскажу, как подключить недорогие китайские датчики влажности к Raspberry Pi. Просмотрев несколько вариантов различных датчиков, остановился на двух наиболее массовых на рынке датчиков. Это DHT11, который привлек своей ценой $3 (с доставкой) и датчик DHT22 (около $10 с доставкой).

Основная разница между ними в диапазоне температур и точности измерения:

DHT11

  • Влажность 20-80% +- 5%
  • Температура 0-50 °С+- 2%
  • Данные считываются в целых единицах.

DHT22

  • Влажность 0-100% +- 5%
  • Температура -40-125 °С +- 0.5%
  • Данные считываются с точностью до десятых.



Подключение

Подключение к Raspberry Pi особой сложности не представляет: подключаем + от датчика к +5V на Raspberry Pi, "-" — к земле, и сигнал к одному из GPIO выводов.


Устанавливаем ПО

Оба датччика используют свой протокол вместо стандартизированного 1 wire, поэтому программное обеспечение для снятия показаний датчика тоже будет отличаться.

Сначала установим библиотеку на С для работы с GPIO www.open.com.au/mikem/bcm2835/index.html
wget http://www.open.com.au/mikem/bcm2835/bcm2835-1.15.tar.gz
tar xzf bcm2835-1.15.tar.gz
cd bcm2835-1.15/
./configure
make
make install


Для чтения данных с датчика за основу был взят файл на С Adafruit_DHT_Driver. Без внесения некоторых изменений работать с DHT22, этот код отказывался, пришлось немного изменить.
Поэтому привожу модифицированную версию.
Файл readDHT.c
//  How to access GPIO registers from C-code on the Raspberry-Pi
//  Example program
//  15-January-2012
//  Dom and Gert
//


// Access from ARM Running Linux

#define BCM2708_PERI_BASE        0x20000000
#define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <bcm2835.h>
#include <unistd.h>

#define MAXTIMINGS 100

#define DHT11 11
#define DHT22 22
#define AM2302 22

int readDHT(int type, int pin);

int main(int argc, char **argv)
{
 if (!bcm2835_init())
       return 1;

 if (argc != 3) {
       printf("usage: %s [11|22|2302] GPIOpin#\n", argv[0]);
       printf("example: %s 2302 4 - Read from an AM2302 connected to GPIO #4\n", argv[0]);
       return 2;
 }
 int type = 0;
 if (strcmp(argv[1], "11") == 0) type = DHT11;
 if (strcmp(argv[1], "22") == 0) type = DHT22;
 if (strcmp(argv[1], "2302") == 0) type = AM2302;
 if (type == 0) {
       printf("Select 11, 22, 2303 as type!\n");
       return 3;
 }

 int dhtpin = atoi(argv[2]);

 if (dhtpin <= 0) {
       printf("Please select a valid GPIO pin #\n");
       return 3;
 }


 printf("Using pin #%d\n", dhtpin);

readDHT(type, dhtpin);
 return 0;

} // main


int bits[250], data[100];
int bitidx = 0;

int readDHT(int type, int pin) {
 int counter = 0;
 int laststate = HIGH;
 int j=0;
 int i=0;
 // Set GPIO pin to output
 bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP);
 bcm2835_gpio_write(pin, HIGH);
 usleep(100);
 bcm2835_gpio_write(pin, LOW);
 usleep(20000);
 bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_INPT);

 data[0] = data[1] = data[2] = data[3] = data[4] = 0;
 // read data!

 for (i=0; i< MAXTIMINGS; i++) {
    counter = 0;
    while ( bcm2835_gpio_lev(pin) == laststate) {
       counter++;
       nanosleep(1);           // overclocking might change this?
       if (counter == 100)
         break;
    }
    laststate = bcm2835_gpio_lev(pin);
    if (counter == 100) break;
    bits[bitidx++] = counter;

    if ((i>3) && (i%2 == 0)) {
     // shove each bit into the storage bytes
     data[j/8] <<= 1;
     if (counter > 16)
       data[j/8] |= 1;
     j++;
    }
 }


#ifdef DEBUG
 for (int i=3; i<bitidx; i+=2) {
    printf("bit %d: %d\n", i-3, bits[i]);
    printf("bit %d: %d (%d)\n", i-2, bits[i+1], bits[i+1] > 15);
 }
#endif

 printf("Data (%d): 0x%x 0x%x 0x%x 0x%x 0x%x\n", j, data[0], data[1], data[2], data[3], data[4]);

 if ((j >= 39) &&
     (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) ) {
    // yay!
    if (type == DHT11)

printf("Temp = %d *C, Hum = %d \%\n", data[2], data[0]);
    if (type == DHT22) {
 float f, h;
       h = data[0] * 256 + data[1];
       printf ("%s\n",h);
       h /= 10;

       f = (data[2] & 0x7F)* 256 + data[3];
       f /= 10.0;
       if (data[2] & 0x80)  f *= -1;
       printf("Temp =  %.1f *C, Hum = %.1f \%\n", f, h);
    }
    return 1;
 }

 return 0;
}


Компилируем
gcc readDHT.c -lbcm2835 -lrt -o readDHT

Пробуем считывать данные
./readDHT {тип датчика 11 или 22}  {номер GPIO вывода Raspberry PI}

Например DHT11 подключен к GPIO4
root@raspberrypi /var/www/application/scripts/DHT # ./readDHT 11 4
Using pin #4
Data (40): 0x23 0x0 0x17 0x0 0x3a
Temp = 23 *C, Hum = 35 %

или DHT22 подключен к GPIO17
root@raspberrypi /var/www/application/scripts/DHT # ./readDHT 22 17
Using pin #17
Data (40): 0x1 0x75 0x0 0xea 0x60
Temp = 23.4 *C, Hum = 37.3 %


При реализации вызова readDHT нужно учитывать, что если скорость обращения будет высокая ( чаще чем раз в секунду) вместо данных Вы будете получать CRC Error.

Сохранение данных с датчиков


Полученные данные нужно куда-то сохранять, приведу пример как можно сохранять данные в Google.docs. Т.к. мне ближе PHP, сохранение сделал на PHP с использованием Zend_Gdata_Spreadsheets.

imageПодготовим google docs. Создадим новую таблицу и дадим ей имя. Дадим имена колонкам первой datetime, второй temperature, третей humidity. Из адресной строки скопируем id нашей таблицы, по нему будет производиться обращение к таблице.

Устанавливаем php:
pi@raspberrypi ~ $ sudo apt-get php5 php5-curl unzip

Создадим папку для нашего проекта:
pi@raspberrypi ~ $ mkdir /home/pi/dht

Загрузим и распакуем Zend Framework:
pi@raspberrypi ~ $ mkdir /home/pi/dht/library
pi@raspberrypi ~ $ cd /home/pi/dht/library
pi@raspberrypi ~ $ wget http://packages.zendframework.com/releases/ZendFramework-1.12.0/ZendFramework-1.12.0-minimal.zip
pi@raspberrypi ~ $ unzip ZendFramework-1.12.0-minimal.zip
pi@raspberrypi ~ $ ln -s ZendFramework-1.12.0-minimal/library/Zend Zend


DHTtoGoogleDocs.php
<?php

ini_set("include_path",get_include_path().':/home/pi/dht/library');

require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata_Spreadsheets');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin');

define('GDATA_USER','googleusername');
define('GDATA_PASSWORD','google user password');
define('GDATA_SPREADSHEET_KEY','spreadsheetkey from url');
define('GDATA_WORKSHEET_ID','od6');

try {

  $t = new Temperature_DHT();
  // get data from sensor
  $data = $t->getData(11,4);

  $service = Zend_Gdata_Spreadsheets::AUTH_SERVICE_NAME;
  $client = Zend_Gdata_ClientLogin::getHttpClient(GDATA_USER, GDATA_PASSWORD, $service);
  $service = new Zend_Gdata_Spreadsheets($client);

  // add row to spreadsheet
  $row = array(
          'datetime'=>date("Y-m-d H:i:s"),
          'temperature'=>$data[0],
          'humidity'=>$data[1],
  );
  $service->insertRow($row, GDATA_SPREADSHEET_KEY, GDATA_WORKSHEET_ID);

} catch (Exception $e) {
  die( $e->getMessage() );
}



class Temperature_DHT

{
       private $_maxFailCount=5;

       public function getData($type, $pin)
       {

               $count = 0;

               while ($count<=$this->_maxFailCount)
               {
                       $count++;
                       $filename = '/home/pi/dht/readDHT';

                       $out = exec   ("$filename $type $pin");
                       if(preg_match("'^Temp = ([0-9\.]+) \*C, Hum = ([0-9\.]+) %'", $out,$result))
                       {


                               return array($result[1],$result[2]);
                       }

               }

       }
}



Проверяем работу файла, запускаем:
pi@raspberrypi ~ $  php DHTtoGoogleDocs.php

Осталось только поставить вызов «php DHTtoGoogleDocs.php» в cron.

В качестве эксперимента данные собирались почти месяц с интервалом в 10 минут в одном помещении и сохранялись в google docs, датчики были расположены рядом. Кому интересно можете посмотреть на разброс значений.
Из данных, которые собирали оба дачика, можно сказать, что если нужно просто понять меняется ли влажность, то датчика DHT11 вполне достаточно. Но, если Вы хотите, что бы отображаемое значение было близко к значению бытовой метеостанции, то лучше использовать DHT22.
Fancy @Fancy
карма
7,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое

Комментарии (33)

  • +4
    Из Raspberry PI постепенно получается этакий Arduino level 2
    • +1
      А самое интересное зачем? Raspberry PI это недорогой компьютер, а Arduino это микроконтроллер, который имеет встроенные ЦАП/АЦП — это 2 устройства сделаны на разные области применения и нет большого смысла перемещать функции одного, на второго.
      Более логично все датчики навешивать на Arduino, а саму Arduino уже подключать к Raspberry PI!
      Тогда будет возможность повешать не только один датчик и быстро публиковать на Хабр свой подвиг, а сделать действительно хорошую стартовую площадку для измерения всего: температуры, влажность, яркость,… и не только замерить, а еще и управлять другими девайсами.
      • 0
        Даже если всего два датчика? Если данных небольшое количество, но частые обращения, то передача их может быть более затратной, даже если обработчик работает быстрее с ними. Я КЭП :)
        • 0
          Какие еще затраты и частые обращения? Это все программно легко реализуется и количество обращений и затраты.

          Если всего 1-2 датчика, это только программа «Hello world», а как же дальше развитие?!
          «Hello world» — это не тупик развития, а только первый шаг.

          С другой стороны, плата Arduino имеет кучу плюшек для работы с температурами/влажностями и тому прочее — подключение батарейки и индикатора. Что бы оперативно посмотреть температуру не придется грузить операционную систему.
      • +1
        А кто говорит, что нельзя все это делать сразу на малинке? Зачем промежуточное звено, если кроме простого сбора информации можно ее обрабатывать, отображать, управлять удаленно и т.д. и т.п… Для этого в малинке и предназначены выводы GPIO.
        • 0
          Это можно делать, если данные потом нужно передать по Ethernet. Но если датчиков много, то лучше собрать с них всех информацию и через Rasperry передать дальше. Не стоит забывать, что Arduino «грузится» 100 ms или секунду, а Rasperry с Linux требуется минута или больше. А вообще, каждой задаче, своё решение. Где то, одна Rasperry, где то Rasperry + Microcontroller * X.
        • 0
          Нет доступа к таймерам, облегчающим декодирование таких сигналов, нет обработки в реальном времени. Вот пример того, как это делается при наличии прямого доступа к железу: habrahabr.ru/post/160017/#comment_5755239 Думаю, на BCM2835 тоже возможно подобное.
      • +1
        Мне нравится, то что устройство одно (Raspberry PI) вместо 2-х (Arduino + Raspberry PI).

        Относительно измерения других параметров: попробую отдельно подготовить информацию о том, как можно производить измерения беспроводных датчиков температуры, яркости и управления реле.

        Управлять с помощью Raspberry PI, тоже не сложно, реле на +3V можно подключить прямо к одному из GPIO.
        • 0
          Тут писал, как работать с датчиками Oregon с помощью Ардуино. Аналогично можно и к «малинке» подключить.
      • 0
        самое интересное, что кому-то удобнее прошить ардуину и радоваться, до следующей перепрошивки. А кому-то, удобнее дергать пины в полноценной операционной системе удобными средствами shell/python/perl/php.
    • +4
      Я кажется начинаю догадываться, почему так получается.
      Raspberry Pi как и arduino сейчас у всех на слуху, люди их покупают в больших количествах, а потом и думают, что с ними делать. В итоге ничего больше не придумывают, как очередную вариацию термометра или мигания светодиодом )
  • 0
    Не могли бы Вы помочь с модернизацией скриптика получения данных с датчиков по 1-wire get_temp.pl, описанного здесь, дабы получать данные совместно и с Далласа, и DНТ, записывая их в rrdtool? Буду признателен.
  • 0
    Какие-то странные данны температуры DHT22 в этой строке
    10/28/12 17:40 25 38 49.6 52.6
    с чем это могло быть связано?

    И заодно — у вас опечатка:
    «или DHT11 подключен к GPIO17»
    должно быть «или DHT22 подключен к GPIO17»
    • 0
      Да, несколько раз температура на DHT22 показывала странные значения. Еще видно, что разброс влажности между датчиками временами был тоже очень большой. Субъективное мнение, что если нужно строить систему управления например отоплением/кондиционированием, то лучше не принимать решение на основе первого значения, а только после серии измерений.
      • 0
        Рекомендую попробовать
      • 0
        Еще обратите внимание на следующие особенности этих датчиков:

        1. Опрос не чаще одного раза в 2 секунды (они реально медленные, но температура и влажность тоже не сильно быстро меняются в обычных условиях)
        2. DHT11 — достаточно «грубый» датчик (температура с погрешностью +2%, влажность +5%) и работает только при положительных температурах
        3. DHT22 — этот получше и работает уже от -40°C и погрешность поменьше ( +0,5%, влажность +2%)

        Но даже зная эти паспортные данные, два датчика (11 и 22), находящиеся рядом, могут показывать влажность, которая отличается на 20% — так что это только «оценка».

        Для измерения температуры DHT11 вообще плохо подходит, имхо. Хотя, возможно, где-то и такие грубые значения сойдут.
        • 0
          Когда натолкнулся на эти датчики, делал эксперимент. Использовал 5 датчиков DHT22, которые расположил в ряд. Писал данные неделю. Как показал эксперимент, один датчик был явно бракованный, у остальных показания влажности разнились до 10%. Температуру все регистрировали одинаково.

          Датчики не плохи для домашнего применения. Но в коммерческий продукт я бы постеснялся их интегрировать.
          • 0
            Полностью согласен.

            Хотя если использовать их в паре с DS18B20, то значение влажности с них (DHT22) — можно взять достаточно смело (но только для домашнего применения в составе погодной станции, например).

            Предпринимать какие-то действия по ним я бы тоже не стал.

            Хотя если у меня газовый отопительный котел «задыхается» (не хватает вытяжки и он результаты горения начинает отдавать в непосредственно в помещение, где стоит) — влажность (особенно под потолком) возрастает очень сильно и этот факт можно использовать для оповещения. Но там увеличение влажности идет сразу на несколько десятков процентов за достаточно короткое время — сложно такое пропустить.

            P.S. котел оборудован собственной штатной системой защиты от всего, что с ним может случиться и сам «встанет», когда поймет, что с вытяжкой проблемы, но случается это обычно сильно позже того, как происходит описанное выше.
            • 0
              А каким образом котёл может выдавать результаты горения в помещение? В камеру горения поступает воздух. Клапан позволяет движение только в одну сторону. Результаты горения вытягиваются наружу по дымоходу за счёт разницы температуры или вентилятором. Газ поступает под давлением из конфорки. Если вытяжка не справляется, то в камере просто сгорит весь кислород, после чего пламя потухнет само. Самое страшное, что может случиться — это газ будет выкидываться в дымоход. И то датчики горения в бойлерах ставили даже в восьмидесятых. Может и раньше, просто не жил с более старыми бойлерами.
              • 0
                Котел современный — Electrolux.

                У него нет принудительной вытяжки (т.е. нет вентилятора, который выдувает продукты горения в вытяжную трубу).

                Из-за непродуманной системы вентиляции (плохая приточная вентиляция, недостаточно утепленная вытяжная труба) бывает такое, что в вытяжной трубе (особенно при сильных морозах) воздушный поток «опрокидывается» — со всеми вытекающими.
                При этом кислород в область горелки подается и она не сразу гаснет, но вот продукты горения (вода и углекислота) начинают сочиться откуда только могут, что и приводит к соответствующему эффекту.

                Безусловно, это аварийная работа котла (и это мониторится не только самим котлом, но и моими «поделками»: мониторятся температуры в доме, температуры батарей, влажность и температура в помещении, где стоит котел (там повыше этот датчик стоит для этих целей), дополнительно к котлу подключен хронотермостат (для комфорта) и параллельно ему еще одна «поделка», которая управляется СМС-командами (если требуется ввести «ручное» управление котлом) и т.п.).
                Котел можно с помощью соответствующей СМС перезапустить (по питанию).
                • 0
                  Спасибо, довольно интересный случай. Немного интересуюсь, так как хороший приятель работает инжинером в British Gas и часто слышу истории, но "чтобы начинают сочиться откуда только могут" ещё не помню.
            • 0
              Так внутри DHT22 вроде как раз и стоит DS18B20 + аналоговый датчик влажности + микросхемка с обвязкой. И показания у них сходятся (десять DS18B20 и два DHT22 при комнатной температуре показывают с разницей максимум +- 0.2 градуса)

              upd: кстати, $10 за DHT22 дороговато, их на ебее по $5 продают с бесплатной доставкой.
  • 0
    Неплохое продолжение статьи про взаимодействие DS18B20 и Малинки, теперь и с DHT22. Ещеб протокол передачи показаний на карту добавили.
    • 0
      Ух ты! Странно, что не было ещё отдельного поста про cosm.
      • 0
        Я не Cosm имел ввиду вообще-то, но тож поддерживается
  • 0
    Отлично.

    Только было бы здорово, если бы показали, что именно вы модифицировали в С Adafruit_DHT_Driver и почему. Кстати, авторам отписали?
    • 0
        bcm2835_gpio_write(pin, HIGH);
        usleep(500000); // 500 ms
        bcm2835_gpio_write(pin, LOW);
      

      заменен на
        bcm2835_gpio_write(pin, HIGH);
        usleep(100); // 500 ms
        bcm2835_gpio_write(pin, LOW);
      

      Остальные изменения не принципиальны. Разработчики в курсе.
      • 0
        Попробовал повторить, на моем Raspberry+DHT22 завелось только с несколькими изменениями в коде. Если интересуют могу выслать. Для себя понял что связка Raspberry+DHT работает крайне неудовлетворительно, очень велик процент ошибок. Для уверенного снятия результатов с этого датчика — жизненно необходим какой нибудь контроллер. На stm32 за пол года измерений раз в 10 минут ошибок пока не видел, а тут каждое 5-10 измерение в топку. Для ради баловства подходит.
        • 0
          Вышлите мне, пожалуйста. Купил DHT22 (как раз для баловства)
  • 0
    Плюсую к нестабильности… Вот сижу и не могу завести никак… всё поставил уже и прозвонил… Новую версию от адафрута заюзал… и нифига… мусор летит и то не с первого раза даже
    • 0
      Я разобрался со своей проблемой. Вот новая версия библиотеки от адафрута github.com/adafruit/Adafruit_Python_DHT
      Там в файле pi_dht_read.c есть такой код в как-бы-реалтайм области

       // Set pin low for ~20 milliseconds.
        pi_mmio_set_low(pin);
        busy_wait_milliseconds(20);
      
        // Set pin at input.
        pi_mmio_set_input(pin);
        // 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.
      


      Если проверить принтом насколько уровень 'still low', то легко убедиться что он действительно LOW. У меня начало работать с цифрой 70. думаю переписать на проверку этого уровня цикличную

      P.S. Пошел разбираться что такой пул реквест и как его сделать…

      Вот рабочая строчка вместо for():
      while (!pi_mmio_input(pin));

  • 0
    У меня не получается записать значения в Google Docs
    В php файле указал свой логин, пароль и key, но скрипт все равно выдает ошибку.

    sudo php DHTtoGoogleDocs.php PHP Notice: Undefined offset: 1 in /home/pi/dht/library/ZendFramework-1.12.0-minimal/library/Zend/Gdata/ClientLogin.php on line 150 DOMDocument cannot parse XML: DOMDocument::loadXML(): Empty string supplied as input

    может есть какие нибудь подводные камни?
  • 0
    Для запуска под Raspberry Pi 2 потребовалось использовать последнюю версию библиотеки bcm2835 (на текущий момент это 1.50). Из дополнительных плюсов — версия 1.50 не требует root для работы с GPIO.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.