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 marczo
#215041
Hello everyone,

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?
Code: Select all
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.

Cheers
Marc
By stephenf
#215070
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.
User avatar
By robin_hodgson
#215081
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:
Code: Select all
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. :(
User avatar
By adam.g
#224335
Hi folks,

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:
Code: Select all
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?
Code: Select all
// 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:
Code: Select all
//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?

Cheers,
Adam
User avatar
By robin_hodgson
#224342
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:
Code: Select all
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:
1) 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.
2) 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:
Code: Select all
//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.
User avatar
By adam.g
#224344
Hi Robin,

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:
Code: Select all
//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?

I appreciate the clarifications!

Cheers,
Adam
User avatar
By robin_hodgson
#224346
Something was nagging me about powering down the cache. I found it again on the Ambiq website: https://support.ambiq.com/hc/en-us/arti ... happening-

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.
User avatar
By adam.g
#224354
Thanks for the note about the cache.

When using the following code to power down the memory, is there anything you must do upon wake?
Code: Select all
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.
Code: Select all
// 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.

Cheers,
Adam
User avatar
By robin_hodgson
#224355
I don't think there is anything extra to do for the RAM on wakeup.

If you change your power down statement to retain all RAM and leave everything else exactly the same, do things work? If so, then your system is probably using RAM outside of the first 64K. Can you check your linker output file to make sure that every single bit of RAM your system is using is within the first 64K? I just checked the system I am working on and I see that by default, my Segger build creates a small stack right at the very top of RAM. That would get blown away if I ever powered down that uppermost block of RAM.

Also, have you tried setting a debugger breakpoint on the instruction after your deep sleep instruction and single-stepping through your wakeup code after the processor comes to life again? It might be that your peripheral wakeup code is responsible for things going astray, and the single-stepping would show you right where it happened.
User avatar
By adam.g
#224368
Hi Robin,

Enabling all the SRAM did the trick! The code must not be retaining the SRAM that's actually in use.
Code: Select all
am_hal_pwrctrl_memory_deepsleep_powerdown(AM_HAL_PWRCTRL_MEM_ALL);
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_384K);
Considering that the current draw of powering up 32K vs 384K SRAM is ~0.6 uA, is there any reason not to retain all of it?

Also, I'm afraid I'm not familiar with linker output files. Can you provide any additional information on where to find them? I'm using the Arduino IDE but have set my build path in preferences.txt and can see the files used to compile the code.

Cheers,
Adam
User avatar
By robin_hodgson
#224371
I think that the disabling of RAM during sleep is really only of value to people who are sweating every single hour of battery life.

My opinion: Arduino is like popping the hood on a modern car and seeing a bunch of plastic covers. It engine bay looks nice and simple, but all those covers just make it harder to get at what is underneath.

So lets's pop the hood and start removing engine covers:
  • Go to Arduino/File/Preferences and check both 'show verbose output' boxes
  • build your sketch
  • look at the second last line in all that verbose output to find out where your build actually got done. Here is an example from my build: "C:\\Users\\robin\\AppData\\Local\\Arduino15\\packages\\SparkFun\\tools\\arm-none-eabi-gcc\\8-2018-q4-major/bin/arm-none-eabi-size" -A "C:\\Users\\robin\\AppData\\Local\\Temp\\arduino_build_183998/ReflowController.ino.axf"
  • cd to the temp directory that Arduino is using: "cd C:\\Users\\robin\\AppData\\Local\\Temp", fixing up the slashes for whatever you are using to get to that directory
  • you should see a map file there. Mine was named 'ReflowController.ino.map'. Open it with your favorite editor.
It's a big, big file. Use your editor to find where all the symbols are assigned to RAM. Look for the highest RAM address you can find. Remember that RAM addresses always start with 0x100xxxxx. Here is my map file for my project showing the largest RAM address:
Code: Select all
.heap           0x10021214        0x0 load address 0x0002c594
                0x10021214                __end__ = .
                0x10021214                PROVIDE (end = .)
 *(.heap*)
                0x10021214                __HeapLimit = .
It looks like the HeapLimit is the last variable in RAM at 0x10021214. I can't actually tell you if that is where the Heap starts or ends (but I bet that it is the first address of the Heap). That is another complication. If your code uses the heap, then how much is it allocating? I am betting that any allocations my sketch makes will be using space after 0x10021214 in my example build. But again, who knows how Arduino does it. I find it better to use a mechanism where I define where the heap is and how big it is so that I know its limits. So maybe with Arduino, it would be easiest to just leave all the RAM turned on. If you decide to use a different development environment in the future, well, they are more complex, but they sure give you control over everything. Plus, nothing beats a hardware debugger. I use the Segger Embedded Studio. Here is the output from its map file:
Code: Select all
***********************************************************************************************
***                                                                                         ***
***                                      LINK SUMMARY                                       ***
***                                                                                         ***
***********************************************************************************************

