Skip to content
This repository was archived by the owner on Apr 27, 2023. It is now read-only.

Timing protocol fix #6

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 86 additions & 117 deletions DHT22.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ The Arduino OneWire lib
*/

#include "DHT22.h"
#include <Arduino.h>
#include <pins_arduino.h>

extern "C" {
Expand All @@ -54,155 +53,128 @@ extern "C" {
#include <avr/pgmspace.h>
}

#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;
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down
44 changes: 34 additions & 10 deletions DHT22.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define _DHT22_H_

#include <inttypes.h>
#include <Arduino.h>

#define DHT22_ERROR_VALUE -995

Expand All @@ -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 && wrongTiming<max;
};


public:
unsigned long wrongTiming;
DHT22();
DHT22(uint8_t pin);
void setPin(uint8_t pin);
DHT22_ERROR_t readData();
short int getHumidityInt();
short int getTemperatureCInt();
DHT22_ERROR_t readDataNow();
short int getHumidityInt();
short int getTemperatureCInt();
void clockReset();
// unsigned long getWrongTiming();
#if !defined(DHT22_NO_FLOAT)
float getHumidity();
float getTemperatureC();
Expand All @@ -40,9 +61,6 @@ class DHT22
};

// Report the humidity in .1 percent increments, such that 635 means 63.5% relative humidity
//
// Converts from the internal integer format on demand, so you might want
// to cache the result.
inline short int DHT22::getHumidityInt()
{
return _lastHumidity;
Expand All @@ -55,27 +73,33 @@ inline short int DHT22::getTemperatureCInt()
return _lastTemperature;
}

// unsigned long DHT22::getWrongTiming() {
// return wrongTiming;
// }


#if !defined(DHT22_NO_FLOAT)
// Return the percentage relative humidity in decimal form
// Converts from the internal integer format on demand, so you might want
// to cache the result.
inline float DHT22::getHumidity()
{
return float(_lastHumidity)/10;
return float(_lastHumidity) * (float)0.1;
}
#endif

#if !defined(DHT22_NO_FLOAT)
// Return the percentage relative humidity in decimal form
//
// Converts from the internal integer format on demand, so you might want
// to cache the result.
inline float DHT22::getTemperatureC()
{
return float(_lastTemperature)/10;
return float(_lastTemperature) * (float)0.1;
}

inline float DHT22::getTemperatureF()
{
return float(_lastTemperature) / 10 * 9 / 5 + 32;
return float(_lastTemperature) * (float)0.1 * 9.0 / 5.0 + 32.0;
}
#endif //DHT22_SUPPORT_FLOAT

Expand Down
13 changes: 13 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
DHT22.cpp - DHT22 sensor library
Developed by Ben Adams - 2011
Protocol Fix by Jens Geisler - 2014

Humidity and Temperature Sensor DHT22 info found at
http://www.sparkfun.com/products/10167
Expand All @@ -11,6 +12,18 @@ To install this library for use with the Arduino IDE, copy it
to the `libraries' folder and restart the IDE. For an example of
how to use it, see File->Examples->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
Expand Down
Loading