Wheel Stop Timer

Hello,

I am trying to use a rotary encoder to measure the rotation of a wheel to determine if it is rotating or not. I also want to keep track of the time it takes for the wheel to stop rotating, in my case "pulses" switches from increasing to decreasing or vice a versa.

Interrupts are being used to detect when both the A and B signals are changing which triggers the "pulses" to increment. The code that I have works well for slow speed rotations, unsure of the RPM because I am just spinning the encoder wheel by hand, but as I spin the wheel faster the timer begins to lose time. The time and timer portion works on it's own as a stopwatch as well as the encoder portion works on it's own. It is when I try to add them together that I am running into issues. Any help with this would be greatly appreciated.

The encoder I am using is a Koyo TRD-N60-RZWD 60 pulse/rev quadrature encoder. The board is an Arduino MEGA 2560 R3, and the LCD is an Adafruit 16x2 RGB LCD.

#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();



#define WHITE 0x7
int rotation = 1;
int count = 0;
float dia = 6;
unsigned long start, finish, elapsed;
bool stopFlag = false;
volatile long pulses, A_SIG=0, B_SIG=1;
int power = 53;
int led1 = 31;
int led2 = 33;
int val = 0;
int prev;
int A = 4;
int B = 5;


void setup(){
  Serial.begin(115200); //sets comm rate
  lcd.begin(16,2);
  lcd.print("Welcome");
  delay(1500);
  lcd.clear();
  lcd.print("Select");
  delay(1000);
  lcd.clear();
  lcd.print("Rotation");
  delay(1000);
  lcd.clear();
  lcd.print("Direction");
  lcd.setCursor(0,1);
  //lcd.print(rotation);
  lcd.setBacklight(WHITE);
  pinMode(power, OUTPUT);
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  attachInterrupt(A, A_RISE, RISING);
  attachInterrupt(B, B_RISE, RISING);
      
}//setup

uint8_t i=0;

void loop(){
  digitalWrite(power, HIGH);
  Serial.println(count);
  while(count < 1){
  uint8_t buttons = lcd.readButtons();
    if(buttons & BUTTON_SELECT){
      ++ count;
      delay(1000);
      //Serial.println(count);
    }
    if(buttons & BUTTON_UP){
      delay(1000);
      ++ rotation;
      if ((rotation % 2) == 0){
        lcd.setCursor(0,1);
        lcd.print("                          ");
        delay(10);
        lcd.setCursor(0,1);
        lcd.print("Clockwise");
        //Serial.print(rotation);
      }
      else{
      lcd.setCursor(0,1);
      lcd.print("CounterClockwise");
      //Serial.print(rotation);
      }
    }
  }
  while(count == 1){
    lcd.clear();
    lcd.print("Pulley Diameter");
    lcd.setCursor(0,1);
    lcd.print(dia/1.00,2);
    lcd.print(" inches");
    //delay(3000);
    count = 2;
  }
  while(count < 3){
    uint8_t buttons = lcd.readButtons();
      if (buttons & BUTTON_SELECT){
        ++ count;
        delay(1000);
        Serial.println(count);
      }
      if(buttons & BUTTON_UP){
          delay(1000);
          dia = dia + 0.25;
          lcd.setCursor(0,1);
          lcd.print(dia/1.00,2);
      }
      else if(buttons & BUTTON_DOWN){
          delay(1000);
          dia = dia - 0.25;
          lcd.setCursor(0,1);
          lcd.print(dia/1.00,2);
      }
  }
  while(count == 3){
    lcd.clear();
    lcd.print("Up To Start");
    count = 4;
    //Serial.print(rotation);
  }
  while(count > 3){
    uint8_t buttons = lcd.readButtons();
    int time = millis();
    prev = val;
    val = pulses;
      if(buttons & BUTTON_UP){
        start = millis();
        lcd.clear();
        lcd.print("Elapsed Time");
        stopFlag = false;
      }
      if(stopFlag == false){
        if(start > 0){
          elapsed = millis() - start;
          lcd.setCursor(0,1);
          lcd.print(elapsed/1000.00,4);
          }
      }
      if((rotation % 2) == 0){
        if (val < prev){
          finish = millis();
          elapsed = finish - start;
          stopFlag = true;
          Serial.print(elapsed/1000.4);
        }
      }
      else{
        if(val > prev){
          finish = millis();
          elapsed = finish - start;
          stopFlag = true;
        }
      }
  }
} 


void A_RISE(){
 detachInterrupt(A);
 A_SIG=1;
 
 if(B_SIG==0)
 pulses++;//moving forward
 if(B_SIG==1)
 pulses--;//moving reverse
 Serial.println(pulses);
 attachInterrupt(A, A_FALL, FALLING);
}

void A_FALL(){
  detachInterrupt(A);
 A_SIG=0;
 
 if(B_SIG==1)
 pulses++;//moving forward
 if(B_SIG==0)
 pulses--;//moving reverse
 Serial.println(pulses);
 attachInterrupt(A, A_RISE, RISING);  
}

void B_RISE(){
 detachInterrupt(B);
 B_SIG=1;
 
 if(A_SIG==1)
 pulses++;//moving forward
 if(A_SIG==0)
 pulses--;//moving reverse
 Serial.println(pulses);
 attachInterrupt(B, B_FALL, FALLING);
}

void B_FALL(){
 detachInterrupt(B);
 B_SIG=0;
 
 if(A_SIG==0)
 pulses++;//moving forward
 if(A_SIG==1)
 pulses--;//moving reverse
 Serial.println(pulses);
 attachInterrupt(B, B_RISE, RISING);
}

Serial.print() in an Interrupt Service Routine is certainly going to have an adverse effect. I don't know about detaching and attaching the interrupt in the ISR.

I removed the Serial.print() that was occurring during the interrupts and that definitely helped with the issue. now when spinning the encoder by hand as fast as possible the timer does not seem to be losing time. I now have to get the encoder connected back up to the wheel it will be measuring and report back if that solved the problem.

For detaching and attaching interrupts within the ISR that is something that I got from this tutorial as I was struggling to read the encoder in the first place. If you know of a better way to read the encoder I will gladly look into it.

Thank you

http://www.kevindarrah.com/download/arduino_code/Encoder.pde

Detaching and re-attaching the interrupt as you are doing is likely to cause you to miss pulses. In any case interrupts are disabled during an ISR and the important thing is make the ISR complete as quickly as possible - i.e. before the next pulse is due.

Also keep in mind that the Arduino uses other interrupts for other things (such as micros() and millis() ) and if your ISR takes too long they will be affected.

The only reason I can think of for detaching an interrupt within an ISR is if you want to stop an interrupt after it triggers for the first time or if you want to switch to a different interrupt.

...R

For detaching and attaching interrupts within the ISR that is something that I got from this tutorial as I was struggling to read the encoder in the first place. If you know of a better way to read the encoder I will gladly look into it.

Your interrupts need to be set for CHANGE, and then read the state of the pin to determine if it went HIGH or LOW. Do not shuffle between RISING and FALLING triggers.

cattledog:
Your interrupts need to be set for CHANGE, and then read the state of the pin to determine if it went HIGH or LOW. Do not shuffle between RISING and FALLING triggers.

I tried to switch my code to read when each signal was switching like cattledog suggested. What I am getting now is my pulses will oscillate between two numbers when rotating the wheel in a constant direction. If I rotate it back and forth between I can get the pulses to increment or decrement depending on which direction I started rotating the wheel first. Also attached is a snip of my serial monitor.

Thank you

volatile long pulses, A_SIG=0, B_SIG=1;
int power = 53;
int A = 4;
int B = 5;


void setup() {
  Serial.begin(115200);
  pinMode(power, OUTPUT);
  attachInterrupt(A, A_CHANGE, CHANGE);
  attachInterrupt(B, B_CHANGE, CHANGE);
}

void loop() {
  digitalWrite(power, HIGH);
}

void A_CHANGE(){
  detachInterrupt(A);
  if (A_SIG==1){
    if(B_SIG==0){
      ++pulses;
    }
    if(B_SIG==1){
     --pulses;
    }
  }
  if(A_SIG==0){
    if(B_SIG==1){
      ++pulses;
    }
    if(B_SIG==0){
      --pulses;
    }
  }
  //Serial.println(pulses);
  attachInterrupt(A,A_CHANGE, CHANGE);
}

void B_CHANGE(){
  detachInterrupt(B);
  if(B_SIG==1){
    if(A_SIG==1){
      ++pulses;
    }
    if(A_SIG==0){
      --pulses;
    }
  }
  if(B_SIG==0){
    if(A_SIG==0){
      ++pulses;
    }
    if(A_SIG==1){
      --pulses;
    }
  }
  //Serial.println(pulses);
  attachInterrupt(B,B_CHANGE, CHANGE);
}

Don't detach the interrupt! It can't interrupt itself since interrupts are temporarily disabled while the interrupt handler runs.

