Skip to main content
Associate II
September 19, 2024
Solved

Using an encoder: Jitter Incorrectly Trigger Interrupts

  • September 19, 2024
  • 4 replies
  • 1808 views

Hello,

I‘m creating a counter that increments/decrements according to cw/ccw direction of an encoder. 
An interrupt is triggered by the rising and falling edge on each channel. 
Due to mechanical jitter, the interrupts are triggered (sometimes) simultaneously and will increment by values greater than 1. I was thinking of using a delay but how can I make my interrupt handlers more robust to prevent this occurring?

 

 

int main(){

 FLASH_Init();

 RCC_Init();

 GPIO_Init();

 USART2_Init();

 EXTI0_Init();

 EXTI1_Init();

 NVIC_Init(); 

 while(1){

 

 }

}

 

void EXTI0_IRQHandler(void){

 if (EXTI -> PR & (0x01U)){

 if (((GPIOA -> IDR & (0x1U)) && (GPIOA -> IDR & (0x2U))) || ((!(GPIOA -> IDR & (0x1U))) && (!(GPIOA -> IDR & (0x2U))))){

 STATE++;

 printf("Encoder counts = %d \n\r",STATE);

 //GPIOA -> ODR ^= (0x01U << 5);

 }

 if (((GPIOA -> IDR & (0x1U)) && (!(GPIOA -> IDR & (0x2U)))) || ((!(GPIOA -> IDR & (0x1U))) && (GPIOA -> IDR & (0x2U)))) {

 STATE --;

 printf("Encoder counts = %d \n\r",STATE);

 }

 }

 EXTI -> PR = (0xFFFFFU);

// NVIC -> ICER[0] |= (0x1U << 6);

// NVIC -> ISER[0] |= (0x1U << 7);

}

 

void EXTI1_IRQHandler(void){

 if (EXTI -> PR & (0x01U<<1)){

 if (((!(GPIOA -> IDR & (0x2U))) && (GPIOA -> IDR & (0x1U))) || ((GPIOA -> IDR & (0x2U)) && (!(GPIOA -> IDR & (0x1U))))) {

 STATE ++;

 printf("Encoder counts = %d \n\r",STATE);

 //GPIOA -> ODR ^= (0x01U << 5);

 }

 if (((GPIOA -> IDR & (0x2U)) && (GPIOA -> IDR & (0x1U))) || ((!(GPIOA -> IDR & (0x2U))) && (!(GPIOA -> IDR & (0x1U))))){

 STATE --;

 printf("Encoder counts = %d \n\r",STATE);

 }

 

 }

 EXTI -> PR = (0xFFFFFU);

// NVIC -> ICER[0] |= (0x1U << 7);

// NVIC -> ISER[0] |= (0x1U << 6);

}

 

Best answer by XPChi

Thanks for the advice. It took a while, but I have 4 encoders running on my PCB now with no issues at all. Here are a few things I've done (which I understand may not be the best approach but have solved the issue):

  • A debounce period that is flagged by the EXTI handler and cleared by a timer after a required period has elapsed
  • The EXTI handler does as little logic processing as possible - it simply sets/clears flags

  • Another timer is used as a 'heartbeat' signal and circular buffer

The printf statement was removed after all testing/development was finalised. This was a good (but troublesome) learning experience

4 replies

mƎALLEm
Technical Moderator
September 19, 2024

Hello @XPChi and welcome to the community,

First of all, remove all these printfs from the interrupt handlers. This blocks the execution for a considerable time.

See this link: https://developer.arm.com/documentation/ka002394/latest/

Second, I'm wondering why you are clearing all the EXTI flags for a specific EXTI interrupt. Also you need to clear the interrupt as soon as you get into the interrupt handler.

See for example how The EXTI interrupt handler is implemented in HAL:

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
 /* EXTI line interrupt detected */
 if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
 {
 __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
 HAL_GPIO_EXTI_Callback(GPIO_Pin);
 }
}

 

"To give better visibility on the answered topics, please click on ""Accept as Solution"" on the reply which solved your issue or answered your question."
XPChiAuthor
Associate II
September 19, 2024

Hi @mƎALLEm , thanks for the welcome. I'm avoiding using HAL as I would rather have full control of all the registers that I'm using and also for debugging purposes.
I'm aware of the interrupt clear, this due to the fact that only EXTI0 and EXTI1 are being used. I clear them after an interrupt has been triggered to prevent a jitter enabling an interrupt incorrectly.

 

I've cleared  the interrupt in the beginning as you suggested, however, this doesn't seem to ignore any mechanical noise.

Thanks 
XPChi

mƎALLEm
Technical Moderator
September 19, 2024

@XPChi wrote:

I'm aware of the interrupt clear, this due to the fact that only EXTI0 and EXTI1 are being used. 


Better to clear them separately.

 


@XPChi wrote:

I'm avoiding using HAL as I would rather have full control of all the registers that I'm using and also for debugging purposes.


I'm not suggesting you to use HAL but inspiring from it.

Did you also remove all the printfs from the IRQ handlers?

"To give better visibility on the answered topics, please click on ""Accept as Solution"" on the reply which solved your issue or answered your question."
TDK
Super User
September 19, 2024

> EXTI -> PR = (0xFFFFFU);

Don't blindly clear all the flags. You may be clearing ones which haven't been processed yet. Only clear the flag you have handled.

"If you feel a post has answered your question, please click ""Accept as Solution""."
Karl Yamashita
Principal
September 19, 2024

Why are you using GPIO interrupts to read the encoder? You're like recreating the wheel.

Instead, use a Timer in encoder mode which does all the hard work for you. 

Just create a new project in STM32CubeIDE in Timer Encoder mode and extract the parts you need from the HAL driver.

If a reply has proven helpful, click on Accept as Solution so that it'll show at top of the post.CAN Jammer an open source CAN bus hacking toolCANableV3 Open Source
mƎALLEm
Technical Moderator
September 20, 2024

@Karl Yamashita wrote:

Why are you using GPIO interrupts to read the encoder? You're like recreating the wheel.

 

 Indeed!

"To give better visibility on the answered topics, please click on ""Accept as Solution"" on the reply which solved your issue or answered your question."
XPChiAuthorBest answer
Associate II
February 5, 2025

Thanks for the advice. It took a while, but I have 4 encoders running on my PCB now with no issues at all. Here are a few things I've done (which I understand may not be the best approach but have solved the issue):

  • A debounce period that is flagged by the EXTI handler and cleared by a timer after a required period has elapsed
  • The EXTI handler does as little logic processing as possible - it simply sets/clears flags

  • Another timer is used as a 'heartbeat' signal and circular buffer

The printf statement was removed after all testing/development was finalised. This was a good (but troublesome) learning experience