In order to calculate the current speed, one option is to read the current position every 20ms. The average speed is position_old minus position_current. This works well if there are many pulses per 20ms but at low speeds, the accuracy drops. Once you get speed=0, then speed=2 and then speed=0 again.

The better approach would be to measure the time between rising flanks. This provides you with the exact time, hence the speed.

  • But can you have two timers active on the same pins? Then Input Capture would be the way to go.
  • Can you configure an ISR to fire at the rising flank while the timer is used as encoder? Then I can use the systicks to derive the speed.

I am using TIM5 CH1&2 on PA0&1.

(I could obviously connect the input pin to another MCU pin and do the measurement there but don't like that workaround.)


  • \$\begingroup\$ If you have an IRQ fire on the encoder's pulses, why use another timer as encoder? In the ISR, you can do both, count total pulses for position and take timings for the current speed. \$\endgroup\$
    – JimmyB
    Commented Sep 22, 2017 at 14:53
  • \$\begingroup\$ +1 for the general problem that lot of DIYers don't take into account. the professional equipment usually uses a FPGA that does frequency/period measurement and the automatically switch over. As for STM32 series for motor control, I would expect a similar device built-in, but didn't heard of it yet. So you may inspect their FOC library, how is implemented the the encoder feedback measurement. \$\endgroup\$ Commented Sep 22, 2017 at 19:24
  • \$\begingroup\$ It is possible with TI DSP with eQEP module: ti.com/lit/ug/sprug05a/sprug05a.pdf \$\endgroup\$ Commented Sep 22, 2017 at 19:37
  • \$\begingroup\$ @JimmyB: Yes, using interrupts and counting myself is an option. But the hardware encoder built into the STM32 has a lot of goodies I don't want to give up on. \$\endgroup\$ Commented Sep 23, 2017 at 6:45

1 Answer 1


Did some more testing and reading and found the solution:

  1. Instead of HAL_TIM_Encoder_Start() use HAL_TIM_Encoder_Start_IT(). Both execute the same code except the latter prepares for interrupts as well.
  2. Enable the interrupt: HAL_NVIC_SetPriority(TIM5_IRQn, 0, 1); HAL_NVIC_EnableIRQ(TIM5_IRQn);
  3. Implement the void TIM5_IRQHandler(void) { HAL_TIM_IRQHandler(&htim5); } so that there is a interrupt handler forwarding the interrupt to the HAL layer.
  4. Implement (or extend) the void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) callback function and add your code.

In my case the code is

if (htim->Instance == TIM5)
    last_possensortick = HAL_GetTick();
    possensorduration = last_possensortick - possensortick_old;
    possensortick_old = last_possensortick;

So I store the time between two interrupts and speed = 1/time. Almost. Only almost because the interrupt fires on a single pin only and the the time has to be between identical events, hence fires on a rising flank only.

The encoder on the other hand is configured as X4 encoder (sConfig.EncoderMode = TIM_ENCODERMODE_TI12;), so the position is updated on both flanks and on both channels. see page 29 at AN4013

Therefore the speed = 4/time instead of 1/time.

And some additional logic is needed if the time=0 and if the interrupt does not fire because the wheel does not move at all. That is the reason I store both, the duration and the lasttick. If lasttick > 2000 then speed = 0;

Above logic works for low speeds only. At high speeds the millisecond resolution and the interrupt execution timing creates more and more errors. Therefore I will use both speed inputs, the speed=old_pos-current_pos; and the speed=4/duration;

  • \$\begingroup\$ This is known as frequency/period measurement. The error is +/- 1/N_counts of whichever type of measurement, so you have to compare N_counts_method_freq VS. N_count_method_period to switch the result in between to get the best result. \$\endgroup\$ Commented Sep 25, 2017 at 11:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.