Force Sleep with WFI and wait for external interrupt


I have a use case where I want to sleep the system and wait for an external interrupt to start the RTOS again. I am aware that there is the tickless idle sleep. However, that doesn’t fit the bill since I don’t want any thread to wake up until a particular interrupt (say from Ethernet MAC) is received. I do have threads that a periodic in nature (wake up every 1 msec, 10 msec, 100 msec etc). All of them should just be suspended until this interrupt is received. Tick Time is 1 msec.

I was thinking of creating a custom portSUPPRESS_TICKS_AND_SLEEP function that can be called from any task. Here are the steps that it would do after disabling most interrupts except the Ethernet MAC interrupt:

  1. vTaskSuspendAll()
  2. prvStopTickInterruptTimer()
  3. disable_interrupts()
  4. prvSleep()
  5. enable_interrupts()
  6. prvStartTickInterruptTimer
  7. xTaskResumeAll()

Is this reasonable or am I missing any steps? Do I need to call vTaskStepTick() after step 4? Is there a better way or an example to achieve this?

Can you use the pre and post sleep processing macros to disable then re-enable all but the MAC interrupt respectively?

@rtel , Yes, this can be done. However, we are still relying on the idle thread for sleep. In my use case, I want to start the sleep procedure from a thread. Unless there’s a way to force all threads to sleep and then let the idle thread handle sleep.

I think having the idle task do the sleep operation also acts as a guide to safe reliable application architecture. All of the tasks should really be doing their own shutdown operations to prepare for sleep properly. Then when there is nothing to do, the idle task notices that and can sleep for a long time. And only the wake-up signals you have chosen can wake you up.

Granted it’s a lot more work to tell the tasks to shut down and wait for a startup request, but ultimately it’s a better design.

@jefftenney In my implementation, there are various tasks. Many of them are waiting on a timer using vTaskDelayUntil. These may wait for 1 msec to say 10 seconds. There are other tasks that are waiting for an event to happen or a notification to occur. I want all of these to sleep when an event occurs (because of say something over UART).

An implementation of vPortSuppressTicksAndSleep will now see that there are tasks waiting to be woken up after certain amount of time. And then would start a timer. Then go to WFI. Would then be woken up by the timer.

I do not want this since I do not want the RTOS to be woken up every 1 msec. I want it to be woken up by an external interrupt (Ethernet as mentioned earlier).

Any ideas on what could be the best way to go about this?

You are right that tickless idle will only suppress as many ticks as allowed by the next task waiting to wake up. If you have tasks waking every 1 or 2 msec, you need to tell them to shut down and wait to be told to start up again.

Here’s one idea to “pause” the tasks so tickless idle will sleep for longer periods. This strategy will work for any task that currently operates on the basis of vTaskDelay(). Change vTaskDelay() to xTaskNotifyTake(), with a timeout equal to the vTaskDelay(). If no request for shutdown comes in, then the call to xTaskNotifyTake() times out at the same time vTaskDelay() would have done. Then add an API function “xyz_stop()” to that module which calls xTaskNotifyGive().

To “unpause” a task (after your ethernet wake-up comes in), add an API function “xyz_start()” to that module that calls xTaskNotifyGive() again. The task code could be waiting for that signal with no timeout. The task won’t do anything and will not be scheduled until somebody calls xyz_start().

1 Like

That is a neat idea Jeff! Here is the pseudo code:


void ModuleTask( void * pvParams )
    BaseType_t xNotificationReceived;
    uint32_t ulNotifiedValue;

    for( ;; )
        xNotificationReceived = xTaskNotifyWait( 0x00,      /* Don't clear any notification bits on entry. */
                                                 ULONG_MAX, /* Reset the notification value to 0 on exit. */
                                                 &ulNotifiedValue, /* Notified value pass out in ulNotifiedValue. */
                                                 pdMS_TO_TICKS( 1000 ) );  /* Block for a second. */

        if( xNotificationReceived == pdFAIL )
            /* No notification was received. Do the periodic work. */
            if( ulNotifiedValue == STOP_NOTIFICATION )
                /* We have been asked to stop. Wait indefinitely for the
                 * start notification. */
                xTaskNotifyWait( 0x00,      /* Don't clear any notification bits on entry. */
                                 ULONG_MAX, /* Reset the notification value to 0 on exit. */
                                 &ulNotifiedValue, /* Notified value pass out in ulNotifiedValue. */
                                 portMAX_DELAY );  /* Block forever. */

                if( ulNotifiedValue != START_NOTIFICATION )
                    /* Should not happen. */
                    configASSERT( pdFALSE );

void ModuleStart( void )
    xTaskNotify( xModuleTaskHandle, START_NOTIFICATION, eSetValueWithOverwrite );

void ModuleStop( void )
    xTaskNotify( xModuleTaskHandle, STOP_NOTIFICATION, eSetValueWithOverwrite );

Thanks @jefftenney @aggarg. Can we instead use xEventGroupWaitBits()? I assume ALL tasks waiting on the same event will be woken up by a single call to xEventGroupSetBits()?

Yes, you can use xEventGroupWaitBits(). Yes, one call to xEventGroupSetBits() would wake all tasks waiting for that specific event or combination of events. Though I don’t know enough about your application to know how that would be advantageous.