[SOLVED] What is wrong with my rotary encoder?

I don't know what else to do!

Usually, problems with encoders are on high-speed rotations. but, my problem is on the low-speed rotation.
When rotating on high-speed (by hand) everything works well. but, rotating very slowly, counter stays almost around the same number (gets lower and higher).

I searched enough and couldn't find the same behavior. I think it doesn't miss pulses but counts backward on rotating slowly.

I didn't use any library. I use 2 interrupts on pinA and pinB with direct port reading. Also, cw is - and ccw +

void outAChange() {
  lastA = intA;
  intA = GPIOB->regs->IDR & decimalOutA ? 1 : 0;
  encoder += intA == intB ? 1 : -1;
}

void outBChange() {
  lastB = intB;
  intB = GPIOB->regs->IDR & decimalOutB ? 1 : 0;
  encoder += intA == intB ? -1 : 1;
}

I also tried reading both pins in each interrupt but the result is the same.

Don’t post snippets (Snippets R Us!)

are your variables volatile ? did you check with an Encoder Library to see if it behaves correctly without your own code? (this one is good)

(would also suggest to use parenthesis for readability in your ternary expressions, not sure everyone knows the precedence of +=(16) versus ==(10) versus a?b:c(16))

It sounds like you may be getting multiple interrupts as the encoder slowly transitions between states. Especially mechanical encoders but you didn't specify.

Thanks for reply

It's an optical encoder HN3806-AB-200N

And yes my variables are volatile. Also, the Encoder library doesn't support my board (stm32f103c8t6 72mhz).

STM32duino ?

did you set the internal pull-up resistor for A and B?

Yes

all code

const int pinA = PB12;
const int pinB = PB13;
const long decimalOutA = 0b0001000000000000;
const long decimalOutB = 0b0010000000000000;

volatile long rotaryHalfSteps = 0;
volatile uint8_t int0signal = 0;
volatile uint8_t int1signal = 0;

void int0()
{
  int0signal = GPIOB->regs->IDR & decimalOutA ? 1 : 0;
  if ( int0signal == int1signal )
    rotaryHalfSteps++;
  else
    rotaryHalfSteps--;
}

void int1()
{
  int1signal = GPIOB->regs->IDR & decimalOutB ? 1 : 0;
  if ( int0signal == int1signal )
    rotaryHalfSteps--;
  else
    rotaryHalfSteps++;
}


void setup()
{
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(pinA), int0, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pinB), int1, CHANGE);
  Serial.begin(115200);
}

void loop()
{
  long actualRotaryTicks = (rotaryHalfSteps / 2);
  Serial.println(actualRotaryTicks);
  delay(50);
}

OK

may be there are 'bounces' when you turn really really slowly? (weird though on optical encoder)

have you tried a bit of a different code in the ISRs to capture the quadrature sequence?

Yes

All codes are doing the same thing. It’s just checking A and B.
Is it ok to use micros() inside interrupts and put a delay between each run?
I mean something like this inside interrupts:

if (micros() - t < 100)
  return

I’m worried about missing pulses.

Reading micros() or millis() is fine (millis just won't get updated)

what would be interesting to see is if you really get a proper sequence of int0 and int1 (you should not get the same one twice in a row)

This might show you the sequence

const int pinA = PB12;
const int pinB = PB13;
const long decimalOutA = 0b0001000000000000;
const long decimalOutB = 0b0010000000000000;

volatile long rotaryHalfSteps = 0;
volatile uint8_t int0signal = 0;
volatile uint8_t int1signal = 0;

const int samples = 200;
volatile uint8_t intValues[samples];
volatile uint8_t flag = false;
volatile uint8_t idx = 0;

void int0()
{
  int0signal = GPIOB->regs->IDR & decimalOutA ? 1 : 0;
  if ( int0signal == int1signal )
    rotaryHalfSteps++;
  else
    rotaryHalfSteps--;
  if ( !flag ) {
    intValues[idx++] = 'a';
    if ( idx >= samples ) flag = 1;
  }
}

void int1()
{
  int1signal = GPIOB->regs->IDR & decimalOutB ? 1 : 0;
  if ( int0signal == int1signal )
    rotaryHalfSteps--;
  else
    rotaryHalfSteps++;
  if ( !flag ) {
    intValues[idx++] = 'b';
    if ( idx >= samples ) flag = 1;
  }
}


void setup()
{
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(pinA), int0, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pinB), int1, CHANGE);
  Serial.begin(115200);
}

void loop()
{
  Serial.println("Counting ticks");
  while( !flag );
  for(int i=0; i<samples; i++ )
    Serial.println(intValues[i]);
  idx = 0;
  flag = 0;
}

Turning slowly invokes one of the ISRs (randomly) multiple times in a row.
I also tried with reading both pins instead of one pin but no difference.
Any idea why one pin changes multiple times in a row?
I will try some different codes.

It seems the solution is to detect false interrupt invokes:

void int0()
{
  int0history = int0signal;
  int0signal = GPIOB->regs->IDR & decimalOutA ? 1 : 0;
  if (int0history == int0signal) return;
  if ( int0signal == (GPIOB->regs->IDR & decimalOutB ? 1 : 0) )
    rotaryHalfSteps++;
  else
    rotaryHalfSteps--;
}

void int1()
{
  int1history = int1signal;
  int1signal = GPIOB->regs->IDR & decimalOutB ? 1 : 0;
  if (int1history == int1signal) return;
  if ( (GPIOB->regs->IDR & decimalOutA ? 1 : 0) == int1signal )
    rotaryHalfSteps--;
  else
    rotaryHalfSteps++;
}

It works so far!
Thanks for reply

