Rotary Encoder on Fast Rotation

Hello,
I am using TAMAGAWA 2500PPR encoder with Arduino Mega 2560.
It has A, A-, B, B- and Z, Z- inputs.

I use MC3486 along with it and trying to count the rotation.

Its working fine when i move it slowly. But when i rotate it with attaching a motor on approx 1500RPM , its not giving proper values.

what I am doing wrong.
I modified the given script to handle Z input.

I am trying to read the voltage on A0 on a particular angle.


More members will see your code if posted properly. Read the how to use this forum-please read sticky to see how to properly post code. Remove useless white space and format the code with the IDE autoformat tool (crtl-t or Tools, Auto Format) before posting code. If the code is too large to post it can be attached. Better yet, make a minimal verifiable program that shows the problem.

How to post images so we do not have to download them:
How to post an image.
Another page on posting images.

I use MC3486 along with it and trying to count the rotation.

Please post a schematic.

I use MC3486 along with it and trying to count the rotation.

Please post a circuit diagram of what you are doing?

I modified the given script to handle Z input.

Please post your code.

I am trying to read the voltage on A0 on a particular angle.

But when i rotate it with attaching a motor on approx 1500RPM , its not giving proper values.

Depending on the meaning of "2500 PPR" the encoder may have 10000 quadrature counts per revolution. Your code only reads half of them, but at 1500 rpm your counts are still very high and the Arduino may not be fast enough.

You are not reading the counter variable in a critical section, so it will get garbled occasionally when
you get an interrupt between reading it's two bytes. The Mega2560 is an 8-bit machine so 16 bit
ints are read in two steps.

A critical section is a code fragment with interrupts turned off:

  noInterrupts() ;
  int counter_copy = counter ;
  interrupts ();

Only disable interrupts for the minimum time necessary, use you local copy which is an
intact snapshot of the counter variable.

Interrupt handling has overhead of several microseconds, so there's a maximum count rate you
can achieve without using a hardware quadrature decoder.

First of all, I am sorry for not following the guidelines properly, and thanks for the help and let me understand how to use the forum.

I am posting here the schematic which I tried with and without 74HC14 but no luck.

It's missing the zero point on high rpm.
I have tried various libraries but it not seems working. my ultimate goal is to read A0 value on high speed along with the encoder angle. any help will be highly appreciated. It would be good if I can monitor it on serial.

#include "EncoderStepCounter.h"

#define ENCODER_PIN1 21
#define ENCODER_INT1 digitalPinToInterrupt(ENCODER_PIN1)
#define ENCODER_PIN2 20
#define ENCODER_INT2 digitalPinToInterrupt(ENCODER_PIN2)

#define ENCODER_PIN3 19
#define ENCODER_INT3 digitalPinToInterrupt(ENCODER_PIN3)

signed long position = 0;
EncoderStepCounter encoder(ENCODER_PIN1, ENCODER_PIN2);
void setup() {
 Serial.begin(115200);
 // Initialize encoder
 encoder.begin();
 // Initialize interrupts
 attachInterrupt(ENCODER_INT1, interrupt, CHANGE);
 attachInterrupt(ENCODER_INT2, interrupt, CHANGE);
 attachInterrupt(ENCODER_INT3, interrupt1, CHANGE);
}

// Call tick on every change interrupt
void interrupt() {
// noInterrupts ();
 encoder.tick();
weight = (analogRead(A0) / samplingRef);

 signed char pos = encoder.getPosition();
 if (pos != 0) {
   position += pos;
   encoder.reset();
   Serial.print((position/ 2500.0) * 360.0);
   Serial.print("*");
   Serial.println(weight);
 }
// interrupts();
}

void interrupt1() {
 
 // noInterrupts ();
 
 position = 0;
//Serial.print("z came");
//interrupts();
}

void loop() {
 
}

weight = (analogRead(A0) / samplingRef);

my ultimate goal is to read A0 value on high speed along with the encoder angle.

What is connected to A0 and what is the samplingRef?

It's missing the zero point on high rpm.

This is the first thing to sort out, as it is a simple interrupt, independent of the encoder reading.

Use pinMode INPUT_PULLUP explicitly for the pin, and make the MODE RISING or FALLING instead of CHANGE so that you only pick up one edge of the pulse.

The library you have selected for the encoder reading is not the best, and uses digitalRead() which will make it slower than other libraries or coding the reading yourself, which would be my recommendation.

First order of business is to get the Z axis pulse reliable, then work on encoder position. Finally integrate the mysterious A() reading. Be aware that the standard analogRead() is slow (~110us) and may create issues with fast interrupts.

hi cattledog,
here is the missing part you asked for.

float VoltageRef = 1; 
float samplingRef = (VoltageRef * 204.8);

I also tried, to change the interrupt orders, like I catch up with the Z first, but in that case, nothing works. Also tried some other libraries.

I have no issue in changing the encoder, as the ultimate goal is to read the voltage from A0 along with the Angle(position) on approx 1500RPM spinning motor.

Thanks

the ultimate goal is to read the voltage from A0 along with the Angle(position) on approx 1500RPM spinning motor.

I think that performing an analogRead() on every tick of a high resolution encoder at 1500 rpm is not reasonable.

What are you trying to do with this setup?

Should you be using an absoulute encoder instead of the incremental encoder if you are after angular position?

As I said, first order of business is a simple interrupt sketch to read the Z axis. How do you know it is missing counts at the high rpm?

I use the following code and its working fine if I rotate the encoder manually (by hand). after 360 degrees it will give turn the counter zero with the message in the z interrupt. As there is much serial.print in the main loop. I thought that might causing issues in reading so I change the code to the above. but I need these values on the monitor also. when below code did not worked on high rpm then i change it to the above-posted one.

is there any other encoder or I change the board from mega to due? or software change can help. I am not sure.

#define ENC_A  21  
#define ENC_B  20  
#define ENC_Z  19  

#define REFRESH_MS  50


#define BAUD_RATE   9600


volatile boolean state_a = 0;
volatile boolean state_b = 0;

volatile int enc_pos = 0;
volatile float weight, hi_weight = 0.0;
volatile float angle, hi_angle = 0.0;



unsigned long elapsedTime = 0;
int temp =0;
uint32_t prev_seconds;
void setup() 
{
     Serial.begin(BAUD_RATE);
    pinMode(ENC_A, INPUT_PULLUP);
    pinMode(ENC_B, INPUT_PULLUP); 
    pinMode(ENC_Z, INPUT); 

    state_a = (boolean) digitalRead(ENC_A);
    state_b = (boolean) digitalRead(ENC_B);

    attachInterrupt(digitalPinToInterrupt(ENC_A), interrupt_enc_a, RISING);
    attachInterrupt(digitalPinToInterrupt(ENC_B), interrupt_enc_b, RISING); 
    attachInterrupt(digitalPinToInterrupt(ENC_Z), interrupt_enc_c, RISING);
  

}


void loop(){
  
  uint32_t currentMillis = millis();
  uint32_t seconds = currentMillis / 1000;

    angle = (enc_pos / 2500.0) * 360.0;
    Serial.print(enc_pos);
    Serial.print(angle); // Angle of measurement
    Serial.print("\t");
    Serial.print(weight);
    Serial.print("\t");
    Serial.print(hi_angle);
    Serial.print("\t");   
    temp = enc_pos;


    delay(REFRESH_MS);
}

}


void interrupt_enc_a()
{
 weight = (analogRead(A0) / samplingRef);
 
 
    if (!state_a) {
        state_b ? enc_pos++: enc_pos--;         
    }
    state_a = !state_a;
}

void interrupt_enc_b()  
{
    state_b = !state_b;
}

void interrupt_enc_c()  
{ 
  //Serial.print("Z is-->");
//   Serial.println(digitalRead(19));
   enc_pos = 0;
// enc_pos_prev = 0;
// enc_pos_change = 0;
}
#define BAUD_RATE   9600

You should definitely use a much higher baud rate. Certainly 115200 at a minimum. Depending on the TTL/USB converter on the Mega you might be able to use significantly higher rates which the Serial monitor lets you select.

