Seems like IF statement is robbing from another IF statement

I have a very functional measuring wheel code. Many thanks to LarryD for being patient and seeing me through this. He has modified what I gave him and gave me a great piece of code structure to model my future projects after. And I have fine tuned what he gave me to better suit my exact project. I have one last step to go that seems to be kicking my tail.

I have included a toggle switch to switch between measuring and menu modes. I decided that in this case, it would be better to switch between the two with a toggle or button. I chose a toggle just because I have a couple of extras. In order to use these two modes i put their functions in some IF statements like this:

// Read the toggle switch state 
  int toggleState = digitalRead(selector); 
  //================================================
  //service menu encoder

  if (toggleState == ON) // Toggle switch is ON 
  
  { 
     // Display the menu

     lcd.setCursor(0, 0);
     //                   1111111111
     //         01234567890123456789
     lcd.print("Saved Measurement#");                  //Overwrites "Ready To Measure".
     
     menuEncoder(); 
    
  } 

  //================================================
  //service the wheel encoder

  if (toggleState == OFF) // Toggle switch is OFF

  { // Display "READY TO MEASURE" 
    lcd.setCursor(0, 0);
    //         01234567890123456789 
    lcd.print("  READY TO MEASURE ");                  //Overwrites "Saved Measurement#".
    wheelEncoder();
  }

The selector is the toggle switch. Looking back I could have named it toggle...

The way this is SUPPOSED to now work is if the switch is on the menu mode is displayed and working. If the switch is off the measuring mode is displayed and working. This does work.

BUT while in menu mode if the wheel encoder is turned, it is incrementing or decrementing the measurement in the background. When I flip the toggle back to measure mode, the new measurement is displayed. WHY is it counting the wheel encoder when it should not even be able to enter the MEASURING if statement due to the position of the toggle switch?

The opposite does not happen. It does not increment or decrement up and down the menu while in measuring mode.

To clarify, I do not want the wheel encoder to be able to count while in MENU mode. This will lead to innaccurate measurements. I have tried flags and state changes to no avail. I can not see anywhere else in the code where the wheel encoder is being called outside its own function.

Can anyone see what is going on? Here is the entire code. I know it needs to be tidied back up. Once I get it going how I want it I will delete unnecessary parts and comments that are no longer relevant. But for now I am keeping them in there "just in case".

//================================================^================================================
//
//  https://forum.arduino.cc/t/program-is-displaying-wrong-screen-after-startup/1339410/32
//
//
//
//  Version    MM/DD/YY    Comments
//  =======    ========    ========================================================================
//  1.00       01-12-25    Running code
//  1.10       01-12-25    Added save switch code
//  1.11       01-15-25    Added notes, added custom startup display characters, commented out heartbeat, 
//                         and disabled onboard LED on pin 13.
//  1.12       01-17-25    Added "static_cast<int>" to displayMeasurement m.inch to display integer instead of decimal
//                         Move footageDisplayed AND m.feet 2 places to the left if greater then 999.99
//                         Change longpress from 2 seconds to 4 seconds

//Notes:
//Measuring Wheel Code measure inch, feet, mile.
//Depending on hardware, 2 variables need changed.
//They are DIAMETER and PULSES PER REV

#include <Wire.h>
#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>

//HILETGO 2004 LCD
LiquidCrystal_I2C lcd(0x27, 20, 4);

//On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);                                //TAISS  E38S6-600-24G from Amazon
Encoder menuEnc(3, 5);                                 //KY040 style

//================================================^================================================
/* U S I N G    C O M M O N    W O R D S    I N S T E A D    O F    L O G I C    W O R D S
makes the code further down easier to read*/

#define PUSHED                    LOW
#define RELEASED                  HIGH

#define ON                        LOW
#define OFF                       HIGH

#define ENABLED                   true
#define DISABLED                  false

//================================================^================================================

// C U S T O M    C F G    S T A R T U P    C H A R A C T E R S 
byte topOfCLeft[] = { B01111, B11111, B11110, B11100, B11100, B11100, B11100, B11100 };
byte topOfCRight[] = { B11110, B11111, B00111, B00011, B00000, B00000, B00000, B00000 };
byte bottomOfCLeft[] = { B11100, B11100, B11100, B11100, B11100, B11110, B11111, B01111 };
byte bottomOfCRight[] = { B00000, B00000, B00000, B00000, B00011, B00111, B11111, B11110 };
byte leftDashes[] = { B00000, B11111, B11111, B00000, B00000, B11111, B00000, B11111 };
byte rightDashes[] = { B00000, B11111, B11111, B00000, B00000, B11111, B00000, B11111 };

//==================E N D   O F   C H A R A C T E R S==============================================

const int decimals              = 3;  //3 decimal places for mileage

int menuIndex                   = -1;
int currentMeasurement          = 0;
//For this particular menu encoder (KY040 style), divide pulse counts by 4 to equal the detent poisitons
int detentDivisor               = 4;

//Menu and EEPROM
int lastMenuPosition            = 0;
int newMenuPosition;

