How to Preserve A Variable in RAM between Software Resets

The Problem and The Solution in Theory

 

The usual answer of “how to retain a variable in RAM through reset?” question in Arduino, AVR, PIC, STM32 or most other MCUs is to store that variable in FLASH, EEPROM or external memory like SPI flash or something like that.

The solution can be something simpler than that if the reset is not coming from losing power (power on reset). If the reset is coming from asserting the reset pin or triggering a Watchdog Timer or even calling a software reset command from code, then the RAM content should be retained after reset from an electrical point of view.

So, why then to worry about a RAM variable if its content will be retained electrically? This is because RAM variables will be located in BSS section, unless you asked explicitly where to locate, in the memory map (a part of RAM space) which is initialized in the startup code, and that’s why value is lost after reset.

If the variable that is required to be retained between soft resets has a fixed value, then you go ahead and use flash or EEPROM approach, but if this variable will be maintained during run time, then it will be hard to be maintained in internal/external flash memory, as this kind of memory technology allows to assert/program bit from 1 to 0, but if you want to make the reverse then you should make a memory erase. This becomes more complicated as erase will not be done in a word level but in a page/sector with certain length of bytes instead, and when there are a lot of other variables and stored values, the developer will need to read the page/sector, store it in a buffer, do the erase, change that word value and then write that buffer back again.

Starting from the fact that RAM should retain its value as long as the power is not lost, and by avoiding the RAM initialization from Startup code, then we can define a section in RAM that not initialized after “soft” reset and keep its value through resets. This section can be defined in the linker script (.ld file).

 

The Solution in Practice

 

Arduino Case (AVR-GCC)

 

The area as described before can be found in the default linker scripts for different AVR MCUS that come with AVR-GCC toolchain in the following directory avr\lib\ldscripts. This section is called noinit  and described as a section for ”Global data not cleared after reset”.

 /* Global data not cleared after reset.  */
  .noinit  ADDR(.bss) + SIZEOF (.bss)  :  AT (ADDR (.noinit))
  {
     PROVIDE (__noinit_start = .) ;
    *(.noinit*)
     PROVIDE (__noinit_end = .) ;
     _end = . ;
     PROVIDE (__heap_start = .) ;
  }  > data

Now, to tell GCC where to put a variable, we need to use __attribute__  with the variable declaration.

To test that, we will write a small sketch to count how many soft resets we did (you can trigger a soft reset by pushing the reset button).  We will use another variable to detect the first run of the firmware, by storing special value in it, whenever this variable value is not matching the special value then this is considered as a “cold” start or a start from power on state rather than “soft” reset one.

uint8_t reset_cnt __attribute__ ((section (".noinit")));
uint32_t first_run __attribute__ ((section (".noinit")));

void setup() {
 Serial.begin(9600);
  if(first_run !=0xDEAD0000)
  {
  reset_cnt = 0;
  Serial.println("First run after power down");
  first_run = 0xDEAD0000;
  }
  reset_cnt ++ ;
}

void loop() {
  Serial.print("Press Reset button #: ");
  Serial.println(reset_cnt);
  while(1);
}

 

You can make sure that first_run and reset_cnt are stored in noinit section, by running the following command in the output directory of the compiled Sketch (Windows case) :

C:\Users\DELL\AppData\Local\Temp\arduino_build_xxxxxx>avr-objdump -t sketch_xxxxxx.ino.elf

And you will find that:
SYMBOL TABLE:
00800100 l    d  .data  00000000 .data
00000000 l    d  .text  00000000 .text
00800148 l    d  .bss   00000000 .bss
008001ee l    d  .noinit        00000000 .noinit
...
008001ee l     O .noinit        00000004 first_run
008001f2 l     O .noinit        00000001 reset_cnt
...

To make things more clear, we will reset these variables after certain number of resets to make sure the logic is working as intended, using the following sketch:
uint8_t reset_cnt __attribute__ ((section (".noinit")));
uint32_t first_run __attribute__ ((section (".noinit")));

void setup() {
  Serial.begin(9600);
  if(first_run != 0xDEAD0000)
  {
  reset_cnt = 0;
  first_run = 0xDEAD0000;
  Serial.println("First run");
  }
  reset_cnt ++ ;
}

void loop() {
  Serial.print("Press Reset button #: ");
  Serial.println(reset_cnt);
  if(reset_cnt>5) {
    first_run = 0 ; reset_cnt = 0;
    Serial.println("Reset");
    }
  while(1);
}

You can remove the __attribute__ ((section (“.noinit”))) and check the program behavior.

 

NRF52 Case (ARM-GCC)

 

I used the NRF52 family in this test and nothing should be different a lot than the AVR case. The developer has to add the new section in the .ld file like the following:

noinit (NOLOAD):
  {
    PROVIDE(__start_noinit_data = .);
    KEEP(*(.noinit))
    PROVIDE(__stop_noinit_data = .);
  } >NOINIT

Where NOINIT is defined previously as a memory section in the memory map with a known origin and length.
NOINIT(rwx) :  ORIGIN = 0xXXXXXXXX, LENGTH = 0xXXXX

Moreover, linker script can make the start and end address available to use in the code using the PROVIDE statement. The developer should declare extern variables with names identical to the ones mentioned in the linker script.
extern uint32_t __start_noinit_data;
extern uint32_t __stop_noinit_data;

Then we can use these variables as iterators to access that section, for example:
for (uint32_t *p = &__start_noinit_data; p < &__stop_noinit_data; p++)

 
Exit mobile version