One button press = endlessly cycling through modes

I'm trying to create an automated fan control circuit for my cubicle, with an IR remote to cycle through the off/manual/auto modes. I think I have the hardware part figured out, but I'm having trouble with the code.


(The flying leads on the left are for checking my demultiplexer outputs once I figure out my mode switching issues... before I actually hook this up to those salvaged Crydom relays and power...)

It will start up properly, displaying the temp in C and F on the first line, and displaying the initial fan mode ("FAN MODE - OFF") on the second line. If I press Vol- or Vol+, nothing happens, which is correct in this mode. When I press the EQ button (to change modes) it will cycle through each mode every 1 second (I have a 1 second delay at the end of my loop, so it cycles the mode every loop after I press the button). It cycles through "FAN MODE - OFF", "MANUAL - OFF", and "AUTO - OFF", and does not stop. The only button press that can stop the loop is Vol+ but only in one circumstance.

If I manage to press the Vol+ button when it is on "MANUAL - OFF", it will change to "MANUAL - HIGH" and stay there. It does not cycle through the LOW or MEDIUM manual settings, just straight from OFF to HIGH. Nothing happens when I press Vol- or Vol+ again. It stays at this value until I press the EQ button again, at which point it goes back to cycling through off, manoff, autooff.

I think I'm close, but I can't figure out the piece of the puzzle I'm missing here, so that each button press will cycle 1 mode and then remain on that mode until the next button press. Does it need some sort of debounce? Am I totally off track here?

Here's my code (read comments at top for short explanation):

/*

  This sketch will control a fan circuit with an IR "CarMP3" remote control.
  
  Fan Modes: (controlled with EQ button)
  fanMode=0 - OFF
  fanMode=1 - MANUAL
  fanMode=2 - AUTO
  
  Fan Speeds: (controlled with Vol- and Vol+ buttons)
  fanSpeed=0 - OFF
  fanSpeed=1 - LOW
  fanSpeed=2 - MEDIUM
  fanSpeed=3 - HIGH

  -Manual mode allows you to select fan speed yourself with Vol- and Vol+ buttons.
  -Auto mode adjusts the fan speed based on temperature reading from DHT11 sensor.

  The 2 outputs muxA and muxB trigger inputs for a demultiplexer to select 4 outputs,
   which are connected to relays that apply power to one line of the fan.

*/

#include <IRremote.h>
#include <DHT.h>
#include <LiquidCrystal.h>

#define DHTPIN 3
#define DHTTYPE DHT11
DHT dht(DHTPIN,DHTTYPE);

const int remote = 2;
const int muxA = 4;
const int muxB = 5;

int fanMode = 0;
int fanSpeed = 0;

LiquidCrystal lcd(8,9,10,11,12,13);

IRrecv irrecv(remote);
decode_results results;

void setup() {
  lcd.begin(16,2);
  dht.begin();
  irrecv.enableIRIn();

  pinMode(muxA, OUTPUT);
  pinMode(muxB, OUTPUT);
}

void loop() {
  float t = dht.readTemperature(); //take temp reading from sensor
  float tf = (t*1.8)+32; //convert Celcius to Farenheit

  lcd.clear();
  
  if (isnan(t)){ //error display for bad temp sensor read
    lcd.setCursor(0,0);
    lcd.print("Failed DHT read");
  }
  else{ //display temps
    lcd.setCursor(0,0);
    lcd.print(t);
    lcd.print("C - ");
    lcd.print(tf);
    lcd.print("F");
  }

  if (irrecv.decode(&results)){
    irrecv.resume();   // Receive the next value
  }
  
  switch(results.value){
    case 16748655:  // EQ button is pressed
        if(fanMode == 2)
          fanMode=0;
        else
          fanMode++;
      break;
    case 16769055:  // Vol- button is pressed
      if(fanMode == 2){
        while(fanSpeed>0){
          fanSpeed--;
        }
      }
      break;
    case 16754775:  //Vol+ button is pressed
      if(fanMode == 1){
        while(fanSpeed<3){
          fanSpeed++;
        }
      }
      break;
    /*
    case default:
      lcd.setCursor(0,1);
      lcd.print("Invalid input!");
      delay(3000);
    */
  }
  
  if(fanMode==2){
    if(t>=29){
      fanSpeed=3;
      lcd.setCursor(0,1);
      lcd.print("AUTO - HIGH");
    }
    else if(t>=26 && t<=28){
      fanSpeed=2;
      lcd.setCursor(0,1);
      lcd.print("AUTO - MEDIUM");
    }
    else if(t>=23 && t<=25){
      fanSpeed=1;
      lcd.setCursor(0,1);
      lcd.print("AUTO - LOW");
    }
    else{
      fanSpeed=0;
      lcd.setCursor(0,1);
      lcd.print("AUTO - OFF");
    }
  }
  else if(fanMode==1){
    if(fanSpeed==3){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - HIGH");
    }
    else if(fanSpeed==2){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - MEDIUM");
    }
    else if(fanSpeed==1){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - LOW");
    }
    else if(fanSpeed==0){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - OFF");
    }
  }
  else if(fanMode==0){
    fanSpeed=0;
    lcd.setCursor(0,1);
    lcd.print("FAN MODE - OFF");
  }
  
  /*
  if(fanSpeed==3){
    muxA=1;
    muxB=1;
  }
  else if(fanSpeed==2){
    muxA=0;
    muxB=1;
  }
  else if(fanSpeed==1){
    muxA=1;
    muxB=0;
  }
  else{
    muxA=0;
    muxB=0;
  }
  */
  
  delay(1000);
}