You also don't need to have so many interrupts. The simplest way to do it is to interrupt only on A rising. Then look at if B is currently high or low at that instant to determine direction. But you don't even care about direction do you?

Any particular reason why you don't want to use a library? Paul Stoffregen's encoder library is very good. Other libraries are probably equally good.

You need the digitalRead() of the A and B pins as the first actions when you enter the interrupt.

A_SIG = digitalRead(A); 
B_SIG = digitalRead(B)

;

As MorganS says, do not detach and reattach the interrupt.

First off, I appreciate your responses.

I am needing to monitor the direction of rotation in order to determine if the wheel ever bounces back the other direction just before coming to a stop. This is common in my application because the wheel is driven via a belt drive directly connected to a combustion engine causing it to briefly "bounce" back the opposite direction before coming to a complete stop.

I took the advice of only reading on the rising edge of signal A, but am still struggling to get the "pulses" to increment or decrement properly. In order to try to trouble shoot this I took out everything that would be handling the incrementing, and only put in an interrupt that would trigger on a RISING A_SIG and print the states of my A and B signals? When I run this I only show that both A and B signals are HIGH. Checking with my Flukemeter shows A HIGH while B is LOW when rotating clockwise and A HIGH while B is HIGH when rotating counterclockwise which is what I would expect. This is my first Arduino project so I am sure I am missing something easy. I will also look further into using the library previously mentioned.

Thank you again!

volatile long pulses, prev;
int power = 53; //power provided to encode by pin 53
int A = 4;  //interrupt 4
int B = 5;  //interrupt 5
int A_SIG;
int B_SIG;


void setup() {
  Serial.begin(115200);
  pinMode(power, OUTPUT);
  pinMode(A_SIG, INPUT);
  pinMode(B_SIG, INPUT);
  attachInterrupt(A, A_RISE, RISING); //attaches interrupt to rising edge of A
}

void loop() {
  digitalWrite(power, HIGH);
}

void A_RISE(){
  A_SIG = digitalRead(A_SIG);
  B_SIG = digitalRead(B_SIG);
  Serial.println(A_SIG);
  Serial.println(B_SIG);
    
}
A_SIG = digitalRead(A_SIG);
  B_SIG = digitalRead(B_SIG);

Are you are using a MEGA with interrupt 4 and 5?

If so, I think that interrupt 4 is on digital pin 19 and interrupt 5 on digital pin 18. Are those the pins attached to encoder A and B terminals?

You need the digitalRead() of the pins which are triggering the A and B interrupts and are connected to the encoder terminals.

see https://www.arduino.cc/en/Reference/AttachInterrupt

Hi,

Have you checked to see if your encoder has open collector outputs.

If so have you got pull up resistors installed. I cannot see PULL_UPs command in your setup to turn internal pullups ON.

Tom.... :slight_smile:

Using the suggestions from TomGeorge and cattledog I was able to get the encoder to read properly. The encoder I am using uses a totem-pole output. By moving some of the code out of the ISR I was able to interrupt on both A_SIG and B_SIG changes without the timer loosing time. This allows me to record 240 pulses per rev which was desired to detect exactly when the wheel comes to a stop.

Now that the rest of the code is working properly I am trying to also display the RPM. When running the code to only display RPM it works as it should and is accurate. When I tried to add it to my main code it will only display RPM smaller than around 5 no matter how fast or slow the wheel is rotating. Is this because I am trying to used floating point math? My reason for doing this was the rounding was giving me a zero value for delT causing my RPM to read zero except at extremely slow rotational speed. Is there a better way to handle this math opearation?

RPM only code

#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

#define WHITE 0x7 // determines what color WHITE is

unsigned long start, finish, elapsed;
bool stopFlag = false;
volatile long pulses;
int power = 53;
int A = 4;
int B = 5;
static int A_SIG=19;
static int B_SIG=18;
int led = 24;
long val=0;
long prev;


volatile byte revs;
float rpm;
unsigned int rotations;
float timeOld;
int ppr = 240; //Pulse Per Rev of Encoder

