Rotary Encoder Menu Code help

I have been working on a pet feeder project based off of Automatic Pet Feeder. I have his code working but now i am tweaking it to my needs and trying to learn new things along the way. I want to add a settings menu that allows a user to use the rotary encoder to set the time and a few other variables. As of right now i have the rotary encoder working for the root layer of my menu. When i select a menu it should launch the function and use the encoder to configuration the numbers. i can not get the encoder to work inside the set time function.

the menu code is based on a cocktail maker code, i haven't cleaned up all those references yet.
Menu Code:

/* Cocktail Production - Menu Test Sketch
 *  Created by VulkanDesign.com 
 *  Free to use -> but: pls pay attention to the copyrights of the included libraries
 *  Granted by Dipl.-Ing. Raimund P. Trierscheid
 */


/* Rotary encoder handler for arduino.

   Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
   Contact: bb@cactii.net

   Quick implementation of rotary encoder routine.

   More info: http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html

*/
// Include the library code
#include <Wire.h>                   // needed for I2C communication
#include <LiquidCrystal.h>      // LCD with I2C adapter
#include <rotary.h>                 // rotary handler
#include <Time.h>
#include <TimeLib.h>
#include <DS1307RTC.h> // Real Time Clock Library
/*  maybe for later enhancements
  #include <EEPROMex.h>
  #include <EEPROMVar.h>
  #include <OneButton.h>
*/





// Initialize the Rotary object
// Rotary(Encoder Pin 1, Encoder Pin 2, Button Pin) Attach center to ground
Rotary r = Rotary(2, 3, 4);        // there is no must for using interrupt pins !!
// Half-step mode
#define HALF_STEP
// Define I2C_LCD
LiquidCrystal  lcd(12, 11, 5, 8, 7, 6);


// Define your cocktail information

int maxNumber = 5;
int hourmaxNumber = 23;
int minmaxNumber = 59;

char* myCocktails[] = {
  "Set Time",
  "First Meal",
  "Second Meal",
  "Loola QTY",
  "Dooly QTY",
  //"Mai Tai",
  //"Bloody Mary",
  //"Mojito",
  //"Cuba Libre",
  //"Tequila Sunrise",
  //"Caipirinha",
  //"Wodka Lemon",
};

int x = 0;


void setup() {


  //initialize serial and wait for port to open :
  Serial.begin(9600);   // only for debugging, comment out later
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("Initialized");
  Serial.println();
    lcd.begin(16, 2);
  lcd.clear (); // go home
  lcd.print ("David S");
  lcd.setCursor(0, 1);
  lcd.print("Pet Feeder");
  delay(2000);
  //lcd.backlight();                      // only needed for 0x3F I2C Adapter

  // ------- Quick 3 blinks of backlight  -------------
  //blinkLCD();

  //char lcdline1[13];
 // sprintf (lcdline1, "Cocktail #:  %02i", x + 1);    // index starts at 0
  lcd.clear();
  lcd.setCursor(0, 0);
  //lcd.print (lcdline1);
  lcd.print("Settings");
  lcd.setCursor(0, 1);
  lcd.print("                ");              // erase previous content
  lcd.setCursor(0, 1);
  lcd.print(myCocktails[x]);
}

void loop() {


  volatile unsigned char result = r.process();
  if (result) {
    result == DIR_CCW ? x = x - 1 : x = x + 1;

    if (x < 0) {             // no values < 0; later: use unsigned int
      //blinkLCD();
      x = maxNumber - 1;    // roll over
    }

    if (x > maxNumber - 1) {           // no more strings
      // ------- Quick 3 blinks of backlight  -------------
      //blinkLCD();
      x = 0;                 // roll over
    }

    //char lcdline1[13];
    //sprintf (lcdline1, "Cocktail #:  %02i", x + 1);
    lcd.clear();
    lcd.setCursor(0, 0);
    //lcd.print (lcdline1);
    lcd.print("Settings");
    lcd.setCursor(0, 1);
    lcd.print("                ");              // erase previous content
    lcd.setCursor(0, 1);
    lcd.print(myCocktails[x]);


  }

  if (r.buttonPressedReleased(25)) {

    switch (x) {
      case 0:
       // lcdScreen1();
        SetTime();                      // Start der Produktion
        break;
      case 1:
       // lcdScreen1();
        Cocktail_2();                      // Start der Produktion
        break;
      case 2:
        //lcdScreen1();
        Cocktail_3();                      // Start der Produktion
        break;
      case 3:
       // lcdScreen1();
        Cocktail_4();                      // Start der Produktion
        break;
      case 4:
        //lcdScreen1();
        Cocktail_5();                      // Start der Produktion
        break;
    }
    //blinkLCD();
    lcd.clear (); // go home
    lcd.setCursor(0, 1);
    x = 0;                                  // reset to start position
    lcd.clear();
    char lcdline1[13];
    lcd.print ("Settings");
    lcd.setCursor(0, 1);
    lcd.print(myCocktails[x]);
  }

}


