SparkFun Forums 

Where electronics enthusiasts find answers.

Topics pertaining to the Arduino Core & software used with the Artemis module and Artemis development boards.
User avatar
By robin_hodgson
#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:
Code: Select all
am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP);
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:
Code: Select all
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.
  }
}
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.

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.
User avatar
By ironhalo
#212611
Thanks a lot for that Robin, that was the missing info I needed to get me to the next step. I now have a basic but functioning sleep/wake routine integrated into my sketch. After an activity timeout period, the unit goes into deep sleep, and I have 3 hardware interrupts (2 buttons and a vibration sensor) that can all successfully wake the unit from sleep. What I am experimenting with now is which peripherals I can disable permanently (in setup code) without affecting my sketch, versus what I'm going to need to disable just before sleep and re-enable just after waking.

What I'm stuck on at the moment is how to re-enable the STIMER to work off the default 48 MHZ HFRC clock after waking. Because I am not waking from sleep via timer/clock (only from GPIO interrupts), I've elected to disable the clock before sleep using:
Code: Select all
am_hal_stimer_config(AM_HAL_STIMER_NO_CLK);

However I'm having a hard time finding the correct syntax to re-enable the STIMER to its default state after waking (and my sketch certainly won't resume functioning without a running system timer). I took a few shots in the dark, but no luck so far. Was thinking it might be along the lines of...
Code: Select all
am_hal_stimer_config(AM_HAL_STIMER_HFRC_48MHZ);

Can anyone lend a hand? I'm sure there is a reference source for this, but I clearly don't know where to look. TIA.
User avatar
By robin_hodgson
#212612
I'm looking at page 663 of the Apollo3 data sheet rev DS-A3-0p10p0. The CLK_SEL field in that register has no 48 MHz setting. The fastest it goes is HFRC divide-by-16, or 3 MHz. I would guess that the default Arduino setting is divide-by-16, but its just a guess. Why not have your program read the setting in that field before going to sleep, save the value, select "no clock" during sleep, and just restore the original setting after wakeup.

Don't forget that with the timers turned off, you can't do time calculations based on millis() that cross a sleep boundary. If that matters...
User avatar
By ironhalo
#212613
Ah thanks, 3MHZ did the trick. Was going off the comments denoting 48Mhz, wasn't aware of the divide-by-16. Good thought to read/save it and restore.

Yeah thanks for the reminder, fortunately I don't need to track time across the sleep cycle. I'm resetting all my millis() timing markers upon waking.
User avatar
By ironhalo
#212614
So, i have so far identified the following items from the lowerPower examples that I can include in my sketch's setup() code without affecting any of my functionality. I am going to continue to experiment to see what else I can disable/shutdown for even more power savings. I'm curious though if some of these items might need to go into my sleep routine so that they are run each time I'm initiating sleep, instead of just once at the beginning of the sketch. For example if the FLASH, Cache, or SRAM reset to their defaults upon coming out of sleep, then I may need to disable them again each time before going back into sleep to retain the power savings of them being disabled?
Code: Select all
    //Turn off ADC
    power_adc_disable();

    // Stop the XTAL.
    am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_XTAL_STOP, 0);

    // Disable the RTC.
    am_hal_rtc_osc_disable();

    // Set the clock frequency.
    am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0);

    // Set the default cache configuration
    am_hal_cachectrl_config(&am_hal_cachectrl_defaults);
    am_hal_cachectrl_enable();

    // Initialize for low power in the power control block
    am_hal_pwrctrl_low_power_init();

    // 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);

    //Turn OFF Flash1
    if (am_hal_pwrctrl_memory_enable(AM_HAL_PWRCTRL_MEM_FLASH_512K))
    {
        while (1)
            ;
    }

    // Power down SRAM
    PWRCTRL->MEMPWDINSLEEP_b.SRAMPWDSLP = PWRCTRL_MEMPWDINSLEEP_SRAMPWDSLP_ALLBUTLOWER32K;
User avatar
By robin_hodgson
#212615
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.

My opinion, of course!
By stephenf
#213493
(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)
Last edited by stephenf on Sun Mar 22, 2020 10:51 pm, edited 1 time in total.
By stephenf
#213494
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).
Code: Select all
/*
  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);
  }
}
By stephenf
#213530
Glad someone found it useful already.

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.
User avatar
By adam.g
#213586
stephenf wrote: Sun Mar 22, 2020 10:42 pm 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.

Cheers,
Adam
User avatar
By robin_hodgson
#213594
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 wrote:
stephenf wrote: Sun Mar 22, 2020 10:42 pm 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.

Cheers,
Adam
 Topic permissions

You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum