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();
//================================================^================================================