thats exacly how you programmed it.
on case 16748655, fanmode++ and when its at 2 then go back to 0.

you must switch to a unkown case when it runned it ones.

or compaire the input. so when old input is differnt from (!=) new input only then increase 1 else do nothing.

EDIT: sorry i wasnt thinking straight but take a look at the IRrealy.ino example from the library. this will explain how to do something only!!! when you receive a new button press. and it also adds a debouce.

Should you not be only changing mode when results.value becomes 16748655 instead of when it is 16748655 ?

spirit - thanks for the pointers, but I'm not sure what you're suggesting... I tried updating my code a little but it hasn't really fixed the issue of cycling through the modes automatically.

UKHeliBob - yeah, maybe that's my hangup... but how do I write that in the code? Where did I go wrong?

/*

  This sketch will control a fan circuit with an IR "CarMP3" remote control.
  
  Fan Modes: (controlled with EQ button)
  fanMode=0 - OFF
  fanMode=1 - MANUAL
  fanMode=2 - AUTO
  
  Fan Speeds: (controlled with Vol- and Vol+ buttons)
  fanSpeed=0 - OFF
  fanSpeed=1 - LOW
  fanSpeed=2 - MEDIUM
  fanSpeed=3 - HIGH

  -Manual mode allows you to select fan speed yourself with Vol- and Vol+ buttons.
  -Auto mode adjusts the fan speed based on temperature reading from DHT11 sensor.

  The 2 outputs muxA and muxB trigger inputs for a demultiplexer to select 4 outputs,
   which are connected to relays that apply power to one line of the fan.

*/

#include <IRremote.h>
#include <DHT.h>
#include <LiquidCrystal.h>

#define DHTPIN 3
#define DHTTYPE DHT11
DHT dht(DHTPIN,DHTTYPE);

const int remote = 2;
const int muxA = 4;
const int muxB = 5;

int fanMode = 0;
int fanSpeed = 0;
int modeStatus = 0;
int speedStatus = 0;
int prevInput = 0;

LiquidCrystal lcd(8,9,10,11,12,13);

IRrecv irrecv(remote);
decode_results results;

void setup() {
  lcd.begin(16,2);
  dht.begin();
  irrecv.enableIRIn();

  pinMode(muxA, OUTPUT);
  pinMode(muxB, OUTPUT);
}

