mouse quad rotary encoder, counter jumps

i have one logitech mouse quad rotary encoder attached to pins 2,3 and two attachInterupts to trigger on CHANGE.

the interrupts update a counter depending on positive or negative direction. the counter is a volatile long which sometimes gives really jumpy values:

// [pin2 state],[pin3 state] [counter]
// ....
1,0 33
0,0 32
0,1 31
1,1 30
1,0 29
0,1 27
1,0 25
0,1 23
1,0 21
0,1 19
1,0 409517
1,1 14
0,0 12
1,1 10
0,0 8
// ....

wiring:

<GND>----<R (330 ohm)>----<LED>----<pin 7, HIGH>

<GND>
   |
<LDR1>----<pin 2>
   |
<5V>

<GND>
   |
<LDR2>----<pin 3>
   |
<5V>

code:

int ledPin = 7;

int aPin = 0;  // digital pin 2
int bPin = 1;  // digital pin 3

volatile int aState = LOW;
volatile int bState = LOW;

volatile int last = 0;

volatile long counter = 0;
volatile int counterChanged = 1;
volatile int changing = 0;

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

  pinMode( ledPin, OUTPUT );
  digitalWrite( ledPin, HIGH );

  attachInterrupt( aPin, aChange, CHANGE );
  attachInterrupt( bPin, bChange, CHANGE );
}

void loop ()
{
  if ( counterChanged && !changing )
  {
    Serial.print( aState );
    Serial.print( "," );
    Serial.print( bState );
    Serial.print( " " );
    Serial.println( counter );
    counterChanged = 0;
  }
}

void aChange ()
{
  changing = 1;
  aState = !aState;
  updateCounter();
  changing = 0;
}

void bChange ()
{
  changing = 1;
  bState = !bState;
  updateCounter();
  changing = 0;
}

void updateCounter ()
{
  int now = (aState << 1) + bState;
  if ( (last == 0 && now == 2) ||
    (last == 2 && now == 3) ||
    (last == 3 && now == 1) ||
    (last == 1 && now == 0) )
  {
    counter++;
    counterChanged = 1;
  }
  else if ( 
  (last == 0 && now == 1) ||
    (last == 1 && now == 3) ||
    (last == 3 && now == 2) ||
    (last == 2 && now == 0) )
  {
    counter--;
    counterChanged = 1;
  }
  else
  {
    // ignore 
  }
  last = now;
}

can anyone see the problem here? why would a counter that's updated with ++ or -- be so jumpy? is this due to the serial communication being to tight in loop()?

many thanks!
F

btw, this is to measure a cd-tray which i'm sliding in / out. i'm trying to get around having to read the mouse via PS/2 (http://vimeo.com/1428015) which will not allow to exactly control the motor to stop at a certain position.

Hi,
From what I see, you are having an interaction between interrupts and the loop code. You tried to protect the counter value with the variable changing, but it doesn't work. The interrupt can occur right in the middle of printing the counter. Since it's a long, changing the value of counter is actually about 4 instructions, even though you used ++/--.

Try this:

void loop ()
{
  long stash;

  if ( counterChanged && !changing )
  {
    Serial.print( aState );
    Serial.print( "," );
    Serial.print( bState );
    Serial.print( " " );
    noInterrupts();
    stash = counter;
    interrupts();
    Serial.println(stash);
    counterChanged = 0;
  }

If that doesn't work, well then you need to ask someone else ;).

haven't tried it yet but sounds right. i'll let you know how it goes, thanks a lot!

F

okey. i tested your code and sorry to say it does not change anything.

what i've found (thanks for the hint) is that if i put all Serial.print()s inside noInterrupt()-interrupt(), then it will give correct readings. i read up on the attachInterrupts and it says that these are halting the "normal" loop() (any code) until the interrupt callbacks are done. actually than the false readings make sense since Serial has a timing factor in it's implementation and therfore interrupting it will result in false output or dropped serial connections (as i have it).

so now that i can tell my hacked mouse rotary-encoder works (values are ok) and since i don't need to send only sense them, i can go ahead. problem solved. magnifico!

F

here's my final code (reading the quad rotary encoder) for now.

int ledPin = 7;

int aPin = 0;  // digital pin 2
int bPin = 1;  // digital pin 3

volatile int aState = LOW;
volatile int bState = LOW;

volatile int last = 0;
volatile int now = 0;

volatile long steps = 0;
volatile float rotations = 0;
volatile int stepsChanged = 1;

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

  pinMode( ledPin, OUTPUT );
  digitalWrite( ledPin, HIGH );

  attachInterrupt( aPin, aChange, CHANGE );
  attachInterrupt( bPin, bChange, CHANGE );
}

void loop ()
{
  long stash;

  if ( stepsChanged )
  {
    noInterrupts();   // make sure interrupts won't interfere with Serial
    Serial.print( aState );
    Serial.print( "," );
    Serial.print( bState );
    Serial.print( "\t" );
    Serial.print( steps );
    Serial.print( "\t" );
    Serial.print( rotations < 0 && int(rotations) == 0 ? "-" : "" );
    Serial.print( int(rotations) );
    Serial.print( "." );
    Serial.println( int(abs(rotations-int(rotations))*100) );
    interrupts();
    stepsChanged = 0;
  }
}

void aChange ()
{
  aState = !aState;
  updatesteps();
}

void bChange ()
{
  bState = !bState;
  updatesteps();
}

void updatesteps ()
{
  now = (aState << 1) + bState;
  if ( (last == 0 && now == 2) ||
    (last == 2 && now == 3) ||
    (last == 3 && now == 1) ||
    (last == 1 && now == 0) )
  {
    steps++;
    stepsChanged = 1;
  }
  else if ( 
  (last == 0 && now == 1) ||
    (last == 1 && now == 3) ||
    (last == 3 && now == 2) ||
    (last == 2 && now == 0) )
  {
    steps--;
    stepsChanged = 1;
  }
  else
  {
    // ignore 
  }
  if (stepsChanged)
  {
    rotations = steps / 260.0;  // one rotation of the encoder wheel = 260
  }
  last = now;
}

F

Hi to all,
i have a similar encoder taken from a mouse.
i have found data sheet and, connection schema

i have tried the latest code found here and works but too often
"steps" variable, change direction when i turn encoder in the same direction!

do you have the same problem or do you have found a solution or someone have a suggestion ?

thanks a lot

attachInterrupt( aPin, aChange, CHANGE );
attachInterrupt( bPin, bChange, CHANGE );

Try changing CHANGE to FALLING or RISING. It will cut your interrupts in half and you may not need the increased resolution (and interrupt burden) the CHANGE give you.

Lefty

thanks retrolefty.
I have tried but is almost the same thing.
i have little best result, with FALLING.
i think is voltage problem, i have used schema with 2 resistor 10ko
between 5v and pin.
Example if i use input 3.3 the result is very bad, so i want try to decrease a resistence value to try stabilize signal.
I will update you :wink:

You need to respond to CHANGEs on both pins, otherwise you cannot determine direction at all!

You only need one interrupt routine attached to both interrupts and its logic should compare actual pin states (digitalRead) with previous pin state and either increment or decrement the counter - that's all. Read the pins to determine their state, don't use the aState and bState variables, these are guesses that will go wrong when you miss an interrupt due to a short glitch - this will spuriously reverse direction.

Do you know if the hardware is debounced by the way?

Never disable interrupts for long - enclosing multiple Serial.print()'s like this will lose you many many interrupts. Just disable around reading the counter to another variable.

actually than the false readings make sense since Serial has a timing factor in it's implementation and therfore interrupting it will result in false output or dropped serial connections (as i have it).

No, the Serial library uses the hardware UART,

You need to respond to CHANGEs on both pins, otherwise you cannot determine direction at all!

I don't think that is an accurate statement. I use a rated '128' step optical encoder starting with generic code from the arduino playground. I was able to to obtain step counts of 128, 256, and 512 per encoder revolution, depending on the combination of interrupting on one or both channels and using FALLING or CHANGE interrupt detection. Both channels need to be wired to input pins, but there is not a need to interrupt on both pins unless increased steps per revolution is desired. The industrial standard for defining a step is four complete transitions of the a and b channels. But this can be doubled twice by interrupting on one or two channels and interrupting on one edge or both edge transition. One can even process a two channel encoder just using digitalRead() functions, but at reduced speed depending on what else the code is doing. One only needs to be able to read both channels to determine step and direction information.

It's almost the same concept that one can 'microstep' a stepping motor to get more smaller but more steps per revolution then the motor is rated at.

Lefty