void setup() {
  Serial.begin(115200);
  lcd.begin(16,2);
  lcd.print("PRESS TO START");
  lcd.setBacklight(WHITE);
  pinMode(power, OUTPUT);
  pinMode(led, OUTPUT);
  pinMode(A_SIG, INPUT);
  pinMode(B_SIG, INPUT);
  attachInterrupt(digitalPinToInterrupt(19), A_CHANGE, CHANGE);
  attachInterrupt(digitalPinToInterrupt(18), B_CHANGE, CHANGE);
  revs = 0;
  rpm = 0;
  timeOld = 0;
  rotations = 0;
  // put your setup code here, to run once:

}
uint8_t i=0;
void loop() {
  digitalWrite(power, HIGH);
  //Serial.println(pulses);
  uint8_t buttons = lcd.readButtons();
  prev = val;
  val = pulses;
  //Serial.println(revs);
  int time = millis();
  if(buttons & BUTTON_UP){
    //pulses = 0;
    start = millis();
    lcd.clear();
    lcd.print("Elapsed Time");
    stopFlag = false;
  }


  if(revs >= 240){
    float rotations = 1;
    float delT = (millis()-timeOld);
    float rpm = (rotations/delT)*60000;
    Serial.println(rpm/1.00,2);
    lcd.setCursor(0,1);
    lcd.print(rpm/1.00,1);
    timeOld = millis();
    revs = 0;
    //lcd.setCursor(10,1);
    //lcd.print(rotations);
  }
    
}



void A_CHANGE(){
  int a = digitalRead(A_SIG);
  int b = digitalRead(B_SIG);
    if((a==1)&(b==0)){
      ++pulses;
      ++revs;
    }
    if((a==1)&(b==1)){
      --pulses;
      ++revs;  
    }
    if((a==0)&(b==1)){
      ++pulses;
      ++revs;
    }
    if((a==0)&(b==0)){
      --pulses;
      ++revs;
    }   
}

void B_CHANGE(){
  int a = digitalRead(A_SIG);
  int b = digitalRead(B_SIG);
    if((a==1)&(b==1)){
      ++pulses;
      ++revs;
    }
    if((a==0)&(b==0)){
      ++pulses;
      ++revs;
    }
    if((a==1)&(b==0)){
      --pulses;
      ++revs;
    }
    if((a==0)&(b==1)){
      --pulses;
      ++revs;
    }
}

Entire main code

#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();



#define WHITE 0x7
int rotation = 1;
int count = 0;
float dia = 6;
unsigned long start, finish, elapsed;
bool stopFlag = false;
volatile long pulses;
int power = 53;
long val = 0;
long prev;
int A = 4;
int B = 5;
static int A_SIG=19;
static int B_SIG=18;

volatile byte revs;
float rpm;
unsigned int rotations;
float timeOld;
int ppr = 240;


void setup(){
  Serial.begin(115200); //sets comm rate
  lcd.begin(16,2);
  lcd.print("Welcome");
  delay(1500);
  lcd.clear();
  lcd.print("Select");
  delay(1000);
  lcd.clear();
  lcd.print("Rotation");
  delay(1000);
  lcd.clear();
  lcd.print("Direction");
  lcd.setCursor(0,1);
  //lcd.print(rotation);
  lcd.setBacklight(WHITE);
  pinMode(power, OUTPUT);
  pinMode(A_SIG, INPUT);
  pinMode(B_SIG, INPUT);
  attachInterrupt(digitalPinToInterrupt(19), A_CHANGE, CHANGE);
  attachInterrupt(digitalPinToInterrupt(18), B_CHANGE, CHANGE);
  revs = 0;
  rpm = 0;
  timeOld = 0;
  rotations = 0;
      
}//setup

uint8_t i=0;