//Checks for preventing redundant overwriting of EEPROM.
float lastInches                = 0.0;
float lastFootage               = 0.0;
float lastMileage               = 0.0;

//Measuring
volatile long wheelPulseCount;
//menu
volatile long menuPulseCount;

long wheelCountStart            = 0;

static float lastTotalInches    = 0.0;

const float inchesPerMile       = 63360.0;
const float inchesPerFoot       = 12.0;
const float feetPerMile         = 5280.0;

float totalInches               = 0.0;
float inchesDisplayed           = 0.0;
//
float totalFootage              = 0.0;
float footageDisplayed          = 0.0;
//
float totalMileage              = 0.0;
float mileageDisplayed          = 0.0;

//Input pins
const byte save                 = 9;
const byte reset                = 10;
const byte backlight            = 11;
int selector                    = 12;             //Toggle between MENU and MEASURE


//Output pins
const byte heartbeatLED         = 13;

//for switch change in state detection
byte lastSave                   = RELEASED;
byte lastReset                  = RELEASED;
byte lastBacklight              = RELEASED;

//TIMER stuff
unsigned long heartbeatTime;
unsigned long switchesTime;

//to determine if we have a long/short switch press
unsigned long saveMillis        = 0;
unsigned long resetMillis       = 0;
unsigned long backlightMillis   = 0;
unsigned long shortPress        = 1000;
unsigned long longPress         = 4000;

bool saveFlag                   = DISABLED;  //When ENABLED, we are allowed to save to EEPROM.
                                             //ENABLED and DISABLED are defined above in #Define.
                                             //Used in measuring and menu functions below.
bool backlightOn                = false;

//Wheel size variables
const float pi                  = M_PI;
const float diameter            = 20.0;      //Change as needed for whichever wheel size

float inchesPerPulse;
float wheelInches;

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

//measurement data structure
struct Measurement
{
  float inch;
  float feet;
  float miles;
};

const int numMeasurements       = 12;
const int startAddress          = 0;


//                                           s e t u p ( )
//================================================^================================================
//
void setup()
{
  Serial.begin(9600);

  //Serial.println("System starting up.");

  //lcd.begin(20, 4, 0x27);
  lcd.init();

  //Backlight can be set to noBacklight and backlightOn = false to
  //turn off backlight at startup
  lcd.backlight();
  backlightOn = true;

  //========================
  lcd.clear();

  /*lcd.setCursor(0, 1);
  //                   1111111111
  //         01234567890123456789
  lcd.print("        CFG         ");*/
  
 
 // SETUP CUSTOM CFG CHARACTER
  lcd.createChar(0,topOfCLeft);
  lcd.createChar(1,topOfCRight);
  lcd.createChar(2,bottomOfCLeft);
  lcd.createChar(3,bottomOfCRight);
  lcd.createChar(4,leftDashes);
  lcd.createChar(5,rightDashes);
  
 //PRINT TOP OF CUSTOM CHARACTERS
  lcd.setCursor(8,1);
  lcd.write(byte(0));
  lcd.setCursor(9,1);
  lcd.write(byte(1));

 //PRINT F & G
  lcd.setCursor(10,1);
  lcd.print("F");
  lcd.setCursor(11,1);
  lcd.print("G");

 //PRINT BOTTOM OF CUSTOM CHARACTERS
  lcd.setCursor(8,2);
  lcd.write(byte(2));
  lcd.setCursor(9,2);
  lcd.write(byte(3));
  lcd.setCursor(10,2);
  lcd.write(byte(4));
  lcd.setCursor(11,2);
  lcd.write(byte(5));

  delay(3000);

  lcd.clear();

  /*lcd.setCursor(0, 0);
  //                   1111111111
  //         01234567890123456789
  lcd.print("  READY TO MEASURE  ");*/                 //Not needed. Display is updated via the if statements with MENU and MEASURING

  //used to show the sketch is running normally
  pinMode(heartbeatLED, OUTPUT);
  digitalWrite(heartbeatLED, LOW);

  //switches
  pinMode(save, INPUT_PULLUP);
  pinMode(reset, INPUT_PULLUP);
  pinMode(backlight, INPUT_PULLUP);
  pinMode(selector, INPUT_PULLUP);                       //Toggle switch

  //========================
  //Calculate the circumference of wheel.
  wheelInches = diameter * pi;
  //Used to calculate inches covered per pulse.
  inchesPerPulse = wheelInches / pulsesPerRev;

}  //End of setup()


