Measuring wheel with rotary encoder


#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
#define encoder0PinA  2
#define encoder0PinB  3
volatile unsigned int encoder0Pos = 0;
//  400ppr encoder with 300mm length wheel so 4 ticks == 3 mm -> 3 / 4 = 0.75
//  dividing by 100 to get decimeters or by 1000 to get meters
#define DECIMETERS_PER_TICK 0.75 / 1000

long newposition;
long oldposition = 0;
unsigned long newtime;
unsigned long oldtime = 0;
long vel;
int first;
int distance;


void setup() {
  
  lcd.begin    (20, 4);
  lcd.setCursor(0,0);
  lcd.print    ("SPEED");
  lcd.setCursor(17,0);
  lcd.print    ("M/H");
  lcd.setCursor(0,1);
  lcd.print    ("DISTANCE");
  lcd.setCursor(19,1);
  lcd.print    ("M");
  lcd.setCursor(0,2);
  lcd.print    ("INPUT SPEED");
  lcd.setCursor(17,2);
  lcd.print    ("M/H");
  lcd.setCursor(0,3);
  lcd.print    ("TIME");  
  lcd.setCursor(11,3);
  lcd.print    ("STOP");


  pinMode(encoder0PinA, INPUT_PULLUP);//input pin with internal pull-up resistor
  pinMode(encoder0PinB, INPUT_PULLUP); //input pin with internal pull-up resistor
  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, doEncoderA, CHANGE);
 // encoder pin on interrupt 1 (pin 3)
  attachInterrupt(1, doEncoderB, CHANGE);
  
  
}

void loop () {
  distance = encoder0Pos * DECIMETERS_PER_TICK/40;
  newposition = encoder0Pos;
  newtime = millis();
  vel =(newposition-oldposition) * 10/(newtime-oldtime);
  
  lcd.setCursor(13,0);
  lcd.print(vel);
  if(vel<1000){ lcd.print(" ");
  delay (100);}
  

  
  oldposition = newposition;
  oldtime = newtime;
  delay(250);
  
  lcd.setCursor(13,1);
  lcd.print(abs(distance));
  if(distance<10000){ lcd.print(" ");}
 
  
  
  
  
}

void doEncoderA() {
  // look for a low-to-high on channel A
  if (digitalRead(encoder0PinA) == HIGH) {

    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == LOW) {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
    else {
      encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }

  else   // must be a high-to-low edge on channel A
  {
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == HIGH) {
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
    }
  }
  //Serial.println (encoder0Pos, DEC);
  // use for debugging - remember to comment out
}

void doEncoderB() {
  // look for a low-to-high on channel B
  if (digitalRead(encoder0PinB) == HIGH) {

    // check channel A to see which way encoder is turning
    if (digitalRead(encoder0PinA) == HIGH) {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
    else {
      encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }

  // Look for a high-to-low on channel B

  else {
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinA) == LOW) {
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
    }
  }
}

trying to make a measuring wheel with a rotary encoder lpd3806, which will display the distance (meters) and the speed(meters per hour). am i doing the calculations correctly ?

Post your results and what you are expecting.

If the results are not what you expect, insert some serial prints at strategic locations to follow program execution and variable values.

i am getting results, but when i turn it clockwise the distance after 10 goes again back to 0
and the speed shows good but after some time it prints big numbers

i want to make it so it will work 0-1000 meters but no under 0 or negative, and from 1000-0
counter clock wise but stop at 0, stop at zero means even if it turns it will not display anything but zero

By 'length' did you mean the length around the outside (a.k.a. CIRCUMFERENCE)? A wheel with a 300 mm circumference would have a diameter of about 9.55 cm.

You say:
dividing by 100 to get decimeters or by 1000 to get meters
but then you calculate:
#define DECIMETERS_PER_TICK 0.75 / 1000
Shouldn't that be METERS_PER_TICK?

Note: If you put your calculation in a macro (like DECIMETERS_PER_TICK) you should put it in parentheses to avoid confusing the expression parser:
#define DECIMETERS_PER_TICK (0.75 / 1000)
Better still, use a 'const' variable so the compiler is sure of the data type:
const float DECIMETERS_PER_TICK = 0.75 / 1000;

You are right, the other calculations are they correct? Do i use the formula correctly ?

Why divide by 40?

