Author Topic: STM32 Encoders (using timers) not throwing an interrupt  (Read 2804 times)

0 Members and 1 Guest are viewing this topic.

Offline Pack34Topic starter

  • Frequent Contributor
  • **
  • Posts: 753
STM32 Encoders (using timers) not throwing an interrupt
« on: June 14, 2023, 11:30:22 pm »
The backstory for what I'm trying to do:
I'm using an STM32H7 to monitor a quadrature encoder. Every X amount of ticks I want an interrupt to be thrown so I can send an external trigger pulse to a keysight DMM.

What I have working, is that the STM32 is monitoring the quadrature pulses on TIM1 and counting. I can pull this using i = TIM1->CNT; all day long. But it doesn't seem like it's attached to the IRQhandler properly so I can send out a pulse.

I did change HAL_TIM_Encoder_Start(&htim1,TIM_CHANNEL_ALL); to HAL_TIM_Encoder_Start_IT(&htim1,TIM_CHANNEL_ALL); but it only seems to be throwing TIM1_CC_IRQHandler which seems to ignore what I'm setting as the period.

From my understanding, what I should be able to do, is to specifiy a period of 1000, which means it should count up to 1000 and then trigger one of the IRQ handler functions to do it's thing. I did notice that the STM32CubeIDE didn't autogenerate a stub function for TIM1_IRQHandler(void).

Code: [Select]
int main(void)
{
  long i;

  HAL_Init();
  SystemClock_Config();

  MX_GPIO_Init();
  MX_ETH_Init();
  MX_USART3_UART_Init();
  MX_TIM1_Init();
  MX_TIM3_Init();
  MX_USB_DEVICE_Init();

  quarterRevolutions = 0 ;

  while (1)
  {
  i = TIM1->CNT;
  }

}

Code: [Select]
static void MX_TIM1_Init(void)
{

  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 1000;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;

  sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 5;

  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 5;

  if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;

  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_Encoder_Start_IT(&htim1,TIM_CHANNEL_ALL);

}
« Last Edit: June 15, 2023, 12:13:01 am by Pack34 »
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 561
  • Country: sk
Re: STM32 Encoders (using timers) not throwing an interrupt
« Reply #1 on: June 15, 2023, 09:05:15 am »
Interrupt upon update (overflow) is not that much useful for Encoder mode, because the encoder may stop/oscillate rapidly around the point of overflow, causing interrupts in rapid succession ultimately losing some. Best practice is to leave the timer at maximum period (ARR) and sample its CNT regularly e.g. from a different timer.

Nonetheless, if you desperately want this interrupt, just enable it by setting TIMx_DIER.UIE (there may be some Cube/HAL incantation for this, I don't know, I don't use Cube/HAL) and write the stub (or the whole ISR) yourself.

Note, that TIM1 has several separate vectors for different interrupt sources, so it's not TIM1_IRQHandler().

https://github.com/STMicroelectronics/STM32CubeH7/blob/c2b13142a354d26b0baa5f505bb5607cd4aadd88/Drivers/CMSIS/Device/ST/STM32H7xx/Source/Templates/gcc/startup_stm32h743xx.s#L172

JW
 

Offline Pack34Topic starter

  • Frequent Contributor
  • **
  • Posts: 753
Re: STM32 Encoders (using timers) not throwing an interrupt
« Reply #2 on: June 15, 2023, 03:09:49 pm »
Interrupt upon update (overflow) is not that much useful for Encoder mode, because the encoder may stop/oscillate rapidly around the point of overflow, causing interrupts in rapid succession ultimately losing some. Best practice is to leave the timer at maximum period (ARR) and sample its CNT regularly e.g. from a different timer.

Nonetheless, if you desperately want this interrupt, just enable it by setting TIMx_DIER.UIE (there may be some Cube/HAL incantation for this, I don't know, I don't use Cube/HAL) and write the stub (or the whole ISR) yourself.

Note, that TIM1 has several separate vectors for different interrupt sources, so it's not TIM1_IRQHandler().

https://github.com/STMicroelectronics/STM32CubeH7/blob/c2b13142a354d26b0baa5f505bb5607cd4aadd88/Drivers/CMSIS/Device/ST/STM32H7xx/Source/Templates/gcc/startup_stm32h743xx.s#L172

JW

For my specific application, it's what's required. I'm attempting to trigger quick voltage measurements at specific encoder ticks per revolution. The stepper motor will always travel in full revolutions. It's a test fixture to evaluate wear in a part over time. Evaluating the wear of different materials throughout the test using speed, revolutions, and braking force as knobs. Significant amount of trials so it needs to be as repeatable as possible. Watching the encoder value in the main loop and then executing the trigger will likely not be fast enough as the stepper will be moving at about 1500RPM with a 4000 CPR encoder. I did pick the H7 though because of the 480MHz clock so if I had to do anything janky to get it to work that the clock speed may make it good enough for the purpose.