void loop() {
  float t = dht.readTemperature(); //take temp reading from sensor
  float tf = (t*1.8)+32; //convert Celcius to Farenheit

  lcd.clear();
  
  if (isnan(t)){ //error display for bad temp sensor read
    lcd.setCursor(0,0);
    lcd.print("Failed DHT read");
  }
  else{ //display temps
    lcd.setCursor(0,0);
    lcd.print(t);
    lcd.print("C - ");
    lcd.print(tf);
    lcd.print("F");
  }

  if (irrecv.decode(&results)){
    irrecv.resume();   // Receive the next value
  }
  if(prevInput != results.value){
  switch(results.value){
    case 16748655:  // EQ button is pressed
        if(fanMode == 2)
          modeStatus=0;
        else
          modeStatus = fanMode+1;
      break;
    case 16769055:  // Vol- button is pressed
      if(fanMode == 1 && fanSpeed>0)
        speedStatus=fanSpeed-1;
      break;
    case 16754775:  //Vol+ button is pressed
      if(fanMode == 1 && fanSpeed<3)
        speedStatus=fanSpeed+1;
      break;
    /*
    case default:
      lcd.setCursor(0,1);
      lcd.print("Invalid input!");
      delay(3000);
    */
  }
  }
  prevInput = results.value;
  fanSpeed=speedStatus;
  fanMode=modeStatus;
  
  if(fanMode==2){
    if(t>=29){
      fanSpeed=3;
      lcd.setCursor(0,1);
      lcd.print("AUTO - HIGH");
    }
    else if(t>=26 && t<=28){
      fanSpeed=2;
      lcd.setCursor(0,1);
      lcd.print("AUTO - MEDIUM");
    }
    else if(t>=23 && t<=25){
      fanSpeed=1;
      lcd.setCursor(0,1);
      lcd.print("AUTO - LOW");
    }
    else{
      fanSpeed=0;
      lcd.setCursor(0,1);
      lcd.print("AUTO - OFF");
    }
  }
  else if(fanMode==1){
    if(fanSpeed==3){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - HIGH");
    }
    else if(fanSpeed==2){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - MEDIUM");
    }
    else if(fanSpeed==1){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - LOW");
    }
    else if(fanSpeed==0){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - OFF");
    }
  }
  else if(fanMode==0){
    fanSpeed=0;
    lcd.setCursor(0,1);
    lcd.print("FAN MODE - OFF");
  }
  
  /*
  if(fanSpeed==3){
    muxA=1;
    muxB=1;
  }
  else if(fanSpeed==2){
    muxA=0;
    muxB=1;
  }
  else if(fanSpeed==1){
    muxA=1;
    muxB=0;
  }
  else{
    muxA=0;
    muxB=0;
  }
  */
  
  delay(1000);
}

