LCD Menu switch lag (Need Help with State Machine Logic)

Howdy All,

I'm working on an Automatic Pet Feeder. Just starting out, i'm working on the Menu and it's interface that uses a 16x2 LCD (from adafruit), and 3 push buttons.

As far as code is concerned (my .ino is attached), i'm using the increasingly popular Menubackend library. I've adapted it to my project, but my current issue is as follows;

How do I remove the 500ms button push event lag without interfering with the debounce code?

As it functions now, I have to hold down the switch for 500ms before it recognizes the button push. If I shorten the debounce time, it understandably starts to simply scroll quickly through the entire menu. Is it just as simple as adding a break in the correct place? Or do I have to restructure the code?

Feeder_Menu_ino.ino (13.9 KB)

Unless the switches are VERY cheap and VERY poor quality, 10 milliseconds should be plenty of debounce time...

Your debounce time is set by the line:

long debounceDelay = 500;    // the debounce time

Just change it to 10 (20 if you're paranoid).

lar3ry:
Unless the switches are VERY cheap and VERY poor quality, 10 milliseconds should be plenty of debounce time...

Your debounce time is set by the line:

long debounceDelay = 500;    // the debounce time

Just change it to 10 (20 if you're paranoid).

I've tried this, and it instantly travels through the menu, and sub menu and then uses the selected object, all in 10ms.. This is what I've tried to do before, and it makes navigation too quick. It needs to "latch" at each menu item, and not rush through.

crashoverride61088:
I've tried this, and it instantly travels through the menu, and sub menu and then uses the selected object, all in 10ms.. This is what I've tried to do before, and it makes navigation too quick. It needs to "latch" at each menu item, and not rush through.

You have split the readbuttons and navigate menus to avoid doing this. readButtons() should ONLY read the buttons, and ONLY set variables to indicate which buttons have been activated or have changed state. navigateMenus() should act on these changes.

I would suggest implementing a state machine using a state variable to detemine what actions are to be taken within each state, and which button(s) trigger the state changes. See Nick Gammon's state machine tutorial at Gammon Forum : Electronics : Microprocessors : State machines

lar3ry:
You have split the readbuttons and navigate menus to avoid doing this. readButtons() should ONLY read the buttons, and ONLY set variables to indicate which buttons have been activated or have changed state. navigateMenus() should act on these changes.

I would suggest implementing a state machine using a state variable to detemine what actions are to be taken within each state, and which button(s) trigger the state changes. See Nick Gammon's state machine tutorial at Gammon Forum : Electronics : Microprocessors : State machines

I'm going to give this a try. I'll post modified code after I figure this out. Thank you for the advice.

Howdy all!

I just want to say, between the link for Nick Gammon's state changes, and this forum post;

http://forum.arduino.cc/index.php?topic=207826.0

I think I have a plan for utilizing a state machine for menu navigation. I think the code I was basing this off of (I.E. the split of reading and navigation) was getting in the way of what I wanted out of it.

My Current Hardware includes;

Arduino Uno R2
Adafruit 16x2 LCD
Adafruit DS1307 RTC
Futaba Micro Servo
3 Momentary Switches with 10k pull down resistor
3.7v Lithium Battery (I'm waiting for the 3v-5v step up module to be delivered, and this is for battery back-up anyway)

After testing, I drew up a play to re-code the menu navigation using a state machine. I've attached my plan for the different states, and am going to attempt to code it right now. My question, is do I need to make a state for items such as "Set date and Time," or would that just be a subroutine?

EDIT

I think I've answered my own question. Code to come.

Pet_Feeder_States.pdf (307 KB)

Here's the basic state machine code. I have no lcd.print yet, but I want to make sure my framework is sound before I add all the bells and whistles (RTC fuctionality, servo, lcd.print, et cetera).

I've eliminated the use of the MenuBackend Library altogether. It was just not needed (in my opinion), but I am open to suggestions of bringing it back.

Also, I've laid the framework for all of the additions and functions. I'm going to be attempting to write the other void functions as described.

Feeder_Menu_1_5_13.ino (7.04 KB)

I've been plugging away.

I can't seem to get the state machine portion functioning. Or maybe the lcd.print is set up incorrectly. Here's the code that shows the state machine.

Portion of code showing the state machine logic. Code in it's entirety is attached.

void pushedNext() {
  
  switch(state) {
    case MAIN:
      break;            //ignore
      
    case SKIP_MEAL:
      state = FEED_NOW;
      break;
      
    case FEED_NOW:
      state = SET_MEALS;
      break;
      
    case SET_MEALS:
      state = SET_CLOCK;
      break;
      
    case SET_BREAKFAST:
      state = SET_DINNER;
      break;
      
    case SET_DINNER:
      state = SET_BREAKFAST;
      break;
      
    case SET_CLOCK:
      state = DATA;
      break;
      
    case DATA:
      state = SKIP_MEAL;
      break;
  }
  
}


void pushedEnter() {
  
  switch(state) {
    case MAIN:
      state = SKIP_MEAL;
      break;
      
    case SKIP_MEAL:
      nextMeal;
      skipMeal;
      state = MAIN;
      break;
      
    case FEED_NOW:
      nextMeal;
      skipMeal;
      feedNow;
      state = MAIN;
      break;
      
    case SET_MEALS:
      state = SET_BREAKFAST;
      break;
      
    case SET_BREAKFAST:
      setBreakfast;
      state = MAIN;
      break;
      
    case SET_DINNER:
      setDinner;
      state = MAIN;
      break;
      
    case SET_CLOCK:
      setClock;
      state = MAIN;
      break;
      
    case DATA:
      scrollData;
      state = MAIN;
      break;
  }
  
}


void pushedEsc() {
  
  switch(state) {
    case MAIN:
      break;           //ignore
      
    case SKIP_MEAL:
    case FEED_NOW:
    case SET_MEALS:
    case SET_BREAKFAST:
    case SET_DINNER:
    case SET_CLOCK:
    case DATA:
      state = MAIN;
      break;
  }
  
}


void  readButtons() {  
  
  int reading;
  int buttonEnterState=LOW;            
  int buttonEscState=LOW;             
  int buttonNextState=LOW;             
    
  //Enter button
                
                  reading = digitalRead(buttonPinEnter);

                  if (reading != lastButtonEnterState) {
                    lastEnterDebounceTime = millis();
                  } 
                  
                  if ((millis() - lastEnterDebounceTime) > debounceDelay) {
                    buttonEnterState=reading;
                    lastEnterDebounceTime=millis();
                  }

                  lastButtonEnterState = reading;
                  
    //Esc button               
                 
                  reading = digitalRead(buttonPinEsc);

                  if (reading != lastButtonEscState) {
                    lastEscDebounceTime = millis();
                  } 
                  
                  if ((millis() - lastEscDebounceTime) > debounceDelay) {
                    buttonEscState = reading;
                    lastEscDebounceTime=millis();
                  }

                  lastButtonEscState = reading;
                     
   //Next button               

                  reading = digitalRead(buttonPinNext);

                  if (reading != lastButtonNextState) {
                    // reset the debouncing timer
                    lastNextDebounceTime = millis();
                  } 
                  
                  if ((millis() - lastNextDebounceTime) > debounceDelay) {
                    buttonNextState = reading;
                   lastNextDebounceTime =millis();
                  }

                  lastButtonNextState = reading;                  
                 
  if (buttonEnterState==HIGH) {
    lastButtonPushed=buttonPinEnter;
  }
  else if(buttonEscState==HIGH) {
    lastButtonPushed=buttonPinEsc;
  }
  else if(buttonNextState==HIGH) {
    lastButtonPushed=buttonPinNext;
  }
  else {
    lastButtonPushed=0;
  }          
  
}

void processInput() {        //hopefully navigate the menus
  
  readButtons();
  lcd.setCursor(0,1);
  
  if (state == MAIN) {
    lcd.print("Press to Start  ");
  }
  else if(state == SKIP_MEAL) {
    lcd.print("Skip Next Meal  ");
  }
  else if(state == FEED_NOW) {
    lcd.print("Feed Now        ");
  }
  else if(state == SET_MEALS) {
    lcd.print("Set Mealtimes   ");
  }
  else if(state == SET_BREAKFAST) {
    lcd.print("Set Breakfast   ");
  }
  else if(state == SET_DINNER) {
    lcd.print("Set Dinner      ");
  }
  else if(state == SET_CLOCK) {
    lcd.print("Set Date/Time   ");
  }
  else if(state == DATA) {
    lcd.print("View Data       ");
  }
  
  switch(lastButtonPushed) {
    case buttonPinEnter:
      pushedEnter;
      break;
      
    case buttonPinEsc:
      pushedEsc;
      break;
      
    case buttonPinNext:
      pushedNext;
      break;
      
    default:
      state = MAIN;
      break;
  }
    
}


void loop() {
  
  processInput();
  digitalClockDisplay();
  
}


void digitalClockDisplay() {
  
  unsigned long currentMillis = millis();
  
  if (timeStatus() != timeSet) {
    lcd.setCursor(0,1);
    lcd.println("The time not set");
    delay(4000);
  }
  
  else if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    lcd.setCursor(10, 0);
    printDigits(minute());
    printDigits(second());
    lcd.println();
  }

}

void printDigits(int digits)
{
  lcd.print(":");
  if(digits < 10)
    lcd.print('0');
  lcd.print(digits);
}

Besides the state machine, I've integrated the Time, TimeAlarms, and DS1307RTC libraries, and ditched the RTC library. Much easier to set the alarms I need. Also makes the variables easy to change, meaning I now have some idea how to code the "setBreakfast" and "setDinner" commands.

int breakfastHour = 4;
int breakfastMinute = 30;
int breakfastSecond = 0;

int dinnerHour = 23;
int dinnerMinute = 5;
int dinnerSecond = 30;
  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  
  Alarm.alarmRepeat(breakfastHour,breakfastMinute,breakfastSecond, breakfastAlarm);    //Alarm set for breakfast every day
  Alarm.alarmRepeat(dinnerHour,dinnerMinute,dinnerSecond, dinnerAlarm);                //Alarm set for dinner every day
void feedNow() {            //Dispenses Food

  lcd.setCursor(0,1);
  lcd.print("Feeding Now     ");
  delay(3000);
}
void breakfastAlarm() {

  feedNow();
  
}


void dinnerAlarm() {

  feedNow();
  
}

Pet_Feeder_0_1.ino (8.37 KB)

      nextMeal;
      skipMeal;

What do you think this is doing? It makes about as much sense as:

    22;
    45;

PaulS:

      nextMeal;

skipMeal;



What do you think this is doing? It makes about as much sense as:


22;
    45;

I haven't written the code for those functions yet. Next meal will test for either breakfast or dinner, then skip meal will get it to skip that meal. I split the functions so that on the lcd screen, it can display "TIme to next meal." Given my experience level (not much), is there a better way?

I haven't written the code for those functions yet.

Nor are you calling them. The function address, which is what you have there, is simply a number, like 22 or 45.

Parentheses following a function name are what indicates that the function is to be called.

Whoops. Thank you! Any idea what's wrong with my state machine logic?

Aha, after your tip, I went through and checked the rest of my call functions, and that explained why the menu would not travel anywhere. I have a slightly unstable, but usable menu. Any thoughts on stability? Now I'm back to where I started, with a "hair trigger" on the switches.

void pushedNext() {
  
  switch(state) {
    case MAIN:
      break;            //ignore
      
    case SKIP_MEAL:
      state = FEED_NOW;
      break;
      
    case FEED_NOW:
      state = SET_MEALS;
      break;
      
    case SET_MEALS:
      state = SET_CLOCK;
      break;
      
    case SET_BREAKFAST:
      state = SET_DINNER;
      break;
      
    case SET_DINNER:
      state = SET_BREAKFAST;
      break;
      
    case SET_CLOCK:
      state = DATA;
      break;
      
    case DATA:
      state = SKIP_MEAL;
      break;
  }
  
}


void pushedEnter() {
  
  switch(state) {
    case MAIN:
      state = SKIP_MEAL;
      break;
      
    case SKIP_MEAL:
      nextMeal();
      skipMeal();
      state = MAIN;
      break;
      
    case FEED_NOW:
      nextMeal();
      skipMeal();
      feedNow;
      state = MAIN;
      break;
      
    case SET_MEALS:
      state = SET_BREAKFAST;
      break;
      
    case SET_BREAKFAST:
      setBreakfast();
      state = MAIN;
      break;
      
    case SET_DINNER:
      setDinner();
      state = MAIN;
      break;
      
    case SET_CLOCK:
      setClock();
      state = MAIN;
      break;
      
    case DATA:
      scrollData();
      state = MAIN;
      break;
  }
  
}


void pushedEsc() {
  
  switch(state) {
    case MAIN:
      break;           //ignore
      
    case SKIP_MEAL:
    case FEED_NOW:
    case SET_MEALS:
    case SET_BREAKFAST:
    case SET_DINNER:
    case SET_CLOCK:
    case DATA:
      state = MAIN;
      break;
  }
  
}


void  readButtons() {  
  
  int reading;
  int buttonEnterState=LOW;            
  int buttonEscState=LOW;             
  int buttonNextState=LOW;             
    
  //Enter button
                
                  reading = digitalRead(buttonPinEnter);

                  if (reading != lastButtonEnterState) {
                    lastEnterDebounceTime = millis();
                  } 
                  
                  if ((millis() - lastEnterDebounceTime) > debounceDelay) {
                    buttonEnterState=reading;
                    lastEnterDebounceTime=millis();
                  }

                  lastButtonEnterState = reading;
                  
    //Esc button               
                 
                  reading = digitalRead(buttonPinEsc);

                  if (reading != lastButtonEscState) {
                    lastEscDebounceTime = millis();
                  } 
                  
                  if ((millis() - lastEscDebounceTime) > debounceDelay) {
                    buttonEscState = reading;
                    lastEscDebounceTime=millis();
                  }

                  lastButtonEscState = reading;
                     
   //Next button               

                  reading = digitalRead(buttonPinNext);

                  if (reading != lastButtonNextState) {
                    // reset the debouncing timer
                    lastNextDebounceTime = millis();
                  } 
                  
                  if ((millis() - lastNextDebounceTime) > debounceDelay) {
                    buttonNextState = reading;
                   lastNextDebounceTime =millis();
                  }

                  lastButtonNextState = reading;                  
                 
  if (buttonEnterState==HIGH) {
    lastButtonPushed=buttonPinEnter;
  }
  else if(buttonEscState==HIGH) {
    lastButtonPushed=buttonPinEsc;
  }
  else if(buttonNextState==HIGH) {
    lastButtonPushed=buttonPinNext;
  }
  else {
    lastButtonPushed=0;
  }          
  
}

void processInput() {        //hopefully navigate the menus
  
  readButtons();
  lcd.setCursor(0,1);
  
  switch(lastButtonPushed) {
    case buttonPinEnter:
      pushedEnter();
      break;
      
    case buttonPinEsc:
      pushedEsc();
      break;
      
    case buttonPinNext:
      pushedNext();
      break;
      
    default:
      break;
  }
  
  if (state == MAIN) {
    lcd.print("Press to Start  ");
  }
  else if(state == SKIP_MEAL) {
    lcd.print("Skip Next Meal  ");
  }
  else if(state == FEED_NOW) {
    lcd.print("Feed Now        ");
  }
  else if(state == SET_MEALS) {
    lcd.print("Set Mealtimes   ");
  }
  else if(state == SET_BREAKFAST) {
    lcd.print("Set Breakfast   ");
  }
  else if(state == SET_DINNER) {
    lcd.print("Set Dinner      ");
  }
  else if(state == SET_CLOCK) {
    lcd.print("Set Date/Time   ");
  }
  else if(state == DATA) {
    lcd.print("View Data       ");
  }
  
}


void loop() {
  
  processInput();
  digitalClockDisplay();
  
}


void digitalClockDisplay() {
  
  unsigned long currentMillis = millis();
  
  if (timeStatus() != timeSet) {
    lcd.setCursor(0,1);
    lcd.println("The time not set");
    delay(4000);
  }
  
  else if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    lcd.setCursor(10, 0);
    printDigits(minute());
    printDigits(second());
    lcd.println();
  }

}

void printDigits(int digits)
{
  lcd.print(":");
  if(digits < 10)
    lcd.print('0');
  lcd.print(digits);
}

Full code attached.

Pet_Feeder_0_2.ino (8.62 KB)

Just an update. I've currently put this project on hiatus. I may return to it someday, but it was definitely one of those "took on too much all at once" projects. On a positive note, I learned from this experience to just build the simplest version of what you need, THEN add extra functionality.
This lesson helped my to finish my first long-term Arduino project. Thank you to all who posted on this topic.