//                                           l o o p ( )
//================================================^================================================
//
void loop()
{
  //================================================           TIMER heartbeat
  //is it time to toggle the heartbeat LED ?
  /*if (millis() - heartbeatTime >= 500)
  {
    //restart this TIMER
    heartbeatTime = millis();

    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }*/


  //================================================           TIMER switches
  //is it time to check our switches ?
  if (millis() - switchesTime >= 50)
  {
    //restart this TIMER
    switchesTime = millis();

    //yes it is time to check the switches
    checkSwitches();
  }

  //================================================
  // Read the toggle switch state 
  int toggleState = digitalRead(selector); 
  //================================================
  //service menu encoder

  if (toggleState == ON) // Toggle switch is ON 
  
  { 
     // Display the menu

     lcd.setCursor(0, 0);
     //                   1111111111
     //         01234567890123456789
     lcd.print("Saved Measurement#");                  //Overwrites "Ready To Measure".
     
     menuEncoder(); 
    
  } 

  //================================================
  //service the wheel encoder

  if (toggleState == OFF) // Toggle switch is OFF

  { // Display "READY TO MEASURE" 
    lcd.setCursor(0, 0);
    //         01234567890123456789 
    lcd.print("  READY TO MEASURE ");                  //Overwrites "Saved Measurement#".
    wheelEncoder();
  }
  
  //================================================
  //Other non-blocking code goes here
  //================================================


}  //END of   loop()


//                                    w h e e l E n c o d e r ( )
//================================================^================================================
//servicing the wheel encoder
void wheelEncoder()
{
  
  //Read the pulses from wheel encoder
  wheelPulseCount = wheelEnc.read();

  //did the wheel turn ?
  if (wheelPulseCount != wheelCountStart)
  {
    //update to the new count
    wheelCountStart = wheelPulseCount;
    
    //enable saving to EEPROM
    saveFlag = ENABLED;

    //re-initialize EEPROM menu index
    menuIndex = -1;

    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. 
    //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.
    
    //Reset footage so display does not get filled up to 8 (including decimal and neagtive sign) spaces.
    if (totalFootage > 99999 || totalFootage < -99999)
    {
      wheelEnc.write(0);
    }

    if (lastTotalInches != totalInches)      //This if statement prevents constant writing to display
    {                                        //every time the loop loops.
    
      lastTotalInches = totalInches;         // Resets (UPDATES) if statement
    }

    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);
   
    //Keep display where it is for under 1,000 feet.
    if (footageDisplayed < 999.9)
    {
        lcd.setCursor(7, 3);
        //                   1111111111
        //         01234567890123456789
        //         IN      FT       MI
        //                 0.0
        lcd.print(footageDisplayed, 1);
    }

    //Move footage display if feet is 1000 feet or greater
    //Makes more room between footage and mileage on display
    if (footageDisplayed > 999.9)
    {
        lcd.setCursor(5, 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);
  }

} //END of   wheelEncoder()