It "looks" like the solution was adding __HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE); at the end of the initialization function for the timer. then add my own functions in main.

I wrote a separate program in python to control the stepper motor and it seems like the initial movements always triggers one additional period. It seems consistent so I'll need to look at toggling an IO when the update fires and comparing that to the encoder counts. I'm betting that, for some reason, it's firing when it does an initial increment off of 0. But I need to confirm that so I can compensate.

Code: [Select]
uint16_t counter = 0 ;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim1)
counter = __HAL_TIM_GET_COUNTER(htim);
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim1)
periodRevolutions = periodRevolutions + 1 ;
}
 

Offline wek

  • Frequent Contributor
  • **
  • Posts: 561
  • Country: sk
Re: STM32 Encoders (using timers) not throwing an interrupt
« Reply #3 on: June 15, 2023, 03:49:45 pm »
The "libraries" such as Cube/HAL tend to force Update upon initialization as a way to force new value for prescaler to be used in the first period. It means that if you enable Update interrupt after such initialization, it will fire immediately. If this is the case, workaround is to clear timer's status register after initialization (simply by writing TIM1->SR=0;, or again some Cube/HAL gibberish for the same).

Depending on the initial state of input signals and the exact sequence of events during initialization, you may also observe a "step backward" (i.e. from the reset value of TIMx_CNT = 0 to TIMx_CNT = ARR) and then when the motor/encoder turns forward, it will cause an Update.

Some motors/mechanical assemblies also tend to vibrate during startup, causing steps to be seen in both directions.

[EDIT] I've just relized you mention a *stepper motor* in conjunction with encoder. Stepper motors... perform... steps, i.e. a very well controlled motion - why the need for encoder at all, then?

JW
« Last Edit: June 15, 2023, 03:56:24 pm by wek »
 
The following users thanked this post: Pack34

Offline Pack34Topic starter

  • Frequent Contributor
  • **
  • Posts: 753
Re: STM32 Encoders (using timers) not throwing an interrupt
« Reply #4 on: June 15, 2023, 04:21:35 pm »
The "libraries" such as Cube/HAL tend to force Update upon initialization as a way to force new value for prescaler to be used in the first period. It means that if you enable Update interrupt after such initialization, it will fire immediately. If this is the case, workaround is to clear timer's status register after initialization (simply by writing TIM1->SR=0;, or again some Cube/HAL gibberish for the same).

Depending on the initial state of input signals and the exact sequence of events during initialization, you may also observe a "step backward" (i.e. from the reset value of TIMx_CNT = 0 to TIMx_CNT = ARR) and then when the motor/encoder turns forward, it will cause an Update.

Some motors/mechanical assemblies also tend to vibrate during startup, causing steps to be seen in both directions.

[EDIT] I've just relized you mention a *stepper motor* in conjunction with encoder. Stepper motors... perform... steps, i.e. a very well controlled motion - why the need for encoder at all, then?

JW

Re: Steps
In order to sync the data acquisition from the stepper motor driver to specific degrees. I'm sending the driver commands to go to perform specified rotations at specific speeds with acceleration and deceleration speeds. The goal is to specify in the program that "I want 10 samples per revolution, over 50 revolutions, at a speed of 1500 RPM". Each sample will measure displacement of the material as it wears down. The stepper motor is massive, runs at 70V, and using an industrial stepper driver. Luckily the displacement sensor just outputs a simple voltage that can be read by the DMM.

Re: Initialization
I bet that's why I'm seeing this artifact after everything kicks off. Seems to always get an update interrupt on the first posedge of channel A and the third negedge of channel A. At least over a handful of trials. This would also seem to be why I always seem to be lagging by a consistent amount of counts between what I'm getting on the STM32 versus the encoder monitor I have attached for the python script.

Re: This
Quote
Depending on the initial state of input signals and the exact sequence of events during initialization, you may also observe a "step backward" (i.e. from the reset value of TIMx_CNT = 0 to TIMx_CNT = ARR) and then when the motor/encoder turns forward, it will cause an Update.

Bingo-bango. Pre-loading to a small arbitrary count of 5 seems to prevent that double-tapping at the beginning. I'm betting it's how the encoder lines are initialiy energized. The encoder interface that I'm using to monitor the counts in python pulls those lines up does not energize until after I open the connection to it. So that transition from low-to-high is likely confusing the STM32 thinking it's rolling back a tick, so triggering an update as it rolls back, then overflows again and triggers another update.
« Last Edit: June 15, 2023, 04:41:46 pm by Pack34 »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf