После подряд 2х поломок кондиционера в серверной и последующего перегрева помещения в течение нескольких суток, встал вопрос о слежении за температурой в ней. Можно было бы ежедневно(ежечасно/ежеминутно) смотреть температуру со встроенных в сервера датчиков температуры используя интерфейс управления IPMI. Но в этом случае присутствует человеческий фактор на который, в данном случае, оказывает свое негативное осознание того, что можно было бы автоматизировать все гораздо лучше. Так случилось, что я как раз не так давно увлекся такой крайне интересной штукой как микроконтроллеры, поэтому задача автоматизации с использованием МК была новой и интересной возможностью реализовать накопленные знания в полезном для мира проекте.


Общая схема работы устройства и использованные инструменты.


За основу было решено взять одну из плат Arduino. Цены на китайские аналоги невысоки и вся необходимая обвязка МК компактно уже размещена на модуле. Изначально предполагалось использовать Arduino Pro + DHT22. Arduino Pro – так как самая дешевая, маленькая, но вполне функциональная плата. DHT22 — потому что этот датчик умеет, в отличие от более дешевого DS18B20, определять еще и влажность. А нам это тоже пригодится. Серверная по забавному стечению обстоятельств находится рядом с туалетом, который может затопить все вокруг себя совершенно без поднятия температуры, например.


При такой схеме, МК в случае превышения критической температуры или влажности, должен был отправлять по витой паре (длинной ~70м) до кабинета ИТ отдела сигнал. Ну, а здесь пиликать зуммером и мигать разноцветными светодиодами всячески оповещая нас о приближающемся апокалипсисе. К сожалению, это простое решение оказалось не годно по причине отсутствия прямой линии (без промежуточных хабов) между серверной и нашим кабинетом.


Фото 1-й версии устройства:


image


Тогда было решено подключить ардуинку к компьютеру по USB и передавать данные о температуре и влажности по UART. А значит нужна уже более навороченная Arduino Nano (у меня с МК ATMega328 на 16МГц). Нужно было заставить ее общаться компом, который работает под ОС Windows 7. На этом компе должна быть установлена серверная часть программы, которая должна получать по UART данные и оповещать все подключенные к ней клиентские модули, работающие где-то в локальной сети, о критических значениях температуры и влажности. На тот момент инструментами для написания программ под эту ОС я не располагал, поэтому пришлось в срочном порядке познакомиться с каким-либо популярным языком программирования, обладающим удобной средой написания ПО для Windows. Я остановился на C#. Прошивка для МК, собственно, писалась в AVR Studio 6 на С.


Итак, поскольку при реализации проекта я не нашел информации с примерами кода на C для датчика DHT22, то в этой статье решил заострить внимание на работе именно с ним. Так же в конце статьи вы найдете все ссылки на исходники и используемый инструментарий, для решения своей схожей задачи.


Кратко о работе датчика DHT22.


Его основные параметры:
Напряжение притания 3,3-6 В
Возможности измерения Влажность 0-100%; температура от -40 до 80C
Точность измерения Влажность +-2%(макс.+-5%); температура +-0,5C
Длительность перида измерения Около 2 секунд

Здесь отмечу, что в программе для МК я опрашиваю датчик примерно каждую секунду. У меня так работает. Если у вас с опросом начнутся проблемы, то начните с увеличения этого периода до 2х секунд.


Распиновка и схема подключения из даташита младшего собрата:


imageimage


Линия данных датчика подключена через 4,7кОм резистор к питанию, значит, в случае если датчик молчит или сломан/отсутствует, то на линии будет логическая единица. Для получения от датчика ответа нужно прижать линию данных к земле на 18 мС.
image


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


Всего данных передается 40 бит или 5 байт. Данные передаются начиная со старшего бита старшего байта.


Байт 5 — Старший байт значения влажности.
Байт 4 — Младший байт значения влажности.
Байт 3 — Старший байт значения температуры.
Байт 2 — Младший байт значения температуры.
Байт 1 — Контрольная сумма.


Ну и схема устройства:


image


К ноге D2 ардуины подключена шина данных датчика. К ноге D4 подключен светодиод питания. Кстати, если он мигает, значит датчик не прошел проверку при инициализации, проще говоря — не найден.


Переходим к коду прошивки.


Сначала краткое описание модулей.


UART_ATMEGA328.h


Это модуль с функциями для приема и передачи сообщений по UART. Кстати, в его составе есть функция void send_int_Uart(int i). Она получает целое 2х байтовое знаковое число, преобразует в строку и передает его по UART. Не ищите где используется эта функция — в данном проекте она не пригодилась.