// ####################   Functions ###################
void SetTime() {
  // produce the requested cocktail -> stepper motor actions
  tmElements_t tm;    // This sectionm reads the time from the RTC, sets it in tmElements tm (nice to work with), then displays it.
  RTC.read(tm); 
  volatile unsigned char Hour = r.process();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("SET TIME HERE");
  lcd.setCursor(3,1);
  lcd.print(":");
  lcd.blink();
  x = Hour;
  if (!(r.buttonPressedReleased(25))) { 
    do  {
        lcd.setCursor(1,1);
        delay(500);
        if (Hour) {
          Hour == DIR_CCW ? x = x - 1 : x = x + 1;
          if (x < 0) {             // no values < 0; later: use unsigned int
              x = hourmaxNumber - 1;    // roll over
          }
          if (x > hourmaxNumber - 1) {           // no more strings
              x = 0;                 // roll over
          }
        }
        tm.Hour = x;
        RTC.write(tm);
        lcd.setCursor(1,1);
        printDigits(tm.Hour);
    } while ((r.buttonPressedReleased(25)));
    lcd.noBlink();
    delay(1000);   
  }
}

void Cocktail_2() {
  // produce the requested cocktail -> stepper motor actions
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Set First Meal");
  lcd.setCursor(0,1);
  lcd.print("Time XX:XX");
  lcd.blink();
  lcd.setCursor(5,1);
  delay(5000);
  lcd.noBlink();
}

void Cocktail_3() {
  // produce the requested cocktail -> stepper motor actions
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Set Second Meal");
  lcd.setCursor(0,1);
  lcd.print("Time XX:XX");
  lcd.blink();
  lcd.setCursor(5,1);
  delay(5000);
  lcd.noBlink();
}

void Cocktail_4() {
  // produce the requested cocktail -> stepper motor actions
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Set Loola Feed");
  lcd.setCursor(0,1);
  lcd.print("QTY: XX");
  lcd.blink();
  lcd.setCursor(5,1);
  delay(5000);
  lcd.noBlink();
}

void Cocktail_5() {
  // produce the requested cocktail -> stepper motor actions
lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Set Dooly Feed");
  lcd.setCursor(0,1);
  lcd.print("QTY: XX");
  lcd.blink();
  lcd.setCursor(5,1);
  delay(5000);
  lcd.noBlink();
}

void printDigits(int digits){   // utility function for digital clock display: prints leading 0
   if(digits < 10)
    lcd.print('0');
   lcd.print(digits);
 }

Any help with getting this sorted would be great. Or even just a topic to research to help me figure it out on my own. Thank you.

Perhaps you can glean something useful from this thread - Encoder button state.

The basic idea is to use the encoder in 'navigation' mode to cycle through the menu options, say 0 thru n. When the button is pressed the code is switched to a whole different section we can call 'parameter adjustment' mode. The then current value in navigation mode is used to activate, via a series of if()s or a switch/case, the code pertaining to adjusting that parameter .

Since there are only two possibilities, a boolean variable toggled by the button can act as the mode select. Pressing the button again toggles you out of adjustment mode and back to navigation mode.

dougp:
Perhaps you can glean something useful from this thread - Encoder button state.

Thanks. Ill give it a read and see what i can come up with. Ill report back my outcome

Ok little update. The menu selection continues to work, i added a Boolean switch to enter "parameter" mode when a menu option was selected. Inside the parameter mode for set the First Meal Time the position count just counts on its own. i used the same process for the rotary encoder as the main menu selection, not sure why its acting differently here.

I simplified my code down to the bare minimum to figure this out before adding anything else to the project.

