Implementing a compressor start delay for fridge control

I'm building a controller to control various aspects of the brewing process.
I think i've pretty much got it working, it reads a temp sensor and depending on the reading and the chosen set point it will turn on a fridge compressor or a heater.
I've included a form of deadband/hysteresis which appears to work satisfactorily.

The next step is to implement some form of startup delay for the fridge compressor to ensure it's not starting up too often.

This code snippet is the part that loops and does the temp control

void settemp(float setPoint)
{
 long lastUpdate = 0;  // Force update
  byte i;
  byte key = 0xFF;
    while (key!= CENTER_KEY) {
    // Update temp
    if( millis() > lastUpdate + 1000) 
    {
      displaytemps();
      displaysetpoint();
        // Read temperature and humidity
        if ((temp1 <= (setPoint + difference)) && (temp1 >= (setPoint - difference)))
        {
         digitalWrite(Heater, LOW);
         digitalWrite(Fridge, LOW); 
         lcd.writeString(10, 0,  "ALL OFF", MENU_NORMAL);
          
        }
        if (temp1 < (setPoint - difference))
        {
            digitalWrite(Heater, HIGH);
            digitalWrite(Fridge, LOW); 
            lcd.writeString(10, 0, "Heating", MENU_NORMAL);       
        }
        if (temp1 > (setPoint + difference))
        {
            digitalWrite(Heater, LOW);
            digitalWrite(Fridge, HIGH); 
            lcd.writeString(10, 0, "Cooling", MENU_NORMAL);          
        }
        
         
      lastUpdate = millis();
    }
    key = checkKeypressed();
  }
  return;
 }

what is the best way of implementing a delay/timer so that the compressor can't start more often than 5 minutes?

The best way is to use the blink-without-delay tutorial as a template.
Pay attention to datatypes, particularly because five minutes represents more milliseconds than you can fit into an "int".

(In your function above, there is no need for the "return" before the closing brace - in C, the return is implicit.)

thanks, it's a long time since i read that tutorial so i'll go and have a look.

Re: return, yeah i didn't think it was necessary but at some point the code wasn't working, i must have added it and changed something else, then once working forgot about it.

You might want to post the entire sketch - in that snippet, it's not apparent where you actually read the temperature, in spite of the comment.

Yes, post all the code - that "lastUpdate" being an automatic looks a little suspect.

Do you actually care about the interval between fridge starts exactly? I assume that you're trying to avoid multiple on/offs when the temperature is around setpoint+difference. Another way to achieve this would be to use two different difference variables. One larger, which controls when you turn a device (heater or cooler) on, and a smaller (or zero) one which controls when you turn it off. This way, when the temperature gets out of range, the selected device will move the temp well into the dead zone before it turns off.

#include <TimeAlarms.h>
#include <Time.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <nokia_3310_lcd.h>

// Temp/humidity display using nokia 3310 LCD display shield from nuelectronics.com

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

//keypad debounce parameter
#define DEBOUNCE_MAX 15
#define DEBOUNCE_ON  10
#define DEBOUNCE_OFF 3 

#define NUM_KEYS 5

#define NUM_MENUS	1
#define NUM_MENU_ITEM	5

// joystick number
#define UP_KEY 3
#define LEFT_KEY 0
#define CENTER_KEY 1
#define DOWN_KEY 2
#define RIGHT_KEY 4

// Pin used by Backlight, so we can turn it on and off. Pin setup in LCD init function
#define BL_PIN 7

// menu starting points
#define MENU_X	0		// 0-83
#define MENU_Y	0		// 0-5


// adc preset value, represent top value,incl. noise & margin,that the adc reads, when a key is pressed
// set noise & margin = 30 (0.15V@5V)
int  adc_key_val[5] ={
  30, 150, 360, 535, 760 };

// debounce counters
byte button_count[NUM_KEYS];
// button status - pressed/released
byte button_status[NUM_KEYS];
// button on flags for user program 
byte button_flag[NUM_KEYS];