DHT11-22_def.h


Содержит макросы настроек подключения датчика. Здесь указывается модель используемого датчика: _DHT22 или его упрощенного варианта _DHT11. PORT, DDR и PIN к которым подключен датчик. И номер пина к которому подключена линия данных. Эти макросы используются в следующем модуле:


DHT11-22.h


Содержит функции для работы с датчиком DHT22 или DHT11.


Код модуля DHT11-22_def.h:


#define _DHT22  /* Модель подключенного датчика. _DHT11 или _DHT22. */

#define _PORT_DHT  PORTD
#define _DDR_DHT   DDRD
#define _PIN_DHT   PIND
#define _PINNUM_DHT   2 /* Нумерация пинов с 0 */

Код модуля DHT11-22.h:


#define F_CPU 16000000UL 
#include "DHT11-22_def.h"

#define _MASK_DHT  ( 0b00000001 << _PINNUM_DHT )
#define _PIN_DHT_GET       ( (_PIN_DHT & _MASK_DHT)>>_PINNUM_DHT )
#define _PORT_DHT_SET(x)   ( ((x)==0) ? (_PORT_DHT&= ~_MASK_DHT) : (_PORT_DHT|= _MASK_DHT) )
#define _DDR_DHT_SET(x)    ( ((x)==0) ? ( _DDR_DHT&= ~_MASK_DHT) : ( _DDR_DHT|= _MASK_DHT) )

static unsigned char  checkSum,  packDHT[5]= {0,0,0,0,0},
    dhtHighDuration=0,  minLevel=0,  maxLevel=0,  dhtSplitLevel=0;  
static float    temperature, humidity;

float getTemp() { return temperature; }
float getHum()  { return humidity; }
unsigned char getCheckSum() { return checkSum; }
unsigned char getMinLevel() {return minLevel; }
unsigned char getMaxLevel() {return maxLevel; }
unsigned char getDhtSplitLevel() {return dhtSplitLevel; }

static char initDHT() { // В случае ошибки инициации датчика возвращает этап на котором она произошла, иначе 0.
    char  dhtErr=0; 
    _DDR_DHT_SET(1);    _PORT_DHT_SET(0);
    _delay_ms(19); 
    asm("cli");
    _DDR_DHT_SET(0);    _PORT_DHT_SET(1);
    _delay_us(10);
    if (!_PIN_DHT_GET)  dhtErr = 1;
    _delay_us(22);
    if ( (_PIN_DHT_GET)&&(!dhtErr) )   dhtErr = 2;
    _delay_us(88);
    if ( (!_PIN_DHT_GET)&&(!dhtErr) )  dhtErr = 3;
    _delay_us(77);
    if ( (_PIN_DHT_GET)&&(!dhtErr) )   dhtErr = 4; 
    return  dhtErr;
}

static void DhtMinMaxCalc()  { // Определяет максимальные и минимальньные длительности высокого уровня от датчика. Они нужны для последующего расчета dhtSplitLevel.
    dhtHighDuration= 0; // Если переменную создавать здесь, то показания неверные и в протеусе значение переменной не дебажится.
    while ( !_PIN_DHT_GET )    _delay_us(1);
    while (  _PIN_DHT_GET )  {
        _delay_us(1);
        dhtHighDuration++;
    }
    if      (minLevel > dhtHighDuration)  minLevel= dhtHighDuration;
    else if (maxLevel < dhtHighDuration)  maxLevel= dhtHighDuration;
}

unsigned char calibrateDHT() { // Для вычисления dhtSplitLevel. При успешной калибровке возвращает 1, иначе 0. 
    if ( initDHT() )  {   // Содержит  asm("cli");
        asm("sei");
        return 0;
    }
    for ( char bit=0;  bit < 40;  bit++)   DhtMinMaxCalc();
    asm("sei");

    dhtSplitLevel= (minLevel + maxLevel) / 2; // dhtSplitLevel - условное количество мкС, при удержании высокого уровня на пине данных датчика больше которых, считаем, что датчик передает 1.
    return 1;
}

