Optimization of incremental encoder

Hi,

  myself chitrarasan, i am a person new to arduino ,now I'm in a project where i have a motor which can run at 600 - 700 rpm. i like to know the position of the motor shaft with an orange incremental encoder and arduion uno , i wrote a simple code for measure the position. The concept of the code is 

There is a start position in the circumference of the shaft which will be detected by a proximity sensor (working properly at all speeds) as soon as the start position is detected, the encoder will start the count. Then, as per the encoder value, two LEDs will go high and low. Then again, the start position will come the encoder value will reset and count from the beginning so the count is new in every rotation of the shaft.so that if a small error occurs in count, they will delete it in the next rotation .

int encoderPin1 = 2;// encoder input
int encoderPin2 = 3;//encoder out put
volatile int lastEncoded = 0;
volatile long encoderValue = 0;//variable containing encoder value
long lastencoderValue = 0;
int lastMSB = 0;
int lastLSB = 0;
int led1 = 13;//led1
int led2 = 12; //led 2
int ir_on = 4;//proximity sensor input which is NPN type
int on;
int a = 0;
int b;
void setup()
{
  Serial.begin (9600);
  pinMode(ir_on, INPUT);
  pinMode(encoderPin1, INPUT);
  pinMode(encoderPin2, INPUT);
  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  digitalWrite(encoderPin2, HIGH); //turn pullup resistor on
  //call updateEncoder() when any high/low changed seen
  //on interrupt 0 (pin 2), or interrupt 1 (pin 3)
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
}
void loop()
{
  //Do stuff here
  on = digitalRead(ir_on);
  if (on == LOW)
  {
    b = 1;
    encoderValue = 0;
  }
  if (b == 1)
  {
    attachInterrupt(0, updateEncoder, CHANGE);
    attachInterrupt(1, updateEncoder, CHANGE);
  }
  Serial.println(encoderValue);
  //delay(1000); //just here to slow down the output, and show it will work even during a delay
}
void updateEncoder()
{
  if (on == HIGH)
  {
    int MSB = digitalRead(encoderPin1); //MSB = most significant bit
    int LSB = digitalRead(encoderPin2); //LSB = least significant bit
    int encoded = (MSB << 1) | LSB; //converting the 2 pin value to single number
    int sum = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

    if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)/*The encoder has 2 digital pins that are either HIGH (1) or LOW (0) right? If we treat the pins as binary, we read them as 00, 01, 10, or 11. The sequence the encoder outputs while spinning clockwise is 00, 01, 11, 10 repeat.

  Incremental Rotary encoder

  So if you have a reading of 01, the next reading can either be 00 or 11 depending on the direction the knob is turned. So by adding the previous encoded value to the beginning of the current encoded value; we get 1 of 8 possible numbers (0001, 0010, 0100, 0111, 1000, 1011, 1110 & 1101) 1101, 0100, 0010 & 1011 all mean cockwise movement. 1110, 0111, 0001 & 1000 are all counter-clockwise.

  So now we can say this: (sum is last reading + current reading)

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++; //clockwise movement
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --; //counter-clockwise movement*/
    {
      encoderValue ++;
    }
    if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000)
    {
      encoderValue --;
    }
    lastEncoded = encoded;
  }
  //store this value for next time

  // LEDs go high and low as per the encoder value value
  if (encoderValue == 0)
  {
    digitalWrite(led1, LOW);
    digitalWrite(led2, LOW);
  }
  if ((encoderValue >= 1 ) && (encoderValue <= 685))
  {
    digitalWrite(led1, HIGH);
    digitalWrite(led2, LOW);
  }
  if ((encoderValue > 685) && (encoderValue <= 2052))
  {
    digitalWrite(led1, LOW);
    digitalWrite(led2, LOW);
  }
  if ((encoderValue > 2052 ) && (encoderValue <= 4104))
  {
    digitalWrite(led1, LOW);
    digitalWrite(led2, HIGH);
  }
}