Memory breakdown:

   67 690 bytes read-only  code
    6 046 bytes read-only  data
  150 701 bytes read-write data

Region summary:

  Name        Range                    Size                Used              Unused      Alignment Loss
  ----------  -----------------  ----------  ------------------  ------------------  ------------------
  FLASH       00010000-0007ffff     458 752      73 740  16.07%     385 000  83.92%          12   0.00%
  RAM         10000000-1005ffff     393 216     150 701  38.33%     242 515  61.67%           0   0.00%
That's a nice summary. If you want details, it has them all too. Here is the overview of the RAM space:
Code: Select all
  0x10000000  __RAM1_segment_start__                        ----  Gb  [ Linker created ]
  0x10000000  __RAM1_segment_used_start__
                                                            ----  Gb  [ Linker created ]
  0x10000000  __RAM_segment_start__                         ----  Gb  [ Linker created ]
  0x10000000  __RAM_segment_used_start__
                                                            ----  Gb  [ Linker created ]
  0x100048B0  __heap_start__                                ----  Gb  [ Linker created ]
  0x100248B0  __heap_end__                                  ----  Gb  [ Linker created ]
  0x10060000  __RAM1_segment_end__                          ----  Gb  [ Linker created ]
  0x10060000  __RAM1_segment_used_end__                     ----  Gb  [ Linker created ]
  0x10060000  __RAM_segment_end__                           ----  Gb  [ Linker created ]
  0x10060000  __RAM_segment_used_end__                      ----  Gb  [ Linker created ]
  0x10060000  __stack_end__                                 ----  Gb  [ Linker created ]
The beauty of this little section is that all of those symbols are available to my program. So if it wanted, it could take a look at __heap_end__ and then calculate what pages of RAM need to have their power retained during deep sleep.

Arduino is a great way to get started fast, but a sub-optimal way to develop a complex application. That's probably enough opinions for one day though. I think there are lots of development systems with hardware debugging now, but I still use Segger. I like it!
User avatar
By adam.g
#224374
Thanks, Robin,

This super helpful!

I had specified a path to output the build files, so it was quite simple to locate the map file. So while we don't know if the heap starts or stops at 0x1001857c, we do know that it's at least 99708 bytes into the total SRAM memory, which explains why retaining the lowest 64K wouldn't work.
Code: Select all
.heap           0x000000001001857c        0x0 load address 0x00000000000137e4
                0x000000001001857c                __end__ = .
                0x000000001001857c                PROVIDE (end = .)
 *(.heap*)
                0x000000001001857c                __HeapLimit = .

And if I'm interpreting the output of your Segger map file correctly, your heap starts at the 0x10000000 and ends at 0x100248B0, or 149680 bytes, meaning that you could safely retain 160K of SRAM?
Code: Select all
am_hal_pwrctrl_memory_deepsleep_retain(AM_HAL_PWRCTRL_MEM_SRAM_160K);
While I'm quite enthusiastic about low-power operation, I think for my applications I could happily keep all of the SRAM powered. I'm currently using the MicroMod Artemis Processor, which due to a design flaw in its op-amp circuit, has a quiescent draw of 150-200 uA. I don't think an extra microamp is going to make a big difference!

Appreciate all your help and I'll definitely look into Segger Embedded Studio. Can it easily be used with the SparkFun board definitions and libraries?

Cheers,
Adam
User avatar
By robin_hodgson
#224396
I had to go read the Artemis MicroMod schematic to see what you meant by op amp design flaw. The bottom line is that the Apollo3 ADC reads between 0 and 2V. It was probably an attempt for MicroMod compatibility on Sparkfun's part that they chose to rescale the ADC inputs with some op-amps so that the ADC range would appear to be 0 to 3.3V. The quad opamp Sparkfun chose takes 120 uA, which is OK, but not great. On a different project, I needed a quad opamp and used a MCP6424 which is 14 uA total. My speed requirements were not that high though. Maybe the Sparkfun opamp runs faster. Anyway, I agree that it is not worth worrying about saving 0.6 uA by powering down SRAM with that opamp in the circuit.

I treat the Segger IDE more like a bare metal environment. I don't care about board definitions because I make my own. I steer clear of the confusingly complex Arduino pin numbering indirection scheme and strictly use Apollo3 pad numbering. I have a fake Arduino compatibility layer to make it slightly easier to import Arduino libraries. By that, I mean that I have definitions for things like millis() and delay() and things that are likely for an Arduino library to use. My latest project includes the Arduino Adafruit GFX library for driving SPI LCDs that has been attached to fprintf(). I finally have a nice, easy to use LCD driver. It lets me do this:
Code: Select all
Adafruit_ST7735* lcd; 
…
fprintf(lcd, “Hello, world!”);
which does just what you would think. So the whole setup is more complex and took some time, but in the end, it is exactly what I want. And debuggable!
You do not have the required permissions to view the files attached to this post.
 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