If you check out the data sheet section “3.5.3.1.2.4 MEMPWDINSLEEP Register”, you can see that the power settings persist. Basically, you define what you want to happen in deep sleep, and it will do it every time. For turning off cache and flash, the impact is that the system is slower to wake up out of deep sleep due to the time required to rewake the flash, and then that the cache will be empty and everything will miss until the cache refills. So wakeup latency gets longer. For systems that deep sleep for long periods, it’s no big deal. For a system that might constantly be popping in and out of deep sleep, then those wakeup latencies start adding up as wasted time when the system is running at full power. Again, your system needs to understand how often it is deepsleeping and for how long so it can make a good decision.
The SRAM is all lot different from a system perspective because unpowered SRAM loses its contents every time you go to deep sleep. In theory, your system could know that each time it went to sleep, it would need to reinitialize things that had been stored in the repowered SRAM, but honestly, what would that look like from a system perspective? It would be seriously troublesome to have a stack or a heap or globals inside an area of SRAM that got powered off. The only thing that power-controlled SRAM might be [easily] useful for would be for something like a large transient buffer that is not needed during sleep. The easiest way to do that would be to create some new linker section called ‘transient’, which you could allocate those special variables into. The linker section would map to an area of SRAM that gets powered off in deep sleep. That would make sure that your stack and heap and globals retained their values, but transient stuff would get blown away. So it’s possible.
But obviously, the simplest approach to powering down SRAM is to figure out how much total SRAM your system needs, and then just power off the rest at all times. Anything other than that will need to be managed carefully.
(this was when I was experimenting with posting - I could only use the quick-reply option, any attempt to post using the full editor gave me a “Forbidden 403” error)
I looked quite hard and couldn’t find an example which uses a GPIO interrupt instead of the timer to wake from deep sleep. It wasn’t quite as intuitive as I thought, so I made an example, which is below in case anyone is interested (pull request for the core is about to be pending).
/*
LowPower_WithWorkAndGPIOInterrupt
Adapted by Stephen Fordyce 2020-03-23 from:
Artemis Low Power: How low can we go?
By: Nathan Seidle
SparkFun Electronics
Date: February 26th, 2020
License: This code is public domain.
*/
uint32_t msToSleep = 5000; //This is the user editable number of ms to sleep between RTC checks
#define TIMER_FREQ 32768L //Counter/Timer 6 will use the 32kHz clock
uint32_t sysTicksToSleep = msToSleep * TIMER_FREQ / 1000;
const byte STATUS_LED = 14;//13 for Redboard onboard LED, 19 for Nano onboard LED
const byte INPUT_BUTTON = 19;//You'll have to add one, it needs to connect to GND for active
bool awakeFlag = true; //Stops wakeFromSleep() being run if the system is already awake
// GPIO Interrupt Service Routine
void myGPIO_ISR(void)
{
detachInterrupt(digitalPinToInterrupt(INPUT_BUTTON)); //Stop interrupt from being triggered again
wakeFromSleep(); //Without waking the processor properly, nothing more will happen. This means wakeFromSleep() is called twice though.
// am_hal_stimer_compare_delta_set(6, 0); //Or, force the timer to run out, and resume from where you left off.
}
void setup(void) {
Serial.begin(115200);
Serial.println("Artemis Low Power (with timer & GPIO wakeup) Example");
pinMode(STATUS_LED, OUTPUT);
pinMode(INPUT_BUTTON, INPUT_PULLUP);
//Initialise stuff like I2C/Wire/SPI here
}
void loop(void) {
Serial.println("Starting loop, and flashing LED");
digitalWrite(STATUS_LED, HIGH);
delay(200);
digitalWrite(STATUS_LED, LOW);
Serial.println("Phew, that was hard work");
goToSleep();
}
//Power everything down and wait for interrupt wakeup
void goToSleep()
{
attachInterrupt(digitalPinToInterrupt(INPUT_BUTTON), myGPIO_ISR, FALLING);
//End stuff like I2C/Wire/SPI here
power_adc_disable(); //Power down ADC. It it started by default before setup().
Serial.println("Going to sleep");
delay(50); //Wait for serial to finish
Serial.end(); //Power down UART(s)
awakeFlag = false;
//Disable all pads except the interrupt button
for (int x = 0 ; x < 50 ; x++)
{
if(x != INPUT_BUTTON)
am_hal_gpio_pinconfig(x , g_AM_HAL_GPIO_DISABLE);
}
//We use counter/timer 6 to cause us to wake up from sleep but 0 to 7 are available
//CT 7 is used for Software Serial. All CTs are used for Servo.
am_hal_stimer_int_clear(AM_HAL_STIMER_INT_COMPAREG); //Clear CT6
am_hal_stimer_int_enable(AM_HAL_STIMER_INT_COMPAREG); //Enable C/T G=6
//Use the lower power 32kHz clock. Use it to run CT6 as well.
am_hal_stimer_config(AM_HAL_STIMER_CFG_CLEAR | AM_HAL_STIMER_CFG_FREEZE);
am_hal_stimer_config(AM_HAL_STIMER_XTAL_32KHZ | AM_HAL_STIMER_CFG_COMPARE_G_ENABLE);
//Setup interrupt to trigger when the number of ms have elapsed
am_hal_stimer_compare_delta_set(6, sysTicksToSleep);
//Power down Flash, SRAM, cache
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_CACHE); //Turn off CACHE
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_FLASH_512K); //Turn off everything but lower 512k
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_SRAM_64K_DTCM); //Turn off everything but lower 64k
//am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); //Turn off all memory (doesn't recover)
// Enable interrupts to the core.
am_hal_interrupt_master_enable();
//Enable the timer interrupt in the NVIC.
NVIC_EnableIRQ(STIMER_CMPR6_IRQn);
//Go to Deep Sleep.
am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP);
/////////////////////////////////////////////////////////////////////
//<Pause here while sleeping> (and/or while interrupt routines run)//
/////////////////////////////////////////////////////////////////////
//Turn off timer interrupt
NVIC_DisableIRQ(STIMER_CMPR6_IRQn);
//Turn off GPIO interrupt
detachInterrupt(digitalPinToInterrupt(INPUT_BUTTON));
//We're BACK!
wakeFromSleep();
Serial.println("End of goToSleep()");
Serial.println();
}
//Power everything up gracefully
void wakeFromSleep()
{
if(awakeFlag) //Already awake
return;
//Power up SRAM, turn on entire Flash
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_MAX);
//Go back to using the main clock
am_hal_stimer_int_enable(AM_HAL_STIMER_INT_OVERFLOW);
NVIC_EnableIRQ(STIMER_IRQn);
am_hal_stimer_config(AM_HAL_STIMER_CFG_CLEAR | AM_HAL_STIMER_CFG_FREEZE);
am_hal_stimer_config(AM_HAL_STIMER_HFRC_3MHZ);
//Turn on ADC
ap3_adc_setup();
//Set any pinModes
pinMode(STATUS_LED, OUTPUT);
pinMode(INPUT_BUTTON, INPUT_PULLUP);
//Optional - start again (will never reach the end of goToSleep() or wakeFromSleep() though)
//Note - global variables will be preserved if you don't power down SRAM (comment out the line)
//setup();
//Restart Serial
Serial.begin(115200);
delay(10);
Serial.println("Back on");
awakeFlag = true;
//Initialise stuff like I2C/Wire/SPI here
}
//Called once number of milliseconds has passed
extern "C" void am_stimer_cmpr6_isr(void)
{
uint32_t ui32Status = am_hal_stimer_int_status_get(false);
if (ui32Status & AM_HAL_STIMER_INT_COMPAREG)
{
am_hal_stimer_int_clear(AM_HAL_STIMER_INT_COMPAREG);
}
}
I didn’t check the power consumption of the Artemis since I only tested on a custom PCB and an Artemis Nano - it should be the same as the timer deepsleep example (see the comments at the top). However total board consumption with power LED removed (including regulator, etc.) from 3.6V input was a fairly satisfying 120-180uA.
stephenf:
I keep getting “Forbidden” messages when I try to post…
Hi Stephen,
It appears that if you include the characters “()” followed by “{” that the SparkFun forums will generate a “403 Forbidden” error. I’ve messaged their support to alert them of this bug, which is pretty significant given that almost every Arduino code sample is likely to include a function that has this exact character combination.
I tip my hat to you, good sir! That same issue has been bothering me for months on this site, but I never could figure out what caused it.
adam.g:
stephenf:
I keep getting “Forbidden” messages when I try to post…
Hi Stephen,
It appears that if you include the characters “()” followed by “{” that the SparkFun forums will generate a “403 Forbidden” error. I’ve messaged their support to alert them of this bug, which is pretty significant given that almost every Arduino code sample is likely to include a function that has this exact character combination.
as I’m following your guidelines and getting started with the low power implementation of the Ambiq, I’m also looking into your codes and, what a surprise, the Ambiq SDK (revision 2.4.2) makes me look a bit deeper into their code. I wasn’t quite sure about what the following function actually does, powering down the selected memory during deep sleep or leaving it powered on?
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_FLASH_512K); //Turn off everything but lower 512k?
It turns out that with setting the Bit FLASH0PWDSLP in MEMPWDINSLEEP register, it does power the selected memory down during deep sleep. This implementation seems to be a bit user-unfriendly, since you usually want to power down the higher memory address ranges instead of just the lower ones.
Please correct me if I’m wrong.
I haven’t gone through all of Stephen’s Code, yet, but that’s something to watch out for.
I must admit that my chief contribution was bringing together sleeping, interrupt/GPIO wake and timer waking, the lower level stuff like that, I only pulled from other examples.
Ah. I finally see what you are pointing out: the enums created by the SDK to define the address ranges for the memory regions are implicitly defined as ranges from address 0 up to some terminal address. Because of this, there is a further implication that the only way to specify SRAM to be powered down during deep sleep would be to have it come first in the address space, followed by the persistent SRAM.
You could argue that a layout like that is user-unfriendly (and you did ), or you could argue that the whole thing is really a non-issue because you are always going to need the linker’s help to effectively manage a system that splits its SRAM into a portion that remains powered always and a portion that loses its contents during every single deep sleep event. Such a system would need to define its default behavior for any variables that it declares: should a default variable retain its contents across a deep sleep or not? Variables that need to persist would need to be allocated to one linker section, and variables that do not persist would need to be allocated into a different linker section. For example, assume that a particular system desires its default to be for SRAM to persist across a deep sleep. That system could define a special linker section called “non_persistent” specifically to hold all variables that are allowed to lose their contents when a deep sleep occurs. Declarations would have the following general form:
int foo;
int bar __attribute__((section("non_persistent")))
Variables like “foo” get put into the DATA section by default, so at some point, the DATA section needs to be assigned to an address range that will persist across deep sleep. Accessing the non-default persistent behavior for a variable like “bar” requires an explicit request for the linker to place the variable into a special section where that behavior will be expected.
Now that the two different behaviors are segregated into separate linker allocation sections, the system would need to explain to the linker what address ranges belong to each section. There is no requirement that the DATA section for persistent SRAM must start at 0. You could just define “non_persistent” to start at 0 and continue on for as much space as it needs. You would then define DATA to start at the next assignable SRAM boundary after “non_persistent” ends, and continue up to the end address of the SRAM to be powered at all times. The mechanism supplied by the SDK would work for that. That said, I kind of agree with you anyway. The SDK is implicitly defining a layout for how it would work without being very clear about it. Also, the first 64K of SRAM is DTCM which has special high-performance characteristics. The way the SDK is set up, it also implies that the non-persistent SRAM will be assigned to the DTCM area, which may or may not be what you want.
The bottom line is that any system that powers down SRAM during deep sleep has some serious system-wide SRAM management issues to work through. For example, imagine an RTOS system that deep-sleeps in its idle task to save power. Such a system would constantly be erasing everything in the non-persistent section. That sort of behavior would be essentially impossible to exploit usefully. It is clear to me that any system that allows portions of its SRAM to get erased by deep sleep would need to be carefully designed from the ground up with that in mind. Under those circumstances, it would be a trivial extra step to just write your own function to set the bits in the power control registers the way you felt like made the most sense. But even then, you need to explain what you are doing to the linker.
And now, enough procrastinating: it’s back to fixing the dryrot in my deck.
Paul and I have recently been revisiting the Artemis low-power code (viewtopic.php?p=224309#p224296) and I’m finding myself slightly perplexed.
Paul makes a good point that the following code does the opposite of what it states:
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_FLASH_512K); //Turn off everything but lower 512k
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_SRAM_64K_DTCM); //Turn off everything but lower 64k
As I understand it, this enables power down only for the lower 512K of flash and 64K of SRAM during deep sleep. All other memory will remain powered-up.
Would the following approach not be preferrable?
// Power down Flash, SRAM, cache
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); // Powerdown all memory during deepsleep
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_FLASH_512K); // Retain lower 512K of CACHE
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_64K_DTCM); // Retain lower 64K of SRAM
Additionally, earlier in this thread the following command is called upon wake:
//Power up SRAM, turn on entire Flash
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_MAX);
As I understand it, this will not actually have any effect on memory and can be omitted. Is this correct?
I wasn’t aware that there was a ‘retain’ call. Thanks! That makes way more sense the way that the emums are defined in the HAL.
I tried your example out in the debugger and watched what happened to the bits in the PWRCTRL registers:
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); // Powerdown all memory during deepsleep
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_FLASH_512K); // Retain lower 512K of CACHE
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_64K_DTCM); // Retain lower 64K of SRAM
It does what it looks like it should. The default is to power down everything, but then you go back and tell the system what you want retained starting from the lower addresses.
Some comments:
the second comment is wrong, you are retaining power to the lower 512K of FLASH and the Cache will be powered down during deep sleep.
Why retain power to any of the flash? It’s a flash so it won’t forget anything even if it loses power during deep sleep. All you buy is that it the system starts up faster when coming out of deep sleep because the processor doesn’t have to wait for the Flash to power up to start fetching instructions. If startup speed is important to you, you might be better off leaving the cache powered up. If startup speed is not critical, let all of the flash power down during deep sleep.
Regarding this:
//Power up SRAM, turn on entire Flash
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_MAX);
I think your are right: it is completely immaterial. There is no need to adjust them when the system wakes because those bits have no meaning when the system is awake. The whole point of the powerdown bits is to be able to preset things so that the system can automatically turn things off and on for you as the system enters and leaves its sleep mode.
Thanks for the quick reply. The CACHE comment was a typo, sorry about that.
You make a good point regarding powering the flash. I was simply going off the previous examples. Paul had suggested to simply use:
//Power down Flash, SRAM, cache
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); // enable powerdown of all memory during deepsleep
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_32K_DTCM); // disable powerdown of the first 32K memory during deepsleep
And adjust the amount of SRAM powered up depending on the requirements of your code. Is SRAM really the only memory that will always need have powered applied?
What the website note says is that all Apollo3 processors seem to have a bug where even if you tell the cache to remain powered up during deep sleep, the cache RAM can suffer from bit-rot while it is sleeping. The work around is to always turn the cache off during deep sleep so that it has to refresh its contents after waking up again.
Why this cache retention issue is not documented in the chip errata is mystifying. And it is not the only issue I am aware of: I have shown that the STIMER counter will skip counts under various, yet extremely common circumstances, as described in this thread viewtopic.php?f=170&t=55246. Ambiq mentions a ‘rare’ double-count issue in another of the website notes, but again, this issue is not documented in the errata. All this does is cause their userbase to waste time rediscovering known issues.
When using the following code to power down the memory, is there anything you must do upon wake?
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL); // Power down all memory during deepsleep
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_64K_DTCM); // Retain lower 64K of SRAM
When I try to implment this code on v1.2.1 or v2.0.6, once the system goes to sleep and either the RTC or WDT ISR trigger a wake-up event, the system hangs and ultimately enters a WDT reset loop.
// v1.2.1 wake-up function
void wakeUp()
{
// Return to using the main clock
am_hal_stimer_config(AM_HAL_STIMER_CFG_CLEAR | AM_HAL_STIMER_CFG_FREEZE);
am_hal_stimer_config(AM_HAL_STIMER_HFRC_3MHZ);
ap3_adc_setup(); // Enable ADC
Wire.begin(); // Enable I2C
Wire.setClock(400000); // Set I2C clock speed to 400 kHz
SPI.begin(); // Enable SPI
Serial.begin(115200); // Open Serial port
}
The previous method of powering-down the memory does not produce this result.