Over the last year my Garduino project has morphed into more of simply a weather station. While I still have plans to activate a water valve with the Arduino, my failure in creating a reliable moisture sensor has led me to more or less indirectly guess the soil moisture my measuring the temperature, rain, and humidity.
To measure the temperature and humidity, I’m using the RHT03 (referred to as an RHT22 and DHT-22 as well), available from Sparkfun, and on eBay.
Most of my work on the RHT03 is based on the work of Craig Ringer and Nethoncho. The RHT03 is a bit of an odd device, with kind of a non-standard signalling protocol. All signalling is done over 1 data line, and the Arduino sets that signal line to an OUTPUT and pulls the line low for about 3ms, and then high for about 30us. At that point, the Arduino sets the data line pin to an INPUT, and the RHT03 takes control of that pin; pulling the line low for 80us, then high for 80us, and then sending the temperature and humidity in a series of pulses. High pulses of 26us represent a 0 and 80us represents a 1. You can try to decipher the whole protocol by reading the datasheet, if you are not too picky about grammar (and if you are reading my blog, you must not be). The following is the code I am using:
#define RHT22_PIN 7 unsigned int temp; unsigned int humidity; void setup() { Serial.begin(9600); Serial.println("Begin"); } void loop() { Serial.println("Reading sensor..."); boolean success = readRHT03(); if (success) { char buf[128]; sprintf(buf, "Unit 1 Reading: %i.%i degrees C at %i.%i relative humidity", temp/10, abs(temp%10), humidity/10, humidity%10); Serial.println(buf); } delay(2000); } boolean readRHT03() { unsigned int rht22_timings[88]; initRHT03(); pinMode(6,OUTPUT); pinMode(5,OUTPUT); digitalWrite(5,LOW); pinMode(RHT22_PIN, OUTPUT); digitalWrite(RHT22_PIN, LOW); delayMicroseconds(3000); digitalWrite(RHT22_PIN, HIGH); delayMicroseconds(30); pinMode(RHT22_PIN, INPUT); pinMode(6,OUTPUT); pinMode(5,OUTPUT); digitalWrite(5,LOW); int state=digitalRead(RHT22_PIN); unsigned int counter=0; unsigned int signalLineChanges=0; TCNT1=0; digitalWrite(5,HIGH); cli(); while (counter!=0xffff) { counter++; if(state!=digitalRead(RHT22_PIN)) { state=digitalRead(RHT22_PIN); digitalWrite(6,state); rht22_timings[signalLineChanges] = TCNT1; TCNT1=0; counter=0; signalLineChanges++; } if(signalLineChanges==83) break; } sei(); digitalWrite(5,LOW); boolean errorFlag=false; if (signalLineChanges != 83) { temp=-1; humidity=-1; errorFlag=true; } else { errorFlag=!(getHumidityAndTemp(rht22_timings)); } return !errorFlag; } void initRHT03() { //for (int i = 0; i < 86; i++) { rht22_timings[i] = 0; } TCCR1A=0; TCCR1B=_BV(CS10); pinMode(RHT22_PIN,INPUT); digitalWrite(RHT22_PIN,HIGH); } // DEBUG routine: dump timings array to serial void debugPrintTimings(unsigned int rht22_timings[]) { // XXX DEBUG for (int i = 0; i < 88; i++) { // XXX DEBUG if (i%10==0) { Serial.print("\n\t"); } char buf[24]; sprintf(buf, i%2==0 ? "H[%02i]: %-3i " : "L[%02i]: %-3i ", i, rht22_timings[i]); Serial.print(buf); } // XXX DEBUG Serial.print("\n"); // XXX DEBUG } boolean getHumidityAndTemp(unsigned int rht22_timings[]) { // 25us = 400, 70us = 1120; humidity=0; for(int i=0;i<32;i+=2) { if(rht22_timings[i+3]>750) { humidity|=(1 << (15-(i/2))); } } temp=0; for(int i=0;i<32;i+=2) { if(rht22_timings[i+35]>750) temp|=(1<<(15-(i/2))); } int cksum=0; for(int i=0;i<16;i+=2) { if(rht22_timings[i+67]>750) cksum|=(1<<(7-(i/2))); } int cChksum=((humidity >> 8)+(humidity & 0xff) + (temp >> 8) + (temp &0xff)) & 0xFF; if(temp & 0x8000) temp=(temp & 0x7fff)*-1; if(cChksum == cksum) return true; return false; }
Signal handling is done in the readRHT03 function. After sending the start sending signal, the Arduino uses Timer/Counter 1 to time the incoming pulses. Originally, I was trying to use micros() to measure the timing, but I got some very erratic results. This gave me the opportunity to use my new Saleae Logic Analyzer. As you can see in the code, I have Pin 6 follow the state of the data input from the RHT03. Normally, there is a 15us delay between the time the RHT03 changes state and the time Pin 6 changes state (the time to execute the if statement and the digitalWrite()), but sometimes it is longer. The highlighted pulse on Channel 1, though, shows the delay is at least 10us longer. To make a long story short, this is caused by interrupts in the Arduino, and turning off the interrupts before I start timing the pulses fixes that problem. Functions like millis() and micros() need interrupts to be update, though, so I could not use them with the interrupts disabled. This means I had to use the hardware timer/counter to time the pulse length, which is what the references to the TCCR1A, TCCR1B and TCNT1 do. Additionally, to measure the time the interrupts are disabled, I set Pin 5 (Channel 2) to go high when the interrupts are disabled and low when they are re-enabled, which gave me about 4ms of “interrupt-less” time. Since my weather station is going to be polled at 60 second intervals, I now know that the timers won’t be running for about 4ms of that time, so I have to set my timers to 59996 to get a 60 second poll.
Anyone using this code should pull out all the references to Pin 5 and 6, unless they want to use them for debugging as well.
The other thing to note is the temperature is pretty inaccurate when the device is in direct sunlight. My original design had the RHT03 sharing the waterproof container of my MAX44009 light sensor, but even though it is open to the air at the bottom on the container, the temperature inside the container got well above 110°F on bright sunny 90°F+ days. I’m now extending the cable to the RHT03 and putting it in it’s own container, shaded by the solar cell. I will update on how well that works.