Retained Memory Tips

Retained memory is a feature of the Particle Photon and Electron that allows data to be preserved across reboots. As a quick refresher:

| Technique    | Fast       | Unlimited | Survives |  Survives | 
|              | Read/Write | Writes    | Reset    | No Power  |
| ------------ | ---------- | --------- | -------- | --------- |
| Regular RAM  | Yes        | Yes       | No       | No        |
| EEPROM       | No         | No        | Yes      | Yes       |
| Retained RAM | Yes        | Yes       | Yes      | No(1)     |

1In some cases you can add a coin cell battery or other power supply to VBAT to preserve retained memory and the real-time clock even when the Photon/P1 is not powered, see below.

Retained memory, or the special static RAM (SRAM) or backup RAM section in the processor, is a special block of 3068 bytes that is preserved when the processor is in deep sleep mode and across resets. On the Photon, it’s also possible to add a coin cell battery to preserve the contents when the power is removed.

Unlike the EEPROM emulation, the SRAM block can be written to at full processor speed, and does not wear out when written to.

Retained memory does not affect the flash size or the RAM used size - it’s allocated out of the separate SRAM block.

Code Examples

Simple Example

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

retained int savedValue = 0;

void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("savedValue=%d", savedValue++);
    delay(1000);
}

Running that code and monitoring the USB serial port:

savedValue=0  <== power on
savedValue=1
savedValue=2
savedValue=3
Serial connection closed.  Attempting to reconnect... <== reset
Serial monitor opened successfully:
savedValue=4
savedValue=5
savedValue=6
savedValue=7
savedValue=8
Serial connection closed.  Attempting to reconnect... <== flashed code
Serial monitor opened successfully:
savedValue=9
savedValue=10
savedValue=11

Structs are OK

In addition to primitives like int, bool, etc. you can retain a struct:

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

struct TestStruct {
	int valueA;
	int valueB;
};

retained TestStruct testStruct;

void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("valueA=%d valueB=%d", testStruct.valueA, testStruct.valueB);

    testStruct.valueA++;

    testStruct.valueB += rand() % 10;

    delay(1000);
}
valueA=0 valueB=0 <== power on
valueA=1 valueB=3
valueA=2 valueB=6
valueA=3 valueB=8
valueA=4 valueB=17
valueA=5 valueB=17
valueA=6 valueB=25
valueA=7 valueB=27
valueA=8 valueB=33
valueA=9 valueB=39
Serial connection closed.  Attempting to reconnect... <== reset
Serial monitor opened successfully:
valueA=10 valueB=48
valueA=11 valueB=51
valueA=12 valueB=54
valueA=13 valueB=56
valueA=14 valueB=65
valueA=15 valueB=65
valueA=16 valueB=73

But not an initialized struct

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

struct TestStruct {
	int value;

	TestStruct() : value(10) {}
};

retained TestStruct testStruct;

void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("value=%d", testStruct.value++);

    delay(1000);
}

In this code, the constructor for TestStruct initializes the value to 10. This overrides the retained value, so it behaves as if not retained.

value=10  <== powered on
value=11
value=12
value=13
Serial connection closed.  Attempting to reconnect...  <= reset here
Serial monitor opened successfully:
value=10
value=11
value=12

Same for actual classes, too

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

class TestClass {
public:
	TestClass() : value(10) {}

	int value;
};

retained TestClass testClass;

void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("value=%d", testClass.value++);

    delay(1000);
}

In this code, the constructor for TestStruct initializes the value to 10. This overrides the retained value, so it behaves as if not retained.

value=10 <== powered on
value=11
value=12
value=13
Serial connection closed.  Attempting to reconnect... <== reset here
Serial monitor opened successfully:
value=10
value=11
value=12

Uninitialized values in classes work though

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

class TestClass {
public:
	TestClass() {}

	int value;
};

retained TestClass testClass;

void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("value=%d", testClass.value++);

    delay(1000);
}

In this example, the constructor doesn’t set the values, so retaining works:

value=0 <== power on
value=1
value=2
value=3
Serial connection closed.  Attempting to reconnect... <== reset
Serial monitor opened successfully:
value=4
value=5
value=6

Static class members are OK too

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

class TestClass {
public:
	TestClass() {}

	static int value;
};

TestClass testClass;
retained int TestClass::value;


void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("value=%d", testClass.value++);

    delay(1000);
}
value=0 <== power on
value=1
value=2
value=3
Serial connection closed.  Attempting to reconnect... <== reset
Serial monitor opened successfully:
value=4
value=5
value=6
value=7

And you can initialize those!

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

class TestClass {
public:
	TestClass() {}

	static int value;
};

TestClass testClass;
retained int TestClass::value = 8;

void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("value=%d", testClass.value++);

    delay(1000);
}

A static class member initializer does work! Note that on power on it’s initialized to 8, but retain still works.

This is different than initializing in a constructor, which does not work.

value=8   <== powered on
value=9
value=10
value=11
Serial connection closed.  Attempting to reconnect... <== reset
Serial monitor opened successfully:
value=12
value=13
value=14
value=15

You can’t retain a String

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

retained String str;


void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("str=%s", str.c_str());

    str.concat("a");

    delay(1000);
}

This doesn’t work, because the string contents are not retained. Also, the String constructor initializes it to an empty string.

str=   <== power on
str=a
str=aa
str=aaa
str=aaaa
Serial connection closed.  Attempting to reconnect... <== reset
Serial monitor opened successfully:
str=
str=a
str=aa
str=aaa

But you can retain a character array

#include "Particle.h"

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY));

retained char str[4] = "abc";

void setup() {
    Serial.begin(9600);
}

void loop() {

    Serial.printlnf("str=%s", str);

    str[0]++;
    if (str[0] >= 127) {
    	str[0] = ' ';
    }

    delay(1000);
}

This works because the primitive character array is retained.

Initialization is also allowed, if you declare it like that. Note that you need to choose in advance how many characters to reserve. In this case it’s 3 characters, plus a trailing null byte string terminator, which is what the [4] is.

str=abc <== power on
str=bbc
str=cbc
Serial connection closed.  Attempting to reconnect... <== reset
Serial monitor opened successfully:
str=ebc
str=fbc
str=gbc
str=hbc
str=ibc

About VBAT

In certain circumstances, the VBAT pin can be used to keep the contents of the retained SRAM (and the real-time clock) when there is no power. The behavior depends a bit on the device.

Photon/P1

Connecting a coin cell battery to VBAT will preserve the retained SRAM when the main power is removed.

Somewhat counter-intuitively if you don’t use a battery, you may want to connect VBAT on the Photon/P1 to GND. This will cause the retained memory to be initialized on first power-up, but retain the values across reset and all sleep modes. If you leave VBAT floating, sometimes the contents will not be initialized after removing power. Sometimes they will, you never know. (Do not connect VBAT to GND on an Electron, for the reasons explained below.)

Electron

The Electron, as it is normally powered by a the LiPo battery, connects VBAT to 3V3 internally. This allows the retained memory to be preserved as long as you have the LiPo battery connected.

As VBAT is connected to 3V3 you must never connect VBAT to GND on the Electron!

There’s actually a zero-ohm resistor that connects the two, so with a hot air rework station you can remove the link and use VBAT like the Photon/P1.

E Series

The E series cellular module does not connect VBAT to 3V3 internally, so you can choose whether you want it to behave like the Electron or Photon/P1 based on how you set up your base board.

Backup Power

If you are using backup power to VBAT, the most common methods are a coin cell battery or a supercap.

When not powered, the Photon uses a maximum of 19 uA from VBAT.

Coin Cell

A CR2032 Lithium coin cell battery is one option for powering VBAT. It’s only used when the Photon isn’t otherwise powered by VIN or USB. You might use a holder like this one from Adafruit.

A CR2032 Lithium battery is 3.0V and is rated at 240 mAh to 2.0V. Since the Photon uses at most 1.9 uA on VBAT, the battery should last over a decade. You could probably use a smaller CR2024 battery and be fine.

Supercap

In some cases, users have successfully used a super capacitor. The advantage is if you need to preserve data for a relatively short amount of time, it can be less expensive. Also, there may be additional restrictions on shipping products with Lithium batteries, so the supercap can solve this dilemma.

This post in the community forum has a great deal of information.

13 Likes

@rickkas7 Great tutorial. If I am looking to free up some valuable regular RAM and want to use 3K of retained RAM that would otherwise go unused, what are the variables I should go for or avoid. If I definitely want to initialise variables on startup (say after a reset) then I will need to set the values in setup()?

Lastly, current motherboard design for photon has the VBAT connected to 3V3? Will this cause a problem using retained RAM if I don’t necessarily need to have the value retained.

I have run the simple example at the top of the thread (OS 1.4.0) and saved value never starts at 0 even with a 1 min power off before test. Device is a standalone argon

Results

savedValue=2139068109
savedValue=2139068110
savedValue=2139068111
savedValue=2139068112
savedValue=2139068113
savedValue=2139068114
savedValue=2139068115
savedValue=2139068116
savedValue=2139068117

Has something changed in this feature?

Power to the device cannot be removed for retained memory to work. Do you have a battery connected?

So I have it powered from USB, ran code and initial value was not 0. Removed power just to be sure that it should have reset to 0.

Tried this feature now on two different sets of code with same result start value is never 0.

If you don’t have a battery connected then it will not work when all power is removed.

You are missing what I am saying - I’m my view whether a battery is or isn’t connected - the first time the code is run - the value should be 0 - then after that point and as long as battery/power is connected - the value will be retained as its last set value.

Using the above example - with or without a battery connected the first time the code is run it is never 0. as expected it does increment from what ever value it first shows.

STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY)); 
retained int savedValue = 0; 
void setup() {
 Serial.begin(9600);
} 

void loop() { 
Serial.printlnf(“savedValue=%d”, savedValue++); 
delay(1000);
}
1 Like

The previous generation of Particle devices definitely clear battery-backed RAM (the hardware reset circuitry does this in the STM32F3), but I’m not sure if that’s 100% guaranteed behavior by the API.

Usually when you are dealing with uninitialized RAM, traditionally it’s not safe to assume it will be zero, and you might need some kind of check on that.

For more advanced uses, may I suggest this library:

It’s not yet tested with the new generation of Particle devices, but there’s no obvious reason why it shouldn’t work for you.

1 Like

Agreed - I’ll take a look - however the documentation and example show a particular outcome and it should work - otherwise it may cause a lot of confusion and time wasting for anyone trying to use the retained function as described. @rickkas7 are there any known issues on GEN3 with retained - or is it just my inexperienced use of retained?

But @shanevanj would be initialising the value on a cold start, but as it seems the device OS doesn't recognise the cold start as such - hence I'd also see this as device OS bug.

The Electron Device API declares when it will be cleared in a table, but the Argon does not even have that section in its API documentation.

It seems to fall under the category of "unspecified".


Speaking of undocumented things, System.sleep(SLEEP_MODE_DEEP) as used in the linked table (without the seconds argument) is also undocumented, at least according to https://docs.particle.io/reference/device-os/firmware/electron/#sleep-sleep-

I just found this because it seemed to be causing unexpected behavior in my code today.

It seems that the implementation of retain memory is not clearly finalized on the Gen3 device (i’ve only tested this on Argon).

i got the same issues with my argon and the retain memory is not initialized at power on, so you need to manage this issue within your code.
Once initialized manually it seems to work as expected (retained during reset for example)

If i’m right the retained memory feature is even not documented in the Argon docs.

see this thread : Retained Memory Not Working Correctly
or
Retained memory support on Gen. 3 devices

i hope Particle will fix that soon as it’s a bit annoying.

Nico

2 Likes

I was looking for a way to retain some variables data after powering OFF my home thermostat, reset it, etc., and this solved that for me. Quick question, how can I check how much SRAM is left available? Is there a function like (System.freeMemory()) to check the SRAM?

Thanks a lot, rickkas7!

Alex

Not to be a nag, but I strongly recommend trying out my library. I think it will solve your problem since it provides checksum and initialization functionality.

1 Like