the code is working properly the only in slow speeds,but at higher speed the count is missing the numbers and going beyond encoders cpr value that is my encoder produces 4096 cpr per revolution but it counts 11,000 or more at very higher speed but my encoder is capable for 5000rpm so ,my hard ware is capable enough for my application ,so i cheked in other website it says the algorithum has to be optimised inorder to make it work for higher rpm.
and i have checked my proximity sensor and it works prity good at higher rpm.
encoder data sheet:
User-Manual-Orange-3806-OPTI-600-AB-OC-Rotary-Encoder-ROBU.IN_ (1).pdf (672.6 KB)

Please use code tags for your code - if you can modify the posting to do this it will help people by making your code readable and copy/pastable

Post datasheets of the proximity sensor and the rotary-encoder.
It is important information how the proximity sensor works:
capacitive, inductive, high-time of the signal, maximum-frequency of the proximity-sensor

and how the rotary-encoder works.
Optical detection, mechanic-elecetrical detection, half-pulse, full-pulse
signal-HIGH of channel A and channel B overlapping or not overlapping

and maybe your microcontroller is simply not fast enough:
4096 pulses per rotation at 700 rpm means
1 / (700 / 60 * 4096) = 0,0000209 seconds until next pulse rushes in
This is 20,9 microseconds which is pretty short.

on an arduino uno 16 MHz digitalwrite needs 48 cycles which is 3 microseconds
based on this post

I don't know how many cycle the other commands need.
You could add a digitalwrite HIGH at the top of your ISR
and a digitalWrite LOWat the end of your ISR and then watch on an oscilloscope how long the pulses are

or storing the value of micros() at the top and storing the value of micros() at the end of your ISR.
You will have to substract the execution-times of the added commands

best regards Stefan

Sounds like you are sometimes missing the start position at high speed. Why not just reset when you go past 4095?

because i need it to start the count at proper position so that the mistake in last rotation will be deleted

User-Manual-Orange-3806-OPTI-600-AB-OC-Rotary-Encoder-ROBU.IN_ (1).pdf (672.6 KB)

Thanks for your time ,now i have attached the data sheet of encoder,the encoder can work upto 5000 rpm. And now can u do this for me, that is ,

now i have got a new code from another post, then i applied it to my application,but I don't know how the code calibrates encoder pulse but the error is very less and it is counting at a range of 2002 to 2019, instead of 4096. it gives values like 2014,2019,2002,2018,2001 at end of every rotation

it means it almost gives 2001 min are 2019 max at end of every revolution, so i think it will be better if we optimise this following code to count up to 4096 pulses

//PORTB pins;
#define encoderPinA 8 //PB0
#define encoderPinB 9 //PB1


//variables changed within interrupt make volatile
volatile long encoderCount;

long lastEncoderValue = 0;
int interval = 1;
unsigned long prevDisplay;
int b = 0 ;
int on;
int ir_on = 4;
void setup() {

  Serial.begin (115200);
  Serial.println("Half of Quadrature Counts");

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);


  //enable pin change interrupts and set pin masks;
  //interrupt pin selection or wires from encoder can be switched for correct cw/ccw

  PCICR |= (1 << PCIE0); //enable group interrupts on PORTB PCINT[7:0]
  PCMSK0 |= (1 << PCINT0); //enable interrupt pin 8
  //PCMSK0 |= (1<<PCINT1);//enable interrupt pin 9

}

void loop() {
  on = digitalRead(ir_on);


  if (on == LOW)
  {
    b = 1;
    encoderCount = 0;
  }
  if (b == 1)
  {

    if (millis() - prevDisplay >= interval)
    {
      prevDisplay += interval;
      noInterrupts();
      long copyEncoderCount = encoderCount;
      //encoderCount = 0;
      interrupts();

      Serial.println(copyEncoderCount);

    }

  }
}

ISR (PCINT0_vect)//handle pin change interrupt for d8 to D13, triggered by 8
{
  if ( bitRead(PINB, 0) == bitRead(PINB, 1)) //compare Pin 8 and 9
  {
    encoderCount++;
  }
  else
  {
    encoderCount--;
  }
}

The datasheet says

