Slowing down my optical encoder

Hello everybody, I have been working on a project to measure belt length for a few months now. I’ve had some challenges along the way but I believe I am getting quite close. I have very recently switched to an optical encoder to try and eliminate bouncing that a mechanical encoder had. The good news is it appears it has solved that problem however if I spin the encoder very quickly it now misses pulses. My thought is to reduce the speed of the encoder 4:1 via pulleys, but I thought I would run it through here and see if perhaps there is a better solution that has not occurred to me.

This is the encoder I am using;
https://www.amazon.com/gp/product/B019RGTX7S/ref=oh_aui_search_detailpage?ie=UTF8&psc=1

I am using an Arduino Nano for this project here is the sketch I am using;

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3f, 20, 4); // set the LCD
//address to 0x3f for a 16 chars and 2 line display
const int clkPin = 3; //the clk attach to pin2
const int dtPin = 2; //the dt attach to pin3
const int swPin = 4 ; //the number of the button
int encoderVal = 0;//raw counts from encoder
const float distancePerCount = 1 ; //adjust as required
float totalDistance = 0.0;
//added variable for interrupt handler
volatile int count = 0;


void setup()
{
  lcd.init();  // initialize the lcd
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Belt Length :");
  //set clkPin,dePin,swPin as INPUT
  pinMode(clkPin, INPUT);
  pinMode(dtPin, INPUT);
  pinMode(swPin, INPUT);
  digitalWrite(swPin, HIGH);
  Serial.begin(9600); // initialize serial communications at 9600 bps

  attachInterrupt(digitalPinToInterrupt(clkPin), isrA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(dtPin), isrB, CHANGE);
}

void loop()
{
  int change = getEncoderTurn();
  if (change != 0)
  {
    encoderVal = encoderVal + change;
    totalDistance = encoderVal * distancePerCount;
    Serial.println(encoderVal); //print the encoderVal on the serial monitor
    Serial.println(totalDistance, 2); //print float with 2 dp
    lcd.setCursor(5, 1);
    //lcd.print(encoderVal);
    lcd.print(totalDistance, 2);
  }

  if (digitalRead(swPin) == LOW) //if button pull down
  {
    //encoderVal = .157;
    encoderVal = 0;
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Belt Length :");
  }

}

int getEncoderTurn(void)
{
  noInterrupts();
  int result = count;
  count = 0;//reset count
  interrupts();
  return result;
}

void isrA() {
  if (digitalRead(dtPin) != digitalRead(clkPin)) {
    count ++;
  } else {
    count --;
  }
}
void isrB() {
  if (digitalRead(clkPin) == digitalRead(dtPin)) {
    count ++;
  } else {
    count --;
  }
}

The encoder outputs are open collector. Do you have pull up resistors on those outputs? You can use the internal pull ups on those pins. When an ISR runs, interrupts are disabled. You shouldn't have to disable them in The ISR. You don't need to use 2 interrupts. Set the interrupt on channel A to falling. In the ISR, check the state of the B pin. If the B pin is high it is rotating one way and if the B pin is low it is rotating the other way. Only 1 ISR needed. Increment or decrement the count accordingly.

You shouldn't have to disable them in The ISR.

The interrupts are disabled in the data transfer from the ISR, not during the ISR itself. That is correct and does not need changing.

You don't need to use 2 interrupts. Set the interrupt on channel A to falling. In the ISR, check the state of the B pin. If the B pin is high it is rotating one way and if the B pin is low it is rotating the other way. Only 1 ISR needed. Increment or decrement the count accordingly.

The number of interrupts to use depends on the resolution you require. With 100 counts per revolution, the encoder can be read in ways to to show 100, 200 or 400 quadrature transitions. The algorithm suggested above(one pin interrupted on one edge) will give the 100 counts. What resolution do you require?

The encoder you are using is specifiec for 5000 rpm. (83.33 rev per second). I doubt that you can be turning it by hand fast enough to miss counts. Reading the count every pass of loop may be causing you the problems, because interrupts are disabled during this reading.

int change = getEncoderTurn();

Try putting the reading on a millis() timer to read periodically. You are outputting to an lcd which is slow, so reading every 250 ms might be a good place to start. Something like this--

if(millis() - lastReadingTime >= interval)
{
   lastReadingTime += interval;
   change = getEncoderTurn(); 
}

cattledog:
The interrupts are disabled in the data transfer from the ISR, not during the ISR itself. That is correct and does not need changing.

The number of interrupts to use depends on the resolution you require. With 100 counts per revolution, the encoder can be read in ways to to show 100, 200 or 400 quadrature transitions. The algorithm suggested above(one pin interrupted on one edge) will give the 100 counts. What resolution do you require?

The encoder you are using is specifiec for 5000 rpm. (83.33 rev per second). I doubt that you can be turning it by hand fast enough to miss counts. Reading the count every pass of loop may be causing you the problems, because interrupts are disabled during this reading.

int change = getEncoderTurn();

Try putting the reading on a millis() timer to read periodically. You are outputting to an lcd which is slow, so reading every 250 ms might be a good place to start. Something like this--

if(millis() - lastReadingTime >= interval)

{
  lastReadingTime += interval;
  change = getEncoderTurn();
}

I assumed that the pulses were occurring to fast for the Arduino to count them, however encoders are a somewhat new device to me. The accuracy I need is around .125 or so. The pulley that the encoder is monitoring will be spinning a couple hundred RPM max, if I am reading the encoder 250ms apart would that not cause pulses to be missed?

Thank you guys for your help with this.

I am reading the encoder 250ms apart would that not cause pulses to be missed?

No. the pulses are counted automatically behind the scenes by the interrupt service routine. That's the power of using interrupts.

When you read the counts and bring the count value into the main program has nothing to do with when the pulses are counted.

The variable count is typed as an int, so as long as there are less than 32,767 counts per sample period all will be well.

Thank you for the information Cattledog, I am going to look into this further this weekend and see if I can't get my head wrapped around the concept. Your advice is greatly appreciated!

Do you measure belt by running it across a pulley/wheel on the encoder shaft? A larger pulley/wheel will turn the shaft more slowly.

The design of my counter has the belt running over a pulley, the encoder was in the center of the pulley and as such driven as the belt moves across the pulley. I was going to move the encoder to a different location and slow it down via pulleys until Cattledog suggested I modify my sketch. I am currently attempting to figure out what my sketch needs to look like in order to successfully execute an interrupt so I can count pulses in the background. With the advice I've been given I believe there is no need to slow my encoder down at this point.

I am hoping you guys can give me a little more guidance here, I've spent quite a lot of time reading about how to use millis but I cannot find any information about a situation such as this one. What I am finding seems to lean mainly towards LED examples which I cannot relate in my brain to this project. I've read this page as well as a few others related to it which yielded no help... Millis information

What I am not understanding is how in the world I relate this code to my current sketch

if(millis() - lastReadingTime >= interval)
{
   lastReadingTime += interval;
   change = getEncoderTurn(); 
}

I am genuinely hung up on this I am really hoping you guys can point me in the right direction here.

if(millis() - lastReadingTime >= interval)
{
   lastReadingTime += interval;
   change = getEncoderTurn(); 
}

I am genuinely hung up on this I am really hoping you guys can point me in the right direction here.

I’m not sure what is confusing you here. Instead of blinking an led with the millis() timer you are reading the value of count from the isr into a variable called “change” with a function called getEncoderTurn() every interval.

In the original code, you were reading the value of count from the isr into a variable called “change” with a function called getEncoderTurn()) every pass through loop. Now you are going to do that at periodic intervals.

There are different ways to integrate the periodic encoder reading with the updated lcd display, but he most simple is to put them bothj on the same timer and the code would look like this. I changed encoderVal from an int to a long because I was getting overflow in my high speed testing.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3f, 20, 4); // set the LCD
//address to 0x3f for a 16 chars and 2 line display
const int clkPin = 3; //the clk attach to pin2
const int dtPin = 2; //the dt attach to pin3
const int swPin = 4 ; //the number of the button
//int encoderVal = 0;//raw counts from encoder
long encoderVal = 0;
const float distancePerCount = 1 ; //adjust as required
float totalDistance = 0.0;
//added variable for interrupt handler
volatile int count = 0;
//added variables for millis() interval timer
unsigned long lastReadingTime;
unsigned long interval = 250;


void setup()
{
  lcd.init();  // initialize the lcd
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Belt Length :");
  //set clkPin,dePin,swPin as INPUT
  pinMode(clkPin, INPUT);
  pinMode(dtPin, INPUT);
  pinMode(swPin, INPUT);
  digitalWrite(swPin, HIGH);
  Serial.begin(9600); // initialize serial communications at 9600 bps

  attachInterrupt(digitalPinToInterrupt(clkPin), isrA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(dtPin), isrB, CHANGE);
  
  lastReadingTime = millis(); //initiallize for timer
}

