cancel
Showing results for 
Search instead for 
Did you mean: 

How to get interrupt after specified number of microseconds?

HTD
Senior III

My idea was simple. Set TIM to have 1MHz after prescaler. Then set ARR to the number of microseconds. So - I toggle GPIO pin each period elapsed and I can measure the time on oscilloscope.

And here come troubles: for set 1us i get 2us. For 2us I get 2us. For 5us I get 5us.

So - times below 2us are unachievable. But it's fine, the shortest time I need is 6us.

But then again, I don't need to spam the interrupts every 6us, I need it only when I send a data bit. So - I set the TIM in the "One Pulse Mode". And here comes another surprise. Whatever time I set - circa 1.9us is added to it.

Here's the fragment of my test code:

static void tim_test_period(TIM_HandleTypeDef* htim)
{
  if (!(data->state & 1))
  {
    data->GPIO->ODR &= ~data->PinBit;
  }
  else
  {
    data->GPIO->ODR |= data->PinBit;
  }
  data->TIM->Instance->CNT = 19;
  __HAL_TIM_ENABLE(data->TIM);
  data->state++;
  if (data->state > 3) data->state = 0;
}
 
void tim_test_start()
{
  debug("Starting TIM test...");
  HAL_TIM_Base_Stop(data->TIM);
  data->TIM->Instance->PSC = 20 - 1; // 20Mhz
  data->TIM->Instance->ARR = 50 - 1; // 200kHz
  HAL_TIM_RegisterCallback(data->TIM, HAL_TIM_PERIOD_ELAPSED_CB_ID, tim_test_period);
  __HAL_TIM_ENABLE(data->TIM);
  __HAL_TIM_ENABLE_IT(data->TIM, TIM_IT_UPDATE);
}

Here's my signal (5us/div):

0693W00000Stu7FQAR.png99.6KHz, accurate enough for what I need.

But why do I need to add 19 to the CNT? This is the time lost somewhere, is it between the timer reaching zero and interrupt triggered? Does it take 1.95us?

The MCU is running on 400MHz clock. The few lines of code in the interrupt handler don't affect the timing at all. When I don't restart the timer, when it runs continuously, I can set any time greater than 2us and it's totally accurate.

So I have 2 options now:

  1. Set the CNT to 19 (or any value equivalent of like 1.95us) on each interrupt. But will it be consistent?
  2. Set the timer to run continuously with 6us period, count the sixes in code and have the fixed accuracy of 6us, but pretty high CPU load even if I don't need those short times.

Option 1 seems tempting, it would be exactly what I want, I start the timer, I get the interrupt exactly n microseconds later. Or 480us later (and the CPU is not bothered during that time). I just wish I knew why do I have that delay in one pulse mode and why it's value is 1.95us.

3 REPLIES 3

Interrupting at 1 MHz probably going to saturate the MCU

ARR of 1 or 2 also probably not desirable.

I'd probably reduce the Prescaler/PSC to zero, and then get the resolution via the Period/ARR

For short delays perhaps freerun the TIM in maximal mode and spin of the delta of CNT

I don't think the prescaler is visible or resetable, that might cause some unpredictability.

There's some setup time, and some time going into interrupt context, not sure that'd amount to ~2us

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

That's what I think, better not touch the prescaler during transmission, even if it would work it would probably introduce glitches / jitter.

Here's my example of sending a sequence of 6us L and 64us H (as in 1-Wire bit 1):

#include "tim_test.h"
 
TimTest_HandleTypeDef tim_test_data =
{
    .TIM = &htim4,
    .GPIO = DS_GPIO_Port,
    .PinBit = DS_Pin,
    .state = 0
};
 
static TimTest_HandleTypeDef* data = &tim_test_data;
 
static inline void set_L() { data->GPIO->ODR &= ~data->PinBit; }
static inline void set_H() { data->GPIO->ODR |= data->PinBit; }
static inline void start_wait(uint16_t us)
{
  data->TIM->Instance->ARR = us * 100 - 1;
  data->TIM->Instance->CNT = 193;
  __HAL_TIM_ENABLE(data->TIM);
}
 
static void tim_test_period(TIM_HandleTypeDef* htim)
{
  next_state:
  switch (data->state)
  {
  case 0:
    set_L();
    start_wait(6);
    break;
  case 1:
    set_H();
    start_wait(64);
    break;
  case 2:
    data->state = 0;
    goto next_state;
  }
  data->state++;
}
 
void tim_test_start()
{
  debug("Starting TIM test...");
  HAL_TIM_Base_Stop(data->TIM);
  __HAL_TIM_ENABLE_IT(data->TIM, TIM_IT_UPDATE);
  data->TIM->Instance->PSC = 2 - 1;
  data->TIM->Instance->ARR = 0;
  HAL_TIM_RegisterCallback(data->TIM, HAL_TIM_PERIOD_ELAPSED_CB_ID, tim_test_period);
  tim_test_period(data->TIM);
}

0693W00000Stu83QAB.png 

It takes exactly 1.93us to compensate for the interrupt delay. However, the signal on the scope looks exactly as it should look. It's generated in a loop and I tried to swipe the touch screen to introduce some interrupts - no visible change in behavior except very small, negligible jitter.

Anyway, I wonder if that kind of code would work... I don't have a free UART, but I need 1-Wire. The only option left is bit-banging. Probably the only way to do bit-banging without hitting other threads too hard is just use the timer interrupt.

S.Ma
Principal

I use a freerunning timer (Lptimer) and calculate the current + delay as new compare trigger event.

Now for bitbang, I would use Timer capture/compare to be less interrupt latency sensitivr. Some timers can dma on compares and capture to relax timings further.