Put EVERY { on a new line. Use Tools + Auto Format to properly indent your code.

Does your transmitter send the came code over and over if you hold a button down? Most do not. If your doesn't the monkey motions with prevInput are not necessary.

The relationship between fanMode, fanSpeed, modeStatus, and speedStatus is not clear. Some comments in your code would really be helpful. Using more than 3 switches on the remote might be in order.

no thats not the way to do it.
as you can see on the edit i made look at the IRremote.ino
and look at the void loop part. dont look at the digitalwrite relay stuff. imagine you code there.

but the irrecv.decode(&results) will be true i guess when you press a button.
then it debounces the presses with the millis stuff(millis() is better then delay() :wink: ). so it doesnt keep triggering.
then you can do your stuff.
reset the millis timer and ir stuff.
and wait for the next press.

spirit - I borrowed some code from "IRrelay.iso", in the IRremote custom library examples. It makes it cycle through the modes properly when I push the buttons... so thanks for that suggestion! I'm going to have to read through it to get a better understanding of the syntax, but I'm glad that part of the circuit is working now...

PaulS - I've just been following the coding style of all the examples and a lot of the other code I've been seeing posted elsewhere, RE:

if(case){
  statement;
}

I understand the following practice, but didn't realize it was so preferred to the other way?

if(case)
{
  statement;
}

Is the second formatting more widely accepted? Why are all the examples and tutorials formatted in the first method I showed?


I've realized now that because of the way I reorganized my code to make the mode switching work properly, I've stopped displaying the temperature in real time. So now it only displays current temperature whenever I press one of the buttons on the remote to change the fan mode or fan speed (which is not how I want it to work, I want real time temp shown)... Derp.

So I have some work to do to patch that up and better understand this... I'll come back and post an update when I've fixed the rest of the code, but thanks for pointing me in the right direction for switching my modes correctly!

/*

  This sketch will control a fan circuit with an IR "CarMP3" remote control.
  
  Fan Modes: (controlled with EQ button)
  fanMode=0 - OFF
  fanMode=1 - MANUAL
  fanMode=2 - AUTO
  
  Fan Speeds: (controlled with Vol- and Vol+ buttons)
  fanSpeed=0 - OFF
  fanSpeed=1 - LOW
  fanSpeed=2 - MEDIUM
  fanSpeed=3 - HIGH

  -Manual mode allows you to select fan speed yourself with Vol- and Vol+ buttons.
  -Auto mode adjusts the fan speed based on temperature reading from DHT11 sensor.

  The 2 outputs muxA and muxB trigger inputs for a demultiplexer to select 4 outputs,
   which are connected to relays that apply power to one line of the fan.

*/

#include <IRremote.h>
#include <DHT.h>
#include <LiquidCrystal.h>

#define DHTPIN 3
#define DHTTYPE DHT11
DHT dht(DHTPIN,DHTTYPE);

const int remote = 2;
const int muxA = 4;
const int muxB = 5;

int fanMode = 0;
int fanSpeed = 0;
int modeStatus = 0;
int speedStatus = 0;
int prevInput = 0;

LiquidCrystal lcd(8,9,10,11,12,13);

IRrecv irrecv(remote);
decode_results results;

void dump(decode_results *results){
  float t = dht.readTemperature(); //take temp reading from sensor
  float tf = (t*1.8)+32; //convert Celcius to Farenheit

  lcd.clear();
  
  if (isnan(t)){ //error display for bad temp sensor read
    lcd.setCursor(0,0);
    lcd.print("Failed DHT read");
  }
  else{ //display temps
    lcd.setCursor(0,0);
    lcd.print(t);
    lcd.print("C - ");
    lcd.print(tf);
    lcd.print("F");
  }
  
  switch(results->value){
    case 16748655:  // EQ button is pressed
        if(fanMode == 2)
          fanMode=0;
        else
          fanMode = fanMode+1;
      break;
    case 16769055:  // Vol- button is pressed
      if(fanMode == 1 && fanSpeed>0)
        fanSpeed=fanSpeed-1;
      break;
    case 16754775:  //Vol+ button is pressed
      if(fanMode == 1 && fanSpeed<3)
        fanSpeed=fanSpeed+1;
      break;
  }
  
  if(fanMode==2){
    if(t>=29){
      fanSpeed=3;
      lcd.setCursor(0,1);
      lcd.print("AUTO - HIGH");
    }
    else if(t>=26 && t<=28){
      fanSpeed=2;
      lcd.setCursor(0,1);
      lcd.print("AUTO - MEDIUM");
    }
    else if(t>=23 && t<=25){
      fanSpeed=1;
      lcd.setCursor(0,1);
      lcd.print("AUTO - LOW");
    }
    else{
      fanSpeed=0;
      lcd.setCursor(0,1);
      lcd.print("AUTO - OFF");
    }
  }
  else if(fanMode==1){
    if(fanSpeed==3){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - HIGH");
    }
    else if(fanSpeed==2){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - MEDIUM");
    }
    else if(fanSpeed==1){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - LOW");
    }
    else if(fanSpeed==0){
      lcd.setCursor(0,1);
      lcd.print("MANUAL - OFF");
    }
  }
  else if(fanMode==0){
    fanSpeed=0;
    lcd.setCursor(0,1);
    lcd.print("FAN MODE - OFF");
  }
  
  /*
  if(fanSpeed==3){
    muxA=1;
    muxB=1;
  }
  else if(fanSpeed==2){
    muxA=0;
    muxB=1;
  }
  else if(fanSpeed==1){
    muxA=1;
    muxB=0;
  }
  else{
    muxA=0;
    muxB=0;
  }
  */
}

void auto(){
  
}

void setup() {
  lcd.begin(16,2);
  dht.begin();
  irrecv.enableIRIn();

  pinMode(muxA, OUTPUT);
  pinMode(muxB, OUTPUT);
}

int on = 0;
unsigned long last = millis();

void loop() {
  if (irrecv.decode(&results)){
    if(millis()-last>250){
      on = !on;
      dump(&results);
    }
    last=millis();
    irrecv.resume();   // Receive the next value
  }
  
  delay(1000);
}

I've just been following the coding style of all the examples and a lot of the other code

Iknowbutitsucks. Spread stuff out so it is readable!

Is the second formatting more widely accepted?

By those that write code for a living? Yes. It is far clearer where a block starts and ends.

At the very least, a space between the statement and the { makes it clearer that a block is starting.

Developing some functions is definitely in order. Create a function to show the data on the LCD. Instead of a big block of code, you have one line. It is far easier to see that you are, or are not, calling that in the right place.

If the IR library does not have a function like IR.resultsClear(); You could write a do/while loop to monitor if the arduino is recieving IR either with the pin the IR receiver is connected to or with a separate IR receiver diode.

do{

  monitorTemp();  // your own custom function to monitor & display temp

}while(!digitalRead(IRpin));  //

processIR();
controlOutput();

the do while loop will "do" the monitorTemp() function that you will write as long as the IRpin is LOW/false, when the pin switches to HIGH/true, the while part will turn false and stop the do loop and the code following it will be run until it loops back to the top of the do/while loop.

This way you have your real time temp monitoring on LCD and control. If you want automatic fan control, you should adjust the fan speed when in auto mode from inside the "monitorTemp();" function. using the arduino map() function, you can proportionally PWM a fan with relation to the temperature if you like.