3

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;
  }
}
spizzak
  • 677
  • 3
  • 10
  • 20
  • 2
    Encoders on the STM32F4 can be directly handled with timers. – Scott Seidman Feb 16 '14 at 04:09
  • 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. – spizzak Feb 16 '14 at 06:21
  • 2
    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. – jippie Feb 16 '14 at 08:21
  • Important details: How exactly are the interrupt inputs being pulled high? Do the interrupt inputs have hysteresis? – Joe Hass Feb 16 '14 at 13:17
  • Aren't you shifting way too much?? Shouldn't the 8 be a 2? – Scott Seidman Feb 16 '14 at 14:11
  • You may not actually need to debounce a quadrature encoder - it may oscillate between two states, but it shouldn't accumulate false counts as only one channel should be in the "bouncing zone" at a time. It's conceivable that a poorly thought out debounce circuit could actually lead to erroneous interpretation. To get both channels bouncing at once, you'd have to have a rotation (commutation) rate close to the bouncing duration, at which point it doesn't seem like it would be possible to define a debounce characteristic which would exclude that without sometimes masking the actual signal. – Chris Stratton Feb 16 '14 at 14:44
  • I've tried the clocking method and while slightly more reliable it was still not usable. – spizzak Feb 16 '14 at 15:30
  • The lines are being pulled high by 10k resistors on the board. I don't see why they would have hysteresis but maybe I'm missing something. – spizzak Feb 16 '14 at 15:31
  • I'm encoding each state as an 8 bit number and the combined states as 16 bit so I think I'm shifting correctly although perhaps not the most efficiently. – spizzak Feb 16 '14 at 15:32
  • @spizzak Hysteresis can be critical if you are trying to detect an edge on an input with a slowly rising edge. You have an RC time constant of 10 ms on your inputs which is very, very slow and leaves them susceptible to false triggering due to noise. The NXP processors I use have hysteresis on the inputs to eliminate this problem, I don't know about ST processors. – Joe Hass Feb 16 '14 at 23:51
  • Good Job!! The only thing I would add to this is to set a flag in the interrupt, and only run the checking in the main loop if there's a change in the encoder status: while(~encoderflag){} ; ..... It's just so you know you're entering the next block at the appropriate place – Scott Seidman Feb 17 '14 at 23:35
  • Minor detail, but you can get away with just one call to `EXTI_ClearITPendingBit`: `EXTI_ClearITPendingBit(EXTI_Line10 | EXTI_Line11)` – Kamil Kisiel Mar 03 '16 at 19:02

1 Answers1

6

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??

Scott Seidman
  • 29,274
  • 4
  • 44
  • 109
  • 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? – spizzak Feb 16 '14 at 15:35
  • Yup. trigger on every transition and read A and B into the bottom two bits of the index. – Scott Seidman Feb 16 '14 at 16:16
  • No need for a state machine, either. One array, by index, tells you if you need to increment or decrement your count. – Scott Seidman Feb 16 '14 at 16:18
  • 1
    Implemented and working great! Thanks for the suggestion. I've updated my code in the original post to the working version. – spizzak Feb 17 '14 at 18:53
  • 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< – Scott Seidman Feb 17 '14 at 23:22
  • This is embedded coding style! We so often see interrupts with a lot of things inside. No! Interrupts should be like the proposed one: Damn fast! – Blup1980 Feb 18 '14 at 06:21
  • Oops, that last bit would be count = count + (incr&(1<>idx - (decr&(1<>idx. I think that's a bit less assembly than (incr&(1<0. I have a feeling that the code might be a bit slower for this approach than the array approach. The correct approach, of course, depends on how much memory you're trying to squeeze out of the system – Scott Seidman Feb 18 '14 at 13:17
  • When completing the transition table, aren't all other transitions either an invalid state or a zero transition? – Michael Jul 16 '19 at 20:01
  • @Michael no. There are legitimate entries for every starting state. 0 1 can go to 0 0 for example. The table snippet I showed is only for starting at 0 0 – Scott Seidman Jul 16 '19 at 21:08
  • @Scott thank you. I think I might be missing a basic understanding of how this encoder or this algorithm works? I thought the goal is to effectively count positive edge transitions with the relationship between A and B determining direction. As such the other transitions are not needed for counting and therefore dont provide useful information to the sum? – Michael Jul 16 '19 at 23:00
  • The interrupt gets run at every transition, so transitions are baked into the cake – Scott Seidman Jul 17 '19 at 02:42
  • I see. I found some assistance on another page using this type of algorithm. The point I missed is that the relationship between pos_count and the DPR of the encoder: 360/(degrees_per_revolution*4) = degrees_per_pos_count – Michael Jul 17 '19 at 12:42