//                                     m e n u E n c o d e r ( )
//================================================^================================================
//
void menuEncoder()
{

  //Read the pulses from wheel encoder
  menuPulseCount = menuEnc.read();

  //================================================
  //MENU  
  //Detent Divisor ensures menu changes at "clicks".
  int newPosition = menuPulseCount / detentDivisor ;

  //did the menu encoder turn ?
  if (newPosition != lastMenuPosition)
  {
    //disable saving to EEPROM
    saveFlag = DISABLED;

    //did the menu encoder turn CW ?
    if (newPosition > lastMenuPosition)
    {
      //update to the new value
      lastMenuPosition = newPosition;

      //next EEPROM index
      menuIndex = menuIndex + 1;

      //are we above the maximum position ?
      if (menuIndex >= 12)
      {
        menuIndex = 0;
      }
    }

    //did the menu encoder turn CCW ?
    else if (newPosition < lastMenuPosition)
    {
      //update to the new value
      lastMenuPosition = newPosition;

      //next EEPROM index
      menuIndex = menuIndex - 1;

      //are we below the minimum position ?
      if (menuIndex < 0)
      {
        menuIndex = 11;
      }
    }

    //update LCD with the saved reading retrieved from EEPROM
    displayMeasurement(menuIndex);
  }

} //END of   menuEncoder()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
//
void checkSwitches()
{
  byte pinState;

  //  S  A  V  E   S  W  I  T  C  H
  //================================================     save switch
  pinState = digitalRead(save);

  //did this switch change state ?
  if (lastSave != pinState)
  {
    //update to the new state
    lastSave = pinState;

    //did this switch get pushed ?
    if ( pinState == PUSHED && currentMeasurement < numMeasurements)
    //Added "&& currentMeasurement < numMeasurements" to stop saving at 12 (numMeasurements) saves
    {
      //the time this switch was closed / pressed
      saveMillis = millis();

      //Serial.println("save switch was closed");

      //Make object "m" for Struct Measurement
      Measurement m = { inchesDisplayed, footageDisplayed, mileageDisplayed };
      //Define address position
      int address = startAddress + currentMeasurement * sizeof(Measurement);
      //PUT data to EEPROM. Data is to large to WRITE to EEPROM
      EEPROM.put(address, m);
      //Increment # of measurements so address will increment in above formula
      currentMeasurement++;
    }

    //was this switch released ?
    else if ( pinState == RELEASED)
    {
      //Serial.print("The save switch was closed for ");
      //Serial.print(millis() - saveMillis);
      //Serial.println("ms.\n");

      //was this a short press
      if (millis() - saveMillis <= shortPress)
          
      {
        
      }

      //was this a long press
      else if (millis() - saveMillis >= longPress)
      {
        //do something
      }
    }

  } //END of this switch

  //  R  E  S  E  T    S  W  I  T  C  H
  //================================================     reset switch
  pinState = digitalRead(reset);

  //did this switch change state ?
  if (lastReset != pinState)
  {
    //update to the new state
    lastReset = pinState;

    //did this switch get pushed ?
    if ( pinState == PUSHED)
    {
      //the time this switch was closed / pressed
      resetMillis = millis();

      //Serial.println("reset switch was closed");

      //reset the encoder count to zero
      wheelEnc.write(0);
    }

    //was this switch released ?
    else if ( pinState == RELEASED)
    {
      //Serial.print("The reset switch was closed for ");
      //Serial.print(millis() - resetMillis);
      //Serial.println("ms.\n");

      //was this a short press
      if (millis() - resetMillis <= shortPress)
      {
        //do something
      }

      //was this a long press
      else if (millis() - resetMillis >= longPress)
      {
        //zero all Measurement objects stored in EEPROM
        clearEEPROM();
      }
    }

  } //END of this switch

  //  B  A  C  K  L  I  G  H  T    S  W  I  T  C  H
  //================================================     backlight switch
  pinState = digitalRead(backlight);

  //did this switch change state ?
  if (lastBacklight != pinState)
  {
    //update to the new state
    lastBacklight = pinState;

    //did this switch get pushed ?
    if ( pinState == PUSHED)
    {
      //the time this switch was closed / pressed
      backlightMillis = millis();

      //Serial.println("backlight switch was closed");

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

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

    //was this switch released ?
    else if ( pinState == RELEASED)
    {
      //Serial.print("The backlight switch was closed for ");
      //Serial.print(millis() - backlightMillis);
      //Serial.println("ms.\n");

      //was this a short press
      if (millis() - backlightMillis <= shortPress)
      {
        //do something
      }

      //was this a long press
      else if (millis() - backlightMillis >= longPress)
      {
        //do something
      }
    }

  } //END of this switch

} //END of   checkSwitches()


//                                        t r u n c a t e ( )
//================================================^================================================
//
float truncate(float num, int dec)
{
  int factor = pow(10, dec);
  return int(num * factor) / float(factor);

}  //END of    truncate()


//                               d i s p l a y M e a s u r e m e n t ( )
//================================================^================================================
//
void displayMeasurement(int index)
{ 
  int address = startAddress + index * sizeof(Measurement);

  //create a temporary Measurement object
  Measurement m;

  //retrieve the Measurement object from EEPROM
  EEPROM.get(address, m);

  /*lcd.setCursor(0, 0);
  //                   1111111111
  //         01234567890123456789
  lcd.print("                    ");            //Commeneted out because it causes flickering*/

  /*lcd.setCursor(0, 0);
  //                   1111111111
  //         01234567890123456789
  lcd.print("Saved Measurement#  ");              // Copied to MENU if statement in loop above to display when toggle is switched.*/
                                                 

  lcd.setCursor(18, 0);
  //                   1111111111
  //         01234567890123456789
  //         Saved Measurement#
  //                           12
  lcd.print(index + 1);                           //Because 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
  lcd.print(static_cast<int>(m.inch));                     //display saved inches

  if (m.feet < 999.9)
  {
     lcd.setCursor(7, 3);
    //                   1111111111
    //         01234567890123456789
    //         IN      FT    MI
    //                0.0
    lcd.print(m.feet, 1);                                  //display saved feet to one decimal
  }  

  if (m.feet > 999.9)                                      //If footage displayed is greater than 5 characters, move 2 places to the left.
  {
     lcd.setCursor(5, 3);
    //                   1111111111
    //         01234567890123456789
    //         IN      FT    MI
    //              0000.0
    lcd.print(m.feet, 1);                                  //display saved feet to one decimal
  }  

 
  lcd.setCursor(15, 3);
  //                   1111111111
  //         01234567890123456789
  //         IN      FT    MI
  //                       0.000
  lcd.print(m.miles, 3);                 //display saved mileage

}  //END of   displayMeasurement()


//                                    c l e a r E E P R O M ( )
//================================================^================================================
//
void clearEEPROM()
{
  //initialize EEPROM menu index
  menuIndex = -1;

  //initialize currentMeasurement EEPROM address
  currentMeasurement = 0;

  //our first EEPROM address
  byte address = 0;

  //create an object with a number we want to save to EEPROM
  Measurement m = {0.0, 0.0, 0.0};

  //clearing all EEPROM objects
  for (int x = 0; x < 12; x++)
  {
    //Update EEPROM
    EEPROM.put(address, m);

    address = address + 12;
  }

  //Serial.println("EEPROM has been cleared.\n");

} //end of   clearEEPROM();




//================================================^================================================


Just off the top of my head, so standard disclaimers about a grain of salt if not a whole salt lick apply, the actual encoder reading is happening in an interrupt, so when you switch back to measuring, you'll get the new value of the encoder at that time. The fact that you haven't been reading it while it changed is irrelevant. You'd have to turn off the interrupts or find someway to reset the read count.

1 Like

I was thinking the same thing, but I am not sure how to counteract it.

I am using the Encoder.h library. I was trying to find a method like encoder.stop or encoder.pause etc to stop the counting in one mode while in the other mode. I have read through the entire gitHub about it but Encoder.h seems to be lacking on anything like this. I can't find anything on google either. I looked at some other libraries, but to no avail.

I did think about storing the last saved measurement and then somehow recalling that measurement when I go back to measuring mode. I am not sure but that seems unnecessarily complicated. PLUS the actual measurement counted by the pulses would not be the same as the measurement. This might cause problems somewhere deep down inside the program. I do not know for sure. But I do know I want all the data to line up unless purposely told to do something different. I do not want to use encoder.write(0) because I want the measurement to pick up where it left off. I might consider encoder.write(lastSavedPulse) or somehting like that. I don't know if that is possible. And it seems like it ought to be easier than saving variables.

This is my first time using encoders (and two in the same project) and is probably the 15th sketch of this project. Every time I fix one thing it causes a problem elsewhere. This is my last issue to figure out.

Here is your code with added special debug printing.

dbgc() prints only once per change in the variable
This means only in case the value of the specified variable has changed a single line that documents this change is printed

So this will make visible in the serial monitor how variables are changing their values

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);

