I am in the middle of programming a simple hvac controller that will eventually hook up to a relay board. I am having issues of roughly 5 degree centegrate spikes when the arduino outputs voltage for "fan" , "heat" and "cool". I have a relay hooked up for the cooling and it seems to be the worse of the three. Maybe its the code itself? Or the temp controller?
Any help would be awesome,
Thanks
long HeatAutoOn = 19;
long AcAutoOn = 25;
long CheckTemp = 10000;
unsigned long time;
long previoustime;
float TempDiffHeat = HeatAutoOn+1.5;
float TempDiffAc = AcAutoOn-1.5;
boolean heat = false;
boolean ac = false;
boolean off = false;
float SUPPLY_VOLTAGE = 5.0;
int tempPin = 0;
int w = 7;
int g = 9;
int y = 8;
int BAUD_RATE = 9600;
void setup()
{
pinMode (w, OUTPUT);
pinMode (g, OUTPUT);
pinMode (y, OUTPUT);
Serial.begin(BAUD_RATE);
}
void loop()
{
float temperature = getVoltage(tempPin);
temperature = (temperature - .5) * 100;
time = millis();
if ((time - previoustime) > CheckTemp ){
previoustime = time;
float temperature = getVoltage(tempPin);
temperature = (temperature - .5) * 100;
Serial.print(temperature);
Serial.println(" C");
}
if (temperature <= HeatAutoOn && heat == false) {
digitalWrite(w, HIGH);
Serial.println("HEAT ON AUTO");
delay(4000);
digitalWrite(g, HIGH);
heat = true;
ac = false;
off = false;
}
if (temperature >= TempDiffHeat && temperature <= TempDiffAc && off == false){
digitalWrite(w, LOW);
Serial.println("HVAC Off");
delay(4000);
digitalWrite(g, LOW);
digitalWrite(y, LOW);
off = true;
heat = false;
ac = false;
}
if (temperature >= AcAutoOn && ac == false) { // turns on A/C
digitalWrite(y, HIGH);
digitalWrite(g, HIGH);
Serial.println("A/C ON");
heat = false;
ac = true;
off = false;
}
}
float getVoltage(int pin)
{
return (analogRead(pin) * .004882814);
}
Sounds like a wiring issue - the code looks mostly ok. The power drawn by the HVAC components at startup seems to be affecting your analog temperature measurement. Most notably with the AC. Could this be because AC draws the most power or is it because you exacerbate matters by turning on the compressor and the fan at the same time? Can you opto-isolate the arduino from the HVAC system?
Easiest solution though is probably to put in additional delays after you start your systems - it's not as if you need to check temps again immediately.
Slight code simplification: why do you read the temp twice?
I'd look into how the relays are powered. Ideally, use something like a 2003a Darlington array chip to do the heavy lifting for you instead of driving them directly from the arduino. The reason being, the arduino can only supply 20mA per channel and 200mA in total. The limits of the Vregulator are also depleted quickly. So I would go to driving a darlington array that uses a separate 12VDC power supply to drive the relays.
Make sure that the 12V power supply can handle the currents that the relays develop. Good setups for these sorts of boards include a Darlington driver as well as diodes to limit inductive kickback when the relay shuts off. Many vendors sell such kits, I have used a 8-relay board from Anykits for a long time, though others offer similar kits (iteadstudio, seeeed, sparkfun, et al). So I would buy a pre-populated board that can take the 5VDC output of the arduino and that should be the end of your TMP troubles.
Last but not least, consider using decimation / oversampling to steady the responses of your TMP sensors. Even without resorting to faster AVR reading routines (i.e. using the simple-to-implement analog.Read) you can get thousands of readings per second. That allows you to improve the resolution of the TMP output by multiple bits... and hopefully remove all spurious noise.
Thank you two for the input. The arduino is currently on hooked up to a bread board so I can test my wiring/code with leds instead of the equipment for now. The AC output from arudino is hooked up to a relay which turns on an led, and that seems to be the worse.
Wildbill
As for --->
Slight code simplification: why do you read the temp twice?
I am completley new to writing code and playing around with the arduino, and this is the first project I am trying out! So I'm sure there will be plenty more redundancies to come, hah. Eventually I would love to implement graphics and chart temps, run times, etc. and also have the ability to control via the internet. However, I'm sure that wont happen too soon as i just learned how to blink an led about 3 weeks ago!
Constantin
Last but not least, consider using decimation / oversampling to steady the responses of your TMP sensors. Even without resorting to faster AVR reading routines (i.e. using the simple-to-implement analog.Read) you can get thousands of readings per second. That allows you to improve the resolution of the TMP output by multiple bits... and hopefully remove all spurious noise.
This sounds very interesting, however I don't really know how to implement your suggestions. haha sorry I am very new. Any tips to help me use this in my code would be great! Thanks
See this paper from Atmel on decimation / oversampling - they're the guys whose chips the Arduino uses. In the process of learning decimation, you'll also have to familiarize yourself with bit-shift operations. But I think you'll find the whole thing to be pretty simple and a very elegant solution to improving the results from comparatively slow-moving signals (like temperature) while also addressing the issue of noise. HTH!
I have read up on decimation and oversampling. I understand the basics of it now, (I think), to increase the resolution of the analog input by 1 bit we must oversample the input 4 times. To oversample the anolog input we must increase the frequency by 2^(2*n). So, if I want to increase the frequency of 10 bits to 12 bits we must increase the frequency by 16 times. Then, we use decimation ,(moving bits around, left or right) instead of using normal averaging (1+1+2/3), as the averaging method. We must right shift the bit by the desired resolution number. ie. '12' . I have no idea how to write that out in code. hahah. That's about as far as I got. Any help would be great!
Read up on bit-shifting. Add up the integers and then shift away. Just keep in mind that if you have to add a lot of integers together that you might need to use a unsigned long to hold all the values. Or you might over-run the numerical limits of the variables you're using as an accumulator (i.e. 32K for int, 65k for unsigned int, etc.).
The actual interference could be of three kinds, either ground-loop currents causing an EMF on the TMP36 circuit, power supply voltage dips on the relay operating, or an electromagnetically induced signal caused by relay and its wiring being too close to sensor wiring.
Its important any large changing currents in the ground wiring do not flow through the sensor circuit - this means take the ground wire to the sensor off a different pin from the relay. Such currents generate a small voltage across the resistance of the wiring and this is added on to the sensor's output.
Its also important the relay isn't overloading the regulator, you can measure how much the supply voltage dips when the relay is on - remember the +5V supply is, by default, the reference for the ADC.
As for induced signals, run the sensor wires away from the relay wires, use shielded cable or twisted pairs (one half ground for each pair). Each relay winding must have a flywheel diode across it BTW - that's essential.
All wiring is fairly close as it is in the development stage write now. I have seperate grounds and used the 3.3V instead. It has helped but I am still getting spikes, although not as bad, when the relay is energized. There is a diode on the relay aswell.
Constantin,
I have been doing a lot of research and borrowing codes from here and there. I found an interesting one where someone was having the same issue and has sped up the readings and averaged it. He has had much luck with it and I am trying to integrate it into my code but I am a little stuck. Basically I just want read this new averaged temp every 3min or so(once the spikes are corrected) and carry on with the code from there. Tell me what you think of what I have done, hopefully its not too messy!
long HeatAutoOn = 19;
long AcAutoOn = 25;
long CheckTemp = 10000;
unsigned long time;
long previoustime;
float T;
float TempDiffHeat = HeatAutoOn+1.5;
float TempDiffAc = AcAutoOn-1.5;
boolean heat = false;
boolean ac = false;
boolean off = false;
int TempPin = 0;
int w = 7;
int g = 9;
int y = 8;
int BAUD_RATE = 9600;
#define InterruptOff TIMSK2 &= ~(1<<TOIE2)
#define InterruptOn TIMSK2 |= (1<<TOIE2)
volatile long TempReadCount = 0;
volatile long Temperature = 0;
volatile int int_counter = 0;
volatile int seconds = 0;
int oldSeconds = 0;
unsigned int tcnt2; //used for timer value
// Aruino runs at 16 Mhz, so we have 1000 Overflows per second...
// this little routine will get hit 1000 times a second.
ISR(TIMER2_OVF_vect) {
long junk;
int i;
int_counter++;
if (int_counter == 1000) {
seconds+=1;
int_counter = 0;
}
// junk = 0; // oversampling according to Atmel avr121 app note
// for(i=0; i<16; i++)
// junk += analogRead(TempPin); //Oversampling
// Temperature += (junk >> 4);
Temperature += analogRead(TempPin);
TempReadCount ++;
TCNT2 = tcnt2; //reset the timer for next time
}
// Timer setup code borrowed from Sebastian Wallin
// http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/
// This was the best explanation I found on the web.
void setupTimer()
{
//Timer2 Settings: Timer Prescaler /1024
// First disable the timer overflow interrupt while we're configuring
TIMSK2 &= ~(1<<TOIE2);
// Configure timer2 in normal mode (pure counting, no PWM etc.)
TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
// Select clock source: internal I/O clock
ASSR &= ~(1<<AS2);
// Disable Compare Match A interrupt enable (only want overflow)
TIMSK2 &= ~(1<<OCIE2A);
// Now configure the prescaler to CPU clock divided by 128
TCCR2B |= (1<<CS22) | (1<<CS20); // Set bits
TCCR2B &= ~(1<<CS21); // Clear bit
/* We need to calculate a proper value to load the timer counter.
* The following loads the value 131 into the Timer 2 counter register
* The math behind this is:
* (CPU frequency) / (prescaler value) = 125000 Hz = 8us.
* (desired period) / 8us = 125.
* MAX(uint8) - 125 = 131;
*/
/* Save value globally for later reload in ISR */
tcnt2 = 131;
/* Finally load end enable the timer */
TCNT2 = tcnt2;
TIMSK2 |= (1<<TOIE2);
sei();
}
#define numReadings 10 // these items used for smoothing temp readings
float readings[numReadings];
int index = 0;
float total = 0.0;
void HandleTemp(){ // lots of smoothing in here. also, uses floats to keep it from jumping
float T; // by whole degrees.
InterruptOff; //so it doesn't change while getting it.
T = Temperature / TempReadCount; //get the average of the readings taken during interrupts
Temperature = 0; //and start over for the next second
TempReadCount = 0;
InterruptOn;
total -= readings[index]; // rolling average over 'numReadings'
readings[index] = T;
total += T;
index++;
if (index >= numReadings)
index = 0;
T = total / (float)numReadings; //average these readings also (smooth it out even more)
T = T * 3.3 / 1024;
T = (T - 0.5) * 100.0;
Serial.print("temp=");
Serial.print(T,DEC);
Serial.print("C ");
// Serial.print((T * 9.0)/5.0 + 32.0);
// Serial.println("F");
//Temp. smoothing borrowed from Dave http://www.desert-home.com/p/super-thermostat.html?showComment=1333581698177
}
void setup()
{
pinMode (A1, OUTPUT);
pinMode (A2, OUTPUT);
pinMode (w, OUTPUT);
pinMode (g, OUTPUT);
pinMode (y, OUTPUT);
digitalWrite (A1, LOW);
digitalWrite (A2, HIGH);
Serial.begin(BAUD_RATE);
}
void loop()
{
time = millis();
uint8_t i;
///*******This is where I believe my problem lies!!! Somehow I can't get the new averaged temperature to be incorporated into the code******/////
///*******I don't know if this is a simple fix or I am just completley out to lunch!!
if ((time - previoustime) > CheckTemp ){
previoustime = time;
HandleTemp();
}
if (Cvalue <= HeatAutoOn && heat == false) {
digitalWrite(w, HIGH);
Serial.println("HEAT ON AUTO");
delay(4000);
digitalWrite(g, HIGH);
heat = true;
ac = false;
off = false;
}
if ( Cvalue >= TempDiffHeat && Cvalue <= TempDiffAc && off == false){
digitalWrite(w, LOW);
Serial.println("HVAC Off");
delay(4000);
digitalWrite(g, LOW);
digitalWrite(y, LOW);
off = true;
heat = false;
ac = false;
}
if (Cvalue >= AcAutoOn && ac == false) { // turns on A/C
digitalWrite(y, HIGH);
digitalWrite(g, HIGH);
Serial.println("A/C ON");
heat = false;
ac = true;
off = false;
}
{
int command = Serial.read();
if(command == '1')
{
digitalWrite(w, HIGH);
Serial.println("HEAT ON");
delay(4000);
digitalWrite(g, HIGH);
}
else if (command == '2')
{
digitalWrite(w, LOW);
Serial.println("HEAT OFF");
delay(4000);
digitalWrite(g, LOW);
}
if(command == '3')
{
digitalWrite(y, HIGH);
digitalWrite(g, HIGH);
Serial.println("A/C ON");
}
else if (command == '4')
{
digitalWrite(y, LOW);
digitalWrite(g, LOW);
Serial.println("A/C OFF");
}
if(command == '!')
{
digitalWrite(g, HIGH);
Serial.println("FAN ON");
}
else if (command == '@')
{
digitalWrite(g, LOW);
Serial.println("FAN OFF");
}
}
}
Doesn't compile. Looks like all you have to do is declare Cvalue as a global and then set it to T in HandleTemp.
That is an awfully elaborate way to handle smoothing the temperature though, especially resorting to interrupts when you're talking about slow temperature changes. I still like my original suggestion better, which was to avoid reading the temp for a few seconds after turning things on or off. Alternatively, just smooth the reading by adding 5% of the latest reading to 95% of an accumulated average.
You don't have to remember to change both constants each time and its provable stable when Reading == Temp even with floating point rounding issues. both 0.95 and 0.05 are inaccurately represented in floating point and in particular 0.95+0.05 may not equal 1.0
When the temperature spikes it stays up until the relay is off.
Then all the smoothing in the world isn't going to help. You have a hardware issue that I don't think software can help you with. If the error is consistent, I suppose you could subtract it out when you know something is running, but that seems really dirty. You need one of the hardware gurus here to chime in. A schematic would help.
I dont understand how to set it to T in Handle Temp
This was some original code that I think is much simpler, and I can acutally get it to work...besides the nasty spike which stays when the relay is energized. I think I will try to tweak this one as I can fully understand it!
#define NUMSAMPLES 10
int samples[NUMSAMPLES];
long HeatAutoOn = 19;
long AcAutoOn = 25;
long CheckTemp = 10000;
unsigned long time;
long previoustime;
float TempDiffHeat = HeatAutoOn+1.5;
float TempDiffAc = AcAutoOn-1.5;
boolean heat = false;
boolean ac = false;
boolean off = false;
float SUPPLY_VOLTAGE = 5.0;
int tempPin = 0;
int w = 7;
int g = 9;
int y = 8;
int BAUD_RATE = 9600;
void setup()
{
pinMode (A1, OUTPUT);
pinMode (A2, OUTPUT);
pinMode (w, OUTPUT);
pinMode (g, OUTPUT);
pinMode (y, OUTPUT);
digitalWrite (A1, LOW);
digitalWrite (A2, HIGH);
Serial.begin(BAUD_RATE);
}
void loop()
{
float averageTemp = getVoltage(tempPin);
averageTemp = (averageTemp - .5) * 100;
time = millis();
uint8_t i;
if ((time - previoustime) > CheckTemp ){
previoustime = time;
// take N samples in a row, with a slight delay
for (i=0; i< NUMSAMPLES; i++) {
samples[i] = analogRead(tempPin);
delay(10);
}
// average all the samples out
averageTemp = 0;
for (i=0; i< NUMSAMPLES; i++) {
averageTemp += samples[i];
}
averageTemp /= NUMSAMPLES;
averageTemp = getVoltage(tempPin);
averageTemp = (averageTemp - .5) * 100;
Serial.print("Degrees C ");
Serial.println(averageTemp);
delay(1000);
}
if (averageTemp <= HeatAutoOn && heat == false) {
digitalWrite(w, HIGH);
Serial.println("HEAT ON AUTO");
delay(4000);
digitalWrite(g, HIGH);
heat = true;
ac = false;
off = false;
}
if (averageTemp >= TempDiffHeat && averageTemp <= TempDiffAc && off == false){
digitalWrite(w, LOW);
Serial.println("HVAC Off");
delay(4000);
digitalWrite(g, LOW);
digitalWrite(y, LOW);
off = true;
heat = false;
ac = false;
}
if (averageTemp >= AcAutoOn && ac == false) { // turns on A/C
digitalWrite(y, HIGH);
digitalWrite(g, HIGH);
Serial.println("A/C ON");
heat = false;
ac = true;
off = false;
}
{
int command = Serial.read();
if(command == '1')
{
digitalWrite(w, HIGH);
Serial.println("HEAT ON");
delay(4000);
digitalWrite(g, HIGH);
}
else if (command == '2')
{
digitalWrite(w, LOW);
Serial.println("HEAT OFF");
delay(4000);
digitalWrite(g, LOW);
}
if(command == '3')
{
digitalWrite(y, HIGH);
digitalWrite(g, HIGH);
Serial.println("A/C ON");
}
else if (command == '4')
{
digitalWrite(y, LOW);
digitalWrite(g, LOW);
Serial.println("A/C OFF");
}
if(command == '!')
{
digitalWrite(g, HIGH);
Serial.println("FAN ON");
}
else if (command == '@')
{
digitalWrite(g, LOW);
Serial.println("FAN OFF");
}
}
}
float getVoltage(int pin)
{
return (analogRead(pin) * .004882814);
}
I will post the shematic shortly thanks again everyone
I much prefer the form:
Code:
Temp += 0.05 * (Reading-Temp) ;
You don't have to remember to change both constants each time and its provable stable when Reading == Temp even with floating point rounding issues. both 0.95 and 0.05 are inaccurately represented in floating point and in particular 0.95+0.05 may not equal 1.0
and it performs faster too (replacing 1 floating multiply by 1 floating subtraction)
The TMP36 is an accurate and easy to use with an ADC and oversampling is certainly the way to measure temperature.
(I would take 50 or so readings every hundred milliseconds and average) but what I don't understand here is why it is
necessary to use any temp data that came during the command to the 'relay' or for that matter any other command
sequence or event, unless the device is in a PID loop. Although the long thermal inertia of the '36 would eliminate even
that concern... IMHO
I did a similar project recently, and had similar issues using a TMP36. I only had a single relay and an LCD shield. Everytime I would energize the relay or turn on the backlight on the LCD shield, my temperature would jump up be several degrees (°C).
I tried taking several raw values and averaging them before converting to a temperature and it helped but still had issues. I noticed that the reference voltage would drop ever so slightly as I turned on the outputs and determined this is what was making the temperature jump. Then I tried using a precision voltage reference (REF5040) but still saw some jumps in temperature. My final solution was to replace the TMP36 with a DS18B20 and all of my issues went away. I did not even have to modify my wiring too much as I switched the analog input I was using to be a digital input and carried on.
This was my first arduino project and I used a clone that I bought off of eBay. I'm not sure if this clone is inferior quality and maybe the TMP36 would work better with a real arduino or not but I was unable to successfully use a TMP36 in a way that would work for me.
For the relay, I used the 5v from the Arduino Uno board which I assumed is not being sourced by the Ardiuno itself, but the voltage regulator and just drove a transistor using the Arduino output. I will admit that I never looked at the schematic to verify the 5v source though. The relay module I used was also from eBay.
Since the Arduino analog inputs are only 10-bit, a raw value change of two jumps almost an entire degree (°C). Filtering helped this obviously but I still saw the temperature going up when the power dropped and because a small raw change caused such a large temperature increase, I ended up moving to a digital device.