3
\$\begingroup\$

I'm trying to get a rotary encoder working on my STM32.

  1. I have channel A & B being pulled up to 3V and debounced with 1uF capacitors.
  2. The board has channel A & B connected to PA11 & PA10 respectively and have configured hardware interrupts for both
  3. I've tried a number of different algorithms to decode the direction of rotation but no matter what I do I can't get consistent alternating interrupts (ie. ABABAB)
  4. I've tried triggering on both edges, just falling, just rising and no matter what I get the interrupts triggering seemingly randomly.

Is there something I'm doing wrong? Are the interrupts not enough to keep up with the speed of the encoder? Is the debouncing not enough (Looks okay on a scope but maybe the interrupts are more sensitive)? Is there a better way to do this in general? I've been stuck on this for a while so any help would be great. Let me know if more info is necessary.

*Code Edited to reflect changes (working algorithm)

INTERRUPT HANDLER

static int8_t states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
uint8_t RotaryCurrentState = 0x00;
uint8_t RotaryTransition = 0;
int8_t RotaryPosition = 0;

/*Rotary Encoder Interrupt Handler
Channel A (IRQ11) & B (IRQ10)

              CW --->
 A (IRQ11)  ¯|___|¯¯¯¯|___|¯¯¯¯
 Interrupts  ^   ^    ^   ^
 B (IRQ 10) ¯¯¯|___|¯¯¯¯¯|___|¯
 Interrupts    ^   ^     ^   ^
                  CCW <---

void EXTI4_15_IRQHandler(void)
{ 
  RotaryCurrentState = (Read_IO(ROTARY_A) << 1) | Read_IO(ROTARY_B);
  RotaryTransition = (RotaryTransition <<2 ) | RotaryCurrentState;
  RotaryPosition = RotaryPosition + states[RotaryTransition & 0x0F];

  EXTI_ClearITPendingBit(EXTI_Line10);  //Clear Channel B
  EXTI_ClearITPendingBit(EXTI_Line11);  //Clear Channel A
}

MAIN.C

//Initialize 
RotaryTransition = Read_IO(ROTARY_A) << 3 | Read_IO(ROTARY_B) << 2 | \
                   Read_IO(ROTARY_A) << 1 | Read_IO(ROTARY_B);

while(1)
{
  //CW Transition
  if (RotaryPosition == 4)
  {
    STM_EVAL_LEDToggle(LED4);
    RotaryPosition = 0;
  }
  //CCW Transition
  else if (RotaryPosition == -4)
  {
    STM_EVAL_LEDToggle(LED3);
    RotaryPosition = 0;
  }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ Encoders on the STM32F4 can be directly handled with timers. \$\endgroup\$ – Scott Seidman Feb 16 '14 at 4:09
  • \$\begingroup\$ Unfortunately I'm stuck using these IOs because of board design and IO limitations. I don't think they can do the decoding using the timer. \$\endgroup\$ – spizzak Feb 16 '14 at 6:21
  • 2
    \$\begingroup\$ These encoders have two channels. The easiest way to use them is to use one as 'clock' and attach a single edge triggered interrupt to it, then the other channel acts as a 'direction' indicator. On each interrupt you inspect the value of the 'direction' channel and either add or subtract one step from the current value. \$\endgroup\$ – jippie Feb 16 '14 at 8:21
  • \$\begingroup\$ Important details: How exactly are the interrupt inputs being pulled high? Do the interrupt inputs have hysteresis? \$\endgroup\$ – Joe Hass Feb 16 '14 at 13:17
  • \$\begingroup\$ Aren't you shifting way too much?? Shouldn't the 8 be a 2? \$\endgroup\$ – Scott Seidman Feb 16 '14 at 14:11
6
\$\begingroup\$

That's not a great algorithm in your handler. You should have ZERO ifs. No decisions.

Store your AB state, i.e., 00 or 01, then append your next state, i.e 0001 means AB went from 00 to 01,thus B changed from 0 to 1. Make this a +1. If starting from 00, and you change to 10, then call this a -1. Build a 16 element array of all possible transitions holding the number that needs to be added to your count if it occurs, noting that some are illegal and need to be handled.

0000       0    0  no transition


0001       1   +1

0010       2   -1

0011       3    0   Illegal, two transitions,  and so on.

Index into this array on every transition, watching for illegal events and dealing with them as you see fit. Add the result to the count. Shift the new values to the old value spot in the index number, and repeat forever

In Pseudocode

 signed int8 add_subt[16] = { 0, 1 ,-1 ,  ....};
 unsigned int8 idx;
 signed int32 pos_count;

main() {
% initialize idx
idx = readA <<3 + readB<<2 + readA<<1 + readB;
while(1){}
}

interrupt_on_any_change(){
idx=idx<<2 & 0x0F + readA<<1 + readB;
pos_count=pos_count+add_subt[idx];
}

You could maintain err_idx to help you flag bad transitions

Doesn't that look a hair simpler??

\$\endgroup\$
  • \$\begingroup\$ These seems to make sense and I saw a similar example online while I was searching that implemented the array as a 4x6 state machine which I thought was an interesting approach. So basically I would trigger on both channels' rising & falling edge and then do I read the value of both lines on every interrupt? \$\endgroup\$ – spizzak Feb 16 '14 at 15:35
  • \$\begingroup\$ Yup. trigger on every transition and read A and B into the bottom two bits of the index. \$\endgroup\$ – Scott Seidman Feb 16 '14 at 16:16
  • \$\begingroup\$ No need for a state machine, either. One array, by index, tells you if you need to increment or decrement your count. \$\endgroup\$ – Scott Seidman Feb 16 '14 at 16:18
  • 1
    \$\begingroup\$ Implemented and working great! Thanks for the suggestion. I've updated my code in the original post to the working version. \$\endgroup\$ – spizzak Feb 17 '14 at 18:53
  • \$\begingroup\$ Good Job!! Originally, I thought you were trying to use this for a motor's quad encoder, in which case interrupts on the DIO's would probably be borderline, but then I actually linked through to the part you were working with!! I think you can actually do this a bit smaller by using two integers, one for increment and one for decrement, and bitmasking to the appropriate bit, something like count + incr&(1<<idx) - decr&(1<<idx) , but if you can afford the array, why not! \$\endgroup\$ – Scott Seidman Feb 17 '14 at 23:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.