Sunday 12 January 2020

STM32 Rotary Encoder with Hardware Interrupts


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;
}
}

Answer



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


No comments:

Post a Comment

arduino - Can I use TI&#39;s cc2541 BLE as micro controller to perform operations/ processing instead of ATmega328P AU to save cost?

I am using arduino pro mini (which contains Atmega328p AU ) along with cc2541(HM-10) to process and transfer data over BLE to smartphone. I...