#define dbgi(myFixedText, variableName,timeInterval) \
  { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  }

#define dbgc(myFixedText, variableName) \
  { \
    static long lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }

#define dbgcf(myFixedText, variableName) \
  { \
    static float lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *


//================================================^================================================
//  https://forum.arduino.cc/t/program-is-displaying-wrong-screen-after-startup/1339410/32
//
//  Version    MM/DD/YY    Comments
//  =======    ========    ========================================================================
//  1.00       01-12-25    Running code
//  1.10       01-12-25    Added save switch code
//  1.11       01-15-25    Added notes, added custom startup display characters, commented out heartbeat,
//                         and disabled onboard LED on pin 13.
//  1.12       01-17-25    Added "static_cast<int>" to displayMeasurement m.inch to display integer instead of decimal
//                         Move footageDisplayed AND m.feet 2 places to the left if greater then 999.99
//                         Change longpress from 2 seconds to 4 seconds

//Notes:
//Measuring Wheel Code measure inch, feet, mile.
//Depending on hardware, 2 variables need changed.
//They are DIAMETER and PULSES PER REV

#include <Wire.h>
#include <EEPROM.h>
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>

//HILETGO 2004 LCD
LiquidCrystal_I2C lcd(0x27, 20, 4);

//On Arduino UNO, pins 2 and 3 are interrupt pins
Encoder wheelEnc(2, 4);                                //TAISS  E38S6-600-24G from Amazon
Encoder menuEnc(3, 5);                                 //KY040 style

//================================================^================================================
/* U S I N G    C O M M O N    W O R D S    I N S T E A D    O F    L O G I C    W O R D S
  makes the code further down easier to read*/

#define PUSHED                    LOW
#define RELEASED                  HIGH

#define ON                        LOW
#define OFF                       HIGH

#define ENABLED                   true
#define DISABLED                  false

//================================================^================================================
// C U S T O M    C F G    S T A R T U P    C H A R A C T E R S
byte topOfCLeft[] = { B01111, B11111, B11110, B11100, B11100, B11100, B11100, B11100 };
byte topOfCRight[] = { B11110, B11111, B00111, B00011, B00000, B00000, B00000, B00000 };
byte bottomOfCLeft[] = { B11100, B11100, B11100, B11100, B11100, B11110, B11111, B01111 };
byte bottomOfCRight[] = { B00000, B00000, B00000, B00000, B00011, B00111, B11111, B11110 };
byte leftDashes[] = { B00000, B11111, B11111, B00000, B00000, B11111, B00000, B11111 };
byte rightDashes[] = { B00000, B11111, B11111, B00000, B00000, B11111, B00000, B11111 };

//================== E N D   O F   C H A R A C T E R S ==============================================

const int decimals              = 3;  //3 decimal places for mileage

int menuIndex                   = -1;
int currentMeasurement          = 0;
//For this particular menu encoder (KY040 style), divide pulse counts by 4 to equal the detent poisitons
int detentDivisor               = 4;

//Menu and EEPROM
int lastMenuPosition            = 0;
int newMenuPosition;

//Checks for preventing redundant overwriting of EEPROM.
float lastInches                = 0.0;
float lastFootage               = 0.0;
float lastMileage               = 0.0;

//Measuring
volatile long wheelPulseCount;
//menu
volatile long menuPulseCount;

long wheelCountStart            = 0;

static float lastTotalInches    = 0.0;

const float inchesPerMile       = 63360.0;
const float inchesPerFoot       = 12.0;
const float feetPerMile         = 5280.0;

float totalInches               = 0.0;
float inchesDisplayed           = 0.0;
//
float totalFootage              = 0.0;
float footageDisplayed          = 0.0;
//
float totalMileage              = 0.0;
float mileageDisplayed          = 0.0;

//Input pins
const byte save                 = 9;
const byte reset                = 10;
const byte backlight            = 11;
int selector                    = 12;             //Toggle between MENU and MEASURE


//Output pins
const byte heartbeatLED         = 13;

//for switch change in state detection
byte lastSave                   = RELEASED;
byte lastReset                  = RELEASED;
byte lastBacklight              = RELEASED;

//TIMER stuff
unsigned long heartbeatTime;
unsigned long switchesTime;

//to determine if we have a long/short switch press
unsigned long saveMillis        = 0;
unsigned long resetMillis       = 0;
unsigned long backlightMillis   = 0;
unsigned long shortPress        = 1000;
unsigned long longPress         = 4000;

bool saveFlag                   = DISABLED;  //When ENABLED, we are allowed to save to EEPROM.
//ENABLED and DISABLED are defined above in #Define.
//Used in measuring and menu functions below.
bool backlightOn                = false;

//Wheel size variables
const float pi                  = M_PI;
const float diameter            = 20.0;      //Change as needed for whichever wheel size

float inchesPerPulse;
float wheelInches;

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

struct Measurement { //measurement data structure
  float inch;
  float feet;
  float miles;
};

const int numMeasurements       = 12;
const int startAddress          = 0;


void setup() { // ===================================== s e t u p ( )=========================================
  Serial.begin(9600);
  //Serial.println("System starting up.");

  lcd.init();   //lcd.begin(20, 4, 0x27);

  //Backlight can be set to noBacklight and backlightOn = false to
  //turn off backlight at startup
  lcd.backlight();
  backlightOn = true;

  lcd.clear();

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

  // SETUP CUSTOM CFG CHARACTER
  lcd.createChar(0, topOfCLeft);
  lcd.createChar(1, topOfCRight);
  lcd.createChar(2, bottomOfCLeft);
  lcd.createChar(3, bottomOfCRight);
  lcd.createChar(4, leftDashes);
  lcd.createChar(5, rightDashes);

  //PRINT TOP OF CUSTOM CHARACTERS
  lcd.setCursor(8, 1);
  lcd.write(byte(0));
  lcd.setCursor(9, 1);
  lcd.write(byte(1));

  //PRINT F & G
  lcd.setCursor(10, 1);
  lcd.print("F");
  lcd.setCursor(11, 1);
  lcd.print("G");

  //PRINT BOTTOM OF CUSTOM CHARACTERS
  lcd.setCursor(8, 2);
  lcd.write(byte(2));
  lcd.setCursor(9, 2);
  lcd.write(byte(3));
  lcd.setCursor(10, 2);
  lcd.write(byte(4));
  lcd.setCursor(11, 2);
  lcd.write(byte(5));

  delay(3000);

  lcd.clear();

  /*lcd.setCursor(0, 0);
    //                   1111111111
    //         01234567890123456789
    lcd.print("  READY TO MEASURE  ");*/                 //Not needed. Display is updated via the if statements with MENU and MEASURING

  //used to show the sketch is running normally
  pinMode(heartbeatLED, OUTPUT);
  digitalWrite(heartbeatLED, LOW);

  //switches
  pinMode(save, INPUT_PULLUP);
  pinMode(reset, INPUT_PULLUP);
  pinMode(backlight, INPUT_PULLUP);
  pinMode(selector, INPUT_PULLUP);                       //Toggle switch

  //========================
  //Calculate the circumference of wheel.
  wheelInches = diameter * pi;
  //Used to calculate inches covered per pulse.
  inchesPerPulse = wheelInches / pulsesPerRev;
}  //End of setup()


void loop() { //========================================= l o o p ( )==========================================
  dbgc("ToL", digitalRead(selector) );

  dbgc("0A", wheelPulseCount);
  dbgc("0B", menuPulseCount);
  
  //================================================           TIMER heartbeat
  //is it time to toggle the heartbeat LED ?
  /*if (millis() - heartbeatTime >= 500)
    {
    //restart this TIMER
    heartbeatTime = millis();

    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
    }*/

  //is it time to check our switches ?
  if (millis() - switchesTime >= 50)  {
    switchesTime = millis();     //restart this TIMER
    checkSwitches();     //yes it is time to check the switches
  }

  // Read the toggle switch state
  int toggleState = digitalRead(selector);
  dbgc("00", toggleState );

  //service menu encoder
  if (toggleState == ON) {// Toggle switch is ON
    // Display the menu
    lcd.setCursor(0, 0);
    //                   1111111111
    //         01234567890123456789
    lcd.print("Saved Measurement#");                  //Overwrites "Ready To Measure".
    menuEncoder();
  }

  //service the wheel encoder

  if (toggleState == OFF) {// Toggle switch is OFF
    // Display "READY TO MEASURE"
    lcd.setCursor(0, 0);
    //         01234567890123456789
    lcd.print("  READY TO MEASURE ");                  //Overwrites "Saved Measurement#".
    wheelEncoder();
  }

  //Other non-blocking code goes here
}  //END of   loop()


//==================================== w h e e l E n c o d e r ( )====================================
//servicing the wheel encoder
void wheelEncoder() {
  wheelPulseCount = wheelEnc.read();  //Read the pulses from wheel encoder

  //did the wheel turn ?
  if (wheelPulseCount != wheelCountStart)  {
    wheelCountStart = wheelPulseCount;     //update to the new count
    saveFlag = ENABLED;     //enable saving to EEPROM
    menuIndex = -1;     //re-initialize EEPROM menu index

    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.
    //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.

    //Reset footage so display does not get filled up to 8 (including decimal and neagtive sign) spaces.
    if (totalFootage > 99999 || totalFootage < -99999) {
      wheelEnc.write(0);
    }

    if (lastTotalInches != totalInches) {     //This if statement prevents constant writing to display
      //every time the loop loops.
      lastTotalInches = totalInches;         // Resets (UPDATES) if statement
    }

    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);

    //Keep display where it is for under 1,000 feet.
    if (footageDisplayed < 999.9)    {
      lcd.setCursor(7, 3);
      //                   1111111111
      //         01234567890123456789
      //         IN      FT       MI
      //                 0.0
      lcd.print(footageDisplayed, 1);
    }

    //Move footage display if feet is 1000 feet or greater
    //Makes more room between footage and mileage on display
    if (footageDisplayed > 999.9) {
      lcd.setCursor(5, 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);
  }
} //END of   wheelEncoder()


//===================================== m e n u E n c o d e r ( )======================================
void menuEncoder() {
  menuPulseCount = menuEnc.read();   //Read the pulses from wheel encoder

  // MENU ================================================
  // Detent Divisor ensures menu changes at "clicks".
  int newPosition = menuPulseCount / detentDivisor ;

  //did the menu encoder turn ?
  if (newPosition != lastMenuPosition) {
    //disable saving to EEPROM
    saveFlag = DISABLED;

    //did the menu encoder turn CW ?
    if (newPosition > lastMenuPosition) {
      //update to the new value
      lastMenuPosition = newPosition;

      //next EEPROM index
      menuIndex = menuIndex + 1;

      //are we above the maximum position ?
      if (menuIndex >= 12) {
        menuIndex = 0;
      }
    }

    //did the menu encoder turn CCW ?
    else if (newPosition < lastMenuPosition) {
      //update to the new value
      lastMenuPosition = newPosition;
      menuIndex = menuIndex - 1;       //next EEPROM index

      //are we below the minimum position ?
      if (menuIndex < 0) {
        menuIndex = 11;
      }
    }
    //update LCD with the saved reading retrieved from EEPROM
    displayMeasurement(menuIndex);
  }
} //END of   menuEncoder()


//=================================== c h e c k S w i t c h e s ( )===================================
void checkSwitches() {
  byte pinState;

  //  S  A  V  E   S  W  I  T  C  H
  //================================================     save switch
  pinState = digitalRead(save);
  dbgc("01", digitalRead(save));
  //did this switch change state ?
  if (lastSave != pinState) {
    //update to the new state
    lastSave = pinState;

    //did this switch get pushed ?
    if ( pinState == PUSHED && currentMeasurement < numMeasurements) {
      //Added "&& currentMeasurement < numMeasurements" to stop saving at 12 (numMeasurements) saves

      //the time this switch was closed / pressed
      saveMillis = millis();

      //Serial.println("save switch was closed");
      //Make object "m" for Struct Measurement
      Measurement m = { inchesDisplayed, footageDisplayed, mileageDisplayed };
      //Define address position
      int address = startAddress + currentMeasurement * sizeof(Measurement);
      //PUT data to EEPROM. Data is to large to WRITE to EEPROM
      EEPROM.put(address, m);
      //Increment # of measurements so address will increment in above formula
      currentMeasurement++;
    }

    //was this switch released ?
    else if ( pinState == RELEASED) {
      //Serial.print("The save switch was closed for ");
      //Serial.print(millis() - saveMillis);
      //Serial.println("ms.\n");

      //was this a short press
      if (millis() - saveMillis <= shortPress) {

      }

      //was this a long press
      else if (millis() - saveMillis >= longPress) {
        //do something
      }
    }
  } //END of this switch

  //  R  E  S  E  T    S  W  I  T  C  H
  //================================================     reset switch
  pinState = digitalRead(reset);
  dbgc("02", digitalRead(reset));
  //did this switch change state ?
  if (lastReset != pinState) {
    //update to the new state
    lastReset = pinState;

    //did this switch get pushed ?
    if ( pinState == PUSHED) {
      //the time this switch was closed / pressed
      resetMillis = millis();

      //Serial.println("reset switch was closed");

      //reset the encoder count to zero
      wheelEnc.write(0);
    }

    //was this switch released ?
    else if ( pinState == RELEASED) {
      //Serial.print("The reset switch was closed for ");
      //Serial.print(millis() - resetMillis);
      //Serial.println("ms.\n");

      //was this a short press
      if (millis() - resetMillis <= shortPress) {
        //do something
      }

      //was this a long press
      else if (millis() - resetMillis >= longPress) {
        //zero all Measurement objects stored in EEPROM
        clearEEPROM();
      }
    }
  } //END of this switch

  //  B  A  C  K  L  I  G  H  T    S  W  I  T  C  H
  //================================================     backlight switch
  pinState = digitalRead(backlight);
  dbgc("03", digitalRead(backlight) );
  //did this switch change state ?
  if (lastBacklight != pinState) {
    //update to the new state
    lastBacklight = pinState;

    //did this switch get pushed ?
    if ( pinState == PUSHED) {
      //the time this switch was closed / pressed
      backlightMillis = millis();

      //Serial.println("backlight switch was closed");

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

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

    //was this switch released ?
    else if ( pinState == RELEASED) {
      //Serial.print("The backlight switch was closed for ");
      //Serial.print(millis() - backlightMillis);
      //Serial.println("ms.\n");

      //was this a short press
      if (millis() - backlightMillis <= shortPress) {
        //do something
      }

      //was this a long press
      else if (millis() - backlightMillis >= longPress) {
        //do something
      }
    }
  } //END of this switch
} //END of   checkSwitches()


//====================================== t r u n c a t e ( )========================================
float truncate(float num, int dec) {
  int factor = pow(10, dec);
  return int(num * factor) / float(factor);
}  //END of    truncate()


//===============================d i s p l a y M e a s u r e m e n t ( )====================================
void displayMeasurement(int index) {
  int address = startAddress + index * sizeof(Measurement);

  Measurement m; //create a temporary Measurement object

  EEPROM.get(address, m);   //retrieve the Measurement object from EEPROM

  /*lcd.setCursor(0, 0);
    //                   1111111111
    //         01234567890123456789
    lcd.print("                    ");            //Commeneted out because it causes flickering*/

  /*lcd.setCursor(0, 0);
    //                   1111111111
    //         01234567890123456789
    lcd.print("Saved Measurement#  ");              // Copied to MENU if statement in loop above to display when toggle is switched.*/

  lcd.setCursor(18, 0);
  //                   1111111111
  //         01234567890123456789
  //         Saved Measurement#
  //                           12
  lcd.print(index + 1);                           //Because 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
  lcd.print(static_cast<int>(m.inch));                     //display saved inches

  if (m.feet < 999.9) {
    lcd.setCursor(7, 3);
    //                   1111111111
    //         01234567890123456789
    //         IN      FT    MI
    //                0.0
    lcd.print(m.feet, 1);                                  //display saved feet to one decimal
  }

  if (m.feet > 999.9) {                                      //If footage displayed is greater than 5 characters, move 2 places to the left.
    lcd.setCursor(5, 3);
    //                   1111111111
    //         01234567890123456789
    //         IN      FT    MI
    //              0000.0
    lcd.print(m.feet, 1);                                  //display saved feet to one decimal
  }

  lcd.setCursor(15, 3);
  //                   1111111111
  //         01234567890123456789
  //         IN      FT    MI
  //                       0.000
  lcd.print(m.miles, 3);                 //display saved mileage
}  //END of   displayMeasurement()


//==================================== c l e a r E E P R O M ( )======================================
void clearEEPROM() {
  menuIndex = -1;   //initialize EEPROM menu index
  currentMeasurement = 0;   //initialize currentMeasurement EEPROM address
  byte address = 0;   //our first EEPROM address
  Measurement m = {0.0, 0.0, 0.0};   //create an object with a number we want to save to EEPROM

  for (int x = 0; x < 12; x++) {  //clearing all EEPROM objects
    EEPROM.put(address, m);     //Update EEPROM
    address = address + 12;
  }
  //Serial.println("EEPROM has been cleared.\n");
} //end of   clearEEPROM();

What makes you conclude this?

There is only a encoder-object

Encoder wheelEnc(2, 4);                                //TAISS  E38S6-600-24G from Amazon
Encoder menuEnc(3, 5);                                 //KY040 style

but no attachInterrupt in the code

I looked in the Encoder library.

1 Like

It happens in the encoder library.

And that is what's happening, I will agree w/ @van_der_decken.

I was looking for the ISR to see about having it ignore counting somehow. And found no attaching or ISR.

You could store the value when you enter menu mode, and reset the accumulation to that value when you exit it.

a7

1 Like

This is the way.

You do change detection on toggleState, and save or update as warranted.

Maybe something like this completely untested snippet:

static lastToggleState = toggleState;
static lastMeasurement;
...
if(toggleState != lastToggleState){
   if(toggleState == ON) { // menu mode
     lastMeasurement = wheelEnc.read();
   } else {
     wheelEnc.write(lastMeasurement);
   }
}

StefanL38--I ran your code and it worked great for debugging. I also read through the tutorial you posted. But to be honest it is beyond my current level of understanding. It goes beyond my very basic understanding of #define. I will keep studying it and maybe something will click.

daveX--That works. It is not a difficult solution and one I was going to implement. But I was hoping it was some other problem that I just wasn't seeing. More like something to delete out of the code, not add to it. I did not want to muddy the waters any more than they already are! But it works. Thank You.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.