// menu definition
char menu_items[NUM_MENU_ITEM][20]={
 
    "Condition 10C",
    "Lager Ferm 15C",
    "Ale Ferm 20C",
    "Strike 72C",
    "Boil 100C"

};
void (*menu_funcs[NUM_MENU_ITEM])(void) = {

    condition,
    lager,
    ale,
    strike,
    boil
 
};

char current_menu_item;
int Heater = 0, Fridge = 1;
int blflag = 1;  // Backlight initially ON
byte i;
float strikeTemp = 72, boilTemp = 100, lagerFermTemp = 15, aleFermTemp = 20, conditionTemp = 10, difference = 0.5, setPoint;
float temp1;
long previousMillis = 0;        // will store last time LED was updated
// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 1000;
Nokia_3310_lcd lcd=Nokia_3310_lcd();

void setup()
{
  
  pinMode(Fridge, OUTPUT);
  pinMode(Heater, OUTPUT);
  sensors.begin();
  Serial.begin(9600);
  readsensors();
  // setup interrupt-driven keypad arrays  
  // reset button arrays
  for(byte i=0; i<NUM_KEYS; i++){
    button_count[i]=0;
    button_status[i]=0;
    button_flag[i]=0;
  }

  // Setup timer2 -- Prescaler/256
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
  TCCR2B &= ~(1<<WGM22);
  TCCR2B = (1<<CS22)|(1<<CS21);      

  ASSR |=(0<<AS2);

  // Use normal mode  
  TCCR2A =0;    
  //Timer2 Overflow Interrupt Enable  
  TIMSK2 |= (0<<OCIE2A);
  TCNT2=0x6;  // counting starts from 6;  
  TIMSK2 = (1<<TOIE2);    

  SREG|=1<<SREG_I;

  lcd.init();
  lcd.clear();

  //menu initialization
  init_MENU();  
  current_menu_item = 0;	 
}

/* loop */
void loop() {

  byte i;
  for(i=0; i<NUM_KEYS; i++) {
    if(button_flag[i] !=0) {

      button_flag[i]=0;  // reset button flag
      switch(i){
      case UP_KEY:
        // current item to normal display
        lcd.writeString(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_NORMAL );
        current_menu_item -=1;
        if(current_menu_item <0)  current_menu_item = NUM_MENU_ITEM -1;
        // next item to highlight display
        lcd.writeString(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_HIGHLIGHT );
        break;
      case DOWN_KEY:
        // current item to normal display
        lcd.writeString(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_NORMAL );
        current_menu_item +=1;
        if(current_menu_item >(NUM_MENU_ITEM-1))  current_menu_item = 0;
        // next item to highlight display
        lcd.writeString(MENU_X, MENU_Y + current_menu_item, menu_items[current_menu_item], MENU_HIGHLIGHT );
        break;
      case LEFT_KEY:
        init_MENU();
        current_menu_item = 0;
        break;   
      case RIGHT_KEY:
        lcd.clear();
        (*menu_funcs[current_menu_item])();
        lcd.clear();
        init_MENU();
        current_menu_item = 0;           
        break;	
      }
    }
  }
}

/* menu functions */
void init_MENU(void) {

  byte i;
  lcd.clear();
  lcd.writeString(MENU_X, MENU_Y, menu_items[0], MENU_HIGHLIGHT );

  for (i=1; i<NUM_MENU_ITEM; i++) {
    lcd.writeString(MENU_X, MENU_Y+i, menu_items[i], MENU_NORMAL);
  }
}

// waiting for center key press
void waitfor_OKkey() {
  byte i;
  byte key = 0xFF;
  while (key!= CENTER_KEY){
    //    key = lcd.get_key();

    for(i=0; i<NUM_KEYS; i++){
      if(button_flag[i] !=0){
        button_flag[i]=0;  // reset button flag
        if(i== CENTER_KEY) key=CENTER_KEY;
      }
    }
  }
}


// Check if joystick is moved or pressed
byte checkKeypressed() {
  byte key = 0xFF;

  //  key = lcd.get_key();

  for(int i=0; i<NUM_KEYS; i++){
    if(button_flag[i] !=0){
      button_flag[i]=0;  // reset button flag
      if(i== CENTER_KEY) key=CENTER_KEY;
    }
  }

  return key;
}