void loop(){
  digitalWrite(power, HIGH);
  Serial.println(count);
  while(count < 1){
  uint8_t buttons = lcd.readButtons();
    if(buttons & BUTTON_SELECT){
      ++ count;
      delay(1000);
      //Serial.println(count);
    }
    if(buttons & BUTTON_UP){
      delay(1000);
      ++ rotation;
      if ((rotation % 2) == 0){
        lcd.setCursor(0,1);
        lcd.print("                          ");
        delay(10);
        lcd.setCursor(0,1);
        lcd.print("Clockwise");
        //Serial.print(rotation);
      }
      else{
      lcd.setCursor(0,1);
      lcd.print("CounterClockwise");
      //Serial.print(rotation);
      }
    }
  }
  while(count == 1){
    lcd.clear();
    lcd.print("Pulley Diameter");
    lcd.setCursor(0,1);
    lcd.print(dia/1.00,2);
    lcd.print(" inches");
    //delay(3000);
    count = 2;
  }
  while(count < 3){
    uint8_t buttons = lcd.readButtons();
      if (buttons & BUTTON_SELECT){
        ++ count;
        delay(1000);
        Serial.println(count);
      }
      if(buttons & BUTTON_UP){
          delay(1000);
          dia = dia + 0.25;
          lcd.setCursor(0,1);
          lcd.print(dia/1.00,2);
      }
      else if(buttons & BUTTON_DOWN){
          delay(1000);
          dia = dia - 0.25;
          lcd.setCursor(0,1);
          lcd.print(dia/1.00,2);
      }
  }
  while(count == 3){
    lcd.clear();
    lcd.print("Up To Start");
    count = 4;
    //Serial.print(rotation);
  }
  while(count > 3){

    uint8_t buttons = lcd.readButtons();
    int time = millis();
    prev = val;
    val = pulses;
      if(buttons & BUTTON_UP){
        //pulses = 0;
        delay(100);
        start = millis();
        lcd.clear();
        lcd.print("Elapsed Time");
        stopFlag = false;
      }
      if(stopFlag == false){
        if(start > 0){
          elapsed = millis() - start;
          lcd.setCursor(0,1);
          lcd.print(elapsed/1000.00,2);
          //Serial.println(pulses);
          if (revs >=240){
            float rotations = 1;
            float delT = (millis()-timeOld);
            float rpm = (rotations/delT)*60000;
            lcd.setCursor(10,1);
            lcd.print(rpm/1.00,1);
            revs = 0;
          }
          }
      }
      if((rotation % 2) == 0){
        if (val < prev){
          finish = millis();
          elapsed = finish - start;
          stopFlag = true;
          val = 0;
          prev = 0;
          pulses = 0;
          //Serial.print(elapsed/1000.2);
        }
      }
      else{
        if(val > prev){
          finish = millis();
          elapsed = finish - start;
          stopFlag = true;
          val = 0;
          prev = 0;
          pulses = 0;
        }
      }
  }
} 


void A_CHANGE(){
  int a = digitalRead(A_SIG);
  int b = digitalRead(B_SIG);
    if((a==1)&(b==0)){
      ++pulses;
      ++revs;
    }
    if((a==1)&(b==1)){
      --pulses;
      ++revs;
    }
    if((a==0)&(b==1)){
      ++pulses;
      ++revs;
    }
    if((a==0)&(b==0)){
      --pulses;
      ++revs;
    }
}

void B_CHANGE(){
  int a = digitalRead(A_SIG);
  int b = digitalRead(B_SIG);
    if((a==1)&(b==1)){
      ++pulses;
      ++revs;
    }
    if((a==0)&(b==0)){
      ++pulses;
      ++revs;
    }
    if((a==1)&(b==0)){
      --pulses;
      ++revs;
    }
    if((a==0)&(b==1)){
      --pulses;
      ++revs;
    }
}
  if(revs >= 240){
    float rotations = 1;
    float delT = (millis()-timeOld);
    float rpm = (rotations/delT)*60000;
    Serial.println(rpm/1.00,2);
    lcd.setCursor(0,1);
    lcd.print(rpm/1.00,1);
    timeOld = millis();
    revs = 0;
    //lcd.setCursor(10,1);
    //lcd.print(rotations);
  }

So when the wheel has rotated by more than 240 'revs' (otherwise known as encoder steps) then you see how long that took? Then you set revs back to zero, even though it may have recorded more than 240 steps? And, it may even have recorded some steps in the time between you finding that it was >=240 and when you set it back to zero?

I can sort of see why you wanted to use a byte here, for the atomic read capability in the presence of interrupts, but that means you must detect this >=240 in the small window of time before it wraps around to zero at 255. Atomicity also means that you should do the reset-to-zero atomically as well.

Personally, I would use an int, because I could not be sure that the rest of my program would always complete inside that 15-count window. Then, after counting a complete rotation, I would SUBTRACT 240, instead of just setting it back to zero. That way I won't lose any steps that occurred above 240 before I got around to detecting it OR after I detected it.

volatile int revs; //silly name, but let's keep the original naming convention
const int RevsPerRotation = 240;  //encoder steps recorded by one complete rotation

void loop() {
  //...other processing...
  int localRevs;
  noInterrupts();
  localRevs = revs; //grab a 'safe copy'
  interrupts(); //allow interrupts to proceed while we are working on stuff

  if(localRevs >= RevsPerRotation) {
    float rotations = (float) localRevs / RevsPerRotation; //promote to float as we may be timing 1.01 rotations
    //..do the rest of the calcs here...

    //now re-set the volatile revs counter, to record that we have processed the counts so far
    //note that there may have been some counts recorded by the interrupt while we were processing
    //so we don't blindly set it to zero.
    noInterrupts();
    revs -= localrevs;
    interrupts();
  }

  //...optional more stuff here...
}

