cancel
Showing results for 
Search instead for 
Did you mean: 

How to get interrupt on counter overflow with Quadrature Encoder with Cube and HAL?

Stefan Wozniak
Associate III
Posted on March 26, 2017 at 14:09

Hi,

I want to use Timer with Quadrature Encoder. I'm new on STM32 so I'm trying to start with Cube and HAL.

I'm able to setup STM32F411 to count pulses but there is an only 16bit counter. I need to measure pulses up to 400k.

Can I rise interrupt on counter overflow to store revolutions? I want to do this using Cube and HAL. I was trying a lot of settings and interrupts but I'm unable to get interrupt from an encoder.

Can you help me?

Thanks in advance.

Steve

#cube #cube-mx #quadrature-encoder #stm32-cube #stm32f411 #quadrature
21 REPLIES 21
Posted on March 28, 2017 at 07:28

Thanks a lot for your help.

Posted on July 23, 2017 at 20:38

Uwe Bonnes wrote:

There is no such interrupt. However there also is no need for that interrupt and a Jitter at the position of that interrupt would be harmful.

This answer is just flat out wrong. First off, the Update interrupt is triggered when there is a counter overflow/underflow. Secondly, by resetting the count away from the overflow value when the overflow interrupt occurs, you can avoid the jitter problem and lost counts. Here is some example code for Timer 4:

In main.c:

volatile int32_t encoder_position_offset = 32768;
TIM_HandleTypeDef htim4;
MX_TIM4_Init();
TIM4->CR1 |= TIM_CR1_URS;
TIM4->ARR = 65535;
TIM4->CNT = encoder_position_offset;
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
HAL_TIM_Base_Start_IT(&htim4);
while (1)
{
 int32_t raw_encoder_count = TIM4->CNT;
 int32_t position = raw_encoder_count - encoder_position_offset;
 printf('%d

', position);
 HAL_Delay(500);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

In stm32f4xx_it.c:

extern volatile int32_t encoder_position_offset;
void TIM4_IRQHandler(void)
{
 if ((TIM4->SR & (TIM_SR_CC3IF | TIM_SR_CC4IF)) && (TIM4->SR & TIM_SR_UIF))
 {
 encoder_position_offset += (TIM4->CR1 & TIM_CR1_DIR) ? 32769 : -32768;
 TIM4->CNT = 32768;
 TIM4->SR = ~(TIM_SR_UIF | TIM_SR_CC1IF| TIM_SR_CC2IF);
 return;
 }
 TIM4->SR = ~TIM_SR_UIF;
 return;
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Posted on July 24, 2017 at 11:58

 Secondly, by resetting the count away from the overflow value when the overflow interrupt occurs, you can avoid the jitter problem and lost counts.

The point is, that if you have two or more overflows within the time while you get to any processing in the interrupt, you may lose steps. By simple periodic sampling, you never lose steps.

I also don't get why do you check CC3F and CC4F and then clear CC1F and CC2F.

JW

Posted on July 24, 2017 at 13:12

waclawek.jan wrote:

The point is, that if you have two or more overflows within the time while you get to any processing in the interrupt, you may lose steps.

Extremely unlikely. The interrupt handler runs for less than a microsecond. If you have multi-megahertz quadrature pulse trains (highly unlikely), then maybe you will have a problem. The interrupt handler resets the quadrature encoder timer count back to the middle of the timer range (32768), so you will not have another overflow interrupt for a long time.

By simple periodic sampling, you never lose steps.

I do periodic sampling... when the counter overflows.

The point is that people don't want to deal with the hassle of making a periodic sampling / correction task. They just want a simple pseudo-32-bit hardware-based non-interrupt-driven quadrature encoder counter.

I also don't get why do you check CC3F and CC4F and then clear CC1F and CC2F.

For some reason, the Update interrupt is called on program startup, and sometimes is called twice on an overflow / underflow. The lines:

  if ((TIM4->SR & (TIM_SR_CC3IF | TIM_SR_CC4IF)) && (TIM4->SR & TIM_SR_UIF))

and

    TIM4->SR = ~(TIM_SR_UIF | TIM_SR_CC1IF | TIM_SR_CC2IF);

filter out these extraneous interrupts. I thought that setting the CR1:URS bit would stop them, but it didn't. I don't know why they are occurring - Maybe because I adjust the counter value in the handler, but probably for another reason. Perhaps later I will go back and do a root cause analysis, but for now the code works and I've moved on to other parts of the project.

Posted on July 24, 2017 at 18:06

waclawek.jan wrote:

I also don't get why do you check CC3F and CC4F and then clear CC1F and CC2F.

Actually, thinking about it more, a better way to reject the spurious interrupts would be to make sure the count is at the overflow / underflow values:

In stm32fxxx_it.c:

extern volatile int32_t encoder_position_offset;
 
void TIM4_IRQHandler(void)
{
 if (~TIM4->SR & TIM_SR_UIF)
 return;
 
 if ((TIM4->CNT == 65535) && (TIM4->CR1 & TIM_CR1_DIR))
 {
 encoder_position_offset += 32769;
 TIM4->CNT = 32768;
 }
 else if ((TIM4->CNT == 0) && (~TIM4->CR1 & TIM_CR1_DIR))
 {
 encoder_position_offset -= 32768;
 TIM4->CNT = 32768;
 }
 TIM4->SR = ~TIM_SR_UIF;
 return;
 // STM32CubeMX generated interrupt handler is here and never gets called
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Posted on September 12, 2017 at 12:07

Why use the complicated solution? Sampe the counter with a frequency that at maximum 2^15 -1 counts happen between sampling or when you want to read out. Add the difference from the last sampled value to the accumulated result. No spurious interrupts,no lost counts.

Posted on September 12, 2017 at 12:37

Uwe Bonnes wrote:

Why use the complicated solution? Sample the counter with a frequency that at maximum 2^15 -1 counts happen between sampling or when you want to read out. Add the difference from the last sampled value to the

accumulated result. No spurious interrupts, no lost counts.

What happens when there isn't a regular pulse frequency? For example, a motor that can vary between stopped to 60k pulses per second? What are you going to do? Run a timer at 32768 / 60000 = ~2 Hz just to prevent an overflow? What a waste of a timer and processing cycles, especially when the motor is stopped.

This code is much less 'complicated' than a periodic sampling task that has to be overengineered to deal with corner cases where a sudden burst may overflow the counter before it can be read. The code is in production and works with 30kHz pulse trains without missed counts, even when there is jitter. It is offered to anyone who wants a solution. No one is forcing you to use it.

As I wrote to Jan:

The point is that people don't want to deal with the hassle of making a periodic sampling / correction task. They just want a simple pseudo-32-bit hardware-based non-interrupt-driven quadrature encoder counter.

Posted on September 12, 2017 at 14:49

That code has races. What happens when there were more counts during overflow of counter and sampling of counter?  What if direction changes?

Posted on September 12, 2017 at 17:12

Actually, you are correct. There is a tiny chance that the overflow interrupt handler could update the value of the count and offset in between when user code reads the count and subtracts the offset. Interrupt-safe code is below.

As for missed counts, the interrupt handler runs for less than a microsecond. Like I said to Jan, if you are dealing with multi-megahertz quadrature pulse trains then you may have a problem. For 9% of real-world applications, you will not lose counts.

As for direction, it makes no difference. All the code does is reset the count to 32768 (the middle of the range). It has no effect on the direction flags or capture event flags.

Uwe Bonnes wrote:

Why use the complicated solution?

This brings up an interesting philosophical point. According to this

https://www.jwz.org/doc/worse-is-better.html

, there are two diametrically opposed schools of thought regarding software engineering:

1. TheMIT approach to engineering (known as 'do the right thing', no matter how complex it makes the code).

2. TheBerkeley approach to engineering (make the code simple, even if it makes the user do more work). Also known as 'worse is better'.

It is clear from your answers that you are a 'Berkeley'-type person. I am an 'MIT'-type person. I would rather make things easier for the end-user, even if it makes my code more complicated. Rather than forcing them to come up with a complicated and inefficient polling mechanism for the encoder count to avoid overflows, I would rather just let them write:

int32_t position = Get_Encoder_Position();�?�?�?�?�?�?�?�?

You call it complicated, I call it simple. It just depends on your perspective.

The interrupt-safe code is below:

In main.c:

extern volatile int32_t Encoder_Position_Offset;
int32_t Get_Encoder_Position();
TIM_HandleTypeDef htim4;
MX_TIM4_Init();
__HAL_TIM_URS_ENABLE(&htim4);
TIM4->ARR = 65535;
TIM4->CNT = Encoder_Position_Offset;
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
HAL_TIM_Base_Start_IT(&htim4);
while (1)
{
 printf('%d

', Get_Encoder_Position());
 HAL_Delay(500);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

In Encoder.c:

volatile int32_t Encoder_Position_Offset = 32768;
int32_t Get_Encoder_Position()
{
 int32_t count, offset, end_offset;
 do
 {
 offset = Encoder_Position_Offset;
 count = TIM4->CNT;
 end_offset = Encoder_Position_Offset;
 }
 while (offset != end_offset);
 return count - offset;
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

In stm32f4xx_it.c:

extern volatile int32_t Encoder_Position_Offset;
void TIM4_IRQHandler()
{
 if (~TIM4->SR & TIM_SR_UIF)
 return;
 
 if ((TIM4->CNT == 65535) && (TIM4->CR1 & TIM_CR1_DIR))
 {
 Encoder_Position_Offset += 32769;
 TIM4->CNT = 32768;
 }
 else if ((TIM4->CNT == 0) && (~TIM4->CR1 & TIM_CR1_DIR))
 {
 Encoder_Position_Offset -= 32768;
 TIM4->CNT = 32768;
 }
 TIM4->SR = ~TIM_SR_UIF;
 return;
 // STM32CubeMX generated interrupt handler is here and never gets called�?
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Posted on September 12, 2017 at 19:52

Actually, the periodical sampling *is* the *right thing* here.

 if you are dealing with multi-megahertz quadrature pulse trains then you may have a problem.

It's not about the sustained rate, but the possible pulse width - the critical moment is reversal.

JW