Simple countdown algorithm for potentiometer and a button

Hello. I am trying to make a UV curing box for my 3d prints. I can display the time for curing on a 16x2. I can start the countdown and display it on the LCD again but countdown stops after 1 second. What am I doing wrong?

#include <TinyWireM.h>        
#include <LiquidCrystal_I2C.h> 

#define GPIO_ADDR     0x3f

LiquidCrystal_I2C lcd(GPIO_ADDR,16,2); 

const int relay = 5;
const int buttonPin = 4;     // the number of the pushbutton pin
const int pot = 3;
const int buzz = 2;
const int ledPin =  1;      // the number of the LED pin


int buttonState = 0;
int sensorValue = 0;
int timer = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(relay, OUTPUT);
  pinMode(buzz, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(pot, INPUT);

  TinyWireM.begin();
  lcd.init();
  lcd.backlight();

}

void loop() {
  buttonState = digitalRead(buttonPin);
  
  if (buttonState == HIGH) {
      buttonPress();
  } else {
      mainScreen();
  }
  delay(20);
}
void buttonPress(){
  
  buttonState == LOW;
  int sec = 59;
  
  while(timer > -1){
    digitalWrite(ledPin, HIGH);
    digitalWrite(relay, HIGH);
    
    lcd.clear();
    lcd.setCursor (0,0);
    lcd.print("Time left:");
    
    lcd.setCursor (0,1);
    lcd.print("Press to cancel");

    lcd.setCursor (12,0);
    lcd.print(timer - 1);
    lcd.setCursor (13,0);
    lcd.print(":");
    lcd.setCursor (14,0);
    lcd.print(sec);
    sec = sec - 1;
    if(sec < 1){
      timer = timer - 1;
      sec = 59;          
      }
    delay(1000);

    if  (buttonState == HIGH){
      break;
      }
  }

  digitalWrite(ledPin, LOW);
  digitalWrite(relay, LOW);
  
  digitalWrite(buzz, HIGH);
  delay(300);
  digitalWrite(buzz, LOW);
  delay(300);
  digitalWrite(buzz, HIGH);
  delay(300);
  digitalWrite(buzz, LOW);
  delay(300);
  digitalWrite(buzz, HIGH);
  delay(1000);
  digitalWrite(buzz, LOW);
  mainScreen();
  }

void mainScreen(){
      sensorValue = analogRead(pot)/100;
      timer = sensorValue;
    
      lcd.clear();
      lcd.setCursor (0,0);
      lcd.print("UV Exposure Time:");
      lcd.setCursor (0,1);
      lcd.print(sensorValue);
      lcd.setCursor (3,1);
      lcd.print("minute(s)");
  }

Oops

1 Like

Fast answer, so fast retry:) I deleted that part and still the same.

Is that supposed to be "sec"?

Take a look at the blink without delay example in the IDE.

How could that be?

I cannot say this is perfect approach but what I am trying to do is to create a countdown on the lcd and keep the uv leds on meanwhile. While the countdown continues, user can interrupt the countdown by pressing the same button that starts the countdown.

I checked the blink without delay example and what I understood is keeping an eye on the time constantly and do something on the set interval. I liked it but I am not sure how should I implement this to my example and why it is necessary.

"Break" usage is to kill the countdown while loop. Is this a wrong way to use?

The while loop I created is in the void loop section. My intention was to keep the arduino in the loop I created until the timer is set to zero. But I guess main loop prevents the loop I created to keep the timer on so the loop I created runs only 1 time although it should run as many time as the second version of the potentiometer value.

I do not know why and how I can fix it. Maybe a completely different approach should be used. I found this: arduino - How can I break this loop on a button press? - Stack Overflow but I couldn't make it work with the lcd.

I changed the delay part looking at the blink without delay example as below. Now leds turn off instantly instead of waiting for 1 second.

#include <TinyWireM.h>        
#include <LiquidCrystal_I2C.h> 

#define GPIO_ADDR     0x3f

LiquidCrystal_I2C lcd(GPIO_ADDR,16,2); 

const int relay = 5;
const int buttonPin = 4;     // the number of the pushbutton pin
const int pot = 3;
const int buzz = 2;
const int ledPin =  1;      // the number of the LED pin


int buttonState = 0;
int sensorValue = 0;
int timer = 0;
unsigned long previousMillis = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(relay, OUTPUT);
  pinMode(buzz, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(pot, INPUT);

  TinyWireM.begin();
  lcd.init();
  lcd.backlight();

}

void loop() {
  buttonState = digitalRead(buttonPin);
  
  if (buttonState == HIGH) {
      buttonPress();
  } else {
      mainScreen();
  }
  delay(20);
}
void buttonPress(){
  
  buttonState == LOW;
  int sec = 59;
  
  while(timer > -1){
    digitalWrite(ledPin, HIGH);
    digitalWrite(relay, HIGH);
    
    lcd.clear();
    lcd.setCursor (0,0);
    lcd.print("Time left:");
    
    lcd.setCursor (0,1);
    lcd.print("Press to cancel");

    lcd.setCursor (12,0);
    lcd.print(timer - 1);
    lcd.setCursor (13,0);
    lcd.print(":");
    lcd.setCursor (14,0);
    lcd.print(sec);
    sec = sec - 1;
    if(sec < 1){
      timer = timer - 1;
      sec = 59;          
      }
    unsigned long currentMillis = millis();

    if (currentMillis - previousMillis >= 1000) {
      // save the last time you blinked the LED
      previousMillis = currentMillis;

      sec = sec - 1;
    }

    if  (buttonState == HIGH){
      break;
      }
  }

  digitalWrite(ledPin, LOW);
  digitalWrite(relay, LOW);
  
  digitalWrite(buzz, HIGH);
  delay(300);
  digitalWrite(buzz, LOW);
  delay(300);
  digitalWrite(buzz, HIGH);
  delay(300);
  digitalWrite(buzz, LOW);
  delay(300);
  digitalWrite(buzz, HIGH);
  delay(1000);
  digitalWrite(buzz, LOW);
  mainScreen();
  }

