2024-11-12 03:19 PM
Hey everyone!
I'm trying to detect the rotations of my rotary encoder. I used external interrupts to detect when the state of the pin changes. My problem is that it seems like when the encoder bounces the interrupt gets triggered, but the state doesn't change. How can that be? The only thing I can think of is that when I read the pin with HAL_GPIO_ReadPin inside the interrupt it has already changed. Can that happen? I would assume that the contents of the IDR are locked while the interrupt is running, but I could find any information about this.
Here is the minimal code from my project.
uint8_t get_rotary_state() {
GPIO_PinState A_state = HAL_GPIO_ReadPin(ENCODER_A_GPIO_Port, ENCODER_A_Pin);
GPIO_PinState B_state = HAL_GPIO_ReadPin(ENCODER_B_GPIO_Port, ENCODER_B_Pin);
return (B_state << 1) + A_state;
}
uint8_t rotary_prev_state = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_pin) {
if (GPIO_pin == ENCODER_A_Pin || GPIO_pin == ENCODER_B_Pin) {
uint8_t rotary_current_state = get_rotary_state();
// This can happen.... somehow
if (rotary_current_state == rotary_prev_state) {
}
rotary_prev_state = rotary_current_state;
}
}
Solved! Go to Solution.
2024-11-13 07:58 AM
> So you are saying that it is generally not a good idea to handle mechanical components using interrups?
Specifically, pin-change interrupts such as EXTI.
From the problem as you've posted, it may appear, that the problem is, that there is no separate interrupt for rising and for falling edge. But it's a bit more involved than just that in case of the STM32 EXTI and also in case of encoder.
The STM32 EXTI is asynchronous and can react on pulses shorter than the system clock period (say if you don't run the mcu at maximum clock, it still will react on very short pulses).
This means, that in case of bouncy switch, or EMI-related pulse, you can have lots of rapid interrupts, far faster than what the mcu can process. And that means that you'll lose some of them, and the resulting encoder position will be off.
With periodic sampling, if its period is shorter than the shortest "clean" signal period between the bouncy edges at the highest possible revolution speed, the result is always correct when the encoder stops turning. This can also reject spurious EMI-induced pulses, if you decrease the sampling period even more (e.g. assuming max. one spurious pulse in the "clean" period, this "clean" period effectively halves). Reading the pins separately does not matter, if you factor in the delay between the readings to this "clean" period (i.e. shorten it by that delay). However, you don't need to read them separately if they are on the same GPIO port, just read that port's IDR. Read this article, it's in German but it's a good seminal work on the topic (based mainly on work of Peter Dannegger).
However, in STM32, I don't even recommend periodic sampling; I do strongly recommend using the built-in encoder feature of the timers.
JW
2024-11-13 12:15 AM
> I would assume that the contents of the IDR are locked while the interrupt is running,
No.
GPIO_IDR always reflects the current state of pin.
Generally, it's a bad idea to handle encoder in an EXTI interrupt, for various reasons. Use either the native encoder modes of timers (where you can also use digital filters at the inputs), or use the old fashioned but reliable periodic sampling method.
JW
2024-11-13 01:06 AM
So you are saying that it is generally not a good idea to handle mechanical components using interrups? How would periodic sampling make this better? There would still be two separate function calls to HAL_GPIO_ReadPin, and the state of the second pin could have changed while reading the first pin.
2024-11-13 07:58 AM
> So you are saying that it is generally not a good idea to handle mechanical components using interrups?
Specifically, pin-change interrupts such as EXTI.
From the problem as you've posted, it may appear, that the problem is, that there is no separate interrupt for rising and for falling edge. But it's a bit more involved than just that in case of the STM32 EXTI and also in case of encoder.
The STM32 EXTI is asynchronous and can react on pulses shorter than the system clock period (say if you don't run the mcu at maximum clock, it still will react on very short pulses).
This means, that in case of bouncy switch, or EMI-related pulse, you can have lots of rapid interrupts, far faster than what the mcu can process. And that means that you'll lose some of them, and the resulting encoder position will be off.
With periodic sampling, if its period is shorter than the shortest "clean" signal period between the bouncy edges at the highest possible revolution speed, the result is always correct when the encoder stops turning. This can also reject spurious EMI-induced pulses, if you decrease the sampling period even more (e.g. assuming max. one spurious pulse in the "clean" period, this "clean" period effectively halves). Reading the pins separately does not matter, if you factor in the delay between the readings to this "clean" period (i.e. shorten it by that delay). However, you don't need to read them separately if they are on the same GPIO port, just read that port's IDR. Read this article, it's in German but it's a good seminal work on the topic (based mainly on work of Peter Dannegger).
However, in STM32, I don't even recommend periodic sampling; I do strongly recommend using the built-in encoder feature of the timers.
JW
2024-11-13 08:10 AM
Thank you for this amazing explonation!
2024-11-13 03:41 PM
No matter how careful you rotate an encode in one direction (at least encoders that I've used), it sometimes bounces backwards. Using just plain EXTI isn't practical without incorporating some kind of filtering.
Make it easier on yourself and use a Timer in Encoder Mode
Take a look at this project which will show you how to set up in Encoder mode and do some filtering on the timer counter. This is so that even if the encoder glitches in the reverse direction, the FW will not register it as going in the reverse direction. You can play with the numbers to change the resolution.
https://github.com/karlyamashita/Nucleo-L432_RotaryEncoder/wiki