#include <rotary.h>

int maxNumber = 5;          //Used to roll over main menu
int hourmaxNumber = 24;     //Used to roll over hour digits
int minutemaxNumber = 60;   //Used to roll over minute digits 
int x =0;  

const int buttonPin = 4;
                
//Feed Settings
int FirstMealHour = 07;
int FirstMealMinute = 00;
int SecondMealHour = 17;
int SecondMealMinute = 30;
int LoolaFeedQTY = 4;
int DoolyFeedQTY = 4;

//Rotary Settings
Rotary r = Rotary(2, 3, 4);        // there is no must for using interrupt pins !!
// Half-step mode
#define HALF_STEP

// Boolean for parameter mode selection
bool TimeState;
bool FirstMealState;

//Main Meal structure
char* MainMenu[] = {
  "Set Time",
  "First Meal",
  "Second Meal",
  "Loola QTY",
  "Dooly QTY",
};

void setup() {
  Serial.begin (9600);
  Serial.println("Start");
}

void loop() {
 volatile unsigned char result = r.process();
  if (result) {
      result == DIR_CCW ? x = x - 1 : x = x + 1;

      if (x < 0) {             // no values < 0; later: use unsigned int
         x = maxNumber - 1;    // roll over
      }

      if (x > maxNumber - 1) {           // no more strings
        x = 0;                 // roll over
      }
    
    Serial.println(MainMenu[x]);
  }
  if (r.buttonPressedHeld(500)) {   //switched to button hold to troubleshoot the parameter mode

    switch (x) {
      case 0:
        TimeState = true;
        Serial.print("Menu ");
        Serial.print (MainMenu[x]);
        Serial.println(" Selected");
        Serial.println("TimeState = TRUE");
        Serial.println("Launching SetTime()");
        //SetTime();                     // Start der Produktion 
        break;
      case 1:
        FirstMealState = true;
        Serial.print("Menu ");
        Serial.print (MainMenu[x]);
        Serial.println(" Selected");
        Serial.print("FirstMealState = ");
        Serial.println(FirstMealState);
        Serial.println("Launching FirstMeal()");
        FirstMeal();                     
        break;
      case 2:
        //SecondMeal();                      
        break;
      case 3:
        //LoolaQTY();                     
        break;
      case 4:
        //DoolyQTY();                      
        break;
    }
    //Serial.print("Menu ");
    //Serial.print (MainMenu[x]);
    //Serial.println(" Selected");
    }
}



// ####################   Functions ###################

void FirstMeal() {
  volatile unsigned char result = r.process();
  if (FirstMealState == true) {
    if (!(digitalRead(buttonPin))) {
      Serial.println("Conditional Statement Worked");
      Serial.println("Set First Meal");
      Serial.print("Time ");
      Serial.print(FirstMealHour);
      Serial.print(":");
      Serial.println(FirstMealMinute);
      Serial.println("Changing Hour");
      x = FirstMealHour;
      do {
        delay(500);
            result == DIR_CCW ? x = x - 1 : x = x + 1;
            if (x < 0) {             // no values < 0; later: use unsigned int
              x = hourmaxNumber - 1;    // roll over
            }
            if (x > hourmaxNumber - 1) {           // no more strings
              x = 0;                 // roll over
            } 
          Serial.println(x);
          delay(500);
      } while ((digitalRead(buttonPin)));
    }
      FirstMealHour = x;
      Serial.print(FirstMealHour);
      Serial.print(":");
      Serial.println(FirstMealMinute);
  }
  FirstMealState = false;
  Serial.print("FirstMealState = ");
  Serial.println(FirstMealState);
}

The end goal is to select a menu option (First Meal Time), then use the rotary encoder to set the hour, push the rotary button it switches to Minutes, set the minutes, and then use the rotary button to go back to the main menu.

Attached is the serial output showing the self counting. The count after "Changing Hour" is with no input on the rotary encoder.

serial.JPG

I got it sorta working now. Using the interrupt example that is part of the rotary.h library i was able to get the rotary encoder to work for the menu selection and parameter editing. One problem though is that in the parameter edit mode the rotary encoder is very slow and bounces a lot. i have zero bounce in the menu selection mode.