void readsensors()
{
   //Request temperatures
  sensors.requestTemperatures();
  temp1 = sensors.getTempCByIndex(0);
  
}

void displaytemps()
{
      readsensors();
      byte precision = 1;
      char floatBuffer[20];
      
      //Request and display temperatures
     sensors.requestTemperatures();
     temp1 = sensors.getTempCByIndex(0);
     dtostrf(temp1, precision+3, precision, floatBuffer);
     lcd.writeStringBig(30, 2, floatBuffer, MENU_NORMAL);
}

void displaysetpoint()
{
      readsensors();
      byte precision = 1;
      char floatBuffer[20];
      

     dtostrf(setPoint, precision+3, precision, floatBuffer);
     lcd.writeString(30, 1, floatBuffer, MENU_NORMAL);
}

void settemp(float setPoint)
{
 long lastUpdate = 0;  // Force update
  byte i;
  byte key = 0xFF;
    while (key!= CENTER_KEY) {
    // Update temp
    if( millis() > lastUpdate + 1000) 
    {
      displaytemps();
      displaysetpoint();
        // Read temperature and humidity
        if ((temp1 <= (setPoint + difference)) && (temp1 >= (setPoint - difference)))
        {
         digitalWrite(Heater, LOW);
         digitalWrite(Fridge, LOW); 
         lcd.writeString(10, 0,  "ALL OFF", MENU_NORMAL);
          
        }
        if (temp1 < (setPoint - difference))
        {
            digitalWrite(Heater, HIGH);
            digitalWrite(Fridge, LOW); 
            lcd.writeString(10, 0, "Heating", MENU_NORMAL);       
        }
        if (temp1 > (setPoint + difference))
        {
            digitalWrite(Heater, LOW);
            digitalWrite(Fridge, HIGH); 
            lcd.writeString(10, 0, "Cooling", MENU_NORMAL);          
        }
        
         
      lastUpdate = millis();
    }
    key = checkKeypressed();
  }
  return;
 }

   

void condition()
{
  setPoint = conditionTemp;
 
  settemp(setPoint);
}
 
void lager()
{
  setPoint = lagerFermTemp;
 
  settemp(setPoint);
};
void ale()
{
  setPoint = aleFermTemp;
 
  settemp(setPoint);
};
void strike()
{
  setPoint = strikeTemp;
 
  settemp(setPoint);
};
void boil()
{
  setPoint = boilTemp;
 
  settemp(setPoint);
};


// The followinging are interrupt-driven keypad reading functions
//  which includes DEBOUNCE ON/OFF mechanism, and continuous pressing detection

// Convert ADC value to key number
char get_key(unsigned int input) {
  char k;

  for (k = 0; k < NUM_KEYS; k++) {
    if (input < adc_key_val[k]) {
      return k;
    }
  }

  if (k >= NUM_KEYS)
    k = -1;     // No valid key pressed

  return k;
}

void update_adc_key() {
  int adc_key_in;
  char key_in;
  byte i;

  adc_key_in = analogRead(0);
  key_in = get_key(adc_key_in);
  for(i=0; i<NUM_KEYS; i++) {
    if(key_in==i) { //one key is pressed 
      if(button_count[i]<DEBOUNCE_MAX)       {
        button_count[i]++;
        if(button_count[i]>DEBOUNCE_ON)         {
          if(button_status[i] == 0)           {
            button_flag[i] = 1;
            button_status[i] = 1; //button debounced to 'pressed' status
          }
        }
      }
    } 
    else  { // no button pressed
      if (button_count[i] >0) {  
        button_flag[i] = 0;	
        button_count[i]--;
        if(button_count[i]<DEBOUNCE_OFF) {
          button_status[i]=0;   //button debounced to 'released' status
        }
      }
    }
  }
}

// Timer2 interrupt routine -
// 1/(160000000/256/(256-6)) = 4ms interval

ISR(TIMER2_OVF_vect) {  
  TCNT2  = 6;
  update_adc_key();
}

