It has taken me a while, but I am finally getting the Artemis module on my Blackboard achieve the deepsleep current consumption levels promised by the Ambiq datasheet. To help others interested in this process, I have created an Arduino sketch that shows the steps I took. This sketch is really just a starting point. Different systems will have different requirements for what peripherals need to be active during deepsleep, or how much memory they need powered during runtime or deepsleep, or if they can tolerate a ‘cold’ cache after waking from deepsleep, just to name a few things.
It is worth noting that deepsleep for any system is a very tricky thing. When you are talking about currents in the microamp and sub-microamp range, all kinds of things start to become measurable effects that might have been previously ignorable. For example, my test board is sleeping right now at 2.5 uA. If I pick the board up off my desk , the power drops to 2.3 uA. If I let go of the board, the power goes back up to 2.5 uA. It is a very repeatable effect. Who knows why, but there you are.
Another class of problems involves the software you might be using: after updating my Arduino system from 1.8.9 to 1.8.10, the deep sleep current jumped from 2.5 uA to 55 uA. I traced that back to what appeared to be another GPIO being configured in a fashion that caused it to suck more current. Changing the appropriate #if in the demo source code (provided below) to invoke the “big hammer” to disable all the GPIOs brought the current back down to 2.5 uA. But to prove that I knew what was going on, I did a search of the GPIOs to see which one it was. And log and behold, it appeared to be none of them. So I changed the source code back to the way it was before. And guess what, the power stayed the same at 2.5 uA, even after a cold start power cycle. So why did it go up in the first place? Again, I don’t have an answer to that one. All I know for sure is that deepsleep is affected by subtle things.
Here’s a final example, and then I’ll shut up: Environment factors also matter. If I exhale a nice, long breath onto my Blackboard, the power consumption goes up over 3 uA and then drifts back down to 2.5 uA over a few seconds as the humid air dissipates.
The bottom line is that there are lots of subtleties involved in any system that is trying to minimize power consumption, and it is a hard problem to truly know that you are on top of all of them. You need to be both careful and ruthless managing your power drains.
Stepping back from the subtleties for a moment, the critical factors that affect deep sleep power consumption are:
Finally, here is an Arduino sketch that puts my Blackboard into a deep sleep at 2.5 uA. Cut and paste the whole mess into your own sketch. Play with it. Try changing memory sizes, or clock sources, and see what changes. You will need a good current meter though. A lot of the changes result in sub-microamp differences in current.
See the photo showing my Blackboard powered from 3.3V being directly injected from an external power supply through my meter and into the measurement connector on the board. I am using Arduino 1.8.10, so who knows if future changes to the Arduino code itself or the Sparkfun Arduino package will affect things.
// An Arduino sketch for a Sparkfun Artemis Blackboard that demonstrates how to
// configure the board for deep sleep.
#define LED_ON HIGH
#define LED_OFF LOW
const char* peripheralNames[] = {
"NONE",
"IOS",
"IOM0",
"IOM1",
"IOM2",
"IOM3",
"IOM4",
"IOM5",
"UART0",
"UART1",
"ADC",
"SCARD",
"MSPI",
"PDM",
"BLEL",
"MAX"
};
void flash(uint32_t count)
{
while (count > 0) {
digitalWrite(LED_BUILTIN, LED_ON);
delay(50);
digitalWrite(LED_BUILTIN, LED_OFF);
delay(100);
count--;
}
}
void panic()
{
while (1) {
flash(1);
}
}
// For some reason, I need this in the VS Code IDE even though I don't in the Arduino IDE
extern Uart Serial1;
// Disable powering all peripherals except UART0 (used by the Arduino Serial object)
void disablePeripherals()
{
// Find out what peripherals are enabled at this point and disable them all
// except UART0 (Arduino 'Serial') which we will still use for a bit:
Serial.println("Initial peripheral power enable states:");
for (uint32_t id = (AM_HAL_PWRCTRL_PERIPH_NONE + 1); id < AM_HAL_PWRCTRL_PERIPH_MAX; id++) {
Serial.print(" ");
Serial.print(id);
Serial.print("[");
Serial.print(peripheralNames[id]);
Serial.print("]: ");
uint32_t enabled;
am_hal_pwrctrl_periph_enabled((am_hal_pwrctrl_periph_e)id, &enabled);
Serial.println(enabled ? "ENABLED" : "disabled");
if (enabled && (id != AM_HAL_PWRCTRL_PERIPH_UART0)) {
am_hal_pwrctrl_periph_disable((am_hal_pwrctrl_periph_e)id);
}
}
Serial.println("All peripherals except UART0 have been powered down.");
}
// Configure the memories for the desired normal runtime and deepsleep operation.
void configureMemories()
{
// The Apollo3 processor can be configured to only power part of the Flash and SRAM
// in order to save power during normal operation.
// For example, if an application doesn't need all of the SRAM, it can arrange to power off the unused parts.
// A further optimization allows the system to power down even more of the SRAM during deep sleep.
//
// In this example, we will ask to only use 1/2 of the flash during normal operation,
// but we will power all 384K of SRAM.
am_hal_pwrctrl_memory_enable(AM_HAL_PWRCTRL_MEM_FLASH_512K);
am_hal_pwrctrl_memory_enable(AM_HAL_PWRCTRL_MEM_SRAM_384K);
// In deepsleep mode, the chip can be configured to turn off even more memory. Note that if
// runmode enables (above) turn some part of the memory off, the sleepmode cannot turn it on.
// These sleepmode settings only allow you to power down memories that runmode had powered up.
// Uncommenting the next line would tell the system to power down all of SRAM except the low 32K of RAM.
// This would take less power, but it would require the codebase to make sure that any information
// that it expected to survive a deepsleep would have to be stored in the low 32K of SRAM.
//PWRCTRL->MEMPWDINSLEEP_b.SRAMPWDSLP = PWRCTRL_MEMPWDINSLEEP_SRAMPWDSLP_ALLBUTLOWER32K;
// The Flash memory can also be powered down during deep sleep.
// changing FLASH0 from being disabled during deep sleep to enabled during sleep adds about 40 uA
// Testing proves that asking flash1 to be enabled during sleep does not increase the power if runmode has already disabled flash1.
PWRCTRL->MEMPWDINSLEEP_b.FLASH0PWDSLP = 1; // '1' indicates that the specified memory will have its power gated OFF during deepsleep
PWRCTRL->MEMPWDINSLEEP_b.FLASH1PWDSLP = 1;
// The cache can also be shut down during deepsleep. This means that when the processor wakes,
// the cache will be 'empty' and everything will be a miss until the cache refills.
// The tradeoff is that turning the cache off will cause the system to be a bit slower to wake up.
PWRCTRL->MEMPWDINSLEEP_b.CACHEPWDSLP = 1;
}
void configureStimer()
{
// The default Arduino environment runs the System Timer (STIMER) off the 48 MHZ HFRC clock source.
// The HFRC appears to take over 60 uA when it is running, so this is a big source of extra
// current consumption in deep sleep.
// For systems that might want to use the STIMER to generate a periodic wakeup, it needs to be left running.
// However, it does not have to run at 48 MHz. If we reconfigure STIMER (system timer) to use the 32768 Hz
// XTAL clock source instead the measured deepsleep power drops by about 64 uA.
CTIMER->STCFG &= ~0xF;
#if 1
CTIMER->STCFG |= 0x3; // selects 32768 Hz via crystal osc. This appears to cost about 0.1 uA versus selecting "no clock"
#else
// This option would be available to systems that don't care about passing time, but might be set
// to wake up on a GPIO transition interrupt.
CTIMER->STCFG |= 0x0; // selects "no clock" (disables STIMER)
#endif
}
void dumpRegs()
{
Serial.println("\nDumping Registers");
// Display which devices currently are powered
Serial.print("PWRCTRL->DEVPWREN: $");
Serial.print((uint32_t)&(PWRCTRL->DEVPWREN), HEX);
Serial.print(": $");
Serial.println((uint32_t)PWRCTRL->DEVPWREN, HEX);
// This register enables power to various memories during runmode.
// If a memory is not enabled in runnmode, it will not be powered in deep sleep either!
Serial.print("PWRCTRL->MEMPWREN: $");
Serial.print((uint32_t)&(PWRCTRL->MEMPWREN), HEX);
Serial.print(": $");
Serial.println((uint32_t)PWRCTRL->MEMPWREN, HEX);
// For every memory that is powered in runmode, this register defines whether the specific
// memory will remain powered during sleepmode.
Serial.print("PWRCTRL->MEMPWDINSLEEP: $");
Serial.print((uint32_t)&(PWRCTRL->MEMPWDINSLEEP), HEX);
Serial.print(": $");
Serial.println((uint32_t)PWRCTRL->MEMPWDINSLEEP, HEX);
// Show the MISC register contents
Serial.print("PWRCTRL->MISC: $");
Serial.print((uint32_t)&(PWRCTRL->MISC), HEX);
Serial.print(": $");
Serial.println((uint32_t)PWRCTRL->MISC, HEX);
Serial.print("BLE Buck ON: ");
Serial.println(PWRCTRL->SUPPLYSTATUS_b.BLEBUCKON);
// Note in Apollo3 manual: "The SIMO buck cannot be dynamically enabled/disabled after initial device reset."
Serial.print("SIMO Buck ON: ");
Serial.println(PWRCTRL->SUPPLYSTATUS_b.SIMOBUCKON);
// The CLOCKENSTAT registers are important because they show you if there are any parts of the processor
// that might be responsible for not letting the HFRC oscillator shut down.
// Show the CLOCKENSTAT register contents
Serial.print("CLKGEN->CLOCKENSTAT: $");
Serial.print((uint32_t)&(CLKGEN->CLOCKENSTAT), HEX);
Serial.print(": $");
Serial.println((uint32_t)CLKGEN->CLOCKENSTAT, HEX);
// Show the CLOCKEN2STAT register contents
Serial.print("CLKGEN->CLOCKEN2STAT: $");
Serial.print((uint32_t)&(CLKGEN->CLOCKEN2STAT), HEX);
Serial.print(": $");
Serial.println((uint32_t)CLKGEN->CLOCKEN2STAT, HEX);
// Show the CLOCKEN3STAT register contents
Serial.print("CLKGEN->CLOCKEN3STAT: $");
Serial.print((uint32_t)&(CLKGEN->CLOCKEN3STAT), HEX);
Serial.print(": $");
Serial.println((uint32_t)CLKGEN->CLOCKEN3STAT, HEX);
}
// The last step is to disable any GPIOs that might be the source of
// stray or leaking current going in or out of the Apollo3.
void disableGpios()
{
#if 1
// Use a big hammer: disable every single GPIO
for (int i = 0; i <= 49; i++) {
am_hal_gpio_pinconfig(i, g_AM_HAL_GPIO_DISABLE);
}
#else
// Disable the GPIOs that we know are in use:
// Disabling the debugger GPIOs saves about 1.2 uA total:
am_hal_gpio_pinconfig(20 /* SWDCLK */, g_AM_HAL_GPIO_DISABLE);
am_hal_gpio_pinconfig(21 /* SWDIO */, g_AM_HAL_GPIO_DISABLE);
// These two GPIOs are critical: the TX/RX connections between the Artemis module and the CH340S on the Blackboard
// are prone to backfeeding each other. To stop this from happening, we must reconfigure those pins as GPIOs
// and then disable them completely:
am_hal_gpio_pinconfig(48 /* TXO-0 */, g_AM_HAL_GPIO_DISABLE);
am_hal_gpio_pinconfig(49 /* RXI-0 */, g_AM_HAL_GPIO_DISABLE);
#endif
}
void setup()
{
// We will use the Serial object while we get set up, then shut it down during our deep sleep test
Serial.begin(115200);
delay(1000);
Serial.print("DeepSleep Testing V1.3 ");
Serial.println(__DATE__ " " __TIME__);
// Shut down the Arduino Serial1 which uses the Apollo3 peripheral UART1
Serial1.end();
// The Arduino environment has almost certainly already done this, but it is important and
// calling it again is harmless: Configure the Apollo3 power control block for low power.
am_hal_pwrctrl_low_power_init();
disablePeripherals();
configureMemories();
configureStimer();
// Before we shut down, dump the state of some of the CPU registers involved with power control
// to verify the results of the memory configuration requests.
dumpRegs();
// Print one last message, allow the serial output to drain, shut down the Arduino Serial object,
// and finally, disable power to the hardware UART0 peripheral
Serial.println("\nEntering sleep mode");
delay(100);
Serial.end();
am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_UART0);
// Last thing: disable GPIOs so that they don't leak current or get backfed by external circuitry
// This step is critical to avoid interactions with the CH340 and its TX/RX LEDs from
// interacting with the Apollo3 processor.
disableGpios();
}
void loop()
{
#if 1
// On my Artemis Blackboard, this results in a power consumption of 2.5 uA
am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP);
#else
// On my Artemis Blackboard, this results in a power consumption of 68.6 uA
am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_NORMAL);
#endif
// We never intend to get here, so blink the LED forever if we do as a panic indication.
// It turns out that the Arduino environment is using the STIMER, probably as its basic timer for
// managing millis() and things like that. When this timer rolls over, it appears to cause an interrupt that will
// take the system out of sleep again. Given that we have reconfigured the STIMER to clock at 32 KHz instead
// of 48 MHz (for the purposes of power testing), we won't wake up for a long time. Also, note that because
// we have messed with the Arduino environment before going to sleep, we can't expect the Arduino environment to function
// properly when we wake up again. To really make use of deepsleep modes, a system would need to reconfigure
// itself after waking up.
pinMode(LED_BUILTIN, OUTPUT);
panic();
}
I hope the community finds this useful!