Measure the speed rotation by using TIM in encoder mode
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-19 7:15 AM
hello, I ask for the following clarification: I am using a two-channel encoder, A and B, and I want to estimate the rotation speed of the encoder shaft as precisely as possible. I started setting the TIM2 in encoder mode.
My encoder generates 1200 pulses per revolution.
I called the function:
HAL_TIM_Encoder_Start_IT(&htim2, TIM_CHANNEL_ALL);
and :
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
counter = __HAL_TIM_GET_COUNTER(htim);
}
which increases the counter variable every time a channel A (and B) get a variation (rising/falling edge settable from CUBE IDE).
At one full revolution of the encoder, with my settings of frequency = 160 MHz, Ps =0 and ARR = 4800, the counter variable starts at zero and goes to the value 4800 at each full revolution, then goes back to zero, and so on (all visible via serial on my laptop by using Putty software). My question is: how can I estimate the rotation speed? I have read a lot of information about this but I am still confused. Any advice?
Solved! Go to Solution.
- Labels:
-
STM32CubeIDE
Accepted Solutions
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-21 12:07 AM - edited ‎2025-04-28 11:47 AM
most probably you are creating to many interrupts.
HAL_TIM_PeriodElapsedCallback
is a call back function. That means that it is called every time (automatically) when the corresponding interrupt occurs. If your MCU is completely busy with handling the interrupts (you are generating), then you'll see that behaviour, that you describe as "freezing".
How to fix this: use an approach where you are not spamming your core with interrupt requests.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-19 7:32 AM
You may set the period to a larger multiple of 4800, setup another timer with a periodic interupt, measure the counter as you do now and calc the counter difference and divide by 1200 (or 4800?) to get the number of rotations.
Vice versa, leave the encoder timer as is, but add an update interrupt generation + handler for the existing encoder timer, set another timer to continuosly count the time and calc the difference between two interrupts to get the time for one rotation.
Consider the expected speed range to decide which way you go, if you count milliseconds or microseconds or whatever suits you application. Take care of handling overflows correctly
hth
KnarfB
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-19 8:18 AM
Measure the TIME for one pulse, many, or a rotation
You could perhaps measure micro-seconds via TIM5 (32-bit)
Up vote any posts that you find helpful, it shows what's working..
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-19 11:54 PM
@Tesla DeLoreanI agree with that you have to measure the time. But measuring the length of one pulse will lead to a very noisy speed signal because the time increments become very small and dividing 1 by something very small tends to go to +- infinity ...
=============
The principle is:
you measure time and you measure angle increments. What I use is: I use a "window" of - lets say 100 ms (for encoders with 1024 - 2048 Tics / rev) and count all the pulses that come within that 100 ms window.
As one pulse corresponds to 360° / Tic-Count (at X1 evaulation) or 360° / 4 x Tic-Count (at X4 evauation) now you have ange / time which is actually a speed value. You just have to convert it with a fixed factor to the unit's you want to see like angle/second or revolutions / minute.
If you need more frequent speed updates you could use e.g. 10 of those 100 ms Windows. Then you get a new speed value every 10 ms, but each window is still 100 ms long. Of course you need to "overlap" these windows.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-20 10:35 AM
Hi, I think there is some problem:
I left the TIM2 in encoder mode and using the function:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
I can print the counts number on the video via serial every time I move the encoder shaft. Using scaling I can then obtain the position in degrees (one complete turn = 360 degrees) but as soon as I enable a timer (TIM6) to do a test and using the function to turn a LED on and off:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin);//,GPIO_PIN_SET);
}
printing to video via serial no longer shows the position of the encoder shaft which remains fixed at a value. Is there a conflict in this sense between the two interrupts? How can I solve this?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-20 11:37 PM - edited ‎2025-04-20 11:37 PM
I don't understand your approach, so I can not comment on that.
I am measuring angular position / speed in this way:
int32_t ctr = (int32_t)(__HAL_TIM_GET_COUNTER(&htim2));
int32_t zctr = (int32_t)(__HAL_TIM_GET_COMPARE(&htim2, TIM_CHANNEL_3));
I use Timer2 in this example: Channel 1 + 2 are set up in encoder mode, Channel 3 is set up as "Input Capture direct mode". Note: make sure to set Encoder Mode to "Encoder Mode Ti1 and Ti2" for X4 Evaluation in CubeMX.
The function containing this code is called periodically with a fixed timing, e.g. triggered by an other timer.
ctr is the position information from the encoder. The setting "encoder mode" does this for you. If you use e.g. a 1024 Tics/rev. Encoder and X4-Evaluation, the counter variable can reach the values 0 - 4095 before it rolls over.
zctr is the position information of the zero tic - if your encoder has one.
Or the other way round: the setting "encoder" mode handles already most of the tasks for you. You just have to give the information of the Tics per Revolution and set the Evaluation mode correctly, then each read of the CNT register will give you the actual angle position of the encoder in increments. If you want/need to, you can reference this position with the Zero-Tic.
Lets say your timing source is the SysTic and you are calling the function with the code above every 1 Millisecond. Then you can compare the last value of ctr with your actual value of ctr. This gives you the information how the angle position has changed (in increments).
In the simplest approach you now have a change of angle and a change of time: if you divide the angle change by the time change you get: v = d phi / d t which is an angular speed. From there you can convert into any unit you want, may it be rev. per minute or rad/s or whatever ...
In reality you will have to filter that value, because 1 Millisecond is very small and especially at low angular speed you will either device e.g. 1 (tic increment) by 0.001s wich leads to a huge value (1000 tics/s). You can decide on your own if you want to use e.g. a block average or e.g. a simple exponential moving average.
Pitfalls: you will have to make sure you read the angle and calculate the angle difference faster than the encoder can roll over. Otherwise you will run into an issue to detect the real change and you will not be able to detect if the angle has increased or decreased when your readout timing is so slow, that the encoder position can more than 180°
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-20 11:59 PM
hi, so the problem I'm having is this: in the function:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
I can correctly get the encoder pulses and print them to the serial as follows, as soon as I rotate the encoder shaft:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
timer_counter = __HAL_TIM_GET_COUNTER(htim);
angle = (timer_counter/4800.0)*360.0;
sprintf(printMessage, "Current angle is: %.2f\n\r", angle);
HAL_UART_Transmit(&huart1, (float*)printMessage, sizeof(printMessage), 300);
}
But there is a problem that I do not understand. In the function:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
that manages the internal timers, I have set two timers (TIM6 and TIM3) that work correctly as long as the external interrupt call is not called. As soon as I rotate the encoder shaft the calls to the internal timers freeze and everything I write in the function:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin);//,GPIO_PIN_SET);
HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin);//,GPIO_PIN_SET);
HAL_GPIO_TogglePin(LED_RED_GPIO_Port,LED_RED_Pin);//,GPIO_PIN_SET);
HAL_GPIO_TogglePin(Test_GPIO_Port,Test_Pin);
}
}
/* USER CODE END 4 */
it doesn't work anymore. How to fix this.
So in the end I have this problem: the generation of the external interrupt freezes my internal timer management. I hope I was clear
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-21 12:07 AM - edited ‎2025-04-28 11:47 AM
most probably you are creating to many interrupts.
HAL_TIM_PeriodElapsedCallback
is a call back function. That means that it is called every time (automatically) when the corresponding interrupt occurs. If your MCU is completely busy with handling the interrupts (you are generating), then you'll see that behaviour, that you describe as "freezing".
How to fix this: use an approach where you are not spamming your core with interrupt requests.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-21 12:39 AM
not clear. I am using only one intertupt (TIM6) in callback function and one external interrupt in encoder mode. It is absurd that when a external interrupt come , it freeze the only one time interrupt request. Please how i can fix, i am using only few interrupt request but i want in future manage more of that
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-21 12:50 AM
I correct myself. Every time I rotate the shaft of my encoder, using the function:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
}
I can display on the USART serial and calculate the number of counts of the timer set in encoder mode.
But now I need an internal timer as an interrupt to establish a time base (TIM6 for example) using the function:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
}
}
But as soon as I rotate the shaft of the encoder, the TIM6 interrupt is frozen and no longer works. timer 6 interrupt only works if the external interrupt of the HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) function is not raised.
So it seems that the simultaneous handling of external interrupts and internal interrupts does not work at the same time. How can I fix this?
