What is the correct way of putting FreeRTOS into deepsleep?

Hello, I’m currently using FreeRTOS port from Nordic Semiconductor.
I need to put the system into deep sleep. For this system deep sleep is when most of peripherals are disabled, and some HW components on PCB as well, such as external memory and GPS module. The system should be able to wake up from accelerometer ISR or cellular HW module ISR.
Currently, I can put the system into deep sleep. It is done by suspending all tasks and stopping timers and suspending timer task as well to get eNoTasksWaitingTimeout state in portSUPPRESS_TICKS_AND_SLEEP(), where system sleeps properly until IRQ occures. However, waking up becomes problematic.
First, some tasks using vTaskDelayUntil() function, which after Resuming task causing it to execute multiple times without delay, for all that time it was skipped.
Second, the same behaviour is occuring with timers. I was trying to Start them and then Reset, but it does not seem to have any effect.
Which leads me to a question that may be the way I’m trying to implement sleep mode is not correct.
Putting the system into sleep mode happens in specific task, which Suspend all other tasks and timers. Waking up is done form HW ISR, where all tasks are resumed using xTaskResumeFromISR() and timers are started using xTimerStartFromISR().

Hi Sergii,

Using tickless idle (configUSE_TICKLESS_IDLE) is the best way to access deep sleep. If you are using a Cortex-M device, the official FreeRTOS port includes a complete tickless-idle implementation. Maybe the Nordic port does too.

To get into deep sleep (and not just normal sleep), you’ll need to implement the configPRE_SLEEP_PROCESSING() and configPOST_SLEEP_PROCESSING as explained in the link. For example you might set the SLEEPDEEP bit if conditions permit deep sleep instead of normal sleep.

Hello Jeff, thank you for your response.
I’m already using tickless idle mode, however it does not provide all desired properties of deep sleep. For example, in my case I do not need tasks to be executed, thus I need to suspend them. In addition, I cannot turn off some HW components that are present on PCB, such as GPS module, because it will take time to get back to live.
All in all, I need to disable tasks and some HW components, thus system can sleep(stays inactive) for a long period of time - hours to days. And that is the problem I described in original question.

It sounds like you have a task that knows when it’s time to use deep sleep, and that task tries to prepare the entire system for deep sleep all on its own, without the other tasks knowing. That design isn’t ideal because it can’t easily manage race conditions and because it leads to the kinds of problems you are seeing.

Instead, let every task do its own preparations for deep sleep, in whatever way is appropriate for that task. Then your code in configPRE_SLEEP_PROCESSING() can notice that it’s time for deep sleep and that all the tasks are ready.

It may sound like a lot of re-work, but it’s probably worth doing.

You are right, there are few supervisor tasks, that know about sleep mode, and others that do simple processing.
However, I still do not see how distributing sleep processing between tasks will solve the problem.
Correct me if I wrong. Let’s say there is a system state that is available to all tasks, and tasks will read it and know when it’s time to prepare for sleep. Thus, they will do some deinit and indicate that they are ready to sleep. In the end they still should either suspend themselves or it should be done by configPRE_SLEEP_PROCESSING(), which in turn will lead to the same problem I had in the begining - after resuming all tasks that use vTaskDelayUntil() will execute non-stop to catch up for the time was spend in sleep, same with timers.
Do you suggest not to suspend tasks at all and keep them idle, so they will wake up see there is still sleep mode, and fall back to sleep?
Or do you suggest not to you vTaskDelayUntil() at all?

The key is if the task KNOWS it is going to sleep, at the wakeup, it can reset the time in the control variable for vTaskDelayUntil to the current time, so it doesn’t need to wind up to the present.

Or configPOST_SLEEP_PROCESSING() can reset the “last wake time” variables to the current tick count.

It seems best not to suspend tasks, as a general rule. Instead, design your APIs to include stop or deinit functions.

Take a GPS task for example. Your GPS API could have a gpsStart() and gpsStop() function. If the application calls gpsStop(), then the GPS task might stop everything it is doing, including all FreeRTOS timers, and then wait for gpsStart() to be called. That waiting would have an infinite timeout. The implementation of gpsStart() might signal a binary semaphore or task notification.

When all your tasks have infinite timeouts, and if no FreeRTOS timers are running, then you will get eNoTasksWaitingTimeout and can use that as an indicator for deep sleep. If your design uses infinite timeouts for other things when deep sleep is not safe, you can come up with another way for configPRE_SLEEP_PROCESSING() to notice it’s time for deep sleep.

Keep in mind that depending on your implementation of tickless idle, time may stand still during deep sleep. The default implementation from FreeRTOS behaves this way because SysTick stops during deep sleep. Not sure about the Nordic implementation if that’s what you are using.

Thanks @jefftenney, we decided to separate task execution code according to operating modes, as was recommended before!

1 Like