I agree with MorganS that "revs" is a particularly bad variable name for encoder counts of which there are 240 to a complete revolution. to keep us all sane, please change it :slight_smile:

RPM only code
if(revs >= 240){
float rotations = 1;
float delT = (millis()-timeOld);
float rpm = (rotations/delT)*60000;
Serial.println(rpm/1.00,2);
lcd.setCursor(0,1);
lcd.print(rpm/1.00,1);
timeOld = millis();
revs = 0;
//lcd.setCursor(10,1);
//lcd.print(rotations);
}

Key piece of rpm code from the full program

if (revs >=240){
            float rotations = 1;
            float delT = (millis()-timeOld);
            float rpm = (rotations/delT)*60000;
            lcd.setCursor(10,1);
            lcd.print(rpm/1.00,1);
            revs = 0;
          }

I do not see where you set timeOld = millis() to time the next 240 encoder counts.

cattledog:
I do not see where you set timeOld = millis() to time the next 240 encoder counts.

That was definitely missing and made the difference in the code functioning properly.

MorganS:
So when the wheel has rotated by more than 240 'revs' (otherwise known as encoder steps) then you see how long that took? Then you set revs back to zero, even though it may have recorded more than 240 steps? And, it may even have recorded some steps in the time between you finding that it was >=240 and when you set it back to zero?

Sorry for the revs issue, that was a leftover from when I was troubleshooting to see if I could get it to count single revolutions. The code now runs like I had intended and was able to test it up to 5000 rpm by attaching my encoder to the spindle of my mill and the timer and rpm were displaying properly.

Thank you everyone for your help with this. This is my first project using arduino and I am intrigued by the seemingly endless possibilities of the platform. I have posted my final updated code in case someone else has a similar problem or wants to optimize it more

Best Regards

Eric

#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();



#define WHITE 0x7                           //defines backlight color
int rotation = 1;
int count = 0;              
float dia = 6;                              //diameter of the encoder wheel
unsigned long start, finish, elapsed;       //variables used in tracking stop time
bool stopFlag = false;                      //triggers the timer loop
volatile long pulses;                       //variable for counting each pulse of the encoder
int power = 53;                             //pin supplying power to encoder
long val = 0;
long prev;
int A = 4;                                  //Interrupt pin that encoder A signal is on
int B = 5;                                  //Interrupt pin that encoder B signal is on
static int A_SIG=19;                        //used for digitalRead of encoder A signal
static int B_SIG=18;                        //used for digitalRead of encoder B signal

volatile int steps;                         //pulses of encoder used for rpm calculations
float rpm;                                  //revolutions per minute
unsigned int rotations;                     //number of rotations
float timeOld;                              //used int delT calculations for rpm
int StepsPerRotation = 240;                 //pulse per vervolution of encoder
int window = 10 * StepsPerRotation;         //number of rotations before rpm will update


void setup(){
  Serial.begin(115200); //sets comm rate
  lcd.begin(16,2);
  lcd.print("Welcome");
  delay(1500);
  lcd.clear();
  lcd.print("Select");
  delay(1000);
  lcd.clear();
  lcd.print("Rotation");
  delay(1000);
  lcd.clear();
  lcd.print("Direction");
  lcd.setCursor(0,1);
  //lcd.print(rotation);
  lcd.setBacklight(WHITE);
  pinMode(power, OUTPUT);                   //sets power pin as OUTPUT               
  pinMode(A_SIG, INPUT);                    //sets A_SIG pin as INPUT for digitalRead
  pinMode(B_SIG, INPUT);                    //sets B_SIG pin as INPUT for digitalRead
  attachInterrupt(digitalPinToInterrupt(19), A_CHANGE, CHANGE);       //attaches interrupt on A_SIG pin reading changing states
  attachInterrupt(digitalPinToInterrupt(18), B_CHANGE, CHANGE);       //attaches interrupt on B_SIG pin reading changing states
  steps = 0;
  rpm = 0;
  timeOld = 0;
  rotations = 0;
      
}//setup

uint8_t i=0;

