Віддалений оповіщувач про критичні температури і вологості на основі МК AVR і датчика 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,5 C Тривалість період вимірювання Близько 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. Код клієнта
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.