static char getDhtBit() { // Возвращает бит данных в зависимости от длительности высокого уровня на пине данных датчика.
    dhtHighDuration= 0; // Если переменную создавать здесь, то показания неверные и в протеусе значение переменной не дебажится.
    while ( !_PIN_DHT_GET )    _delay_us(1);
    while (  _PIN_DHT_GET )  {
        _delay_us(1);
        dhtHighDuration++;
    }
    if ( dhtHighDuration < dhtSplitLevel )  return 0;
    return 1;
}
//###############################################
#if defined _DHT11
static void calcResults() { // Получает из прочитанного пакета данных от датчика humidity, temperature, checkSum для DHT11.
    temperature= packDHT[2];
    humidity= packDHT[0];
    checkSum= packDHT[4];
}
//###############################################
#elif defined _DHT22
static void calcResults() { // Получает из прочитанного пакета данных от датчика humidity, temperature, checkSum для DHT22.
    temperature= packDHT[3]*0.1 + (packDHT[2] & 0b01111111)*25.6;
    if (packDHT[2] & 0b10000000)  temperature*= -1;
    humidity=  packDHT[1]*0.1 + packDHT[0]*25.6;
    checkSum= packDHT[4];
}
#endif
//###############################################

unsigned char readDHT() { // Возращает 1, если чтение датчика прошло успешно, иначе 0.   
    if ( initDHT() )  {   // Содержит  asm("cli");
        asm("sei");
        return 0;
    }
    for (unsigned char byte=0;  byte < 5;  byte++) {   // Начало считывания пакета данных от датчика.
        packDHT[byte] = 0;
        for ( char bit=0;  bit < 8;  bit++)
            packDHT[byte]= (packDHT[byte] << 1) | getDhtBit();
    }
    asm("sei");

    calcResults();
    return 1;
}

Комментарии по коду.


1. Анализ контрольной суммы добавлять не стал, хотя и сохраняю ее в переменной checkSum.


2. При подсчете длительности периода высокого уровня на шине данных для определения значения получаемого бита решил обойтись без использования таймера МК поскольку посчитал это допустимым упрощением для данного проекта. Но добавил функцию unsigned char calibrateDHT(), в которой подсчитывается переменная dhtSplitLevel содержащая среднеарифметическое значение между самым коротким периодом высокого уровня (при передаче бита 0) и самым длинным периодом высокого уровня (при передаче бита 1). Далее эта переменная используется для определения значений битов данных при последующих обращениях к датчику.


3. Для использования в проекте более дешевого датчика DHT11, нужно указать его в модуле DHT11-22_def.h вместо DHT22. Правда, работу с ним протестировать не удалось по причине его отсутствия.


Пример использования:


int main() {  
    // ...
    char st[6];  
    _delay_ms(999); 
    calibrateDHT();
    while(1)  {
        _delay_ms(999); 
        if ( !readDHT() )  continue;

        itoa( getTemp(), st, 10); // Запись в переменную st целой части значения температуры.
        // Здесь передаем переменную st по UART, на LCD или еще что-то делаем... 

        itoa( getHum(), st, 10); // Запись в переменную st целой части значения влажности.
        // Здесь передаем переменную st по UART, на LCD или еще что-то делаем...
    }  
}

Ну и напоследок фото и видео работы получившегося устройства:


image





Ресурсы:


  1. Для записи .hex-а в ардуино использовал программу Xloader v1.00
  2. Для приема и передачи сообщений по UART во время отладки и тестирования использовал программу Terminal v1.9b by Br@y++
  3. Готовые прошивка для Arduino Nano (ATMega328, 16МГц), сервер и клиент под Windows, а так же печатка платы (ссылка)
  4. Код прошивки
  5. Код сервера
  6. Код клиента