void loop()
{
  if (millis() - lastReadingTime >= interval)
  {
    lastReadingTime += interval;
    int change = getEncoderTurn();

    if (change != 0)
    {
      encoderVal = encoderVal + change;
      totalDistance = encoderVal * distancePerCount;
      Serial.println(change); //added for debug
      Serial.println(encoderVal); //print the encoderVal on the serial monitor
      Serial.println(totalDistance, 2); //print float with 2 dp
      lcd.setCursor(5, 1);
      //lcd.print(encoderVal);
      lcd.print(totalDistance, 2);
    }
  }//end of code block executed every interval

  if (digitalRead(swPin) == LOW) //if button pull down
  {
    //encoderVal = .157;
    encoderVal = 0;
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Belt Length :");
  }
}

int getEncoderTurn(void)
{
  noInterrupts();
  int result = count;
  count = 0;//reset count
  interrupts();
  return result;
}

void isrA() {
  if (digitalRead(dtPin) != digitalRead(clkPin)) {
    count ++;
  } else {
    count --;
  }
}
void isrB() {
  if (digitalRead(clkPin) == digitalRead(dtPin)) {
    count ++;
  } else {
    count --;
  }
}

Thank you Cattledog, as much as it shouldn’t have been it was the getEncoderTurn() that was messing me up. Having said that while this does improve the situation the encoder still remains problematic. If the belt is pulled across the pulley at anything other than really slow it skips pulses, regardless of what the interval is set to. I know I am not getting anywhere near the 5,000 RPM limit of the encoder but still I am missing pulses which is throwing my reading off. I’ve attached a picture so my setup is visible, it’s a very simple 1:1 setup with the pulley and encoder.

The encoder outputs are open collector. Do you have pull up resistors on those outputs?

Question from reply #1. What is your answer?

Ooops sorry about that, yes I do in fact have pull up resistors on my outputs.

One additional thought. If you comment out all the switchPin/reset code, do you still see the missing pulses?

If there is something wrong in that circuit, perhaps you are getting resets which look like missing counts.

What value pullups do you have on the encoder outputs?

I will check out the switchPin/reset thank you, I am using 10k resistors on my outputs.

My other thought that I will be fixing as soon as I am done writing this is perhaps the breadboard I am using is causing some issues as well. I will be soldering everything solid so this will no longer even be a question.

If there is still a problem after you get everything solid, one more thing to try is to eliminate the lcd and just run the encoder with Serial output with a high baud rate like Serial.begin(115200). There could be a problem on the i2c bus (which is interrupt driven) and if there is a problem there, it could cause the encoder to loose interrupts.

I am using 10k resistors on my outputs.

Try a lower value like 4.7k or 2.2K which will give a stronger pullup.

Maxxheadroom:
Hello everybody, I have been working on a project to measure belt length for a few months now.

What speeds are you operating at?
The item you showed reckons it can handle maybe 5000 RPM.

Your loop currently does a whole bunch of things before it gets back to the command that checks on whether something has changed or not with the 'counts'.

You probably need a way to get the interrupts to handle the counting --- completely.

This could mean something like ...... making a couple of variables 'volatile', and whenever an interrupt occurs....the interrupt routine does the incrementing or decrementing of counts.

In the end, your loop should only contain commands for quickly grabbing the overall volatile count (with interrupts turned off very briefly only).

BTW do you need information about direction? If not you only have to count pulses from one channel which is trivial using a counter.

Southpark:
What speeds are you operating at?
The item you showed reckons it can handle maybe 5000 RPM.

Your loop currently does a whole bunch of things before it gets back to the command that checks on whether something has changed or not with the 'counts'.

You probably need a way to get the interrupts to handle the counting --- completely.

This could mean something like ...... making a couple of variables 'volatile', and whenever an interrupt occurs....the interrupt routine does the incrementing or decrementing of counts.

In the end, your loop should only contain commands for quickly grabbing the overall volatile count (with interrupts turned off very briefly only).

The speeds are reasonably slow, the belt will be pulled across the pulley by hand. This is a measurement device to measure out belt lengths so the pulley speeds should remain relatively slow, certainly nowhere near the 5k RPM limit of the encoder. Like I said earlier this is quite a large step for me as previously my Arduino experience has been lighting, further complicating things right now out of the blue I have had a few major seizures it is also severely effecting my memory. This has made this project a little challenging for me, I am sorry if I seem needy I am trying very hard to keep up with all this new stuff.