Program Is Displaying Wrong Screen after Startup

Ok I am back already. The same project, different issue. To recap, I am building a measuring wheel that displays inches, footage and mileage. I have done more coding for this project. After hours of reading, learning, and a few more youtube videos, I think I should now have this measuring wheel to where I push a save button and save the current measurement into eeprom. I should be able to save up to 12 measurements right now. There are main "functions" of the program.

#1 is measuring and displaying the current measurement. I can also reset that measurement to zero. I have got that figured out.
#2 is saving measurements to EEPROM. I am not totally sure about this one yet. I have not tried to save any measurements yet. I have to get the screen working properly.
#3 is displaying and scrolling through the saved measurements with another rotary encoder.

I am working with arduino UNO R3, the wheel encoder is the same TAISS encoder, the LCD is 20 x 04. The menu encoder I believe is a KY080 style. It came in a starter kit. It has 20 detents and 80 pulses.

Here is my new issue to work through. Upon startup/bootup CFG (my company's initials) is briefly displayed, then the screen is set up with "Ready To Measure". If the menu function in the loop is commented out, this works correctly. If the menu function in the loop is not commented, Saved Measurement #12 displays instead of Ready to Measure. BUT rotating the wheel encoder will jump over to Ready To Measure and it will start measuring AND turning the menu encoder will scroll through the saved measurement menu.

I have tried many different combinations of if statements, state changes, etc, and get the same result with all of it. One other piece to the puzzle, I added Serial.prints in both the measuring and menu functions. Immediatley after bootup, the serial monitor is rapidly displaying "in measuring" and "in menu" back and forth, so it is jumping back and forth between those two functions. I just can't figure out why.

Here is the code. And don't forget to tell me where I can tidy it up if needed.

/* My Measuring Wheel Project. Calculations based off of
     wheel diameter and pulses per revolution.
     Adjust these two value based on actual products used.
*/
#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

// On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);
Encoder menuEnc(3,5);
int detentDivisor=4;/*For this particular menu encoder, divide pulse
                         counts by 4 to equal the detent poisitons*/

//Menu and EEPROM
int lastPosition=-999;/*Arbitrary number to ENSURE upon start up 
                         that newPosition does not equal lastPosition.*/
int menuIndex=0;
const char* menuItems[] = { 
     "Measurement 1", 
     "Measurement 2", 
     "Measurement 3", 
     "Measurement 4",
     "Measurement 5", 
     "Measurement 6", 
     "Measurement 7", 
     "Measurement 8",
     "Measurement 9", 
     "Measurement 10", 
     "Measurement 11",
     "Measurement 12", 
     }; 
const int menuLength = sizeof(menuItems) / sizeof(menuItems[0]);
const int numMeasurements=12;
const int startAddress=0;
int currentMeasurement=0;
struct Measurement {
     int inch;
     float feet;
     float miles;
};
//Measuring
volatile long wheelPulseCount;
long wheelCountStart = 0;

float totalInches = 0.0;
static float lastTotalInches = 0.0;
float inchesDisplayed = 0.0;
float totalFootage = 0.0;
float footageDisplayed = 0.0;
float totalMileage = 0.0;
float mileageDisplayed = 0.0;
const float inchesPerMile=63360.0;
const float inchesPerFoot=12.0;
const float feetPerMile=5280.0;

//Button pins
int save=9;
int reset = 10;
int backlight=11;

unsigned long lastDebounceTime=millis();//Debounce millis delay
int resetButtonDelay=1000;
int backlightButtonDebounceDelay=1000;//Debounce Millis delay
int saveButtonDelay=150;

bool backlightOn=false;
bool measuringActive=false;
bool inMenu=false;

//Display accurate mileage to 3 decimals
int decimals=3;
float truncate(float num, int dec){
     int factor=pow(10,dec);
     return int(num*factor)/float(factor);
}

// Wheel size variables
float diameter = 15.0;//Change as needed for whichever wheel size
float inchesPerPulse;
float wheelInches;
float pi=M_PI;

int pulsesPerRev = 2400;//Change as needed depending on encoder used

void setup() {
  Serial.begin(9600);
  Serial.println("In setup()");
  lcd.init();
  lcd.backlight();
  backlightOn=true;
  lcd.clear();
  lcd.setCursor(8,1);
  lcd.print("CFG");
  delay (3000);
  lcd.clear();
  lcd.setCursor (2,0);
  lcd.print("READY TO MEASURE");
  //Prevent LED13 from automatically turning on
  pinMode (13, OUTPUT);
  digitalWrite (13,LOW);
  //Buttons
  pinMode(reset, INPUT_PULLUP);
  pinMode(save, INPUT_PULLUP);
  pinMode(backlight,INPUT_PULLUP);
  Serial.println("After pinModes");
  //One time formulas
  wheelInches = diameter * pi;//Calculate the circumference of wheel. 
  inchesPerPulse = wheelInches / pulsesPerRev;//Used to calculate inches covered per pulse.
  //Initialize states to ensure proper startup.
  measuringActive=true;//Force Measuring mode to start at startup
  inMenu=false; //Ensure NOT in menu at startup
  menuIndex=0;//Ensure menu starts at 1st menu
  lastPosition=menuEnc.read()/detentDivisor;//Initilaize lastPosition
  Serial.println("After initializations");
}



void measuring(){
     Serial.println("In Measuring()");
  if (wheelPulseCount != wheelCountStart) {//Starts the counting process
    wheelCountStart = wheelPulseCount;
    if (!measuringActive){
     measuringActive=true;
     lcd.clear();
     lcd.setCursor (2,0);
     lcd.print("READY TO MEASURE");
    }
  }
  
if (measuringActive){
  // Calculate the remaining inches within a foot
  inchesDisplayed = abs(static_cast<int>(totalInches)) % 12;/*The % sign uses float, but total
                                                       inches is an int, so static_cast<int>
                                                       turns it back to an integer. ABS means use
                                                       absolute value. Prevents inches from being 
                                                       displayed in negatives.*/
  footageDisplayed = totalFootage;//Displays footage
  mileageDisplayed = truncate(totalMileage, decimals);//Displays mileage. Truncate displays actual
                                                  //value to the specified decimal places. NOT the 
                                                  //rounded value.

  if (totalFootage>99999 || totalFootage<-99999){
     wheelEnc.write(0);
  }

  if (lastTotalInches != totalInches) { /*This if statement prevents constant monitoring of the 
                                        serial monitor. It only displays new data when the data 
                                        changes*/
    lastTotalInches = totalInches; // Resets if statements

     lcd.setCursor (0,2);
     lcd.print ("IN      FT      MI");
     lcd.setCursor(0,3);
     lcd.print("    ");
     lcd.setCursor(0,3);
     lcd.print (inchesDisplayed,0);
     lcd.setCursor (4,3);
     lcd.print("        ");
     lcd.setCursor(5,3);
     lcd.print(footageDisplayed,1);
     lcd.setCursor(15,3);
     lcd.print("      ");
     lcd.setCursor(14,3);
     lcd.print(mileageDisplayed,3);

    
    }
  }
}

void resetButton(){
  if (digitalRead(reset) == LOW) {//If RESET button is pressed
    if ((millis()-lastDebounceTime)>resetButtonDelay){
      if (measuringActive){
    wheelEnc.write(0);//Resets encoder counts to zero and displays all zeros on serial monitor
      }
   }
  }
}

void backlightButton() {
  if (digitalRead(backlight)==LOW){
     if ((millis()-lastDebounceTime)>backlightButtonDebounceDelay){//Debounce timer
      lastDebounceTime=millis();
     if (backlightOn==true){
     lcd.noBacklight();
     backlightOn=false;
     }
  
  else if (backlightOn==false) {
     lcd.backlight();
     backlightOn=true;
      }
     }
  }
}

void saveMeasurement(){
     if (save==LOW && currentMeasurement<numMeasurements){
          if ((millis()-lastDebounceTime)>saveButtonDelay){
               lastDebounceTime=millis();
       Measurement m={inchesDisplayed, footageDisplayed, mileageDisplayed}; 
       int address=startAddress+currentMeasurement*sizeof(Measurement); 
       EEPROM.put(address,m);
       currentMeasurement++;
       
     }
  }
}

void menu(){
     Serial.println("In Menu()");
     int newPosition=(menuEnc.read()/detentDivisor)-1; /*DetentDivisor ensures menu changes at "clicks"
                                                       Subtracting one ensures menustarts at first position*/
     if (newPosition != lastPosition){
          lastPosition=newPosition;
          menuIndex=newPosition % numMeasurements;
          if (menuIndex<0){
               menuIndex += numMeasurements;
          } 
          displayMeasurement (menuIndex);
          measuringActive=false;//Disable measuring abiliity while in menu screens
          inMenu=true;
     }                                                  
}

void displayMeasurement(int index){
     int address = startAddress + index * sizeof(Measurement);
     Measurement m;
     EEPROM.get (address, m);

     lcd.clear();
     lcd.setCursor (0,0);
     lcd.print ("Saved Measurement#");
     lcd.setCursor (18,0);
     lcd.print (index +1); //Beacause index starts at zero.
     lcd.setCursor (0,2);
     lcd.print ("IN      FT      MI");
     lcd.setCursor(0,3);
     lcd.print("    ");
     lcd.setCursor(0,3);
     lcd.print (m.inch);
     lcd.setCursor (4,3);
     lcd.print("        ");
     lcd.setCursor(5,3);
     lcd.print(m.feet);
     lcd.setCursor(15,3);
     lcd.print("      ");
     lcd.setCursor(14,3);
     lcd.print(m.miles);
}

void loop() {
  wheelPulseCount = wheelEnc.read();//Read the pulses from encoder
  wheelInches = diameter * pi;//Calculate the circumference of wheel. 
  inchesPerPulse = wheelInches / pulsesPerRev;//Used to calculate inches covered per pulse.
  totalInches = wheelPulseCount * inchesPerPulse;//Calculate total inches covered
  totalFootage = totalInches/inchesPerFoot;//Calculate the footage
  totalMileage = totalInches/inchesPerMile;//Calculate the mileage.


measuring();

menu();

resetButton();
backlightButton();
saveMeasurement();

}


Are you sure the encoder is KY080?
I know the KY040.
Can you post a link that shows the KY080?
To make it easier to help, please also post the schematic of your project.

  • How are your switches wired ?

  • Always show us a good schematic of your proposed circuit.
    Show us good images of your β€˜actual’ wiring.

  • For de-bouncing, suggest you just sample the switches every 50ms.

  • Look for a change in state in a switch to determine if it is closed/pushed.

Actually I don't know FOR SURE what it is. I probably misspoke about 080. It is a generic encoder with no identifying features that came in a kit that looks just like all of the KY040 encoders I see on the internet. But I did make a code for it awhile back and discovered it gives 80 pulses per revolution. Maybe it is a quadrature encoder?

I don't know how or where to make a schematic of my circuit, any suggestions?

The encoder does function properly in the program. The program is just bouncing back and forth between menu() and measuring(). according to the serial monitor.

The switches are all wired one side to the UNO, one side to ground, and pinMode (INPUT_PULLUP). I am using the switch on KY encoder as the save button. But have not tried it yet. I want to get the screen right before I start saving to eeprom.

Step 1 is to do an Auto Format, you have spacing of 1, 2, 4.

Here it is after being auto formatted. You learn something every day. Thanks.

Also, I did a serial.print in each function just to check if it is going to that function. I saw that is is checking all of the functions and it hit me! Of course it is swapping rapidly between the different functions. It is in the loop and that is what the loop does.

But why is the wrong screen being shown after bootup? Remember i want it to display the Ready to Measure screen immediatley after bootup, not Saved Measurement #12.

/* My Measuring Wheel Project. Calculations based off of
     wheel diameter and pulses per revolution.
     Adjust these two value based on actual products used.
*/
#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

// On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);
Encoder menuEnc(3, 5);
int detentDivisor = 4; /*For this particular menu encoder, divide pulse
                         counts by 4 to equal the detent poisitons*/

//Menu and EEPROM
int lastPosition = -999; /*Arbitrary number to ENSURE upon start up 
                         that newPosition does not equal lastPosition.*/
int menuIndex = 0;
const char* menuItems[] = {
  "Measurement 1",
  "Measurement 2",
  "Measurement 3",
  "Measurement 4",
  "Measurement 5",
  "Measurement 6",
  "Measurement 7",
  "Measurement 8",
  "Measurement 9",
  "Measurement 10",
  "Measurement 11",
  "Measurement 12",
};
const int menuLength = sizeof(menuItems) / sizeof(menuItems[0]);
const int numMeasurements = 12;
const int startAddress = 0;
int currentMeasurement = 0;
struct Measurement {
  int inch;
  float feet;
  float miles;
};
//Measuring
volatile long wheelPulseCount;
long wheelCountStart = 0;

float totalInches = 0.0;
static float lastTotalInches = 0.0;
float inchesDisplayed = 0.0;
float totalFootage = 0.0;
float footageDisplayed = 0.0;
float totalMileage = 0.0;
float mileageDisplayed = 0.0;
const float inchesPerMile = 63360.0;
const float inchesPerFoot = 12.0;
const float feetPerMile = 5280.0;

//Button pins
int save = 9;
int reset = 10;
int backlight = 11;

unsigned long lastDebounceTime = millis();  //Debounce millis delay
int resetButtonDelay = 1000;
int backlightButtonDebounceDelay = 1000;  //Debounce Millis delay
int saveButtonDelay = 150;

bool backlightOn = false;
bool measuringActive = false;
bool inMenu = false;

//Display accurate mileage to 3 decimals
int decimals = 3;
float truncate(float num, int dec) {
  int factor = pow(10, dec);
  return int(num * factor) / float(factor);
}

// Wheel size variables
float diameter = 15.0;  //Change as needed for whichever wheel size
float inchesPerPulse;
float wheelInches;
float pi = M_PI;

int pulsesPerRev = 2400;  //Change as needed depending on encoder used

void setup() {
  Serial.begin(9600);
  Serial.println("In setup()");
  lcd.init();
  lcd.backlight();
  backlightOn = true;
  lcd.clear();
  lcd.setCursor(8, 1);
  lcd.print("CFG");
  delay(3000);
  lcd.clear();
  lcd.setCursor(2, 0);
  lcd.print("READY TO MEASURE");
  //Prevent LED13 from automatically turning on
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  //Buttons
  pinMode(reset, INPUT_PULLUP);
  pinMode(save, INPUT_PULLUP);
  pinMode(backlight, INPUT_PULLUP);
  Serial.println("After pinModes");
  //One time formulas
  wheelInches = diameter * pi;                  //Calculate the circumference of wheel.
  inchesPerPulse = wheelInches / pulsesPerRev;  //Used to calculate inches covered per pulse.
  //Initialize states to ensure proper startup.
  measuringActive = true;                         //Force Measuring mode to start at startup
  inMenu = false;                                 //Ensure NOT in menu at startup
  menuIndex = 0;                                  //Ensure menu starts at 1st menu
  lastPosition = menuEnc.read() / detentDivisor;  //Initilaize lastPosition
  Serial.println("After initializations");
}



void measuring() {
  Serial.println("In Measuring()");
  if (wheelPulseCount != wheelCountStart) {  //Starts the counting process
    wheelCountStart = wheelPulseCount;
    if (!measuringActive) {
      measuringActive = true;
      lcd.clear();
      lcd.setCursor(2, 0);
      lcd.print("READY TO MEASURE");
    }
  }

  if (measuringActive) {
    // Calculate the remaining inches within a foot
    inchesDisplayed = abs(static_cast<int>(totalInches)) % 12; /*The % sign uses float, but total
                                                       inches is an int, so static_cast<int>
                                                       turns it back to an integer. ABS means use
                                                       absolute value. Prevents inches from being 
                                                       displayed in negatives.*/
    footageDisplayed = totalFootage;                           //Displays footage
    mileageDisplayed = truncate(totalMileage, decimals);       //Displays mileage. Truncate displays actual
                                                               //value to the specified decimal places. NOT the
                                                               //rounded value.

    if (totalFootage > 99999 || totalFootage < -99999) {
      wheelEnc.write(0);
    }

    if (lastTotalInches != totalInches) { /*This if statement prevents constant monitoring of the 
                                        serial monitor. It only displays new data when the data 
                                        changes*/
      lastTotalInches = totalInches;      // Resets if statements

      lcd.setCursor(0, 2);
      lcd.print("IN      FT      MI");
      lcd.setCursor(0, 3);
      lcd.print("    ");
      lcd.setCursor(0, 3);
      lcd.print(inchesDisplayed, 0);
      lcd.setCursor(4, 3);
      lcd.print("        ");
      lcd.setCursor(5, 3);
      lcd.print(footageDisplayed, 1);
      lcd.setCursor(15, 3);
      lcd.print("      ");
      lcd.setCursor(14, 3);
      lcd.print(mileageDisplayed, 3);
    }
  }
}

void resetButton() {
  Serial.println("In reset");
  if (digitalRead(reset) == LOW) {  //If RESET button is pressed
    if ((millis() - lastDebounceTime) > resetButtonDelay) {
      if (measuringActive) {
        wheelEnc.write(0);  //Resets encoder counts to zero and displays all zeros on serial monitor
      }
    }
  }
}

void backlightButton() {
  Serial.println("In backlight");
  if (digitalRead(backlight) == LOW) {
    if ((millis() - lastDebounceTime) > backlightButtonDebounceDelay) {  //Debounce timer
      lastDebounceTime = millis();
      if (backlightOn == true) {
        lcd.noBacklight();
        backlightOn = false;
      }

      else if (backlightOn == false) {
        lcd.backlight();
        backlightOn = true;
      }
    }
  }
}

void saveMeasurement() {
  Serial.print("In save");
  if (save == LOW && currentMeasurement < numMeasurements) {
    if ((millis() - lastDebounceTime) > saveButtonDelay) {
      lastDebounceTime = millis();
      Measurement m = { inchesDisplayed, footageDisplayed, mileageDisplayed };
      int address = startAddress + currentMeasurement * sizeof(Measurement);
      EEPROM.put(address, m);
      currentMeasurement++;
    }
  }
}

void menu() {
  Serial.println("In Menu()");
  int newPosition = (menuEnc.read() / detentDivisor) - 1; /*DetentDivisor ensures menu changes at "clicks"
                                                       Subtracting one ensures menustarts at first position*/
  if (newPosition != lastPosition) {
    lastPosition = newPosition;
    menuIndex = newPosition % numMeasurements;
    if (menuIndex < 0) {
      menuIndex += numMeasurements;
    }
    displayMeasurement(menuIndex);
    measuringActive = false;  //Disable measuring abiliity while in menu screens
    inMenu = true;
  }
}

void displayMeasurement(int index) {
  int address = startAddress + index * sizeof(Measurement);
  Measurement m;
  EEPROM.get(address, m);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Saved Measurement#");
  lcd.setCursor(18, 0);
  lcd.print(index + 1);  //Beacause index starts at zero.
  lcd.setCursor(0, 2);
  lcd.print("IN      FT      MI");
  lcd.setCursor(0, 3);
  lcd.print("    ");
  lcd.setCursor(0, 3);
  lcd.print(m.inch);
  lcd.setCursor(4, 3);
  lcd.print("        ");
  lcd.setCursor(5, 3);
  lcd.print(m.feet);
  lcd.setCursor(15, 3);
  lcd.print("      ");
  lcd.setCursor(14, 3);
  lcd.print(m.miles);
}

void loop() {
  wheelPulseCount = wheelEnc.read();               //Read the pulses from encoder
  wheelInches = diameter * pi;                     //Calculate the circumference of wheel.
  inchesPerPulse = wheelInches / pulsesPerRev;     //Used to calculate inches covered per pulse.
  totalInches = wheelPulseCount * inchesPerPulse;  //Calculate total inches covered
  totalFootage = totalInches / inchesPerFoot;      //Calculate the footage
  totalMileage = totalInches / inchesPerMile;      //Calculate the mileage.


  measuring();

  menu();

  resetButton();
  backlightButton();
  saveMeasurement();
}

  • Nothing to do with your problem, however, I just cannot stand { } being on a code line :sunglasses:

  • Also, white space is your friend.

/* My Measuring Wheel Project. Calculations based off of
     wheel diameter and pulses per revolution.
     Adjust these two value based on actual products used.
*/

#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

// On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);
Encoder menuEnc(3, 5);
int detentDivisor = 4; /*For this particular menu encoder, divide pulse
                         counts by 4 to equal the detent poisitons*/

//Menu and EEPROM
int lastPosition = -999; /*Arbitrary number to ENSURE upon start up
                         that newPosition does not equal lastPosition.*/
int menuIndex = 0;

const char* menuItems[] =
{
  "Measurement 1",
  "Measurement 2",
  "Measurement 3",
  "Measurement 4",
  "Measurement 5",
  "Measurement 6",
  "Measurement 7",
  "Measurement 8",
  "Measurement 9",
  "Measurement 10",
  "Measurement 11",
  "Measurement 12",
};

const int menuLength = sizeof(menuItems) / sizeof(menuItems[0]);
const int numMeasurements = 12;
const int startAddress = 0;
int currentMeasurement = 0;

struct Measurement
{
  int inch;
  float feet;
  float miles;
};

//Measuring
volatile long wheelPulseCount;
long wheelCountStart = 0;

float totalInches = 0.0;
static float lastTotalInches = 0.0;
float inchesDisplayed = 0.0;
float totalFootage = 0.0;
float footageDisplayed = 0.0;
float totalMileage = 0.0;
float mileageDisplayed = 0.0;
const float inchesPerMile = 63360.0;
const float inchesPerFoot = 12.0;
const float feetPerMile = 5280.0;

//Button pins
int save = 9;
int reset = 10;
int backlight = 11;

unsigned long lastDebounceTime = millis();  //Debounce millis delay
int resetButtonDelay = 1000;
int backlightButtonDebounceDelay = 1000;  //Debounce Millis delay
int saveButtonDelay = 150;

bool backlightOn = false;
bool measuringActive = false;
bool inMenu = false;

//Display accurate mileage to 3 decimals
int decimals = 3;

//================================================^================================================
float truncate(float num, int dec)
{
  int factor = pow(10, dec);
  return int(num * factor) / float(factor);
  
}

// Wheel size variables
float diameter = 15.0;  //Change as needed for whichever wheel size
float inchesPerPulse;
float wheelInches;
float pi = M_PI;

int pulsesPerRev = 2400;  //Change as needed depending on encoder used

//================================================^================================================
void setup()
{
  Serial.begin(9600);
  Serial.println("In setup()");
  lcd.init();
  lcd.backlight();
  backlightOn = true;
  lcd.clear();
  lcd.setCursor(8, 1);
  lcd.print("CFG");
  delay(3000);
  lcd.clear();
  lcd.setCursor(2, 0);
  lcd.print("READY TO MEASURE");
  //Prevent LED13 from automatically turning on
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  //Buttons
  pinMode(reset, INPUT_PULLUP);
  pinMode(save, INPUT_PULLUP);
  pinMode(backlight, INPUT_PULLUP);
  Serial.println("After pinModes");
  //One time formulas
  wheelInches = diameter * pi;                  //Calculate the circumference of wheel.
  inchesPerPulse = wheelInches / pulsesPerRev;  //Used to calculate inches covered per pulse.
  //Initialize states to ensure proper startup.
  measuringActive = true;                         //Force Measuring mode to start at startup
  inMenu = false;                                 //Ensure NOT in menu at startup
  menuIndex = 0;                                  //Ensure menu starts at 1st menu
  lastPosition = menuEnc.read() / detentDivisor;  //Initilaize lastPosition
  Serial.println("After initializations");
  
} //END of   setup()

//================================================^================================================
void measuring()
{
  Serial.println("In Measuring()");
  if (wheelPulseCount != wheelCountStart)    //Starts the counting process
  {
    wheelCountStart = wheelPulseCount;
    if (!measuringActive)
    {
      measuringActive = true;
      lcd.clear();
      lcd.setCursor(2, 0);
      lcd.print("READY TO MEASURE");
    }
  }

  if (measuringActive)
  {
    // Calculate the remaining inches within a foot
    inchesDisplayed = abs(static_cast<int>(totalInches)) % 12; /*The % sign uses float, but total
                                                       inches is an int, so static_cast<int>
                                                       turns it back to an integer. ABS means use
                                                       absolute value. Prevents inches from being
                                                       displayed in negatives.*/
    footageDisplayed = totalFootage;                           //Displays footage
    mileageDisplayed = truncate(totalMileage, decimals);       //Displays mileage. Truncate displays actual
    //value to the specified decimal places. NOT the
    //rounded value.

    if (totalFootage > 99999 || totalFootage < -99999)
    {
      wheelEnc.write(0);
    }

    if (lastTotalInches != totalInches)
    {
      /*This if statement prevents constant monitoring of the
                                          serial monitor. It only displays new data when the data
                                          changes*/
      lastTotalInches = totalInches;      // Resets if statements

      lcd.setCursor(0, 2);
      lcd.print("IN      FT      MI");
      lcd.setCursor(0, 3);
      lcd.print("    ");
      lcd.setCursor(0, 3);
      lcd.print(inchesDisplayed, 0);
      lcd.setCursor(4, 3);
      lcd.print("        ");
      lcd.setCursor(5, 3);
      lcd.print(footageDisplayed, 1);
      lcd.setCursor(15, 3);
      lcd.print("      ");
      lcd.setCursor(14, 3);
      lcd.print(mileageDisplayed, 3);
    }
  }
  
}

//================================================^================================================
void resetButton()
{
  Serial.println("In reset");
  if (digitalRead(reset) == LOW)    //If RESET button is pressed
  {
    if ((millis() - lastDebounceTime) > resetButtonDelay)
    {
      if (measuringActive)
      {
        wheelEnc.write(0);  //Resets encoder counts to zero and displays all zeros on serial monitor
      }
    }
  }
  
}

//================================================^================================================
void backlightButton()
{
  Serial.println("In backlight");
  if (digitalRead(backlight) == LOW)
  {
    if ((millis() - lastDebounceTime) > backlightButtonDebounceDelay)    //Debounce timer
    {
      lastDebounceTime = millis();
      if (backlightOn == true)
      {
        lcd.noBacklight();
        backlightOn = false;
      }

      else if (backlightOn == false)
      {
        lcd.backlight();
        backlightOn = true;
      }
    }
  }
  
}

//================================================^================================================
void saveMeasurement()
{
  Serial.print("In save");
  if (save == LOW && currentMeasurement < numMeasurements)
  {
    if ((millis() - lastDebounceTime) > saveButtonDelay)
    {
      lastDebounceTime = millis();
      Measurement m = { inchesDisplayed, footageDisplayed, mileageDisplayed };
      int address = startAddress + currentMeasurement * sizeof(Measurement);
      EEPROM.put(address, m);
      currentMeasurement++;
    }
  }
  
}

//================================================^================================================
void menu()
{
  Serial.println("In Menu()");
  int newPosition = (menuEnc.read() / detentDivisor) - 1; /*DetentDivisor ensures menu changes at "clicks"
                                                       Subtracting one ensures menustarts at first position*/
  if (newPosition != lastPosition)
  {
    lastPosition = newPosition;
    menuIndex = newPosition % numMeasurements;
    if (menuIndex < 0)
    {
      menuIndex += numMeasurements;
    }
    displayMeasurement(menuIndex);
    measuringActive = false;  //Disable measuring abiliity while in menu screens
    inMenu = true;
  }
  
}

//================================================^================================================
void displayMeasurement(int index)
{
  int address = startAddress + index * sizeof(Measurement);
  Measurement m;
  EEPROM.get(address, m);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Saved Measurement#");
  lcd.setCursor(18, 0);
  lcd.print(index + 1);  //Beacause index starts at zero.
  lcd.setCursor(0, 2);
  lcd.print("IN      FT      MI");
  lcd.setCursor(0, 3);
  lcd.print("    ");
  lcd.setCursor(0, 3);
  lcd.print(m.inch);
  lcd.setCursor(4, 3);
  lcd.print("        ");
  lcd.setCursor(5, 3);
  lcd.print(m.feet);
  lcd.setCursor(15, 3);
  lcd.print("      ");
  lcd.setCursor(14, 3);
  lcd.print(m.miles);
  
}

//================================================^================================================
void loop()
{
  wheelPulseCount = wheelEnc.read();               //Read the pulses from encoder
  wheelInches = diameter * pi;                     //Calculate the circumference of wheel.
  inchesPerPulse = wheelInches / pulsesPerRev;     //Used to calculate inches covered per pulse.
  totalInches = wheelPulseCount * inchesPerPulse;  //Calculate total inches covered
  totalFootage = totalInches / inchesPerFoot;      //Calculate the footage
  totalMileage = totalInches / inchesPerMile;      //Calculate the mileage.

  measuring();
  menu();
  resetButton();
  backlightButton();
  saveMeasurement();
  
} //END of   loop()

won't subtracting 1 when setting newPosition make it different than lastPosition?

I have last position set at -999 in the global variables. I think subtracting 1 would make it -1, so new position still will not equal lastPosition. The -1 ensures 4th pulse lands on a detent.

I did restructure the entire thing. I am getting better results now. But have a few other problems.

Here are the new problems:
#1 The menu encoder will only decrement down to the original zero point. I would like it to keep on decrementing, scrolling through the menu backwards as long as the user is turning it. What happens if you go below the starting spot is I think the menuPulses are going below zero, or maybe in the opposite direction. And then when you scroll back clockwise, or "up" in the menu, it has to catch up before the menu start to increment again. I will do some serial prints later after work and try to see what is going on here.

#2 It starts in Ready To Measure screen now. It will measure. When the menu encoder is turned clockwise, it will automatically go to menu and start scrolling. But when the wheel starts to move again, it should automatically go to Ready To Measure screen and start measuring. It does not do this. I will do some serial print statements later on that as well and see if it is even counting measurements.

#3 The "S" in Saved Measurement does not show up in cell 0,0. I did run a "Hello World" example and move it to 0,0. I did confirm that cell 0,0 does work. So it is something with my structuring. I did not include the Wire.h library, because my understanding is LiquidCrystal_I2C already uses it.

LarryD I liked your idea of separating the different sections. It is much easier to read. Thanks for that!

Here is the restructured code:

#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

// On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);
Encoder menuEnc(3, 5);
int detentDivisor = 4; /*For this particular menu encoder, divide pulse
                         counts by 4 to equal the detent poisitons*/

//Menu and EEPROM
int lastMenuPosition = -999; /*Arbitrary number to ENSURE upon start up 
                         that newPosition does not equal lastPosition.*/
int newMenuPosition;
volatile long menuPulseCount;

int menuIndex = 0;
const char* menuItems[] = {
  "Measurement 1",
  "Measurement 2",
  "Measurement 3",
  "Measurement 4",
  "Measurement 5",
  "Measurement 6",
  "Measurement 7",
  "Measurement 8",
  "Measurement 9",
  "Measurement 10",
  "Measurement 11",
  "Measurement 12",
};
const int menuLength = sizeof(menuItems) / sizeof(menuItems[0]);
const int numMeasurements = 12;
const int startAddress = 0;
int currentMeasurement = 0;
struct Measurement {
  int inch;
  float feet;
  float miles;
};
//Measuring
volatile long wheelPulseCount;
long wheelCountStart = 0;

float totalInches = 0.0;
static float lastTotalInches = 0.0;
float inchesDisplayed = 0.0;
float totalFootage = 0.0;
float footageDisplayed = 0.0;
float totalMileage = 0.0;
float mileageDisplayed = 0.0;
const float inchesPerMile = 63360.0;
const float inchesPerFoot = 12.0;
const float feetPerMile = 5280.0;

//Button pins
int save = 9;
int reset = 10;
int backlight = 11;

unsigned long lastDebounceTime = millis();  //Debounce millis delay
int resetButtonDelay = 1000;
int backlightButtonDebounceDelay = 1000;  //Debounce Millis delay
int saveButtonDelay = 150;

bool backlightOn = false;
bool measuringActive = false;
bool inMenu = false;

//Display accurate mileage to 3 decimals
int decimals = 3;
float truncate(float num, int dec) {
  int factor = pow(10, dec);
  return int(num * factor) / float(factor);
}

// Wheel size variables
float diameter = 15.0;  //Change as needed for whichever wheel size
float inchesPerPulse;
float wheelInches;
float pi = M_PI;

int pulsesPerRev = 2400;  //Change as needed depending on encoder used

void setup() {
  Serial.begin(9600);
  Serial.println("In setup()");
  lcd.init();
  lcd.backlight();
  backlightOn = true;
  lcd.clear();
  lcd.setCursor(8, 1);
  lcd.print("CFG");
  delay(3000);
  lcd.clear();
  lcd.setCursor(2, 0);
  lcd.print("READY TO MEASURE");
  //Prevent LED13 from automatically turning on
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  //Buttons
  pinMode(reset, INPUT_PULLUP);
  pinMode(save, INPUT_PULLUP);
  pinMode(backlight, INPUT_PULLUP);
  //One time formulas
  wheelInches = diameter * pi;                  //Calculate the circumference of wheel.
  inchesPerPulse = wheelInches / pulsesPerRev;  //Used to calculate inches covered per pulse.
  //Initialize states to ensure proper startup.
  measuringActive = true;  //Force Measuring mode to start at startup
  inMenu = false;          //Ensure NOT in menu at startup
  // menuIndex = 0;                                  //Ensure menu starts at 1st menu
  //lastPosition = menuEnc.read() / detentDivisor;  //Initilaize lastPosition
}
//End of setup
//===============================================================================================
void displayMeasurement(int index) {
  int address = startAddress + index * sizeof(Measurement);
  Measurement m;
  EEPROM.get(address, m);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Saved Measurement#");
  lcd.setCursor(18, 0);
  lcd.print(index + 1);  //Beacause index starts at zero.
  lcd.setCursor(0, 2);
  lcd.print("IN      FT      MI");
  lcd.setCursor(0, 3);
  lcd.print("    ");
  lcd.setCursor(0, 3);
  lcd.print(m.inch);
  lcd.setCursor(4, 3);
  lcd.print("        ");
  lcd.setCursor(5, 3);
  lcd.print(m.feet);
  lcd.setCursor(15, 3);
  lcd.print("      ");
  lcd.setCursor(14, 3);
  lcd.print(m.miles);
}
//===============================================================================================

void loop() {

  wheelPulseCount = wheelEnc.read();  //Read the pulses from wheel encoder
  menuPulseCount = menuEnc.read();    //Read pulses from menu encoder
  if (wheelPulseCount > 0) {
    measuringActive = true;
    inMenu == false;
  }

  if (menuPulseCount > 0) {
    measuringActive = false;
    inMenu = true;
  }

  //===============================================================================================
  if (measuringActive == true && inMenu == false) {

    if (wheelPulseCount != wheelCountStart) {  //Starts the counting process
      measuringActive = true;
      wheelCountStart = wheelPulseCount;
      totalInches = wheelPulseCount * inchesPerPulse;  //Calculate total inches covered
      totalFootage = totalInches / inchesPerFoot;      //Calculate the footage
      totalMileage = totalInches / inchesPerMile;      //Calculate the mileage.

      // Calculate the remaining inches within a foot
      inchesDisplayed = abs(static_cast<int>(totalInches)) % 12; /*The % sign uses float, but total
                                                       inches is an int, so static_cast<int>
                                                       turns it back to an integer. ABS means use
                                                       absolute value. Prevents inches from being 
                                                       displayed in negatives.*/
      footageDisplayed = totalFootage;                           //Displays footage
      mileageDisplayed = truncate(totalMileage, decimals);       //Displays mileage. Truncate displays actual
                                                                 //value to the specified decimal places. NOT the
                                                                 //rounded value.

      if (totalFootage > 99999 || totalFootage < -99999) {
        wheelEnc.write(0);
      }

      if (lastTotalInches != totalInches) { /*This if statement prevents constant monitoring of the 
                                        serial monitor. It only displays new data when the data 
                                        changes*/
        lastTotalInches = totalInches;      // Resets if statements
      }
      lcd.setCursor(2, 0);
      lcd.print("READY TO MEASURE");
      lcd.setCursor(0, 2);
      lcd.print("IN      FT      MI");
      lcd.setCursor(0, 3);
      lcd.print("    ");
      lcd.setCursor(0, 3);
      lcd.print(inchesDisplayed, 0);
      lcd.setCursor(4, 3);
      lcd.print("        ");
      lcd.setCursor(5, 3);
      lcd.print(footageDisplayed, 1);
      lcd.setCursor(15, 3);
      lcd.print("      ");
      lcd.setCursor(14, 3);
      lcd.print(mileageDisplayed, 3);
    }
  }
  //==============================================================================================
  if (digitalRead(reset) == LOW) {  //If RESET button is pressed
    if ((millis() - lastDebounceTime) > resetButtonDelay) {
      wheelEnc.write(0);  //Resets encoder counts to zero and displays all zeros on serial monitor
    }
  }
  //=================================================================================================
  if (digitalRead(backlight) == LOW) {
    if ((millis() - lastDebounceTime) > backlightButtonDebounceDelay) {  //Debounce timer
      lastDebounceTime = millis();
      if (backlightOn == true) {
        lcd.noBacklight();
        backlightOn = false;
      }

      else if (backlightOn == false) {
        lcd.backlight();
        backlightOn = true;
      }
    }
  }
  //=================================================================================================
  if (save == LOW && currentMeasurement < numMeasurements) {
    if ((millis() - lastDebounceTime) > saveButtonDelay) {
      lastDebounceTime = millis();
      Measurement m = { inchesDisplayed, footageDisplayed, mileageDisplayed };
      int address = startAddress + currentMeasurement * sizeof(Measurement);
      EEPROM.put(address, m);
      currentMeasurement++;
    }
  }
  //===============================================================================================
  if (measuringActive == false && inMenu == true) {
    int newPosition = (menuEnc.read() / detentDivisor) - 1; /*DetentDivisor ensures menu changes at "clicks"
                                                       Subtracting one ensures menustarts at first position*/
    if (newPosition != lastMenuPosition) {
      inMenu = true;
      lastMenuPosition = newPosition;
      menuIndex = newPosition % numMeasurements;
      if (menuIndex < 0) {
        menuIndex += numMeasurements;
      }
      displayMeasurement(menuIndex);
    }
  }
}

doesn't that indicate a change? and in this case, when there isn't one

Hmmm. Good point. That needs closer inspection.

I kind of have it working. It might be ugly but the display is changing as needed and how I want it. It was all about keeping track of multiple states. There may be a better way but this is working.

BUT can anyone tell me why the "S" in ("Saved Measurement#") on line 125 of this code will show up for only a split second, barely enough to even see it before it disappears? The rest of that line displays correctly. I have verified that the cell (0,0) on my LCD does work. I made a quick sample code and printed a letter to that cell. It does work.

Here is the code:

#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

// On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);
Encoder menuEnc(3, 5);
int detentDivisor = 4; /*For this particular menu encoder, divide pulse
                         counts by 4 to equal the detent poisitons*/

//Menu and EEPROM
int lastMenuPosition = -999; /*Arbitrary number to ENSURE upon start up 
                         that newPosition does not equal lastPosition.*/
int newMenuPosition;
volatile long menuPulseCount;

int menuIndex = 0;
const char* menuItems[] = {
  "Measurement 1",
  "Measurement 2",
  "Measurement 3",
  "Measurement 4",
  "Measurement 5",
  "Measurement 6",
  "Measurement 7",
  "Measurement 8",
  "Measurement 9",
  "Measurement 10",
  "Measurement 11",
  "Measurement 12",
};
const int menuLength = sizeof(menuItems) / sizeof(menuItems[0]);
const int numMeasurements = 12;
const int startAddress = 0;
int currentMeasurement = 0;
struct Measurement {
  int inch;
  float feet;
  float miles;
};
//Measuring
volatile long wheelPulseCount;
long wheelCountStart = 0;

float totalInches = 0.0;
static float lastTotalInches = 0.0;
float inchesDisplayed = 0.0;
float totalFootage = 0.0;
float footageDisplayed = 0.0;
float totalMileage = 0.0;
float mileageDisplayed = 0.0;
const float inchesPerMile = 63360.0;
const float inchesPerFoot = 12.0;
const float feetPerMile = 5280.0;

//Button pins
int save = 9;
int reset = 10;
int backlight = 11;

unsigned long lastDebounceTime = millis();  //Debounce millis delay
int resetButtonDelay = 1000;
int backlightButtonDebounceDelay = 1000;  //Debounce Millis delay
int saveButtonDelay = 150;

bool backlightOn = false;
bool measuringActive = false;
bool inMenu = false;
bool rowCleared = false;

//Display accurate mileage to 3 decimals
int decimals = 3;
float truncate(float num, int dec) {
  int factor = pow(10, dec);
  return int(num * factor) / float(factor);
}

// Wheel size variables
float diameter = 15.0;  //Change as needed for whichever wheel size
float inchesPerPulse;
float wheelInches;
float pi = M_PI;

int pulsesPerRev = 2400;  //Change as needed depending on encoder used

void setup() {
  Serial.begin(9600);
  Serial.println("In setup()");
  lcd.init();
  lcd.backlight();
  backlightOn = true;
  lcd.clear();
  lcd.setCursor(8, 1);
  lcd.print("CFG");
  delay(3000);
  lcd.clear();
  lcd.setCursor(2, 0);
  lcd.print("READY TO MEASURE");
  //Prevent LED13 from automatically turning on
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  //Buttons
  pinMode(reset, INPUT_PULLUP);
  pinMode(save, INPUT_PULLUP);
  pinMode(backlight, INPUT_PULLUP);
  //One time formulas
  wheelInches = diameter * pi;                  //Calculate the circumference of wheel.
  inchesPerPulse = wheelInches / pulsesPerRev;  //Used to calculate inches covered per pulse.
  //Initialize states to ensure proper startup.
  measuringActive = true;  //Force Measuring mode to start at startup
  inMenu = false;          //Ensure NOT in menu at startup
  // menuIndex = 0;                                  //Ensure menu starts at 1st menu
  //lastPosition = menuEnc.read() / detentDivisor;  //Initilaize lastPosition
}
//End of setup
//===============================================================================================
void displayMeasurement(int index) {
  int address = startAddress + index * sizeof(Measurement);
  Measurement m;
  EEPROM.get(address, m);
  lcd.setCursor(0,0);
  lcd.print("                    ");
  lcd.setCursor(0, 0);
  lcd.print("Saved Measurement#");
  lcd.setCursor(18, 0);
  lcd.print(index + 1);  //Beacause index starts at zero.
  lcd.setCursor(0, 2);
  lcd.print("IN      FT      MI");
  lcd.setCursor(0, 3);
  lcd.print("    ");
  lcd.setCursor(0, 3);
  lcd.print(m.inch);
  lcd.setCursor(4, 3);
  lcd.print("        ");
  lcd.setCursor(5, 3);
  lcd.print(m.feet);
  lcd.setCursor(15, 3);
  lcd.print("      ");
  lcd.setCursor(14, 3);
  lcd.print(m.miles);
}
//===============================================================================================

void loop() {

  wheelPulseCount = wheelEnc.read();  //Read the pulses from wheel encoder
  menuPulseCount = menuEnc.read();    //Read pulses from menu encoder
  if (wheelPulseCount > 0) {
    measuringActive = true;
    inMenu == false;
  }

  if (menuPulseCount > 0) {
    measuringActive = false;
    inMenu = true;
  }

  //===============================================================================================
  //MEASURE
  if ((measuringActive == true && inMenu == false) || (wheelPulseCount != wheelCountStart && inMenu == true)) {
    measuringActive = true;
    inMenu == false;
    if ((wheelPulseCount != wheelCountStart)&&!rowCleared) {  //Starts the counting process
      lcd.setCursor (0,0);
      lcd.print("                    ");
      rowCleared=true;
    }
    else if (wheelPulseCount != wheelCountStart){
      
      measuringActive = true;
      wheelCountStart = wheelPulseCount;
      totalInches = wheelPulseCount * inchesPerPulse;  //Calculate total inches covered
      totalFootage = totalInches / inchesPerFoot;      //Calculate the footage
      totalMileage = totalInches / inchesPerMile;      //Calculate the mileage.

      // Calculate the remaining inches within a foot
      inchesDisplayed = abs(static_cast<int>(totalInches)) % 12; /*The % sign uses float, but total
                                                       inches is an int, so static_cast<int>
                                                       turns it back to an integer. ABS means use
                                                       absolute value. Prevents inches from being 
                                                       displayed in negatives.*/
      footageDisplayed = totalFootage;                           //Displays footage
      mileageDisplayed = truncate(totalMileage, decimals);       //Displays mileage. Truncate displays actual
                                                                 //value to the specified decimal places. NOT the
                                                                 //rounded value.

      if (totalFootage > 99999 || totalFootage < -99999) {
        wheelEnc.write(0);
      }

      if (lastTotalInches != totalInches) { /*This if statement prevents constant monitoring of the 
                                        serial monitor. It only displays new data when the data 
                                        changes*/
        lastTotalInches = totalInches;      // Resets if statements
      }
      lcd.setCursor(2, 0);
      lcd.print("READY TO MEASURE");
      lcd.setCursor(0, 2);
      lcd.print("IN      FT      MI");
      lcd.setCursor(0, 3);
      lcd.print("    ");
      lcd.setCursor(0, 3);
      lcd.print(inchesDisplayed, 0);
      lcd.setCursor(4, 3);
      lcd.print("        ");
      lcd.setCursor(5, 3);
      lcd.print(footageDisplayed, 1);
      lcd.setCursor(15, 3);
      lcd.print("      ");
      lcd.setCursor(14, 3);
      lcd.print(mileageDisplayed, 3);

      Serial.print("totInch ");  //Check if counter is counting while other if statements
      Serial.println(totalInches);
    }
  }
  //==============================================================================================
  //RESET
  if (digitalRead(reset) == LOW) {  //If RESET button is pressed
    if ((millis() - lastDebounceTime) > resetButtonDelay) {
      wheelEnc.write(0);  //Resets encoder counts to zero and displays all zeros on serial monitor
    }
  }
  //=================================================================================================
  //BACKLIGHT
  if (digitalRead(backlight) == LOW) {
    if ((millis() - lastDebounceTime) > backlightButtonDebounceDelay) {  //Debounce timer
      lastDebounceTime = millis();
      if (backlightOn == true) {
        lcd.noBacklight();
        backlightOn = false;
      }

      else if (backlightOn == false) {
        lcd.backlight();
        backlightOn = true;
      }
    }
  }
  //=================================================================================================
  //SAVE
  if (save == LOW && currentMeasurement < numMeasurements) {
    if ((millis() - lastDebounceTime) > saveButtonDelay) {
      lastDebounceTime = millis();
      Measurement m = { inchesDisplayed, footageDisplayed, mileageDisplayed };
      int address = startAddress + currentMeasurement * sizeof(Measurement);
      EEPROM.put(address, m);
      currentMeasurement++;
    }
  }
  //===============================================================================================
  //MENU
  if (measuringActive == false && inMenu == true) {
    int newPosition = (menuEnc.read() / detentDivisor)- 1; /*DetentDivisor ensures menu changes at "clicks"
                                                         //Subtracting one ensures menustarts at first position*/
    if (newPosition != lastMenuPosition) {
      inMenu = true;
      lastMenuPosition = newPosition;
      menuIndex = newPosition % numMeasurements;
      if (menuIndex < 0) {
        menuIndex += numMeasurements;
      }
      displayMeasurement(menuIndex);
      rowCleared=false;
    }
  }
}

  • Print the value of menuLength, what do you get ?

Hint

inMenu == false; <β€”β€”β€”β€”<<<<

menuLength returns 12. What does that mean?

Oh! to many = signs. You know how when your pulling weeds and you think they are all gone but you come back an hour later and see so many you missed? Looking at this code is kind of like that to me. Lot's of things I miss until my mind is clear for a while.

By the way-- I fixed that and it did not change the disappearing "S". AND I noticed another menu problem. It will not decrement below the place where the menuEncoder started when it enters the MENU if statement. It will go from 1 (it actually starts at #12 for the first menuEncoder pulse. I don't like that but as soon as it get's to the detent it starts at 1. I can deal with it for now but would love to fix it where it starts at 1) up to 12 and keep going up to 1 through 12. Again and again like it is supposed to. But when descending through the menu it will go backwards until it hits the point where it started. If I continue to turn the knob backwards, the screen won't decrement anymore, but something is happening in the background. Because when I turn the knob to increment the screen positions again the pulse count has to "catch up" back to where it started when the menu was called. And ONLY THEN will start incrementing again.

I am pulling my hair out with this menu stuff. I am reading and reading and reading, but can not figure it all out. I think of something and try it. It might or might not work, but if it does solve one thing it causes another problem. I am glad it's a hobby. If I was a pro I would be starving!

  • When you make changes to a sketch, always show us the new version.
  • Did you fix this one too ?
  • Try these changes:

Edit, added some comments

#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

//On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);
Encoder menuEnc(3, 5);

//For this particular menu encoder, divide pulse counts by 4 to equal the detent poisitons
int detentDivisor                    = 4;

//Menu and EEPROM
//Arbitrary number to ENSURE upon start up that newPosition does not equal lastPosition.
int lastMenuPosition                 = -999;
int newMenuPosition;
volatile long menuPulseCount;

int menuIndex                        = 0;

const char* menuItems[] =
{
  "Measurement 1",
  "Measurement 2",
  "Measurement 3",
  "Measurement 4",
  "Measurement 5",
  "Measurement 6",
  "Measurement 7",
  "Measurement 8",
  "Measurement 9",
  "Measurement 10",
  "Measurement 11",
  "Measurement 12",
};

const int menuLength                 = sizeof(menuItems) / sizeof(menuItems[0]);
const int numMeasurements            = 12;
const int startAddress               = 0;
int currentMeasurement               = 0;

struct Measurement
{
  float inch;
  float feet;
  float miles;
};

//Measuring
volatile long wheelPulseCount;
long wheelCountStart                = 0;

float totalInches                   = 0.0;
static float lastTotalInches        = 0.0;
float inchesDisplayed               = 0.0;
float totalFootage                  = 0.0;
float footageDisplayed              = 0.0;
float totalMileage                  = 0.0;
float mileageDisplayed              = 0.0;
const float inchesPerMile           = 63360.0;
const float inchesPerFoot           = 12.0;
const float feetPerMile             = 5280.0;

//Button pins
int save                            = 9;
int reset                           = 10;
int backlight                       = 11;

unsigned long lastDebounceTime             = millis();  //Debounce millis delay
unsigned long resetButtonDelay             = 1000;
unsigned long backlightButtonDebounceDelay = 1000;      //Debounce Millis delay
unsigned long saveButtonDelay              = 150;

bool backlightOn                    = false;
bool measuringActive                = false;
bool inMenu                         = false;
bool rowCleared                     = false;

//Display accurate mileage to 3 decimals
int decimals                        = 3;

// Wheel size variables
float diameter                      = 15.0;  //Change as needed for whichever wheel size
float inchesPerPulse;
float wheelInches;
float pi                            = M_PI;

int pulsesPerRev                    = 2400;  //Change as needed depending on encoder used


//================================================^================================================
void setup()
{
  Serial.begin(9600);
  Serial.println("In setup()");

  lcd.init();
  lcd.backlight();
  backlightOn = true;

  lcd.clear();

  lcd.setCursor(0, 1);
  //                   1111111111
  //         01234567890123456789
  lcd.print("        CFG         ");
  delay(3000);

  lcd.clear();

  lcd.setCursor(0, 0);
  //                   1111111111
  //         01234567890123456789
  lcd.print("  READY TO MEASURE  ");

  //Prevent LED13 from automatically turning on
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

  //Buttons
  pinMode(reset, INPUT_PULLUP);
  pinMode(save, INPUT_PULLUP);
  pinMode(backlight, INPUT_PULLUP);

  //One time formulas
  //Calculate the circumference of wheel.
  wheelInches = diameter * pi;
  inchesPerPulse = wheelInches / pulsesPerRev;
  //Used to calculate inches covered per pulse.
  //Initialize states to ensure proper startup.
  //Force Measuring mode to start at startup
  measuringActive = true;
  //Ensure NOT in menu at startup
  inMenu = false;

  //menuIndex = 0;                                  //Ensure menu starts at 1st menu
  //lastPosition = menuEnc.read() / detentDivisor;  //Initilaize lastPosition

} //End of setup


//================================================^================================================
void loop()
{
  //Read the pulses from wheel encoder
  wheelPulseCount = wheelEnc.read();
  //Read pulses from menu encoder
  menuPulseCount = menuEnc.read();

  if (wheelPulseCount > 0)
  {
    measuringActive = true;
    inMenu = false;
  }

  if (menuPulseCount > 0)
  {
    measuringActive = false;
    inMenu = true;
  }

  //================================================
  //MEASURE
  if ((measuringActive == true && inMenu == false) || (wheelPulseCount != wheelCountStart && inMenu == true))
  {
    measuringActive = true;
    inMenu = false;

    //Starts the counting process
    if ((wheelPulseCount != wheelCountStart) && !rowCleared)
    {
      lcd.setCursor (0, 0);
      lcd.print("                    ");
      rowCleared = true;
    }

    else if (wheelPulseCount != wheelCountStart)
    {
      measuringActive = true;
      wheelCountStart = wheelPulseCount;
      totalInches = wheelPulseCount * inchesPerPulse;  //Calculate total inches covered
      totalFootage = totalInches / inchesPerFoot;      //Calculate the footage
      totalMileage = totalInches / inchesPerMile;      //Calculate the mileage.

      // Calculate the remaining inches within a foot
      inchesDisplayed = abs(static_cast<int>(totalInches)) % 12; /*The % sign uses float, but total
                                                       inches is an int, so static_cast<int>
                                                       turns it back to an integer. ABS means use
                                                       absolute value. Prevents inches from being
                                                       displayed in negatives.*/
      footageDisplayed = totalFootage;                           //Displays footage
      mileageDisplayed = truncate(totalMileage, decimals);       //Displays mileage. Truncate displays actual
      //value to the specified decimal places. NOT the
      //rounded value.

      if (totalFootage > 99999 || totalFootage < -99999)
      {
        wheelEnc.write(0);
      }

      if (lastTotalInches != totalInches)
      {
        /*This if statement prevents constant monitoring of the
                                          serial monitor. It only displays new data when the data
                                          changes*/
        lastTotalInches = totalInches;      // Resets if statements
      }

      lcd.setCursor(0, 0);
      //                   1111111111
      //         01234567890123456789
      lcd.print("  READY TO MEASURE  ");

      lcd.setCursor(0, 2);
      //                   1111111111
      //         01234567890123456789
      lcd.print("IN      FT    MI    ");

      lcd.setCursor(0, 3);
      //                   1111111111
      //         01234567890123456789
      lcd.print("                    ");

      lcd.setCursor(0, 3);
      //                   1111111111
      //         01234567890123456789
      //         IN      FT    MI
      //         2
      lcd.print(inchesDisplayed, 0);

      lcd.setCursor(8, 3);
      //                   1111111111
      //         01234567890123456789
      //         IN      FT    MI
      //                 0.0
      lcd.print(footageDisplayed, 1);

      lcd.setCursor(14, 3);
      //                   1111111111
      //         01234567890123456789
      //         IN      FT    MI
      lcd.print("      ");

      lcd.setCursor(14, 3);
      //                   1111111111
      //         01234567890123456789
      //         IN      FT    MI
      //                       0.000
      lcd.print(mileageDisplayed, 3);

      Serial.print("totInch ");  //Check if counter is counting while other if statements
      Serial.println(totalInches);
    }
  }

  //================================================
  //RESET
  if (digitalRead(reset) == LOW)    //If RESET button is pressed
  {
    if ((millis() - lastDebounceTime) > resetButtonDelay)
    {
      wheelEnc.write(0);  //Resets encoder counts to zero and displays all zeros on serial monitor
    }
  }

  //================================================
  //BACKLIGHT
  if (digitalRead(backlight) == LOW)
  {
    if ((millis() - lastDebounceTime) > backlightButtonDebounceDelay)    //Debounce timer
    {
      lastDebounceTime = millis();
      if (backlightOn == true)
      {
        lcd.noBacklight();
        backlightOn = false;
      }

      else if (backlightOn == false)
      {
        lcd.backlight();
        backlightOn = true;
      }
    }
  }

  //================================================
  if (save == LOW && currentMeasurement < numMeasurements)
  {
    if ((millis() - lastDebounceTime) > saveButtonDelay)
    {
      lastDebounceTime = millis();
      Measurement m = { inchesDisplayed, footageDisplayed, mileageDisplayed };
      int address = startAddress + currentMeasurement * sizeof(Measurement);
      EEPROM.put(address, m);
      currentMeasurement++;
    }
  }

  //================================================
  //MENU
  if (measuringActive == false && inMenu == true)
  {
    int newPosition = (menuEnc.read() / detentDivisor) - 1; /*DetentDivisor ensures menu changes at "clicks"
                                                         //Subtracting one ensures menustarts at first position*/
    if (newPosition != lastMenuPosition)
    {
      inMenu = true;
      lastMenuPosition = newPosition;
      menuIndex = newPosition % numMeasurements;

      if (menuIndex < 0)
      {
        menuIndex += numMeasurements;
      }

      displayMeasurement(menuIndex);

      rowCleared = false;
    }
  }

} //END of   loop()


//================================================^================================================
float truncate(float num, int dec)
{
  int factor = pow(10, dec);
  return int(num * factor) / float(factor);

} //END of    truncate()


//================================================^================================================
void displayMeasurement(int index)
{
  int address = startAddress + index * sizeof(Measurement);

  Measurement m;

  EEPROM.get(address, m);
  
  lcd.setCursor(0, 0);
  //                   1111111111
  //         01234567890123456789
  lcd.print("                    ");

  lcd.setCursor(0, 0);
  //                   1111111111
  //         01234567890123456789
  lcd.print("Saved Measurement#  ");

  lcd.setCursor(18, 0);
  //                   1111111111
  //         01234567890123456789
  //         Saved Measurement#
  //                           12
  lcd.print(index + 1);  //Beacause index starts at zero.

  lcd.setCursor(0, 2);
  //                   1111111111
  //         01234567890123456789
  //         ^
  lcd.print("IN      FT    MI    ");

  lcd.setCursor(0, 3);
  //                   1111111111
  //         01234567890123456789
  //         ^
  lcd.print("                    ");

  lcd.setCursor(0, 3);
  //                   1111111111
  //         01234567890123456789
  //         IN      FT    MI
  //         0.00
  lcd.print(m.inch);

  lcd.setCursor(8, 3);
  //                   1111111111
  //         01234567890123456789
  //         IN      FT    MI
  //                 0.00
  lcd.print(m.feet);

  lcd.setCursor(14, 3);
  //                   1111111111
  //         01234567890123456789
  //         IN      FT    MI
  //                       0.00
  lcd.print(m.miles);

} //END of   displayMeasurement()

Thank you. I just copied and pasted your code to a new sketch to run it. I see how you set up the displays. I will type it out later and maybe some of the method will stick in the ol' brain. It brought my "S" back. I was afraid if I cleared an entire line at a time it would cause flickering across the whole line. But not so in this case. I am going to study it more and try to see just what exactly is going on and why that one works.

I also have an idea to get the Saved Measurements menu to index backwards below the starting point where it first entered the menu. It might be sloppy code I don't know. But I am trying to learn. I have an idea. I will post the newest code again after I fiddle with it some. Hopefully a cleaner method hits me while I am at work today. Sometimes that happens. Walk away and things get more clear.

I thought this would be an easy first time project. At least I am learning a little something beyond blink an onboard LED.

  • Remember when writing to EEPROM, you only have about 100,000 write cycles before the EEPROM is unusable.
    Be careful you only write data when absolutely necessary.