void loop(){
  digitalWrite(power, HIGH);                          //turns on power to encoder
  Serial.println(count);
  while(count < 1){                                   //brings up menu options until BUTTON_SELECT is pressed causing count to increase
  uint8_t buttons = lcd.readButtons();                //reads button on LCD shield
    if(buttons & BUTTON_SELECT){
      ++ count;
      delay(1000);
      //Serial.println(count);
    }
    if(buttons & BUTTON_UP){
      delay(1000);
      ++ rotation;
      if ((rotation % 2) == 0){                       //if rotation is divisible by 2, even number, the rotation direction is set as Clockwise
        lcd.setCursor(0,1);
        lcd.print("                          ");
        delay(10);
        lcd.setCursor(0,1);
        lcd.print("Clockwise");
        //Serial.print(rotation);
      }
      else{
      lcd.setCursor(0,1);
      lcd.print("CounterClockwise");
      //Serial.print(rotation);
      }
    }
  }
  while(count == 1){                                  //swithces menu to display options for pulley diameter selection
    lcd.clear();
    lcd.print("Pulley Diameter");
    lcd.setCursor(0,1);
    lcd.print(dia/1.00,2);
    lcd.print(" inches");
    //delay(3000);
    count = 2;
  }
  while(count < 3){
    uint8_t buttons = lcd.readButtons();
      if (buttons & BUTTON_SELECT){
        ++ count;
        delay(1000);
        Serial.println(count);
      }
      if(buttons & BUTTON_UP){
          delay(1000);
          dia = dia + 0.25;                           //increments pulley diameter by 0.25 inches when up button is pressed
          lcd.setCursor(0,1);
          lcd.print(dia/1.00,2);                      //prints new pulley diamter to LCD
      }
      else if(buttons & BUTTON_DOWN){
          delay(1000);
          dia = dia - 0.25;                           //decrements pulley diamter by 0.25 inches when down button is pressed
          lcd.setCursor(0,1);
          lcd.print(dia/1.00,2);                      //prints new pulley diameter to LCD
      }
  }
  while(count == 3){
    lcd.clear();
    lcd.print("Up To Start");
    count = 4;
    //Serial.print(rotation);
  }
  while(count > 3){

    uint8_t buttons = lcd.readButtons();
    int time = millis();
    prev = val;
    val = pulses;
      if(buttons & BUTTON_UP){                        //starts timer when the up button is pressed
        //pulses = 0;
        delay(100);
        start = millis();
        lcd.clear();
        lcd.print("Elapsed Time");
        stopFlag = false;                             //timer will continue to run until stopFlag == true
      }
      if(stopFlag == false){
        if(start > 0){
          elapsed = millis() - start;
          lcd.setCursor(0,1);
          lcd.print(elapsed/1000.00,2);
          //Serial.println(pulses);
          int LocalSteps;                             //begining of RPM reading code
          noInterrupts();                             
          LocalSteps = steps;                         //obtains a 'safe copy' of the step count o the encoder 
                                                      //because interrupts are disables at this time
          interrupts();
          if (LocalSteps >= window){                  //keeps RPM from calculating until encoder makes 10 revolutions
            float rotations = (float) LocalSteps / StepsPerRotation;    //keeps the value as a float to account for partial revolutions
            float delT = (millis()-timeOld);          //time difference between last set of RPM readings
            float rpm = (rotations/delT)*60000;       //conversion between revolutions per millisecond to revolutions per minute
            lcd.setCursor(10,1);
            lcd.print(rpm/1.00,1);
            timeOld = millis();
            noInterrupts();                           
            steps -= LocalSteps;                      //resets the volatile steps counter while accunrinting for any missed steps
                                                      //while processing other parts code
            interrupts();
          }
          }
      }
      if((rotation % 2) == 0){                        //if rotation direction is divisible by 2, even, then wheel
                                                      //should be rotating in the clockwise direction
        if (val < prev){                              //if wheel ever rotates counterclockwise timer will stop
          finish = millis();
          elapsed = finish - start;
          stopFlag = true;
          val = 0;
          prev = 0;
          pulses = 0;
          //Serial.print(elapsed/1000.2);
        }
      }
      else{
        if(val > prev){                                //if wheel ever rotates clockwise timer will stop
          finish = millis();
          elapsed = finish - start;
          stopFlag = true;
          val = 0;
          prev = 0;
          pulses = 0;
        }
      }
  }
} 


void A_CHANGE(){                                      //ISR for every A_SIG change
  int a = digitalRead(A_SIG);                         //takes snapshot of state of A_SIG
  int b = digitalRead(B_SIG);                         //takes snapshot of state of B_SIG
    if((a==1)&(b==0)){                                //if A_SIG is HIGH and B_SIG is LOW, increase pulses (clockwise rotation)
      ++pulses;
      ++steps;
    }
    if((a==1)&(b==1)){                                //if A_SIG is HIGH and B_SIG is HIGH, decrease pulses (counterclockwise rotation)
      --pulses;
      ++steps;
    }
    if((a==0)&(b==1)){                                //if A_SIG is LOW and B_SIG is HIGH, increase pulses (clockwise rotation)
      ++pulses;
      ++steps;
    }
    if((a==0)&(b==0)){                                //if A_SIG is LOW and B_SIG is LOW, decrease pulses (counterclockwise rotation)
      --pulses;
      ++steps;
    }
}

void B_CHANGE(){                                      //ISR for every B_SIG change
  int a = digitalRead(A_SIG);                         //takes a snapshot of state of A_SIG
  int b = digitalRead(B_SIG);                         //takes a snapshot of state of B_SIG
    if((a==1)&(b==1)){                                //if A_SIG is HIGH and B_SIG is HIGH, increase pulses (clockwise rotation)
      ++pulses;
      ++steps;
    }
    if((a==0)&(b==0)){                                //if A_SIG is LOW and B_SIG is LOW, increase pulses (clockwise rotation)
      ++pulses;
      ++steps;
    }
    if((a==1)&(b==0)){                                //if A_SIG is HIGH and B_SIG is LOW, decrease pulses (counterclockwise rotation)
      --pulses;
      ++steps;
    }
    if((a==0)&(b==1)){                                //if B_SIG is LOW and B_SIG is HIGH, decrease pulses (conterclockwise rotation)
      --pulses;
      ++steps;
    }
}

I have realized that my timer displays slightly different results on the serial monitor than it does on the LCD. In my case, the serial monitor displays an elapsed time 0.04 seconds faster than the what is printed on the LCD. So far I have not been able to find anything about this through searching and was wondering if anyone knew why this might be?

Here is the portion of code doing the timing

while(count > 3){

    uint8_t buttons = lcd.readButtons();
    int time = millis();
    prev = val;
    val = pulses;
      if(buttons & BUTTON_UP){                        //starts timer when the up button is pressed
        //pulses = 0;
        delay(100);
        start = millis();
        lcd.clear();
        lcd.print("Elapsed    RPM");
        stopFlag = false;                             //timer will continue to run until stopFlag == true
      }
      if(stopFlag == false){
        if(start > 0){
          analogWrite(led1, brightness);
          analogWrite(led2, 0);
          elapsed = millis() - start;
          lcd.setCursor(0,1);
          lcd.print(elapsed/1000.00,4);
          //Serial.println(pulses);
          int LocalSteps;                             //begining of RPM reading code
          noInterrupts();                             
          LocalSteps = steps;                         //obtains a 'safe copy' of the step count o the encoder 
                                                      //because interrupts are disables at this time
          interrupts();
          if (LocalSteps >= window){                  //keeps RPM from calculating until encoder makes 10 revolutions
            float rotations = (float) LocalSteps / StepsPerRotation;    //keeps the value as a float to account for partial revolutions
            float delT = (millis()-timeOld);          //time difference between last set of RPM readings
            float rpm = (rotations/delT)*60000*(encdia/dia);       //conversion between revolutions per millisecond to revolutions per minute
            lcd.setCursor(10,1);
            lcd.print(rpm/1.00,1);
            timeOld = millis();
            noInterrupts();                           
            steps -= LocalSteps;                      //resets the volatile steps counter while accunrinting for any missed steps
                                                      //while processing other parts code
            interrupts();
          }
          }
      }
      if((rotation % 2) == 0){                        //if rotation direction is divisible by 2, even, then wheel
                                                      //should be rotating in the clockwise direction
        if (val < prev){                              //if wheel ever rotates counterclockwise timer will stop
          analogWrite(led1,0);
          analogWrite(led2, brightness);
          finish = millis();
          elapsed = finish - start;
          Serial.print(elapsed/1000.00,4);
          stopFlag = true;
          val = 0;
          prev = 0;
          pulses = 0;
        }
      }
      else{
        if(val > prev){                                //if wheel ever rotates clockwise timer will stop
          analogWrite(led1,0);
          analogWrite(led2, brightness);
          finish = millis();
          elapsed = finish - start;
          Serial.print(elapsed/1000.00,4);
          stopFlag = true;
          val = 0;
          prev = 0;
          pulses = 0;
          //Serial.print(elapsed/1000.00,4);
        }
      }
  }
}

You have printing (both LCD and print monitor) sprinkled throughout the code. You should have separate functions for measuring, calculating and printing. Also, avoid using delay(). Also, you could get better precision/accuracy if you use micros() for timing.