here is the working code as of right now. I would greatly appreciate it if someone could help me with the why the encoder is so slow in the FirstMeal();. I will admit that i do not understand the ISR that the example provided, i just copied it over and tired to adapt my code to it.

#include <rotary.h>

int maxNumber = 5;          //Used to roll over main menu
int hourmaxNumber = 24;     //Used to roll over hour digits
int minutemaxNumber = 60;   //Used to roll over minute digits 
int x =0;  

const int buttonPin = 4;
                
//Feed Settings
int FirstMealHour = 07;
int FirstMealMinute = 00;
int SecondMealHour = 17;
int SecondMealMinute = 30;
int LoolaFeedQTY = 4;
int DoolyFeedQTY = 4;

//Rotary Settings
Rotary r = Rotary(2, 3, 4);        // there is no must for using interrupt pins !!
// Half-step mode
#define HALF_STEP

// Boolean for parameter mode selection
bool TimeState;
bool FirstMealState;
bool CLOCKWISE;
bool COUNTERCLOCKWISE;

//Main Meal structure
char* MainMenu[] = {
  "Set Time",
  "First Meal",
  "Second Meal",
  "Loola QTY",
  "Dooly QTY",
};

ISR(PCINT2_vect) {
  unsigned char result = r.process();
  //if (result == DIR_NONE){}
  if (result == DIR_CW) {
    result == DIR_CW ? x = x + 1 : x = x - 1;
    CLOCKWISE = true;
  }
  else if (result == DIR_CCW) {
    result == DIR_CCW ? x = x - 1 : x = x + 1;
    COUNTERCLOCKWISE = true;  
 }
}

void setup() {
  Serial.begin (9600);
  Serial.println("Start");
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();
}

void loop() {
  
  if (CLOCKWISE == true) {
    if (x < 0) {             // no values < 0; later: use unsigned int
         x = maxNumber - 1;    // roll over
    }
    if (x > maxNumber - 1) {           // no more strings
        x = 0;                 // roll over
    }
    Serial.println(MainMenu[x]);
    CLOCKWISE = false;
  }
  else if (COUNTERCLOCKWISE == true) {
    if (x < 0) {             // no values < 0; later: use unsigned int
         x = maxNumber - 1;    // roll over
    }
    if (x > maxNumber - 1) {           // no more strings
        x = 0;                 // roll over
    }
    Serial.println(MainMenu[x]);
    COUNTERCLOCKWISE = false;
  }
  
  if (r.buttonPressedHeld(500)) {   //switched to button hold to troubleshoot the parameter mode

    switch (x) {
      case 0:
        TimeState = true;
        Serial.print("Menu ");
        Serial.print (MainMenu[x]);
        Serial.println(" Selected");
        Serial.println("TimeState = TRUE");
        Serial.println("Launching SetTime()");
        //SetTime();                     // Start der Produktion 
        break;
      case 1:
        FirstMealState = true;
        Serial.print("Menu ");
        Serial.print (MainMenu[x]);
        Serial.println(" Selected");
        Serial.print("FirstMealState = ");
        Serial.println(FirstMealState);
        Serial.println("Launching FirstMeal()");
        FirstMeal();                     
        break;
      case 2:
        //SecondMeal();                      
        break;
      case 3:
        //LoolaQTY();                     
        break;
      case 4:
        //DoolyQTY();                      
        break;
    }
  }
}

// ####################   Functions ###################

void FirstMeal() {

if (FirstMealState == true) {
  if (!(digitalRead(buttonPin))) {
      Serial.println("Conditional Statement Worked");
      Serial.println("Set First Meal");
      Serial.print("Time ");
      Serial.print(FirstMealHour);
      Serial.print(":");
      Serial.println(FirstMealMinute);
      Serial.println("Changing Hour");
      x = FirstMealHour;
      do {
        delay(500);
        if (CLOCKWISE == true) {
          if (x < 0) {             // no values < 0; later: use unsigned int
            x = hourmaxNumber - 1;    // roll over
          }
          if (x > hourmaxNumber - 1) {           // no more strings
            x = 0;                 // roll over
          }
          Serial.println(x);
          CLOCKWISE = false;
        }
        else if (COUNTERCLOCKWISE == true) {
          if (x < 0) {             // no values < 0; later: use unsigned int
            x = hourmaxNumber - 1;    // roll over
          }
          if (x > hourmaxNumber - 1) {           // no more strings
            x = 0;                 // roll over
          }
          Serial.println(x);
          COUNTERCLOCKWISE = false;
        }
      } while ((digitalRead(buttonPin)));
      FirstMealHour = x;
      delay(500);
      Serial.println("Now Changing Minute");
      x = FirstMealMinute;
      do {
        delay(500);
        if (CLOCKWISE == true) {
          if (x < 0) {             // no values < 0; later: use unsigned int
            x = minutemaxNumber - 1;    // roll over
          }
          if (x > minutemaxNumber - 1) {           // no more strings
            x = 0;                 // roll over
          }
          Serial.println(x);
          CLOCKWISE = false;
        }
        else if (COUNTERCLOCKWISE == true) {
          if (x < 0) {             // no values < 0; later: use unsigned int
            x = minutemaxNumber - 1;    // roll over
          }
          if (x > minutemaxNumber - 1) {           // no more strings
            x = 0;                 // roll over
          }
          Serial.println(x);
          COUNTERCLOCKWISE = false;
        }
      } while ((digitalRead(buttonPin)));
      FirstMealMinute = x;
      delay(500);
      Serial.print(FirstMealHour);
      Serial.print(":");
      Serial.println(FirstMealMinute);
  }
}
FirstMealState = false;
Serial.print("FirstMealState = ");
Serial.println(FirstMealState);
}



 
/*  
 *   if (FirstMealState == true) {
    //if (!(digitalRead(buttonPin))) {
      Serial.println("Conditional Statement Worked");
      Serial.println("Set First Meal");
      Serial.print("Time ");
      Serial.print(FirstMealHour);
      Serial.print(":");
      Serial.println(FirstMealMinute);
      Serial.println("Changing Hour");
      x = FirstMealHour;
      do {
        delay(500);
            volatile unsigned char result = r.process();
            if (result) {
                result == DIR_CCW ? x = x - 1 : x = x + 1;

                //if (x < 0) {             // no values < 0; later: use unsigned int
                     //x = hourmaxNumber - 1;    // roll over
                //}

                //if (x > hourmaxNumber - 1) {           // no more strings
                    // x = 0;                 // roll over
                //}
            Serial.println(x);
            }
            delay(500);
      } while ((digitalRead(buttonPin)));
    
      FirstMealHour = x;
      Serial.print(FirstMealHour);
      Serial.print(":");
      Serial.println(FirstMealMinute);
  }
  FirstMealState = false;
  Serial.print("FirstMealState = ");
  Serial.println(FirstMealState);
 }
*/

I get "Error compiling for board Arduino/Genuino Uno."

Which board are you using?

dougp:
I get "Error compiling for board Arduino/Genuino Uno."

Which board are you using?

Im using an Uno. i just copied the code i included in that post into my IDE 1.8.7 and it complied fine except the "warning: ISO C++ forbids converting a string constant to 'char*' " warning.

This doesn't look right to me - no expert.

result == DIR_CW ? x = x + 1 : x = x - 1;

Try putting the condition inside parentheses.

Are you expecting to use the encoder library mentioned in the first post, the one by Buxton?

dougp:
This doesn't look right to me - no expert.

result == DIR_CW ? x = x + 1 : x = x - 1;

Try putting the condition inside parentheses.

Alright, ill give this a try when i get home tonight

dougp:
Are you expecting to use the encoder library mentioned in the first post, the one by Buxton?

Well im pretty new at this and so far this is the only method i have gotten to work that has no bouncing and give consistent results. I know its based on a state machine but i am not actually able to understand the code enough to implement one on my own.

I ask about the library because when I entered Rotary.h rather than rotary.h the 'can't compile' error goes away and is replaced by:

'class Rotary' has no member named 'buttonPressedHeld'.

dougp:
I ask about the library because when I entered Rotary.h rather than rotary.h the 'can't compile' error goes away and is replaced by:

'class Rotary' has no member named 'buttonPressedHeld'.

Hmm strange. This is where i got the library from Hilfe bei der Menüstruktur - Deutsch - Arduino Forum I had to translate the page to read through it but post #6 is where i got the menu code and the library.

That thread is two years old. Maybe the library was updated since? I mention it because I have the library installed and it wants a capital 'R'. ?? In any case, it seems our library copies differ.