I do not have a z axis encoder to test with so I can not really evaluate what you have.

Regarding faster processors, you may want to take a look at the Teensy family.
https://www.pjrc.com/teensy/techspecs.html

They are well supported, and can use the Arduino IDE. A very good encoder library (encoder.h) will work on Teensy's but you may not be needing that.

I tried almost every baud rate, especially 57600 and 115200.
yes, I am thinking to move on Arduino Due or Teensy 4.0, but never worked on Teensy so not sure how much is it different.

I have seen a youtube video in which 74HC14 is used along with OLED to achieve what I need but there are some different components also used. I did not find that detail.

Anyways thank you for the support. If still you find something which can help, please do share.
Thanks

If still you find something which can help, please do share.

One last thing to look at is speeding up the analogRead().

Here's the link to Nick Gammon's excellent tutorial

As explained in the tutorial, it would be worth a try to take the clock prescaler from 128 to 16 to see if that helps the performance at the higher rpm.

Yes I was about to mention that due to analogRead, encoder position going in -ve or incorrect no matter if I use manual or fast spinning.

Hi, cattledog,
I tried the code from nick Gammons tutorial. But majorly I don't understand what's going on there :).
but I managed to adjust it with my code. now performance a little better, but it's giving very random values. like if the pin is floating then it gives the maximum volt :frowning:

I tried both the way by putting it in interrupt as well as the main loop, giving same result. any idea?

 delay(REFRESH_MS);

Since you have a 50 ms delay in your code it makes no sense to perform the analogRead() in the interrupt. You only get the last value anyway, before the display in loop.

I understand that you are trying to time the analogRead() to occur at the time that the last encoder position is updated, but would think that doing the reading in loop is likely to be very close the value at the reported position.

It's not clear from you code what you actually do with "weight".

Floating point math on the 8 bit classic AVR's is quite slow as there is no hardware support. Converting floating point operations to integer operations by scaling number up is often used to speed things up. Your floating point division of analogRead() by sampling ref would be a good candidate.

There are plenty of online references to reducing floating point operations with Arduinos.

I have made many changes, and seems closer but if you check out the code on your given link under the following header.

Read without blocking

The library function analogRead "blocks", that is, it waits until the conversion is done. However you don't have to do that. This simple code below starts conversion and then checks in the main loop if it is finished. When it is finished it displays the result:

It gives output something like this on serial monitor, if I only execute that code.

1023
0
1023
1023
0
26
1023
0
0
1023
333
0
723

What is the analogRead() connected to, what does the value tell you, and how are you planning to use the information?

Its not analogRead() now, It's reading voltages from A0 by using the following code.
If A0 remains float( without connecting with supply or voltages) its giving those values. these are the ADC values. but why it is so random.

const byte adcPin = 0;  // A0
  
bool working;
  
void setup ()
  {
  Serial.begin (115200);
  Serial.println ();

  ADCSRA =  bit (ADEN);   // turn ADC on
  ADCSRA |= bit (ADPS0) |  bit (ADPS1) | bit (ADPS2);  // Prescaler of 128
  ADMUX =   bit (REFS0) | (adcPin & 0x07);  // AVcc   
  }  // end of setup

void loop () 
  { 
  if (!working)
    {
    bitSet (ADCSRA, ADSC);  // start a conversion
    working = true;
    }
    
  // the ADC clears the bit when done
  if (bit_is_clear(ADCSRA, ADSC))
    {
    int value = ADC;  // read result
    working = false;
    Serial.println (value);
    delay (500);  
    }
    
  }  // end of loop

If A0 remains float( without connecting with supply or voltages) its giving those values. these are the ADC values. but why it is so random.

I think you answered your own question. A floating pin can produce any value.

Floating input pins are aerials sensing the nearby voltages very sensitively. Usually the stray electric fields in
the environment are mainly mains-derived at 10 to 40V in amplitude, plus any nearby pins switching
between 0V and 5V.

Electrically they are "flapping in the breeze" to steal an expression from Dave at EEVblog!

Basically floating pins aren't useful, always connect something.