just seen there may be some relics from a previous iteration of this code (the time and timealarm library includes for example)

So i'm thinking:

add this to void loop:

  if(currentMillis - previousMillis > compDelay) {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   
    compSafe = 1;
  }

and change void settemp to this:

void settemp(float setPoint)
{
 long lastUpdate = 0;  // Force update
  byte i;
  byte key = 0xFF;
    while (key!= CENTER_KEY) {
    // Update temp
    if( millis() > lastUpdate + 1000) 
    {
      displaytemps();
      displaysetpoint();
        // Read temperature and humidity
        if ((temp1 <= (setPoint + difference)) && (temp1 >= (setPoint - difference)))
        {
         digitalWrite(Heater, LOW);
         digitalWrite(Fridge, LOW); 
         lcd.writeString(10, 0,  "ALL OFF", MENU_NORMAL);
          
        }
        if (temp1 < (setPoint - difference))
        {
            digitalWrite(Heater, HIGH);
            digitalWrite(Fridge, LOW); 
            lcd.writeString(10, 0, "Heating", MENU_NORMAL);       
        }
        if (temp1 > (setPoint + difference)) && (compSafe = 1)     
        {
          
            digitalWrite(Heater, LOW);
            digitalWrite(Fridge, HIGH); 
            lcd.writeString(10, 0, "Cooling", MENU_NORMAL);          
        }
        
         
      lastUpdate = millis();
    }
    key = checkKeypressed();
  }
 }

That way the void loop constantly check to see if it's been long enough since the last compressor started and the temp control loop won't start the compressor unless it is safe.

i just need a way to record when the compressor was last started

I don't think that's going to do it. This:

compSafe = 1

Should be this:

compSafe ==1

But even then, I don't think the method is sound - it looks to me that once you have selected an option from the menu and called SetTemp, it stays there until you press a button, presumably to cancel the action, so the code in loop won't be called while you're managing the temp. You just need a static unsigned long in SetTemp that you capture millis in when you turn the fridge on. Test millis against that in your if to decide whether you can turn it back on yet.

wildbill:
I don't think that's going to do it. This:

compSafe = 1

Should be this:

compSafe ==1

But even then, I don't think the method is sound - it looks to me that once you have selected an option from the menu and called SetTemp, it stays there until you press a button, presumably to cancel the action, so the code in loop won't be called while you're managing the temp.

of course! should have realised that

Ok i've added this, better?

unsigned long currentMillis = millis();  
if ((temp1 > (setPoint + difference)) && ((currentMillis - fridgeLastOn) >= 300000))
        {
            fridgeLastOn = millis();
            digitalWrite(Heater, LOW);
            digitalWrite(Fridge, HIGH); 
            lcd.writeString(10, 0, "Cooling", MENU_NORMAL);          
        }

I've declared fridgeLastOn globally as an unsigned long with value 0, that way if the controlled gets reset it won't run the fridge too soon

That's pretty much it. You should change 300000 to 300000UL, the compiler tends to assume that a numeric literal is an int and truncates it. The rest is fine, although you could get rid of currentMillis and just use millis() directly.

Given that you're trying to keep the temp to +/- 0.5 of a degree, the accuracy, especially repeatability of your temperature sensor may become an issue. I'd be interested to hear how it performs.

Thanks, i'll have a play with this later.

I'm using a onewire ds18b20, it seems to perform ok in testing, i'm also intrigued about how good it's going to be.

I'll try to remember to report back

Had a thought today, although my if statement 'deadband' will stop the fridge and heater fighting each other, there might still be some chatter around the two extremities of the deadband.
for example, if the temp rises above the setpoint + difference the fridge will come on, once it's dropped below setpoint + difference it will turn off.

How can i tell it to come on at the setpoint + difference but not go off until it hit's the setpoint?

This would be a good candidate for a state machine - there are plenty of examples in the forums. However, you probably don't want to rejig your entire sketch just for this change.

So, replace the if that implements "ALL OFF" with two separate ifs. One checks that heater is on and temp > setpoint and turns it off. The other handles the fridge.

thankyou, i might investigate state machines all the same, might work out tidier in the end