- Sat Feb 22, 2020 3:24 pm
#212601
The answer is both simple and complicated. The simple part is that putting the processor into deep sleep is as simple as calling the appropriate HAL routine:
What worked for me was a much simpler approach. My system only powers its peripherals when it is using them, otherwise they are powered down. For example, I will turn on an I2C peripheral to do a transfer, then shut it down again after the transfer was complete. The power on/off stuff is built into my transfer mechanism so that it is automatic from a caller's point of view. When I call my I2C transfer routine, it makes sure that the IOM gets powered up, the transfer gets performed, and the IOM gets shut down again. The caller is not even aware that it is happening. The important part is that my system is free to invoke a call to am_hal_sysctrl_sleep() whenever it feels like, and there is nothing to do before shutting down, or after waking up. All peripherals that can be powered off will be powered off when the deep sleep happens. Peripherals that are in use will remain powered across the deep sleep, but that's perfect since they are being used. If they weren't in use, they would have been powered off. I can still have parts of the system that may need to manage their power states across deep sleep requests, but those specific parts of the system know what they need to do, and they do it without requiring a complex centralized sleep manager.
If that sort of approach would work for you, I can attest that it is effective. I am using FreeRTOS and have things set up so that FreeRTOS calls deep sleep when it has nothing to do. Since my unneeded peripherals are already shut down, the deep sleep represents the lowest possible power for my system. But it means that FreeRTOS is aggressively deep sleeping whenever there are no tasks ready to run. System-wise, a very simple approach.
Code: Select all
Waking up is just as simple. Just remember that you can't put the processor to sleep without defining some sort of mechanism to wake it again. The mechanism needs to generate an interrupt, like a timer timing out or some external event creating a GPIO interrupt, or an incoming serial character, or something. The interrupt that wakes the process from deep sleep will cause the interrupt handler to get executed, after which execution will return from am_hal_sysctrl_sleep() and start running whatever code followed the sleep call. So the wakeup is easy too. To answer your question about setting up the interrupt to wake from deep sleep, you literally just have to put your code following the call the am_hal_sysctrl_sleep() that put the processor to sleep. It's that easy. In an Arduino-style world, you could do something like this:
am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP);
Code: Select all
The trickier part is that to save maximum power, it is required to turn off various unused bits of the processor before going to deep sleep. That means that you have to turn them on again after waking. One way to do that is to create some sort of centralized software sleep manager that knows what parts of your system to shut down before sleeping and what parts to restart after waking up. I didn't like that approach, because it seemed like a hypothetical sleep manager would need to have intimate knowledge of all kinds of areas of the system, including how to interpret their operating state. It gets complicated fast, and hard to maintain.void loop()
{
// Do normal loop stuff performing all required tasks
if (nothing-left-to-do) {
// We have decided that there is nothing to do, so we can sleep.
// First, make sure that some interrupt exists that will wake us.
setUpWakeInterrupts();
am_hal_sysctrl_sleep(DEEP_SLEEP);
// When the interrupt completes, we get here.
// As written, we just let this iteration of loop() complete.
// The Arduino driver code will reinvoke loop() just like normal.
}
}
What worked for me was a much simpler approach. My system only powers its peripherals when it is using them, otherwise they are powered down. For example, I will turn on an I2C peripheral to do a transfer, then shut it down again after the transfer was complete. The power on/off stuff is built into my transfer mechanism so that it is automatic from a caller's point of view. When I call my I2C transfer routine, it makes sure that the IOM gets powered up, the transfer gets performed, and the IOM gets shut down again. The caller is not even aware that it is happening. The important part is that my system is free to invoke a call to am_hal_sysctrl_sleep() whenever it feels like, and there is nothing to do before shutting down, or after waking up. All peripherals that can be powered off will be powered off when the deep sleep happens. Peripherals that are in use will remain powered across the deep sleep, but that's perfect since they are being used. If they weren't in use, they would have been powered off. I can still have parts of the system that may need to manage their power states across deep sleep requests, but those specific parts of the system know what they need to do, and they do it without requiring a complex centralized sleep manager.
If that sort of approach would work for you, I can attest that it is effective. I am using FreeRTOS and have things set up so that FreeRTOS calls deep sleep when it has nothing to do. Since my unneeded peripherals are already shut down, the deep sleep represents the lowest possible power for my system. But it means that FreeRTOS is aggressively deep sleeping whenever there are no tasks ready to run. System-wise, a very simple approach.