Удаленный оповещатель о критических температуре и влажности на основе МК AVR и датчика DHT22

в 11:32, , рубрики: avr, C, DHT11, DHT22, датчик влажности, датчик температуры, микроконтроллеры, МК, программирование микроконтроллеров

После подряд 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. Код клиента

Автор: Ox2A

Источник

Поделиться новостью

* - обязательные к заполнению поля