void mainScreen(){
      sensorValue = analogRead(pot)/100;
      timer = sensorValue;
    
      lcd.clear();
      lcd.setCursor (0,0);
      lcd.print("UV Exposure Time:");
      lcd.setCursor (0,1);
      lcd.print(sensorValue);
      lcd.setCursor (3,1);
      lcd.print("minute(s)");
  }

One possible reason is because your code needs to keep checking the button during the countdown, in case it is pressed again. It can't do that during delay(1000), so any button press could easily be missed, unless you held the button down for at least a second, so that it is held down at the point between delay(1000)s when it gets checked. But you can check the button more frequently, like 10 or 100 times per second, while still using delay(). You could use delay(10) and have a third variable to count the hundredths of seconds, as well as the variables to count seconds and minutes that you have now.

But if you try that, you will immediately get another problem: You won't be able to press the button to start the timer for a short enough time to prevent it stopping again 10ms later. You need to check out the "state change" example sketch to see how that handles detecting button presses.

You need to remove those delay() and just track elapsed time... something like this (untested)

#include <TinyWireM.h>
#include <LiquidCrystal_I2C.h>

#define GPIO_ADDR     0x3f

LiquidCrystal_I2C lcd(GPIO_ADDR, 16, 2);

const int relay = 5;
const int buttonPin = 4;     // the number of the pushbutton pin
const int pot = 3;
const int buzz = 2;
const int ledPin =  1;      // the number of the LED pin

enum  { IDLE, RUNNING };
int state = IDLE;
int lastButtonState;
unsigned long startTime;

int timer;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(relay, OUTPUT);
  pinMode(buzz, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(pot, INPUT);

  TinyWireM.begin();
  lcd.init();
  lcd.backlight();
}


void loop() {
  int buttonState = digitalRead(buttonPin);

  if ( buttonState != lastButtonState ) {
    if (buttonState == HIGH) {
      if ( state == RUNNING ) {
        // button pressed while running so cancel
        cancel();
        state = IDLE;
      }
      else {
        // button pressed to start cycle
        buttonPress();
        state = RUNNING;
      }
      delay(20);
    }
  }
  lastButtonState = buttonState;

  if ( state == IDLE ) {
    mainScreen();
  }
  if ( state == RUNNING ) {
    updateDisplay();
  }
}

void buttonPress() {

  digitalWrite(ledPin, HIGH);
  digitalWrite(relay, HIGH);

  lcd.clear();
  lcd.setCursor (0, 0);
  lcd.print("Time left:");

  lcd.setCursor (0, 1);
  lcd.print("Press to cancel");
  startTime = millis();
}

void cancel() {
  digitalWrite(ledPin, LOW);
  digitalWrite(relay, LOW);
}

void updateDisplay() {

  // calculate elapsed seconds
  unsigned long elapsedSeconds = (millis() - startTime) / 1000;
  int timeLeft = timer - elapsedSeconds;

  if ( timeLeft <= 0 ) {
    // all done
    cancel();
    digitalWrite(buzz, HIGH);
    delay(300);
    digitalWrite(buzz, LOW);
    delay(300);
    digitalWrite(buzz, HIGH);
    delay(300);
    digitalWrite(buzz, LOW);
    delay(300);
    digitalWrite(buzz, HIGH);
    delay(1000);
    digitalWrite(buzz, LOW);
    state = IDLE;
  }
  else {
    // update the display
    int min = timeLeft / 60;
    int sec = timeLeft % 60;
    lcd.setCursor (12, 0);
    if ( min < 10 ) lcd.print('0');
    lcd.print(min);
    lcd.print(':');
    if ( sec < 10 ) lcd.print('0');
    lcd.print(sec);
  }
}

void mainScreen() {
  int sensorValue = analogRead(pot) / 100;
  timer = sensorValue * 60;

  lcd.clear();
  lcd.setCursor (0, 0);
  lcd.print("UV Exposure Time:");
  lcd.setCursor (0, 1);
  lcd.print(sensorValue); lcd.print("  ");
  lcd.setCursor (3, 1);
  lcd.print("minute(s)");
}
1 Like

Still oops

Wow, that worked perfectly. I tried to comprehend the code completely but I failed:( Feeling so stupid now.

I made this, this complicated because I wanted to use 1 button I think. I am writing down all the process and trying to understand the logic.

Btw, the seconds part does not fit the screen so I changed it to lcd.setCursor (11, 0); instead of lcd.setCursor (12, 0);. And lcd.print("minute(s)"); part refreshes itself so fast that it is hard to read it but it is alright. I have bigger problems now:)

Thank you very much!

I should show the countdown part too:

Change your exposure time to be more like the countdown. Do not clear the display and constantly redraw it. rather, just update the value and maybe use MM:SS.

1 Like