Why multiply by 10?

So change:
encoder0Pos = encoder0Pos - 1; // CCW
to

 if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;          // CCW

Are you using an Arduino with 16-bit integers? If so, this counter is only good for a little over 49 meters. You should change it to 'unsigned long' to get your 1000 meters.

Because 'encoder0Pos' is 'volatile' it should be fetched only with interrupts disabled.

  noInterrupts();
  newposition = encoder0Pos;
  interrupts();
  distance = newposition * DECIMETERS_PER_TICK/40;

Thank you very very much!! I will make the changes and will report back to you

If you make changes and want further help, please post a new reply with the latest version of your sketch. That way we can tell which of the suggested changes you have made.

i think my speed(vel) formual is wrong

vel =(newposition-oldposition)/(newtime-oldtime);

should i change it for Rpm and from rpm find meters per hour ?

No need to change it to RPM first if you want meters per hour.

If your 'position' values are in meters and your 'time' values are in milliseconds you are calculating 'val' in meters per millisecond. To get meters per hour, multiply by milliseconds per hour.

60 minutes per hour
6060 (3600) seconds per hour
3600
1000 (3600000) milliseconds per hour
3.6 million is too large for a 16-bit 'int' so use a 32-bit 'long int'.

`vel_mph = vel * 3600000l;'

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
#define encoder0PinA  2
#define encoder0PinB  3
unsigned long encoder0Pos = 0;
//  400ppr encoder with 300mm length wheel so 4 ticks == 3 mm -> 3 / 4 = 0.75
//  dividing by 100 to get decimeters or by 1000 to get meters
#define METERS_PER_TICK 0.75 / 1000

long newposition;
long oldposition = 0;
unsigned long newtime;
unsigned long oldtime = 0;
long vel;
int first;
int distance;
long vel_mph;


void setup() {
  
  lcd.begin    (20, 4);
  lcd.setCursor(0,0);
  lcd.print    ("SPEED");
  lcd.setCursor(17,0);
  lcd.print    ("M/H");
  lcd.setCursor(0,1);
  lcd.print    ("DISTANCE");
  lcd.setCursor(19,1);
  lcd.print    ("M");
  lcd.setCursor(0,2);
  lcd.print    ("INPUT SPEED");
  lcd.setCursor(17,2);
  lcd.print    ("M/H");
  lcd.setCursor(0,3);
  lcd.print    ("TIME");  
  lcd.setCursor(11,3);
  lcd.print    ("STOP");


  pinMode(encoder0PinA, INPUT_PULLUP);//input pin with internal pull-up resistor
  pinMode(encoder0PinB, INPUT_PULLUP); //input pin with internal pull-up resistor
  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, doEncoderA, CHANGE);
 // encoder pin on interrupt 1 (pin 3)
  attachInterrupt(1, doEncoderB, CHANGE);
  
  
}

void loop () {
  noInterrupts();
  newposition = encoder0Pos;
  interrupts();
  distance = newposition * METERS_PER_TICK;
  newtime = millis();
  vel =(newposition-oldposition)/(newtime-oldtime);
  vel_mph = vel * 3600000l;
  
  lcd.setCursor(13,0);
  lcd.print(vel_mph);
  if(vel<1000){ lcd.print(" ");
  delay (100);}
  

  
  oldposition = newposition;
  oldtime = newtime;
  delay(250);
  
  lcd.setCursor(13,1);
  lcd.print(abs(distance));
  if(distance<10000){ lcd.print(" ");}
 
 
  
  
  
}

void doEncoderA() {
  // look for a low-to-high on channel A
  if (digitalRead(encoder0PinA) == HIGH) {

    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == LOW) {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }

  else   // must be a high-to-low edge on channel A
  {
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == HIGH) {
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }
  //Serial.println (encoder0Pos, DEC);
  // use for debugging - remember to comment out
}

void doEncoderB() {
  // look for a low-to-high on channel B
  if (digitalRead(encoder0PinB) == HIGH) {

    // check channel A to see which way encoder is turning
    if (digitalRead(encoder0PinA) == HIGH) {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }

  // Look for a high-to-low on channel B

  else {
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinA) == LOW) {
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;          // CCW
    }
  }
}

please take a look, i couldn't understand what you mean.

This should be:
volatile unsigned long encoder0Pos = 0;
It is changed in the interrupts and used in loop().

This should be:
#define METERS_PER_TICK (0.75 / 1000)
This makes it act more like a float constant when substituting into an expression.

attachInterrupt() should always use digitalPinToInterrupt():

  // encoder pin on pin 2
  attachInterrupt(digitalPinToInterrupt(2), doEncoderA, CHANGE);
 // encoder pin on pin 3
  attachInterrupt(digitalPinToInterrupt(3), doEncoderB, CHANGE);

Oops. I forgot that the 'position' variables are in ticks, not in meters. This should be:
vel =((newposition - oldposition) * METERS_PER_TICK) / (newtime-oldtime);

This is going to be 0 for any velocity below 3.6 million meters per hour. Try making 'vel' a float value:
float vel;

i made the changes, but now it's showing 0.00 and if i spin it very fast 0.01

What do you think , should i also include the previous code you said?

vel_mph = vel * 3600000l;'
```