Seems when you move slowly you get « bounces » then..

J-M-L:
Seems when you move slowly you get « bounces » then…

Can you explain more? I can’t understand how it bounces.
The interrupt code runs multiple times when even there is no on a single change. Interrupt says A changed but when we check inside the function, A didn’t change. And the same for B.

Imagine the encoder is rotating and coming up on a transition for interrupt A. As you slowly rotate, this transition is at the very edge of changing state and firing interrupt A. Then it does, but you are still on the edge of transition so the pin may momentarily read low which causes another CHANGE interrupt. By the time you are actually reading the pin, it may read HIGH again since you are still on the edge of the transition. Although the encoder may be optical, it is still mechanical movement which, at some level, even microscopic, is jittery.

The only perfect, instantaneous transition is in a textbook and our heads.

I thought microcontroller is fast enough to detect that edge changes in both increasing and decreasing.
I mean theoretically when on edge it reads 1, runs encoder++
and when it reads 0, also on the edge, runs encoder--

Thanks for explanation

If "bouncing" is the problem, check you the State Table approach to handling encoders. It inherently handles bouncing as part of its algorithm --- Buxtronix: Rotary encoders, done properly

Here's my Encoder Library that uses this technique. Feel free to download and modify it for STM32.

It looks that i have similar problem but i dont think its directly connected with speed, maybe partly.
This post is update to my last post where i have used basic rotary arduino encoder.

The encoder that i use now is the same as the one in video accept mine has 400 PPR.

All together it works great but for some unknown reason it's loosing or adding steps.
Am using this encoder for counting liquid flow ( pump ). and there is no way i can recreate test scenario.
E.g. if i pour 1 liter i can see that the motor shaft of the pump, has turned 10 times.
Based on this i have define number of impulses. So, i turn 10x and you can see on display 001.00 and this is correct, BUT if i turn next 10 turns or 20 or 50, there is no way i can get 002.00 , 005.00 etc values.

I always get something between like 001.44 / 005.31 etc. It is important to mention that there may be some "wrong" full turns but there is no way that he misses so much steps.
Can some one explain why is this happening ?
We have cut all the delays and only thing that has left is the part where we send/read data from raspberry.
The code in attach is the main test code, but if necessary i can send whole working code ( with display output ).

//------------- main TEST code -----------------
//---- there no code snippet on chromium in ubuntu 12-- --

volatile unsigned int temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder

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

pinMode(2, INPUT_PULLUP); // internal pullup input pin 2

pinMode(3, INPUT_PULLUP); // internalเป็น pullup input pin 3
//Setting up interrupt
//A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
attachInterrupt(0, ai0, RISING);

//B rising pulse from encodenren activated ai1(). AttachInterrupt 1 is DigitalPin nr 3 on moust Arduino.
attachInterrupt(1, ai1, RISING);
}

void loop() {
// Send the value of counter
if( counter != temp ){
Serial.println (counter);
temp = counter;
}
}

void ai0() {
// ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
// Check pin 3 to determine the direction
if(digitalRead(3)==LOW) {
counter++;
}else{
counter--;
}
}

void ai1() {
// ai0 is activated if DigitalPin nr 3 is going from LOW to HIGH
// Check with pin 2 to determine the direction
if(digitalRead(2)==LOW) {
counter--;
}else{
counter++;
}
}

@arnix
I recommend checking my previously posted code + fixed code. Your method for counting steps will not work as expected. (I experienced these types of codes with my encoder)

Edit: Here is the working code on STM32F103C8T6. Make sure to change the pin numbers and reading methods based on your board.

const int pinA = PB12;
const int pinB = PB13;
const long decimalOutA = 0b0001000000000000;
const long decimalOutB = 0b0010000000000000;

volatile long rotaryHalfSteps = 0;
volatile uint8_t int0signal = 0;
volatile uint8_t int1signal = 0;


void int0()
{
  int0history = int0signal;
  int0signal = GPIOB->regs->IDR & decimalOutA ? 1 : 0;
  if (int0history == int0signal) return;
  if ( int0signal == (GPIOB->regs->IDR & decimalOutB ? 1 : 0) )
    rotaryHalfSteps++;
  else
    rotaryHalfSteps--;
}

void int1()
{
  int1history = int1signal;
  int1signal = GPIOB->regs->IDR & decimalOutB ? 1 : 0;
  if (int1history == int1signal) return;
  if ( (GPIOB->regs->IDR & decimalOutA ? 1 : 0) == int1signal )
    rotaryHalfSteps--;
  else
    rotaryHalfSteps++;
}


void setup()
{
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(pinA), int0, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pinB), int1, CHANGE);
  Serial.begin(115200);
}

void loop()
{
  long actualRotaryTicks = (rotaryHalfSteps / 2);
  Serial.println(actualRotaryTicks);
  delay(50);
}

mahdiyari:

const int pinA = PB12;

const int pinB = PB13;
const long decimalOutA = 0b0001000000000000;
const long decimalOutB = 0b0010000000000000;

void loop()
{
  long actualRotaryTicks = (rotaryHalfSteps / 2);
  Serial.println(actualRotaryTicks);
  delay(50);
}

i would recommend declaring pinA and PINB as const uint8_t and the 2 bitmasks as const uint32_t

For avoiding possible unwanted misplaced interrupt in non 32 bit architecture with guaranteed read atomicity, I would put this assignment long actualRotaryTicks = (rotaryHalfSteps / 2);in a critical protected (ISR Blocked) section. (With your architecture it should be OK for a shift left if the data is word aligned, so just in case, declare actualRotaryTicks as int32_t which is guaranteed to be word aligned not sure if long is).