diff --git a/DHT22.cpp b/DHT22.cpp index f0e36cd..9575b49 100644 --- a/DHT22.cpp +++ b/DHT22.cpp @@ -45,7 +45,6 @@ The Arduino OneWire lib */ #include "DHT22.h" -#include #include extern "C" { @@ -54,155 +53,128 @@ extern "C" { #include } -#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) #define DIRECT_MODE_INPUT(base, mask) ((*(base+1)) &= ~(mask)) #define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) |= (mask)) #define DIRECT_WRITE_LOW(base, mask) ((*(base+2)) &= ~(mask)) -//#define DIRECT_WRITE_HIGH(base, mask) ((*(base+2)) |= (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+2)) |= (mask)) -// This should be 40, but the sensor is adding an extra bit at the start -#define DHT22_DATA_BIT_COUNT 41 +#define DHT22_DATA_BIT_COUNT (40) -DHT22::DHT22(uint8_t pin) -{ - _bitmask = digitalPinToBitMask(pin); - _baseReg = portInputRegister(digitalPinToPort(pin)); - _lastReadTime = millis(); +DHT22::DHT22() { + _bitmask= 0; + _lastReadTime= 0; + init(); +} + +DHT22::DHT22(uint8_t pin) { + _lastReadTime = 0; + setPin(pin); +} + +void DHT22::init() { _lastHumidity = DHT22_ERROR_VALUE; _lastTemperature = DHT22_ERROR_VALUE; } -// -// Read the 40 bit data stream from the DHT 22 -// Store the results in private member data to be read by public member functions -// -DHT22_ERROR_t DHT22::readData() -{ - uint8_t bitmask = _bitmask; - volatile uint8_t *reg asm("r30") = _baseReg; - uint8_t retryCount; - uint8_t bitTimes[DHT22_DATA_BIT_COUNT]; +void DHT22::setPin(uint8_t pin) { + _bitmask = digitalPinToBitMask(pin); + _baseReg = portInputRegister(digitalPinToPort(pin)); + init(); +} + +DHT22_ERROR_t DHT22::readData() { + unsigned long currentTime= millis(); + + if((long)(currentTime - _lastReadTime) < 2000) { + // Caller needs to wait 2 seconds between each call to readData + return DHT_ERROR_TOOQUICK; + } + _lastReadTime = currentTime; + + return readDataNow(); +} + + +// Read the sensor without checking the last read time +DHT22_ERROR_t DHT22::readDataNow() { + volatile uint8_t *reg asm("r30") = _baseReg; + bool bitTimes[DHT22_DATA_BIT_COUNT]; int currentHumidity; int currentTemperature; uint8_t checkSum, csPart1, csPart2, csPart3, csPart4; - unsigned long currentTime; - int i; - + + if(_bitmask==0) return DHT_ERROR_NOT_PRESENT; currentHumidity = 0; currentTemperature = 0; checkSum = 0; - currentTime = millis(); - for(i = 0; i < DHT22_DATA_BIT_COUNT; i++) - { - bitTimes[i] = 0; - } - - if(currentTime - _lastReadTime < 2000) - { - // Caller needs to wait 2 seconds between each call to readData - return DHT_ERROR_TOOQUICK; - } - _lastReadTime = currentTime; // Pin needs to start HIGH, wait until it is HIGH with a timeout cli(); - DIRECT_MODE_INPUT(reg, bitmask); + DIRECT_WRITE_HIGH(reg, _bitmask); + DIRECT_MODE_INPUT(reg, _bitmask); sei(); - retryCount = 0; - do - { - if (retryCount > 125) - { + if(!levelTime(LOW, 0, 80)) return DHT_BUS_HUNG; - } - retryCount++; - delayMicroseconds(2); - } while(!DIRECT_READ(reg, bitmask)); + // Send the activate pulse cli(); - DIRECT_WRITE_LOW(reg, bitmask); - DIRECT_MODE_OUTPUT(reg, bitmask); // Output Low + DIRECT_WRITE_LOW(reg, _bitmask); + DIRECT_MODE_OUTPUT(reg, _bitmask); // Output Low sei(); - delayMicroseconds(1100); // 1.1 ms + delayMicroseconds(1000); // 1.1 ms cli(); - DIRECT_MODE_INPUT(reg, bitmask); // Switch back to input so pin can float + DIRECT_WRITE_HIGH(reg, _bitmask); + DIRECT_MODE_INPUT(reg, _bitmask); // Switch back to input so pin can float sei(); - // Find the start of the ACK Pulse - retryCount = 0; - do - { - if (retryCount > 25) //(Spec is 20 to 40 us, 25*2 == 50 us) - { - return DHT_ERROR_NOT_PRESENT; - } - retryCount++; - delayMicroseconds(2); - } while(!DIRECT_READ(reg, bitmask)); - // Find the end of the ACK Pulse - retryCount = 0; - do - { - if (retryCount > 50) //(Spec is 80 us, 50*2 == 100 us) - { + +// Wait for hi level + if(!levelTime(LOW, 0, 80)) + return DHT_BUS_HUNG; + +// Wait for response, should be 40us hi + if(!levelTime(HIGH, 0, 60)) + return DHT_ERROR_NOT_PRESENT; + + // ACK Pulse lo + if(!levelTime(LOW, 50, 90)) + return DHT_ERROR_ACK_TOO_LONG; + + // ACK Pulse hi + if(!levelTime(HIGH, 50, 90)) return DHT_ERROR_ACK_TOO_LONG; - } - retryCount++; - delayMicroseconds(2); - } while(DIRECT_READ(reg, bitmask)); + // Read the 40 bit data stream - for(i = 0; i < DHT22_DATA_BIT_COUNT; i++) + for(int i = 0; i < DHT22_DATA_BIT_COUNT; i++) { // Find the start of the sync pulse - retryCount = 0; - do - { - if (retryCount > 35) //(Spec is 50 us, 35*2 == 70 us) - { + if(!levelTime(LOW, 16, 70)) return DHT_ERROR_SYNC_TIMEOUT; - } - retryCount++; - delayMicroseconds(2); - } while(!DIRECT_READ(reg, bitmask)); + // Measure the width of the data pulse - retryCount = 0; - do - { - if (retryCount > 50) //(Spec is 80 us, 50*2 == 100 us) - { + if(!levelTime(HIGH, 0, 100)) return DHT_ERROR_DATA_TIMEOUT; - } - retryCount++; - delayMicroseconds(2); - } while(DIRECT_READ(reg, bitmask)); - bitTimes[i] = retryCount; + + // Spec: 0 is 26 to 28 us + // Spec: 1 is 70 us + bitTimes[i] = wrongTiming>40; } - // Now bitTimes have the number of retries (us *2) - // that were needed to find the end of each data bit - // Spec: 0 is 26 to 28 us - // Spec: 1 is 70 us - // bitTimes[x] <= 11 is a 0 - // bitTimes[x] > 11 is a 1 - // Note: the bits are offset by one from the data sheet, not sure why - for(i = 0; i < 16; i++) - { - if(bitTimes[i + 1] > 11) - { + + // EOF Pulse lo + if(!levelTime(LOW, 16, 75)) + return DHT_ERROR_SYNC_TIMEOUT; + + + for(int i = 0; i < 16; i++) { + if(bitTimes[i]) currentHumidity |= (1 << (15 - i)); - } } - for(i = 0; i < 16; i++) - { - if(bitTimes[i + 17] > 11) - { + for(int i = 0; i < 16; i++) { + if(bitTimes[i + 16]) currentTemperature |= (1 << (15 - i)); - } } - for(i = 0; i < 8; i++) - { - if(bitTimes[i + 33] > 11) - { + for(int i = 0; i < 8; i++) { + if(bitTimes[i + 32]) checkSum |= (1 << (7 - i)); - } } _lastHumidity = currentHumidity & 0x7FFF; @@ -211,9 +183,7 @@ DHT22_ERROR_t DHT22::readData() // Below zero, non standard way of encoding negative numbers! // Convert to native negative format. _lastTemperature = -(currentTemperature & 0x7FFF); - } - else - { + } else { _lastTemperature = currentTemperature; } @@ -222,9 +192,8 @@ DHT22_ERROR_t DHT22::readData() csPart3 = currentTemperature >> 8; csPart4 = currentTemperature & 0xFF; if(checkSum == ((csPart1 + csPart2 + csPart3 + csPart4) & 0xFF)) - { return DHT_ERROR_NONE; - } + return DHT_ERROR_CHECKSUM; } diff --git a/DHT22.h b/DHT22.h index 6981be7..4df62c7 100644 --- a/DHT22.h +++ b/DHT22.h @@ -2,6 +2,7 @@ #define _DHT22_H_ #include +#include #define DHT22_ERROR_VALUE -995 @@ -20,18 +21,38 @@ typedef enum class DHT22 { private: + void init(); uint8_t _bitmask; volatile uint8_t *_baseReg; unsigned long _lastReadTime; short int _lastHumidity; short int _lastTemperature; - + + inline bool levelTime(uint8_t level, unsigned long min, unsigned long max) { + volatile uint8_t *reg asm("r30") = _baseReg; + long t; + + t= micros(); + for(uint8_t i= 0; (((*reg) & _bitmask) ? 1 : 0)==level && i<100; i++) + delayMicroseconds(6); + + wrongTiming= micros() - t; + + return wrongTiming>min && wrongTimingExamples->DHT22->Serial . +The original library, after sending the sensor read-out initiation signal, waited 20us to 40us on a LOW signal, when it should have been looking for a 20us to 40 us sensor "setup time" HIGH signal. Then the original library expected a 80us HIGH "ACK pulse" when it should have been looking for a 80us LOW "ACK pulse" and a second 80us HIGH "ACK pulse". Thus the original library "saw" an extra pair of low and hi signals which it interpreted as a first invalid data-bit. +Somehow the original worked quite well for me (and apparently for many others aswell) for a long time. Only on sensors connected via very long (>20m) twisted pair cable I sometimes didn't get a sensors to communicate. Interestingly when this happened it happened right at the first read and continued to persist. But after resetting my arduino a couple of times it started to work again and didn't go bad ever after (until - maybe - the next reset). So, I always thought it had something to do with the line being in a bad state at startup. +But then I wanted to read six sensors and was not able to get of them working at the same time, no matter how often I resetted. So, I wrote a sketch to see what is going on on the sensor line and found out that the original library must have misinterpreted the spec. +So, here is a corrected version, in the hope it will prevent others from the frustration I suffered. + +Version 0.6: 24-May-2014 by Jens Geisler +Fixed the protocol according to https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf +Added test sketch to analyse the timing of the sensor +Added method setPin to change the sensor pin and thus reuse one DHT22 object for reading several sensors +Added method readDataNow to read a sensor without the 2s time limit. +Changed detection limit for "1" bits to 50us + Version 0.5: 15-Jan-2012 by Craig Ringer Update to support Arduino 1.0 Make accessors inlineable so they can be optimised away diff --git a/examples/TestTiming/TestTiming.ino b/examples/TestTiming/TestTiming.ino new file mode 100644 index 0000000..6fb864e --- /dev/null +++ b/examples/TestTiming/TestTiming.ino @@ -0,0 +1,110 @@ +#include + + +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*(base+1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*(base+2)) &= ~(mask)) + +const byte pin= 7; +uint8_t _bitmask; +volatile uint8_t * _baseReg; +uint8_t timesHi[45]; +uint8_t timesLo[45]; +DHT22 dht22; + +void setup() { + // initialize serial communication at 9600 bits per second: + Serial.begin(9600); +} + +void loop() { + Serial.println("Start"); + uint8_t res= sense(); + Serial.print("Times Read: "); + Serial.println(res); + for(byte i= 0; i<45; i++ ) { + Serial.print("# "); + Serial.print(i); + Serial.print("Time Lo: "); + Serial.print(timesLo[i]*2); + Serial.print(" Time Hi: "); + Serial.println(timesHi[i]*2); + } + + + delay(3000); + dht22.setPin(pin); + DHT22_ERROR_t e= dht22.readData(); + if (e==DHT_ERROR_NONE) { + Serial.print("T: "); + Serial.print(dht22.getTemperatureC()); + Serial.print(", H: "); + Serial.println(dht22.getHumidity()); + } else { + Serial.print("E: "); + Serial.println(e); + } + while(1); +} + +uint8_t sense() { + _bitmask = digitalPinToBitMask(pin); + _baseReg = portInputRegister(digitalPinToPort(pin)); + uint8_t bitmask = _bitmask; + volatile uint8_t *reg asm("r30") = _baseReg; + + uint8_t retryCount; + for(byte i= 0; i<45; i++ ) { + timesHi[i]= 0; + timesLo[i]= 0; + } + + cli(); + DIRECT_MODE_INPUT(reg, bitmask); + sei(); + retryCount = 0; + do { + if (retryCount > 125) + { + return 0; + } + retryCount++; + delayMicroseconds(2); + } while(!DIRECT_READ(reg, bitmask)); // while low + + // Send the activate pulse + cli(); + DIRECT_WRITE_LOW(reg, bitmask); + DIRECT_MODE_OUTPUT(reg, bitmask); // Output Low + sei(); + delayMicroseconds(1100); // 1.1 ms + cli(); + DIRECT_MODE_INPUT(reg, bitmask); // Switch back to input so pin can float + sei(); + + for(byte i= 0; i<45; i++ ) { + retryCount = 0; + do { + if (retryCount > 100) { + return i+1; + } + + retryCount++; + delayMicroseconds(2); + } while(!DIRECT_READ(reg, bitmask)); // while lo + timesLo[i]= retryCount; + + retryCount = 0; + do { + if (retryCount > 100) { + return i+1; + } + + retryCount++; + delayMicroseconds(2); + } while(DIRECT_READ(reg, bitmask)); // while hi + timesHi[i]= retryCount; + } + return 46; +} diff --git a/keywords.txt b/keywords.txt index 5a1297f..7991572 100644 --- a/keywords.txt +++ b/keywords.txt @@ -17,6 +17,7 @@ getHumidity KEYWORD2 getHumidityInt KEYWORD2 getTemperatureC KEYWORD2 getTemperatureCInt KEYWORD2 +getWrongTiming KEYWORD2 clockReset KEYWORD2 #######################################