Are you sure that you have 4096 ppr?

NPN-opencollector-output:
How are your encoder-pins connected to the microcontroller?

yes ,my encoder when i tested it slowly with hands it counts 4096 .

red - 5v
black - Gnd
green - 9
white - 8

On this screenshot that youi posted there is no information about the interface-type

What kind of interface does your encoder type 3806-OPTI1024-ABZ-LD have?

TTL-output?
Open-collector-Output?

The datasheet you posted is a different type and the type intself is not even mentioned on the datasheet itself. It is only in the filename

Can you post the link of that website where you got the screenshot from?

Your algorithm is for half the encoder counts. 4096/2 per rev.

Here is a pin change version which reads the full 4 quadrature transitions.

const char encTable[16] = {0, 1, -1, 0, -1, 0, -0, 1, 1, 0, 0, -1, 0, -1, 1, 0}; //gives -1, 0 or 1 depending on encoder movement
volatile long encoderCount;
volatile long errorCount;
volatile byte encState;
unsigned long prevDisplay;
unsigned long interval = 1000;

void setup()
{
  Serial.begin(115200);

  pinMode(8,INPUT_PULLUP ); //output A
  pinMode(9,INPUT_PULLUP ); //output B

  //Enable pin change interrupts on pins 8 and 9 PB 0 and 1
  PCICR |= (1 << PCIE0); //enable group interrupts on PORTB PCINT[7:0]
  PCMSK0 |= (1 << PCINT0); //enable interrupt pin 8
  PCMSK0 |= (1 << PCINT1); //enable interrupt pin 9
}
void loop ()
{
  if (millis() - prevDisplay >= interval)
  {
    prevDisplay += interval;
    noInterrupts();
    long copyEncoderCount = encoderCount;
    encoderCount = 0;
    long copyErrorCount = errorCount;
    errorCount = 0;
    interrupts();

    Serial.print(copyEncoderCount);
    Serial.print('\t');
    Serial.println(copyErrorCount);
  }
}

ISR (PCINT0_vect)
{
  encState = ((encState << 2) | ((PINB) & B00000011)) & B00001111; //use encoder bits and last state to form index
  encoderCount += encTable[encState];//update actual position on encoder movement
  if (encTable[encState] == 0)
    errorCount++;
}

thanks for helping me a lot ,3806-OPTI1024 -ABZ-LD this model no is correct ,and in the manual it has been mentioned we dont need the external resistor as it is present is arduino i have uploaded a part image of user manual containing this info , and this link is the vedio from which i copied my first program

thanks for the code it worked well but, can u please explain how the code works and still threre is some error , that is count at every end of the cycle vary between 4000 to 4040 ,it is just a 1.5 pecent error in total but it will good if we are able to delete them

there is some error , that is count at every end of the cycle vary between 4000 to 4040

Please post the exact code you are running.

I would like to see how you are reading the z-axis pulse and getting the count.

Well if you do a deeper analyse of the code that cattledog posted you will see that cattledog's code uses a time-interval of 1000 milliseconds to print the values.

This means how many counts will be printed depends on the rpm.

If you modified cattledog's code to print each time a full rotation occurred the minimum you have to post your complete code for further advice.

You are assuming that your proximity sensor is ultrafast = switching in femto-seconds. You haven't provided any information how fast your proximity sensor really is. You haven't provided any information about the proximity-sensor at all. And you haven't checked or measured with an oscilloscope how fast your proximity-sensor really is.

So the differencies in the counted numbers might be caused by the limited switching-speed of your proximity-sensor.

best regards Stefan

Several faults I see imediately. attachInterrupt() should be called once for each pin, in setup(), and never called again.

Secondly you are not accessing the volatile variables in critical sections so they will get garbled (unless you are on a 32 bit microcontroller).

To read encoderValue safely in a critical section try this:

  noInterrupts() ;
  long copyEncoderValue = encoderValue ;
  interrupts() ;
  // now use copyEncoderValue at leisure

