2023-07-01 06:06 AM
I am attempting to use a rotary encoder (Bourns PEC11R-4215F-S0024) with an STM32F091 Nucleo board. I would like to have it count a consist number of "counts" for every "click" of the encoder.
Hardware Setup: Pin A is connected to PA0, Pin B is connected to PA1 and Pin C (common) is connected to the ground on the nucleo board. This is all done with jumper wires.
CubeMX setup: Clock Tree set to have a max frequency of 48MHz using the internal clock. PA1 and PA2 are set to have Pull-Up Resistors, TIM2 is set to Combined Channels = Encoder Mode. All the TIM2 parameters are left at default except for Encoder Mode = Encoder Mode TI1 and TI2, Polarity of both are set to Falling Edge. I have also activated the TIM2 global interrupt.
I generated the code and added to it so that I could turn the encoder and it would copy the CNT register to a counter variable within a CaptureCallback function. I have attached my main.c file.
I would expect this to add 4 (or subtract 4 depending on the direction I turn the encoder) to the CNT register and then each time the CNT register changes it should trigger the CaptureCallback function to copy that value to the counter variable. This is not what I get though. the CNT register updates properly and it does trigger the CaptureCallback function but the counter variable rarely updates correctly. Often counter will get a completely different value than what is in the register, instead of incrementing by 4 it might increment by 2 or 10 or some other number. the CNT register is always correct, but counter variable is not.
I have checked with a logic analyzer and the encoder is clean, no glitches or anything like that on the transitions and the A and B signals are out of phase with each other as they should be. One thing I have noticed is that while the CNT register is updating properly it appears that the CaptureCallback function is called multiple times each time the CNT register updates. I've also found that if I set a breakpoint in the CaptureCallback function it will pause until I tell it to run again and then it will enter the proper value into the counter variable...every time.
Viewing the TIM2 Peripheral in Keil while I turn the encoder I see that the CNT register is accuratly increaseing/decreasing by 4 on each click, the CCR1 Register does not perfectly follow the CNT register...sometimes it does, sometimes it doesn't. Also, the value of my variable "counter" that is being set inside the capture callback function is not following the CNT register like it should be.
During testing one thing I have noticed is that the CaptureCallback function is being entered considerably more often than it the CNT value is changing. For instance, the value of CNT may increase by 4 but the CaptureCallback function is entered 5 times over when it should be entered once. My understanding is that this CaptureCallback function should only be entered once when the CNT value changes, is this not the case? I have found that if i set a breakpoint inside the CaptureCallback function and then manually run the program again after it breaks it works perfectly every time. Do I need a delay within the CaptureCallback function to make this work right?
I suspect what is happening is that the CaptureCallback function is executing too fast for the register to update properly. Can this be slowed down just enough for the register to be finished updating before the CaptureCallback function is executed?
I also noticed that the CNT register and the CCR1 register increases/decreases do not always match. Shouldn't these match? If not, could this be why I'm not getting the right value in counter?
Solved! Go to Solution.
2023-07-01 09:38 AM
> I have checked with a logic analyzer and the encoder is clean, no glitches or anything like that on the transitions
What logic analyzer, more precisely, what was the sampling rate?
Bouncing on encoders with mechanical contacts can have surprisingly high frequencies, and if you run the timer at tens of megahertzs it is perfectly capable of capturing pulses below 100ns. That's most likely could explain all the symptoms you've described above.
Generally, it's a bad idea to base any processing of timer in encoder mode on interrupts (e.g. an often seen method to solve counter rollovers is to use the Update interrupt, and that similarly can fail because of either bounces or the timer stopping at the rollover point, vibrating somewhat, generating a series of pulses faster than the ISR can handle).
IMO the best method to handle the TIMx in encoder mode is to read out TIMx_CNT often enough so that counter rollovers are properly resolved (i.e. successive readouts have to occur no further than half of revolution at the highest possible speed). One way to achieve this is to sample TIMx_CNT from interrupt or DMA of a different timer; but for manually turned encoders, usually sampling them in the superloop (if your firmware design is of that kind) usually suffices.
Some users use hardware filters on A and B signals (simple RC filters), and they may be a good idea, but generally I'd still recommend not to rely on those and maintain what I've written above.
JW
2023-07-01 09:38 AM
> I have checked with a logic analyzer and the encoder is clean, no glitches or anything like that on the transitions
What logic analyzer, more precisely, what was the sampling rate?
Bouncing on encoders with mechanical contacts can have surprisingly high frequencies, and if you run the timer at tens of megahertzs it is perfectly capable of capturing pulses below 100ns. That's most likely could explain all the symptoms you've described above.
Generally, it's a bad idea to base any processing of timer in encoder mode on interrupts (e.g. an often seen method to solve counter rollovers is to use the Update interrupt, and that similarly can fail because of either bounces or the timer stopping at the rollover point, vibrating somewhat, generating a series of pulses faster than the ISR can handle).
IMO the best method to handle the TIMx in encoder mode is to read out TIMx_CNT often enough so that counter rollovers are properly resolved (i.e. successive readouts have to occur no further than half of revolution at the highest possible speed). One way to achieve this is to sample TIMx_CNT from interrupt or DMA of a different timer; but for manually turned encoders, usually sampling them in the superloop (if your firmware design is of that kind) usually suffices.
Some users use hardware filters on A and B signals (simple RC filters), and they may be a good idea, but generally I'd still recommend not to rely on those and maintain what I've written above.
JW
2023-07-01 09:42 AM - edited 2023-07-04 01:14 PM
well, for a manual rotated encoder, events are in 10...500ms range, and only 1 event happens, then again wait...
i would use the pin change interrupt, to check this "seldom" event, no need to use a high speed counter.
i made it like this :
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
nobody wants - so delete.
/* USER CODE END EXTI9_5_IRQn 1 */
}
DG_A + _B are the pins (with pullup) to the encoder , rotate is (volatile int16_t ) number , that counts up or down
2023-07-02 12:44 PM
Jan,
Thank you for your quick response, I this is exactly how I managed to get it working. I disabled the global interrupt and simply put this read in my "superloop":
counter = TIM2->CNT;
This has completely resolved my problem. The only reason I was trying to do it in an interrupt is that I was always taught that you should do as much as possible in either function calls or interrupts for both "best practices" as well as best efficiency.
As for my logic analyzer I was using a Siglent SDS2204X Plus oscilloscope with 16 channel digital channel probe (not my preferred scope, but its what was available). This scope has a sampling rate of 2GSa/s...unfortunately I think you may be correct that this scope may not be capable of capturing the noise pulses.
The proof of concept I'm working on is limited to off the shelf components/eval boards that can just plug into the Nucleo board due to the deadline I'm on. Because of this, a custom PCB with filter components added isn't an option...but I will consider this for the future. This if my first time using a rotary encoder so this is all new to me.
2023-07-02 02:27 PM
Hi Nicholas,
There's an overview of the topic on https://www.mikrocontroller.net/articles/Drehgeber although it's not STM32-specific.
JW