cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F446 how to use rotary encoder with timer?

Raymond Buck
Senior

I have an application I am converting from another processor to a STM32F446 board. The application uses a rotary encoder to detect counts and direction.

I have looked at several videos online on how to use a timer for rotary encoder purposes. I'm beginning to think the timer approach is the wrong way to go.

I am using an optical encoder so contact bounce is not an issue. I started by using a new encoder on the 446 board. However I was getting strange results. So I pulled one of the original encoders from the one of the other processor boards. I see the same results with the encoder that has worked perfectly for years. I get strange counts like 1,4,5,9,13,16,17,20. Also, when I turn the encoder counter clockwise and go past the starting point, I see negative numbers. The encoder is a 32 count PR unit.

In the old application, I had the A and B outputs connected to two pins on the CPU. I used a lookup table that is widely available to determine direction and count. The lookup table is foolproof and looks like this: {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}. I'm sure users here have seen this solution also.

I need to count each state change as one pulse and determine the direction of the encoder movement. Should I just forget about using the timer for encoder purposes and use two GPIOs with my original code? The timers may be useful for some applications but apparently are not suitable for what I need. If they would work for what I need, could someone point me to a link that shows how to use them?

6 REPLIES 6
TDK
Guru

Using the encoder mode of a timer is going to be way more computationally efficient and less prone to error than polling GPIOs.

There are a lot of examples out there:

https://github.com/STMicroelectronics/STM32CubeF4/blob/3d6be4bd406f275728e0a321cc371c62a3100533/Projects/STM324xG_EVAL/Examples/TIM/TIM_Encoder/Src/main.c

If you feel a post has answered your question, please click "Accept as Solution".
Raymond Buck
Senior

TDK, Thanks for the reply.

I found the issue with the strange counts. I had a 10 msec delay in my while(1) loop. Removing that cleared the count problem. However, I then saw 4 counts per transition change for the encoder. I changed the timer setup to only trigger on T1 instead of T1 and T2. I then saw only 2 counts per transition change. I need 1 count per transition change.

Another thing I noticed is that the timers somehow seem to be able to tell if the encoder is between transition changes. I am reading the TIM2->CNT in my while loop. I am toggling a LED each time I see TIM2->CNT increase by 4. If I turn the encoder real slow I can see the LED for some reason will vary between half brightness and full brightness. I don't know what is causing that unless the timer channels are seeing edge transitions that are confusing the count register and making the LED rapidly toggle.

I looked at the code in the link you posted. But that doesn't help me with what I am trying to do. I'm not sure what you mean by the timers being less prone to error than polling GPIOs. The method I have been using has worked flawlessly for the last 5 years with the other CPU. That is why I asked if I should just forget about the timer method and continue to use the same routines I have been using.

The method I presently use doesn't read the encoder by polling. It uses an interrupt routine that is triggered by state changes on the I/O pins. I am not concerned about efficiency as the application sits idle 99.999% of the time. The only time anything happens is when the user turns the encoder.

I have found lots of examples online but have yet to find an example where the timer method can generate 1 count per quadrature transition.

> I used a lookup table that is widely available to determine direction and count. The lookup table is

> foolproof and looks like this: {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}. I'm sure users here have seen this

> solution also.

Can you please give some link or other hint?

The encoder mode on TIM is okay, but as you've noticed, only x2 and x4 mode (the newer STM32 such as 'G4 have different modes, and IIRC x1 mode too). Do resist the temptation to use the prescaler to divide. Divide in software. You may want to add some hysteresis (I suspect your original algorithm incorporates some).

JW

Raymond Buck
Senior

Thanks for the links.

That algorithm is a x4 decoder, so I wonder how does that fit your requirement for x1 decoding.

In STM32 timers, you can debounce in hardware, see TIMx_CCMR1.IC1F description.

You can of course always do the sampling/decoding in software, if that fits your purpose.

JW

Raymond Buck
Senior

I couldn't get my original code to work with interrupts on the 446 board. I spent a couple of hours and finally gave up. I think it has something to do with the way interrupts are handled by HAL. The old processor is a PIC that allows me to clear all pending interrupts within the interrupt routine. So when the encoder was rotated one channel triggered the interrupt. But there was also an interrupt pending from the other channel which I cleared in the ISR.

HAL sees both interrupts but I could not clear the pending interrupt from the other channel in the callback function. I tried doing this:

__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_1);

But after the first interrupt was handled, HAL still called the pending interrupt. Probably due to 4 state transitions being seen??? I guess I could put a counter variable in the callback function and only take action every 4 interrupts. I may test that in the next day or so. In the meantime........

I found a better way of handling the lookup function here: https://daniellethurow.com/blog/2021/8/30/how-to-use-quadrature-rotary-encoders

I adopted the code to work on the new 446 board in polling mode. It appears to be reliable after 30 minutes of testing. I am doing this in the main while(1) loop. Nothing else really happens there so speed isn't a problem. The old processor ran at 32 MHz and the 446 board is running at 180 MHz.

I will experiment a little more to see if I can get this code to run with interrupts. If not, the polling method will be fine for my purposes, It might not be as reliable with a mechanical encoder but the optical encoder solves the contact bounce issue.