Timer callback executes immediately after start/reset of timer

I’m using SiliconLabs Gecko SDK v4.1.2 which comes with FreeRTOS Kernel V10.4.3. I’m using FreeRTOS timer to trigger messages to another task after 5 seconds. My code starts/resets the timer when the button is pressed, and stops the timer when released. If I hold the button for 5 seconds, the callback function is called as expected. The problem i’m having is if I press the button (start/reset timer) and release the button (stop timer), then wait 5 seconds, and press the button again (start/reset timer), the timer callback function is called immediately, which is unexpected. I added some code to check the timer expiration tick value vs current, and for the normal case, the timer ticks are -1 which is expected. For the bad case, I can see timer tick delta of -5000+, etc. Then when i added some guard code to check the timer tick delta to see if it was a ‘real’ timer expiration, the timer stopped working at all. I’ve spent more than 2 days trying different things, running out of ideas. :upside_down_face:

#define MEDIUM_DURATION_MS ((TickType_t) 5000  )
#define LONG_DURATION_MS   ((TickType_t) 10000 - MEDIUM_DURATION_MS )

/* Button press timer handles */
static TimerHandle_t medium_button_timer = NULL;
static TimerHandle_t long_button_timer = NULL;

#define MEDIUM_TIMER_ID   (size_t)1
#define LONG_TIMER_ID     (size_t)2

/* timer callback function */
void button_timer_cb( TimerHandle_t xTimer )
{
  BaseType_t timer_stat = pdFALSE;

  if( xTimerIsTimerActive( medium_button_timer ) == pdTRUE) {
      //printf("M EXP\n");
  }
  if( xTimerIsTimerActive( long_button_timer ) == pdTRUE) {
      //printf("L EXP\n");
  }

  size_t timer_id = (size_t) pvTimerGetTimerID(xTimer);
  if(  timer_id == MEDIUM_TIMER_ID ) {
      send_rx_msg_button( BTN_MEDIUM_HOLD );
      /* start long timer, without task block time */
      timer_stat = xTimerStart(long_button_timer, 0);
      //printf("L START\n");
      app_assert( timer_stat == pdPASS, "Unable start timer" );
  }
//  else if ( timer_id == LONG_TIMER_ID && realExpiry == true ) {
  else if ( timer_id == LONG_TIMER_ID ) {
      send_rx_msg_button( BTN_LONG_HOLD );
  }
  else {
      ;
  }
}

void button_init(void) {

 medium_button_timer = xTimerCreate ( "medium button timer",
                                      pdMS_TO_TICKS(MEDIUM_DURATION_MS),
                                      pdFALSE,
                                      (void *)MEDIUM_TIMER_ID,
                                      button_timer_cb);
 long_button_timer = xTimerCreate ( "long button timer",
                                      pdMS_TO_TICKS(LONG_DURATION_MS),
                                      pdFALSE,
                                      (void *)LONG_TIMER_ID,
                                      button_timer_cb);
}

/* on button change callback, executed in interrupt context */
void sl_button_on_change(const sl_button_t *handle) {
  //printf("B-ENTRY\n");
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  BaseType_t timer_stat = pdFALSE;

  if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_PRESSED) {

      /* reset medium timer */
      timer_stat = xTimerStartFromISR(medium_button_timer, &xHigherPriorityTaskWoken);
      //printf("M START\n");
      app_assert( timer_stat == pdPASS, "Unable start timer" );
  }
  else if (sl_button_get_state(handle) == SL_SIMPLE_BUTTON_RELEASED) {
      if( xTimerIsTimerActive( medium_button_timer ) == pdTRUE) {
          /* medium button timer is active, assume 'short press/release'  */
          send_rx_msg_button_from_isr( BTN_SHORT_PRESS );
      }
      else if ( xTimerIsTimerActive( long_button_timer ) == pdTRUE) {
      }
      else {
          /* both timers already have expired (long press) */
          ;
      }

      timer_stat = xTimerStopFromISR(medium_button_timer, &xHigherPriorityTaskWoken );
      app_assert( timer_stat == pdPASS, "Unable stop timer" );
      //printf("M STOP\n");
      timer_stat = xTimerStopFromISR(long_button_timer, &xHigherPriorityTaskWoken );
      app_assert( timer_stat == pdPASS, "Unable stop timer" );
  }
  else {
      //printf("NEITHER\n");
      app_assert_s(false);
  }

  if( xHigherPriorityTaskWoken != pdFALSE )
  {
      /* Call the interrupt safe yield function here (actual function
      depends on the FreeRTOS port being used). */
      portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  }

  //printf("B-EXIT\n");
}

xTimerReset()? Just looked over the code briefly, apologies.

ive tried both xTimerResetFromISR, xTimerStartFromISR, xTimerStart, xTimerReset
all produce the same result

So the timer task is not invoking the callback even when the timer is expired. Can you break the code in the debugger and see what the code is doing?

Seems like some memory corruption. Have you defined configASSERT and enabled stack overflow checking?

Thanks for responding. I switched to debug build configuration, added EFM_ASSERT=1 to ensure configASSERT is defined, and put break in SiLabs defined function (assertEFM). The code is acting the same, and no breakpoint is occurring. I really like the idea that FreeRTOS has a default timer task, but I’m more inclined to just create my own task, set event or other in the button ISR to simplify, and start/stop timers from the lower priority task rather than the ISR. I might even bypass the timer complete and just compare tick values. If you suspect I’ve actually stumbled across some bug in FreeRTOS, please advise. I feel like I’m experienced embedded SW developer, and this should have worked as is. Maybe there’s still some bug in my code, but i can’t see it.

#define configCHECK_FOR_STACK_OVERFLOW          2
void assertEFM(const char *file, int line)
{
  (void)file;  /* Unused parameter */
  (void)line;  /* Unused parameter */

  while (true) {
  }
}

i even doubled the timer stack size from 160 to 320, same result

Are you using tickless idle?

ooh… tickless idle IS turned on due to SiliconLabs power manager for Bluetooth Low Energy. I’m going to figure out how to disable and see if that makes a difference. I think you’re on to something :smile:

Functions xTimerStartFromISR() and xTimerResetFromISR() may not behave as you would expect with tickless idle enabled.

You can use xTimerChangePeriodFromISR() as a workaround. That function sets the period of the timer, but importantly in your case it also re-starts the timer. And it provides a workaround because the start time of the timer is captured after all the tick count corrections of tickless idle.

2 Likes

yeah, it ends up with the SiliconLabs implementation, i can’t turn off the power manager since Bluetooth Low Energy requires it, and just changing configUSE_TICKLESS_IDLE to 0 causes other build issues. I’ll try your workaround.

OH MY GOSH this suggestion worked! Thanks for saving my weekend :slight_smile:
The above code works perfectly fine now, no issues at all. Sanity restored, humanity saved.

1 Like