I cant see your current sketch so I don't know what you should change.

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
#define encoder0PinA  2
#define encoder0PinB  3
volatile unsigned long encoder0Pos = 0;
//  400ppr encoder with 300mm length wheel so 4 ticks == 3 mm -> 3 / 4 = 0.75
//  dividing by 100 to get decimeters or by 1000 to get meters
#define METERS_PER_TICK (0.75 / 1000)

long newposition;
long oldposition = 0;
unsigned long newtime;
unsigned long oldtime = 0;
float vel;
int first;
int distance;



void setup() {
  
  lcd.begin    (20, 4);
  lcd.setCursor(0,0);
  lcd.print    ("SPEED");
  lcd.setCursor(17,0);
  lcd.print    ("M/H");
  lcd.setCursor(0,1);
  lcd.print    ("DISTANCE");
  lcd.setCursor(19,1);
  lcd.print    ("M");
  lcd.setCursor(0,2);
  lcd.print    ("INPUT SPEED");
  lcd.setCursor(17,2);
  lcd.print    ("M/H");
  lcd.setCursor(0,3);
  lcd.print    ("TIME");  
  lcd.setCursor(11,3);
  lcd.print    ("STOP");


  pinMode(encoder0PinA, INPUT_PULLUP);//input pin with internal pull-up resistor
  pinMode(encoder0PinB, INPUT_PULLUP); //input pin with internal pull-up resistor
  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(digitalPinToInterrupt(2), doEncoderA, CHANGE);
 // encoder pin on interrupt 1 (pin 3)
  attachInterrupt(digitalPinToInterrupt(3), doEncoderB, CHANGE);
  
  
}

void loop () {
  noInterrupts();
  newposition = encoder0Pos;
  interrupts();
  distance = newposition * METERS_PER_TICK;
  newtime = millis();
  vel =((newposition - oldposition) * METERS_PER_TICK) / (newtime-oldtime);
  
  lcd.setCursor(13,0);
  lcd.print(vel);
  
  

  
  oldposition = newposition;
  oldtime = newtime;
  delay(250);
  
  lcd.setCursor(13,1);
  lcd.print(abs(distance));
  if(distance<10000){ lcd.print(" ");}
 
 
  
  
  
}

void doEncoderA() {
  // look for a low-to-high on channel A
  if (digitalRead(encoder0PinA) == HIGH) {

    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == LOW) {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }

  else   // must be a high-to-low edge on channel A
  {
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinB) == HIGH) {
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }
  //Serial.println (encoder0Pos, DEC);
  // use for debugging - remember to comment out
}

void doEncoderB() {
  // look for a low-to-high on channel B
  if (digitalRead(encoder0PinB) == HIGH) {

    // check channel A to see which way encoder is turning
    if (digitalRead(encoder0PinA) == HIGH) {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;         // CCW
    }
  }

  // Look for a high-to-low on channel B

  else {
    // check channel B to see which way encoder is turning
    if (digitalRead(encoder0PinA) == LOW) {
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      if (encoder0Pos > 0)
   encoder0Pos = encoder0Pos - 1;          // CCW
    }
  }
}

It just shows,0.00 but when i turn it very fast only show 0.01

That's because you are displaying meters per millisecond. To get meters per HOUR you need to multiply by 3.6 million.

0.01 meter per millisecond is 360 THOUSAND meters an hour. 360 kilometers per hour.