/*! * file DHT.cpp * mainpage DHT series of low cost temperature/humidity sensors. * section intro_sec Introduction * section author Author * section license License */ #include "DHT.h" #define MIN_INTERVAL 2000 /**< min wartość interwału */ #define TIMEOUT \ UINT32_MAX /**< Używana programowo do przekroczenia limitu czasu. Nie limit czasu. Wpisz: uint32_t. */ /*! @brief Tworzy nową klasę DHT * @param pin numer pinu do którego podłączony jest czujnik * param type typ czujnika: DHT21, 22 i tp. * @param count liczba czujników */ DHT::DHT(uint8_t pin, uint8_t count) { (void)count; // Obejście, aby uniknąć ostrzeżenia kompilatora. _pin = pin; #ifdef __AVR _bit = digitalPinToBitMask(pin); _port = digitalPinToPort(pin); #endif _maxcycles = microsecondsToClockCycles(1000); //Limit czasu 1 milisekundy odczytu impulsów z czujnika DHT. Zauważ, że liczba jest teraz ignorowana, //ponieważ algorytm odczytu DHT dostosowuje się w oparciu o szybkość procesora. } /*! * @brief Skonfiguruj piny czujnika i ustaw czasy ściągania. * @param usec Opcjonalnie podaj czas podciągania (w mikrosekundach) przed rozpoczęciem odczytu DHT. Domyślnie 55 (patrz deklaracja funkcji w DHT.h). */ void DHT::begin(uint8_t usec) { // Ustaw piny pinMode(_pin, INPUT_PULLUP); // Użycie tej wartości gwarantuje, że millis() - lastreadtime będzie od razu >= MIN_INTERVAL. // Zwróć uwagę, że to przypisanie jest zawijane, ale odejmowanie również. _lastreadtime = millis() - MIN_INTERVAL; DEBUG_PRINT("Maksymalne cykle zegara DHT: "); DEBUG_PRINTLN(_maxcycles, DEC); pullTime = usec; } /*! * @brief Odczyt temperatury * @param S Skala. Wartość logiczna: * - prawda = stopnie Fahrenheita * - fałsz = Celsjusz * @param force true w trybie wymuszania * @return Wartość temperatury w wybranej skali */ float DHT::readTemperature(bool S, bool force) { float f = NAN; if (read(force)) { f = ((word)(data[2] & 0x7F)) << 8 | data[3]; f *= 0.1; if (data[2] & 0x80) f *= -1; if (S) f = convertCtoF(f); } return f; } /*! * @brief konwertuje stopnie Celsjusza na Fahrenheita */ float DHT::convertCtoF(float c) { return c * 1.8 + 32; } /*! * @brief konwertuje stopnie Fahrenheita na Celsjusza */ float DHT::convertFtoC(float f) { return (f - 32) * 0.55555; } /*! * @brief odczyt wilgotności * @param force tryb odczytu * @return float value - wilgotność w % */ float DHT::readHumidity(bool force) { float f = NAN; if (read(force)) { f = ((word)data[0]) << 8 | data[1]; f *= 0.1; } return f; } /*! * @brief Obliczany wskaźnik ciepła * Uproszczona wersja, która odczytuje temperaturę i wilgotność z czujnika * @param isFahrenheit * prawda, jeśli stopnie Fahrenheita, fałsz, jeśli stopnie Celsjusza * (domyślnie prawda) * @return float wskaźnik ciepła */ float DHT::computeHeatIndex(bool isFahrenheit) { float hi = computeHeatIndex(readTemperature(isFahrenheit), readHumidity(), isFahrenheit); return hi; } /*! * @brief obliczany wskaźnik ciepła Używając równań Rothfusza i Steadmana. (http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml) * @param temperature temperatura w wybranej skali * @param percentHumidity wilgotność w procentach * @param isFahrenheit prawda, jeśli stopnie Fahrenheita, fałsz, jeśli stopnie Celsjusza * @return float wskaźnik ciepła */ float DHT::computeHeatIndex(float temperature, float percentHumidity, bool isFahrenheit) { float hi; if (!isFahrenheit) temperature = convertCtoF(temperature); hi = 0.5 * (temperature + 61.0 + ((temperature - 68.0) * 1.2) + (percentHumidity * 0.094)); if (hi > 79) { hi = -42.379 + 2.04901523 * temperature + 10.14333127 * percentHumidity + -0.22475541 * temperature * percentHumidity + -0.00683783 * pow(temperature, 2) + -0.05481717 * pow(percentHumidity, 2) + 0.00122874 * pow(temperature, 2) * percentHumidity + 0.00085282 * temperature * pow(percentHumidity, 2) + -0.00000199 * pow(temperature, 2) * pow(percentHumidity, 2); if ((percentHumidity < 13) && (temperature >= 80.0) && (temperature <= 112.0)) hi -= ((13.0 - percentHumidity) * 0.25) * sqrt((17.0 - abs(temperature - 95.0)) * 0.05882); else if ((percentHumidity > 85.0) && (temperature >= 80.0) && (temperature <= 87.0)) hi += ((percentHumidity - 85.0) * 0.1) * ((87.0 - temperature) * 0.2); } return isFahrenheit ? hi : convertFtoC(hi); } /*! * @brief Odczytaj wartość z czujnika lub zwróć ostatnią z mniej niż dwóch sekund * @param force prawda, jeśli używasz trybu force * @return zwróć wartość zmiennoprzecinkową */ bool DHT::read(bool force) { // Sprawdź, czy czujnik został odczytany mniej niż dwie sekundy temu i wróć wcześniej, aby użyć ostatniego odczytu. uint32_t currenttime = millis(); if (!force && ((currenttime - _lastreadtime) < MIN_INTERVAL)) { return _lastresult; // zwróć ostatni poprawny pomiar } _lastreadtime = currenttime; // Zresetuj 40 bitów odebranych danych do zera. data[0] = data[1] = data[2] = data[3] = data[4] = 0; #if defined(ESP8266) yield(); // Obsługuj WiFi / zresetuj program watchdog #endif // Wyślij sygnał startu. Zobacz arkusz danych DHT dla pełnego schematu sygnału: // http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf // Przejdź w stan wysokiej impedancji, aby umożliwić podciąganiu podniesienie poziomu linii danych i rozpoczęcie procesu odczytu. pinMode(_pin, INPUT_PULLUP); delay(1); // Najpierw ustaw niską linię danych na okres zgodnie z typem czujnika pinMode(_pin, OUTPUT); digitalWrite(_pin, LOW); delayMicroseconds(1100); // karta danych mówi „co najmniej 1ms” delay(20); // data sheet says at least 18ms, 20ms just to be safe uint32_t cycles[80]; { // End the start signal by setting data line high for 40 microseconds. pinMode(_pin, INPUT_PULLUP); // Delay a moment to let sensor pull data line low. delayMicroseconds(pullTime); // Now start reading the data line to get the value from the DHT sensor. // Turn off interrupts temporarily because the next sections // are timing critical and we don't want any interruptions. InterruptLock lock; // First expect a low signal for ~80 microseconds followed by a high signal // for ~80 microseconds again. if (expectPulse(LOW) == TIMEOUT) { //DEBUG_PRINTLN(F("DHT timeout waiting for start signal low pulse.")); _lastresult = false; return _lastresult; } if (expectPulse(HIGH) == TIMEOUT) { //DEBUG_PRINTLN(F("DHT timeout waiting for start signal high pulse.")); _lastresult = false; return _lastresult; } // Now read the 40 bits sent by the sensor. Each bit is sent as a 50 // microsecond low pulse followed by a variable length high pulse. If the // high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds // then it's a 1. We measure the cycle count of the initial 50us low pulse // and use that to compare to the cycle count of the high pulse to determine // if the bit is a 0 (high state cycle count < low state cycle count), or a // 1 (high state cycle count > low state cycle count). Note that for speed // all the pulses are read into a array and then examined in a later step. for (int i = 0; i < 80; i += 2) { cycles[i] = expectPulse(LOW); cycles[i + 1] = expectPulse(HIGH); } } // Timing critical code is now complete. // Inspect pulses and determine which ones are 0 (high state cycle count < low // state cycle count), or 1 (high state cycle count > low state cycle count). for (int i = 0; i < 40; ++i) { uint32_t lowCycles = cycles[2 * i]; uint32_t highCycles = cycles[2 * i + 1]; if ((lowCycles == TIMEOUT) || (highCycles == TIMEOUT)) { //DEBUG_PRINTLN(F("DHT timeout waiting for pulse.")); _lastresult = false; return _lastresult; } data[i / 8] <<= 1; // Now compare the low and high cycle times to see if the bit is a 0 or 1. if (highCycles > lowCycles) { // High cycles are greater than 50us low cycle count, must be a 1. data[i / 8] |= 1; } // Else high cycles are less than (or equal to, a weird case) the 50us low // cycle count so this must be a zero. Nothing needs to be changed in the // stored data. } /* DEBUG_PRINTLN(F("Received from DHT:")); DEBUG_PRINT(data[0], HEX); DEBUG_PRINT(F(", ")); DEBUG_PRINT(data[1], HEX); DEBUG_PRINT(F(", ")); DEBUG_PRINT(data[2], HEX); DEBUG_PRINT(F(", ")); DEBUG_PRINT(data[3], HEX); DEBUG_PRINT(F(", ")); DEBUG_PRINT(data[4], HEX); DEBUG_PRINT(F(" =? ")); DEBUG_PRINTLN((data[0] + data[1] + data[2] + data[3]) & 0xFF, HEX); */ // Check we read 40 bits and that the checksum matches. if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) { _lastresult = true; return _lastresult; } else { //DEBUG_PRINTLN(F("DHT checksum failure!")); _lastresult = false; return _lastresult; } } // Expect the signal line to be at the specified level for a period of time and // return a count of loop cycles spent at that level (this cycle count can be // used to compare the relative time of two pulses). If more than a millisecond // ellapses without the level changing then the call fails with a 0 response. // This is adapted from Arduino's pulseInLong function (which is only available // in the very latest IDE versions): // https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_pulse.c uint32_t DHT::expectPulse(bool level) { #if (F_CPU > 16000000L) uint32_t count = 0; #else uint16_t count = 0; // To work fast enough on slower AVR boards #endif // On AVR platforms use direct GPIO port access as it's much faster and better // for catching pulses that are 10's of microseconds in length: #ifdef __AVR uint8_t portState = level ? _bit : 0; while ((*portInputRegister(_port) & _bit) == portState) { if (count++ >= _maxcycles) { return TIMEOUT; // Exceeded timeout, fail. } } // Otherwise fall back to using digitalRead (this seems to be necessary on // ESP8266 right now, perhaps bugs in direct port access functions?). #else while (digitalRead(_pin) == level) { if (count++ >= _maxcycles) { return TIMEOUT; // Exceeded timeout, fail. } } #endif return count; }