Поделиться с друзьями
-->

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


  1. x893
    14.09.2016 14:45
    +1

    Отличная курсовая работа. Для серверной конечно тоже пойдет, но как-то стрёмно. Чем не подошли датчики в серверах, дисках — не понятно. И почтой можно, и SMS, и голосом звонить в случае аварии. Конечно и выключать нужно оборудование.


    1. Francyz
      14.09.2016 16:10

      Или тот же NetPing


      1. Ox2A
        14.09.2016 19:00

        Глянул нетпинг — отличный проект. Но зачем стрелять из пушки по воробьям? Цена данного данного устройства — 350 рублей, а сколько стоит нетпинг с необходимой мне минимальной функциональностью? Ну и все-таки тот проект закрытый, как я понимаю, там код никто не предоставляет.


  1. mcleod095
    14.09.2016 15:12

    Неплохо, но только вопрос, если приведен код стандартной либы, то зачем он здесь, если свой, то зачем использовать прерывания?
    Ну и в догонку, а не проще использовать ds18b20, недорогие, 3 провода и до 127 штук на шину. Время опроса 700мс, хоть каждую секунду снимай показания.


    1. compdemon
      14.09.2016 16:48

      DHT22 умеет еще и влажность мерять. Плюс в сравнении с DHT11 (тоже, кстати, недорог) заявлена хорошая точность измерений (почти на уровне SHT21).
      Возможно поэтому.


    1. Ox2A
      14.09.2016 19:39

      Как я написал в статье, примеров кода на си (не скетчи) для датчика DHT11/22 не удалось найти, поэтому привожу здесь, естественно, свой код. Блокирую прерывания на период подсчета таймингов, чтобы те не вклинивались, если будут. В данном проекте работу прерываниями не использую, но, может, буду их использовать в следующем.


  1. LumberJack
    14.09.2016 19:19
    +1

    Ардуинка+EtherShield, свободный порт в свиче, пара десятков строк кода в IDE из примеров и вот уже устройство начинает отсылать данные на сервер. А там уже крути ими куда захочешь.


    1. Ox2A
      15.09.2016 14:48

      Отличный вариант, я его тоже рассматривал. Только именно серверную часть, написанную на С# пришлось бы перенести в МК. Научить МК отвечать клиентам. К сожалению, не нашел вовремя как можно подключить найденную для него ардуиновскую библиотеку к коду на Си в Atmel Studio. А сам, понял, что быстро не осилю написание библиотеки для работы с этим модулем, поэтому нашел другое решение. А вот по поводу «пара десятков строк кода в IDE» — вы не правы, для нормальной и надежной эксплуатации устройства 20ю строками кода не обойдешься.


  1. jo_b1ack
    14.09.2016 19:26

    Делал нечто подобное, только слал инфу в заббикс кроном через zabbix_sender с помощью самописной прожки, опрашивающей com

    скрин


  1. htol
    14.09.2016 19:30

    Если не хотите, чтобы третий раз, когда сгорел кондей, северной опять стало плохо. Внедрите в код проверку на максимальное время ожидания. Вот заглючет чего-нить и не выйдете больше из своего while()…


    1. Ox2A
      14.09.2016 19:44

      Вы про какой именно из while-ов? Если устройство не сможет адекватно ответить серверу, а сервер клиентам в сети, то клиенты сразу запаникуют сообщениями из трея. За пару месяцев работы пока таких прецедентов не было.


      1. htol
        14.09.2016 20:24

        Ну например вот этот while ( !_PIN_DHT_GET ) _delay_us(1); в getDhtBit(). Если есть проверка в клиенте, то это конечно хорошо. А потом вам захочется сделать устройство автономным, а этот кусок так и останется опасным. У каждого из пульса есть задокументированная максимальная длительность. Вот это значение и надо ставить как выход из соответсвующих циклов.


        1. Ox2A
          14.09.2016 20:48

          Ага, только когда датчик молчит, то на пине данных наиболее вероятна 1, поэтому ваше замечание больше подойдет для цикла, где считаю длину паузы высокого уровня:
          while ( _PIN_DHT_GET ) {… }
          Но там дополнительные условия я бы добавлять не хотел, дабы не увеличивать время работы цикла подсчета таймингов. Пусть эта возможная ошибка отлавливается на уровне сервера и клиента. А если устройство будет автономным, то да, этот момент нужно будет отследить.


          1. htol
            14.09.2016 21:08

            Дело ваше. Если что-то плохое может случится, то оно обязательно случится. Чтобы не парится с проверкой условия в цикле и не жалеть циклов процессора, можно все переделать на использование таймера в режиме capture и сделать все на прерываниях.


  1. gabirx
    14.09.2016 20:17

    у DHT22 через пару лет в погребе поползла температура вверх. На 10+ градусов стал врать. Пока руки не дошли разобраться в чем причина…


    1. htol
      14.09.2016 20:26

      У него в самом даташите перечислены все случаи при которых он начинает деградировать. В целом дешевая поделка не расчитаная на тяжелые условия эксплуатации.


    1. Ox2A
      14.09.2016 21:52

      Для более жестких условий можно использовать DHT21.


      1. gabirx
        14.09.2016 22:11

        Да я бы не сказал, что условия жесткие — семенной картофель ведь сохраняется. Ну и банки с огурцами :)


        1. Ox2A
          14.09.2016 22:26

          Возможно, условия становятся жесткими на 2й год.


  1. Shamrel
    14.09.2016 20:23
    -2

    Разве эта статья не для Geektimes?


  1. Godless
    14.09.2016 22:22

    для серверной нужно нечто больше чем dht22 по uart.
    нужна полноценная система мониторинга на выбор + смс всей команде в критических случаях. Вы не поверите сколько времени можно экономить на диагностике, поиске проблем, и, главное, на времени реакции имея zabbix/nagios/etc. Были случаи когда умирал кондей без электричества. и по той же причине уже не было тырнета вне серверной.
    Продумайте апгрейд)) а температуру можно снимать с жестких дисков из смарта и датчиков с материнки в любой ОС.
    А за поделку 5. симпатично вышло. ;)


    1. Ox2A
      15.09.2016 13:31

      Спасибо, за инфу о zabbix-е. Он тут уже упоминался. Глянул его сейчас, радует, что эта система мониторинга еще и бесплатна, на досуге нужно будет с ней познакомиться. Но в простом варианте установки системы мониторинга на сервере с настройкой уведомлений, получается, если уведомление по к-л причинам не доходит до меня, то я не узнаю о критической ситуации. В своем же проекте для мониторинга я применил связку клиент-сервер. Клиент постоянно опрашивает сервер и если тот вдруг пропал из сети, то клиент тут же начинает махать флажками и кричать, и я узнаю о критической ситуации точно так же как и при нормальном сообщении с сервера о критической Т, например.


      1. Godless
        16.09.2016 10:44

        Вы все это сможете настроить в любой системе мониторинга. Начните настраивать и вы все поймете, вон Заббикс 3.2 вышел. Любой USB свисток и симка в заббикс сервер (+ почта ессно) не дадут вам пропустить событие.
        Безусловно проблема пропуска уведомления существует. Каждый решает по своему. Мне нравится почта + смс при очень важных событиях. Мы например сейчас меняем сервера, переключаем часто туда-сюда функционал, адреса и не мудрено что-то упустить. Дак вот система мониторинга позволяет ОЧЕНЬ оперативно понимать что не так в сети.
        Пропал тырнет — смс. Повысилась температура на ХДД — смс, восстановилась температура — смс. Не пингуется полчаса основной сайт конторы на хостинге — смс.
        У нас серверов не много, справляется и ПК на 775 сокете. У меня требования были только 2 ядра и поддрежка х64 (для бубунты 16.04). Ну и памяти отрыли 2 гига. все.
        А вот к заббиксу уже интересно прикрутить сирену в кабинет с красно/желто/зеленой мигающей лампочкой))) Или вообще звуковые уведомления.


        И еще. Я заббикс привожу как пример, потому что он мне нравится и работает у нас. Nagios ни чуть не хуже. Может он устроит вас больше.


  1. monah_tuk
    15.09.2016 10:48

    Вопрос не в тему: где вы покупаете стойки? Те что используете для сборки "корпуса", что на последнем фото. Или это просто длинный болт с накрученной изолентой? :)


    1. Ox2A
      15.09.2016 13:33

      Это болты с термоусадкой.


  1. akashihi
    15.09.2016 13:28

    Смотрю популярная проблема. Как раз на днях делал подобный измеритель в серверную на stm32.
    image


  1. ugsm
    15.09.2016 13:33

    Можно использовать встроенный светодиод на ардуине (D13) — минус 2 детали. DHT22 уже имеет резистор подтяжки (в отличие от DHT11) — еще минус деталь. Схема упрощается до трех проводков.
    Минус DHT11 — только целые показания температуры и влажности, DHT22 выдает десятые доли. Ну и точность/разброс значений у разных экземпляров тоже в пользу DHT22


    1. Ox2A
      15.09.2016 13:36

      Я знаю, что на ардуино-модулях вместе с этим датчиком установлен подтягивающий резистор. А так DHT22 в схемах везде нарисован с добавлением резистора, так же как и DHT11. Вы точно не путаете?


      1. ugsm
        15.09.2016 14:10

        В даташите https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf нарисовано подключение без подтяжки. На что намекает и пункт в особенностях датчика: *Extra components not needed (из того же даташита).
        Как показала практика работы с несколькими такими датчиками, резистор не нужен (однако не проверял на длинных расстояниях, 1-2 м было всегда достаточно).
        В конце статьи https://habrahabr.ru/company/flprog/blog/244083/ есть внутренности датчика, похоже, что резистор уже есть.


        1. Ox2A
          15.09.2016 14:33

          Я, честно говоря, как-то с сомнением отнесся к той схеме подключения DHT22 из даташита, подумав, что она просто является результатом его корявого оформления)


  1. Dagobertus
    16.09.2016 10:08
    +1

    Каждый сисадмин знакомый с микроконтроллерами должен сделать свою железку для мониторинга серверной.
    Я не исключение, только решил не городить ворох самодельных утилит и серверов, а докупил модуль для подключения в локальную сеть и добавил минимально необходимую поддержку snmp. Теперь смотрю за условиями среды через кактус.
    Одна беда библиотека snmp отъела большой объем памяти контроллера.