Skip to main content
Graduate
June 27, 2024
Question

Incremental encoder HAL_TIM_IC_CaptureCallback does not trigger on each step

  • June 27, 2024
  • 9 replies
  • 3118 views

Hello Community,

I have an incremental encoder with 30 detents interfaced on STM32H5562 where I have set my TIMER2 in Encoder mode  and using interrupt as shown below:

 

static void MX_TIM2_Init(void)

{

 

/* USER CODE BEGIN TIM2_Init 0 */

 

/* USER CODE END TIM2_Init 0 */

 

TIM_Encoder_InitTypeDef sConfig = {0};

TIM_MasterConfigTypeDef sMasterConfig = {0};

 

/* USER CODE BEGIN TIM2_Init 1 */

 

/* USER CODE END TIM2_Init 1 */

htim2.Instance = TIM2;

htim2.Init.Prescaler = 0;

htim2.Init.CounterMode = TIM_COUNTERMODE_UP;

htim2.Init.Period = 4294967295;

htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

sConfig.EncoderMode = TIM_ENCODERMODE_TI12;

sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING;

sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;

sConfig.IC1Prescaler = TIM_ICPSC_DIV1;

sConfig.IC1Filter = 0;

sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING;

sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;

sConfig.IC2Prescaler = TIM_ICPSC_DIV1;

sConfig.IC2Filter = 0;

if (HAL_TIM_Encoder_Init(&htim2, &sConfig) != HAL_OK)

{

Error_Handler();

}

sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)

{

Error_Handler();

}

/* USER CODE BEGIN TIM2_Init 2 */

 

HAL_TIM_Encoder_Start_IT(&htim2,TIM_CHANNEL_ALL); //start the encoder timer 2

 

 

/* USER CODE END TIM2_Init 2 */

 

}

 

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)

{

if (htim == &htim2)

{

counter1 = __HAL_TIM_GET_COUNTER(&htim2);

}

 

 

The problem I have is that HAL_TIM_IC_CaptureCallback is triggered every second step of the encoder.

I would like the HAL_TIM_IC_CaptureCallback to trigger on each turn(detent).

On the other hand, if I use the __HAL_TIM_GET_COUNTER(&htim2) inside the while loop the counter is updated on each turn.

 

Do you know how this can be achieved within the interrupt?

 

Following is a part of the encoder datasheet.

KostasPapouis_0-1719494168885.png

 


Thank you.

    This topic has been closed for replies.

    9 replies

    Graduate
    June 30, 2024

    Hello,

    The reason the HAL_TIM_IC_CaptureCallback is fired every second turn is because the nature of the incremental encoder I have. The A & B signals go low every second turn and both IC1Polarity and IC2Polarity was set to TIM_ICPOLARITY_FALLING.

     

    I have changed the sConfig.IC2Polarity  from  TIM_ICPOLARITY_FALLING to TIM_ICPOLARITY_RISING and now the HAL_TIM_IC_CaptureCallback is fired on each turn of the incremental encoder.

     

    The only issue I have is that the counter value decrements when I rotate the encoder clockwise. I need the counter value to increment when I rotate the encoder clockwise and decrement when I rotate the encoder counter clockwise. Is there any way that this can be fixed in firmware?

    Any help is much appreciated.

     

     

    Graduate II
    July 1, 2024

    The timer's "Encoder" mode uses encoder events as the "clock" feeding the counter, which can configured in either  "Up" or "Down" counter mode. Choose which one you need.

    (Update: this is wrong, I forgot the DIR(ection) is also taken over by the encoder mode)

     

    Sidenote: if you're not using a very nice encoder, be aware that mechanical ones may require debouncing. If you do need to debounce the inputs, you can use the timer's handy "input filter" feature to do so.

     

    Graduate
    July 1, 2024

    Hi Barry,

    Changing the TIM_COUNTERMODE_UP to TIM_COUNTERMODE_DOWN did not fix my problem.

    The only way I get the counter to increase while rotate the encoder clockwise is to flip the A & B cables. This is not desirable though as I prefer to do it in the firmware.

     

    I tried changing the  IC1Selection and IC2Selection from TIM_ICSELECTION_DIRECTTI (TIM Input 1, 2, 3 or 4 is selected to be connected to IC1, IC2, IC3 or IC4, respectively) to TIM_ICSELECTION_INDIRECTTI (TIM Input 1, 2, 3 or 4 is selected to be connected to IC2, IC1, IC4 or IC3, respectively) however it did not work.

     

     

    Graduate II
    July 1, 2024

    My bad, you're right. the DIR(ection) signal is also generated by the encoder, I forgot about that.

    Did you try playing with the polarities for each channel? The reference manual has all the details, you should go through it.

     

    This is from G4 RM but should apply

    encoder.jpg

     

    In the RM, this table is followed by two waveform diagrams, you should look at those.

    Super User
    July 1, 2024

    You can change direction with setting polarity of *one* of the signals, i.e. setting one of the TIMx_CCER.CCxP bits.

    I don't understand why would you want to have interrupts upon every edge; I don't think it's possible and I don't quite understand why would changing polarity change that. [EDIT] Re-reading your initial post, I now see you want one interrupt per *detent* - that might be possible if there are several (2 or 4) steps per *detent* and it may then depend on selected polarity. What I wrote about direction of counting above holds. You also can always reverse direction of rotation in software, simply by subtracting current TIMx_CNT value from (TIMx_ARR + 1).  [/EDIT]

    I don't use Cube.

    JW

    Graduate
    July 1, 2024

    Hi @waclawek.jan ,

    When I set the two signal polarities to  TIM_ICPOLARITY_FALLING the HAL_TIM_IC_CaptureCallback triggers every second turn. The direction is correct though.

    Changing the direction of just one of the signals causes the HAL_TIM_IC_CaptureCallback  to triggere on every turn however  the direction is the opposite o want it to be.

    I am using 0011 setting on SMS[3:0]


    Graduate II
    July 1, 2024

    JW, the forum lets you add a "signature" to your profile, which is automatically appended to every post. Most people use it to solicit donations or gameification (sp?) credits. You might want to place your "motto" there.

    Graduate
    July 1, 2024

    Hi @waclawek.jan  and @BarryWhit ,

    Thanks for your help on this.

    I have noticed that sometimes the counter1 value within the HAL_TIM_IC_CaptureCallback funtion ( counter1 = __HAL_TIM_GET_COUNTER(&htim2) ) does not update even though the HAL_TIM_IC_CaptureCallback  function does trigger.

    On the other hand when I call the the same __HAL_TIM_GET_COUNTER(&htim2) within the while loop the counter always updates.

    I have tried adding a filter in the firmware but this did not improve things. On my PCB there is hardware debounce circuit for the A & B signals. 

    Is this a limitation of reading the counter value within the interrupt?

     

    Thank you. 

     

     

    Graduate II
    July 1, 2024

    I don't know. Possibly (well, not impossibly), it's due to the mechnical nature of the device, which must have some amount of recoil. It's possible that when you turn the knob just so, a pulse is generated, followed by a slight recoil in the reverse direction. Perhaps a tight loop is fast enough to catch the change, and a slower interrupt invocation isn't. (Though in that case, a polling loop should catch both the +1 and the -1 that immediately follows, so maybe not).

    If this is critical to your design, hook up a logic analyzer and watch the waveforms closely as you turn. If you find hard proof that the waveforms are clean and bounce-free, but they aren't being decoded properly, show us.

     

    I have tried adding a filter in the firmware but this did not improve things.

    I can tell you that when was working with an encoder, tuning the input filter for debouncing was far more finicky than I  expected. But I had no hardware debouncing, and the weird behavior I saw without the filter was not subtle. Once tuned, it worked reliably. I did not however examined whether there was any slop to its operation, there might have been.

    Graduate
    July 2, 2024

    Hi @BarryWhit ,

    Following is a bit more info on what is happening:

    You can see below on the oscilloscope the capture of the two signals where Encoder_A is on CH1 and Encoder_B is on CH2.

    When Encoder_B signal goes low first the two counters are not the same. I believe this happens because the polarity of the Encoder_B on the firmware is set to rising but the signal goes low instead therefore missing 1 count within the interrupt. In the while loop the counter is updates correctly though. 

    KostasPapouis_0-1719912663311.png

     

    KostasPapouis_1-1719912695830.png

     

    On the other hand, when Encoder goes high first the two counters (in the interrupt and while loop)are the same:

    KostasPapouis_2-1719912716017.png

     

    KostasPapouis_3-1719912732112.png

     

    The same behaviour occurs on the opposite direction.

     

    As a recap following is how the two signals are configured:

    htim2.Instance = TIM2;

    htim2.Init.Prescaler = 0;

    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;

    htim2.Init.Period = 4294967295;

    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

    sConfig.EncoderMode = TIM_ENCODERMODE_TI12;

    sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING;

    sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;

    sConfig.IC1Prescaler = TIM_ICPSC_DIV1;

    sConfig.IC1Filter = 0;

    sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;

    sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;

    sConfig.IC2Prescaler = TIM_ICPSC_DIV1;

    sConfig.IC2Filter = 0;

     

     

     

     

    Graduate II
    July 2, 2024

    Right. So, you have two edges per counter tick.

     

    Input Capture events normally (i.e. I expect that it does) triggers on a switch in logic level, not when the counter changes value.  So, if a pin toggles, but (due to polarity, turn direction, whatever) this edge is not what triggers the change to the counter value, when you read the counter value in the callback, it hasn't yet seen the other edge. When the other edge occurs, the counter updates, but there's no callback, because the 2nd edge does not trigger an Input Capture event. Does that make sense?

    Graduate
    July 2, 2024

    Yes absolutely.

    In my application I will be just reading the counter value lets say on a different timer interrupt and not within the HAL_TIM_IC_CaptureCallback. 

     

     Thanks for your help.

    Graduate II
    July 2, 2024

    Wait for JW to weight in though. He is as knowledgeable as could be and, re-reading his earlier comment, I (and AFAICT, the system's behavior) seem to be at odds with his description. Always risky that. Let's see what he says.

     

    Also, quite possible, switching the polarity of the other line instead, might align the capture event with the counter change event. Though perhaps only in one direction (but then again-again, maybe your application has a direction you care more about). I've not looked at this too closely tbh.

     

    You could also capture the edge on the second input (using another channel), and that way you can be sure you catch the change in a callback. I don't think turning a 30detent encoder would physically result in an interrupt storm, so this might be practical. But it might be even better, as you said, not to to have to rely on the callback at all. If you're concerned it's a bug or problem - I don't think it is. Just a nuance.