// ----------------------------------------------------------------------------- // DHTXX Sensor // Copyright (C) 2017-2019 by Xose PĂ©rez // ----------------------------------------------------------------------------- #if SENSOR_SUPPORT && DHT_SUPPORT #pragma once #include "../gpio.h" #include "../utils.h" #include "BaseSensor.h" enum class DHTChipType { DHT11, DHT12, DHT21, DHT22, AM2301, SI7021 }; // Note: backwards compatibility for configuration headers #define DHT_CHIP_DHT11 DHTChipType::DHT11 #define DHT_CHIP_DHT12 DHTChipType::DHT12 #define DHT_CHIP_DHT22 DHTChipType::DHT22 #define DHT_CHIP_DHT21 DHTChipType::DHT21 #define DHT_CHIP_AM2301 DHTChipType::AM2301 #define DHT_CHIP_SI7021 DHTChipType::SI7021 int dhtchip_to_number(DHTChipType chip) { switch (chip) { case DHTChipType::DHT11: return 11; case DHTChipType::DHT12: return 12; case DHTChipType::DHT21: case DHTChipType::AM2301: return 21; case DHTChipType::DHT22: case DHTChipType::SI7021: return 22; } return -1; } class DHTSensor : public BaseSensor { public: // --------------------------------------------------------------------- // Public // --------------------------------------------------------------------- static constexpr double DummyValue = -255.0; static constexpr size_t MaxErrors = 5; using Data = std::array; using TimeSource = espurna::time::CoreClock; static constexpr auto MinInterval = espurna::duration::Milliseconds { 2000 }; ~DHTSensor() { gpioUnlock(_gpio); } // --------------------------------------------------------------------- void setGPIO(unsigned char gpio) { _gpio = gpio; } void setType(DHTChipType type) { _type = type; } // --------------------------------------------------------------------- unsigned char getGPIO() const { return _gpio; } int getType() const { return dhtchip_to_number(_type); } DHTChipType getChipType() const { return _type; } // --------------------------------------------------------------------- // Sensor API // --------------------------------------------------------------------- unsigned char id() const override { return SENSOR_DHTXX_ID; } unsigned char count() const override { return 2; } // Initialization method, must be idempotent void begin() override { // Manage GPIO lock (note that this only handles the basic *hw* I/O) if (_previous != GPIO_NONE) { gpioUnlock(_previous); } _previous = GPIO_NONE; if (!gpioLock(_gpio)) { _error = SENSOR_ERROR_GPIO_USED; return; } _previous = _gpio; // Set now to fail the check in _read at least once _last_ok = TimeSource::now(); _ready = true; } // Pre-read hook (usually to populate registers with up-to-date data) void pre() override { _error = SENSOR_ERROR_OK; _read(); } // Descriptive name of the sensor String description() const override { char buffer[20]; snprintf_P(buffer, sizeof(buffer), "DHT%d @ GPIO%hhu", dhtchip_to_number(_type), _gpio); return String(buffer); } // Address of the sensor (it could be the GPIO or I2C address) String address(unsigned char) const override { return String(_gpio, 10); } // Type for slot # index unsigned char type(unsigned char index) const override { if (index == 0) return MAGNITUDE_TEMPERATURE; if (index == 1) return MAGNITUDE_HUMIDITY; return MAGNITUDE_NONE; } // Current value for slot # index double value(unsigned char index) override { if (index == 0) return _temperature; if (index == 1) return _humidity; return 0; } protected: // --------------------------------------------------------------------- // Protected // --------------------------------------------------------------------- void _read_critical(Data& dhtData) { digitalWrite(_gpio, LOW); if ((_type == DHT_CHIP_DHT11) || (_type == DHT_CHIP_DHT12)) { espurna::time::blockingDelay( espurna::duration::Milliseconds(20)); } else if (_type == DHT_CHIP_SI7021) { espurna::time::critical::delay( espurna::duration::critical::Microseconds(500)); } else { espurna::time::critical::delay( espurna::duration::critical::Microseconds(1100)); } digitalWrite(_gpio, HIGH); espurna::time::critical::delay( espurna::duration::critical::Microseconds(40)); pinMode(_gpio, INPUT_PULLUP); espurna::time::critical::delay( espurna::duration::critical::Microseconds(10)); unsigned long low = 0; unsigned long high = 0; unsigned char byteInx = 0; unsigned char bitInx = 7; // No errors, read the 40 data bits for( int k = 0; k < 41; k++ ) { // Starts new data transmission with >50us low signal low = _signal(100, LOW); if (low == 0) { _error = SENSOR_ERROR_TIMEOUT; return; } // Check to see if after >70us rx data is a 0 or a 1 high = _signal(100, HIGH); if (high == 0) { _error = SENSOR_ERROR_TIMEOUT; return; } // Skip the first bit if (k == 0) continue; // add the current read to the output data // since all dhtData array where set to 0 at the start, // only look for "1" (>28us us) if (high > low) { dhtData[byteInx] |= (1 << bitInx); } // index to next byte if (bitInx == 0) { bitInx = 7; ++byteInx; } else { --bitInx; } } } void _read() { if (TimeSource::now() - _last_ok < MinInterval) { if ((_temperature == DummyValue) && (_humidity == DummyValue)) { _error = SENSOR_ERROR_WARM_UP; } else { _error = SENSOR_ERROR_OK; } return; } pinMode(_gpio, OUTPUT); // Send start signal to DHT sensor if (++_errors > MaxErrors) { _errors = 0; digitalWrite(_gpio, HIGH); espurna::time::blockingDelay( espurna::duration::Milliseconds(250)); } Data dhtData{}; noInterrupts(); _read_critical(dhtData); interrupts(); if (_error != SENSOR_ERROR_OK) { return; } // Verify checksum if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) { _error = SENSOR_ERROR_CRC; return; } // Get humidity from Data[0] and Data[1] if (_type == DHT_CHIP_DHT11) { _humidity = dhtData[0]; } else if (_type == DHT_CHIP_DHT12) { _humidity = dhtData[0]; _humidity += dhtData[1] * 0.1; } else { _humidity = dhtData[0] * 256 + dhtData[1]; _humidity /= 10; } // Get temp from Data[2] and Data[3] if (_type == DHT_CHIP_DHT11) { _temperature = dhtData[2]; } else if (_type == DHT_CHIP_DHT12) { _temperature = (dhtData[2] & 0x7F); _temperature += dhtData[3] * 0.1; if (dhtData[2] & 0x80) { _temperature *= -1; } } else { _temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3]; _temperature /= 10; if (dhtData[2] & 0x80) { _temperature *= -1; } } _last_ok = TimeSource::now(); _errors = 0; _error = SENSOR_ERROR_OK; } unsigned long _signal(unsigned long maximum, bool state) { unsigned long ticks = 1; while (digitalRead(_gpio) == state) { if (++ticks > maximum) return 0; espurna::time::critical::delay( espurna::duration::critical::Microseconds(1)); } return ticks; } DHTChipType _type = DHT_CHIP_DHT22; unsigned char _gpio = GPIO_NONE; unsigned char _previous = GPIO_NONE; TimeSource::time_point _last_ok; size_t _errors = 0; bool _warmup = false; double _temperature = DummyValue; double _humidity = 0.0; }; #endif // SENSOR_SUPPORT && DHT_SUPPORT