The reason this is necessary is that microcontroller will otherwise read the long variable byte-by-byte with nothing to stop the interrupt running between the bytes. A critical section is executed atomically (from the perspective of the interrupt routine), mirroring how the interrupt executes atomically from the perspective of the rest of the program.

const char encTable[16] = {0, 1, -1, 0, -1, 0, -0, 1, 1, 0, 0, -1, 0, -1, 1, 0}; //gives -1, 0 or 1 depending on encoder movement
volatile long encoderCount;
volatile long errorCount;
volatile byte encState;
unsigned long prevDisplay;
unsigned long interval = 0;
int b = 0 ;
int on;
int ir_on = 4;

void setup()
{
  Serial.begin(115200);

  pinMode(8, INPUT_PULLUP ); //output A
  pinMode(9, INPUT_PULLUP ); //output B

  //Enable pin change interrupts on pins 8 and 9 PB 0 and 1
  PCICR |= (1 << PCIE0); //enable group interrupts on PORTB PCINT[7:0]
  PCMSK0 |= (1 << PCINT0); //enable interrupt pin 8
  PCMSK0 |= (1 << PCINT1); //enable interrupt pin 9
}
void loop ()
{
  on = digitalRead(ir_on);


  if (on == LOW)
  {
    b = 1;
    encoderCount = 0;
  }
  if (b == 1)
  {
    if (millis() - prevDisplay >= interval)
    {
      prevDisplay += interval;
      noInterrupts();
      long copyEncoderCount = encoderCount;
      //encoderCount = 0;
      long copyErrorCount = errorCount;
      //errorCount = 0;
      interrupts();

      Serial.print(copyEncoderCount);
      Serial.print('\t');
      Serial.println(copyErrorCount);
    }
  }
}

ISR (PCINT0_vect)
{
  encState = ((encState << 2) | ((PINB) & B00000011)) & B00001111; //use encoder bits and last state to form index
  encoderCount += encTable[encState];//update actual position on encoder movement
  if (encTable[encState] == 0)
    errorCount++;
}
const char encTable[16] = {0, 1, -1, 0, -1, 0, -0, 1, 1, 0, 0, -1, 0, -1, 1, 0}; //gives -1, 0 or 1 depending on encoder movement
volatile long encoderCount;
volatile long errorCount;
volatile byte encState;
unsigned long prevDisplay;
unsigned long interval = 0;
int b = 0 ;
int on;
int ir_on = 4;

void setup()
{
  Serial.begin(115200);

  pinMode(8, INPUT_PULLUP ); //output A
  pinMode(9, INPUT_PULLUP ); //output B

  //Enable pin change interrupts on pins 8 and 9 PB 0 and 1
  PCICR |= (1 << PCIE0); //enable group interrupts on PORTB PCINT[7:0]
  PCMSK0 |= (1 << PCINT0); //enable interrupt pin 8
  PCMSK0 |= (1 << PCINT1); //enable interrupt pin 9
}
void loop ()
{
  on = digitalRead(ir_on);


  if (on == LOW)
  {
    b = 1;
    encoderCount = 0;
  }
  if (b == 1)
  {
    if (millis() - prevDisplay >= interval)
    {
      prevDisplay += interval;
      noInterrupts();
      long copyEncoderCount = encoderCount;
      //encoderCount = 0;
      long copyErrorCount = errorCount;
      //errorCount = 0;
      interrupts();

      Serial.print(copyEncoderCount);
      Serial.print('\t');
      Serial.println(copyErrorCount);
    }
  }
}

ISR (PCINT0_vect)
{
  encState = ((encState << 2) | ((PINB) & B00000011)) & B00001111; //use encoder bits and last state to form index
  encoderCount += encTable[encState];//update actual position on encoder movement
  if (encTable[encState] == 0)
    errorCount++;
}

proximity sensor : KOSMOS LM12-3004NA

DATA SHEET:

THE WORKING FREQUENCY OF THIS MODEL IS 1000HZ .

There are several possible reasons for the count not being 4096 per second.

How do you know the exact rpm of the encoder?
How do you know that 1000ms is exactly that length of time given the precision of the processor clock source?

What are you really trying to accomplish?