Persist Variables Across MCU Restarts

For many IoT projects it can be helpful save some information across MCU restarts. This could be information about external device state or what happened just prior to the restart. The latter is key to better diagnostics for your IoT devices. Once devices are outside the lab and no longer tethered to a debugging probe, this capability is crucial.

For this post, I will be using an Adafruit Feather M0 Express. The Feather M0 Express has a Microchip Cortex M0+ MCU (ATSAMD21G18). I am also using the Arduino SAMD framework. Some folks downplay the Arduino, but unless you need RTOS functionality it offers a relatively simple and widely supported approach to embedded development.

The central part of this topic is controlling the loader utility; it is also frequently called the linker. Arduino uses the GCC toolchain. The linker in the GCC toolchain is ld.exe. For those not familiar with linking… in summary, the linker takes various blocks of code output from the compiler and arranges them into a binary file. This process includes finalizing addressing, padding sections for proper alignment, and outputting a fully formed binary executable.

Variables from your embedded application are placed in standard areas of memory, typically this memory is initialized (aka cleared) as your executable is started. The key task to making variables persistent across restarts is designating a section of memory that is not initialized or cleared at program start. To protect the variables, we need to adjust to the “linker script” to designate a no initialization section.

The approach discussed here is only intended to persist variables across processor restarts. We are protecting RAM memory from default initialization; if the processor is power-cycled, RAM contents are lost. If your application needs persistence across power loss, investigate flash memory options (future article).

The process I will walk through has 3 steps:

  • Determine the space needed for the application variables you want to protect.

  • Create (or adjust for your needs) a no initialization section in the linker script. I typically call this section noinit.

  • Decorate the variables you want to protect with a linker instruction.

Determine Space Requirements

First consider the information you need to protect. I place that information typically in one or two structs; I am using C style structs, not C++ classes for this. These are going to be global variables. I find this to be a small number of variables that are closely related. If there are multiple subsystems or topics with this need each is a separate struct variable. By example device state and diagnostics could (should be) separate structs, but both can be protected as shown later in this post.

Look at your declarations for the variables you want to protect. I typically will put all “protected” variables in one or two structs (I am using C style structs, not C++ classes for this). These are going to be global variables.

typedef struct diagnosticInfo_tag
{
    uint16_t diagMagic;
    diagRcause_t rcause;        // cause of last reset
    uint8_t bootLoops;          // boot-loop detection
    uint8_t notifCode;          // code from application notification callback
    char notifMsg[20];          // message from application notification callback
    char hwVersion[40];         // device HW version that may\may not be recorded
    char swVersion[12];         // embedded app version that may\may not be recorded

    /* ASSERT capture info */
    uint32_t pc;
    uint32_t lr;
    uint32_t line;
    uint32_t fileId;
 
    /* Application communications state info */
    int16_t commState;                // indications: TCP/UDP/SSL connected, MQTT state, etc.
    int16_t ntwkState;                  // indications: LTE PDP, etc.
    int16_t signalState;                 // indications: rssi, etc.

    /* Hardfault capture */
    uint16_t ufsr;
    uint32_t r0;
    uint32_t r1;
    uint32_t r2;
    uint32_t r3;
    uint32_t r12;
    uint32_t return_address;
    uint32_t xpsr;
} diagnosticInfo_t;

To calculate the total space required add up your project’s struct field sizes and/or individual variables you are going to protect. You will also need to consider padding between fields. Padding is implementation dependent. A rule of thumb is that struct fields are typically aligned on an offset that is a multiple of the size of the data element’s size. Example: a 16-bit field starts on a 2-byte offset, a 32-bit field starts on a 4-byte offset, etc. If you analyze the above struct using GCC’s default behavior, the  struct above will occupy 60-bytes. There is no padding required to keep fields aligned at their desired boundaries. Most of the int field sizes are self-evident; a couple of other types (for GCC on Arduinos): char is 1-byte, pointers are 4-bytes.

Adjust the Linker Script

I strongly recommend that your backup any files you plan to change prior to starting. Things can go wrong in big ways when you start adjusting system files! The easiest and most effective way to check your changes when things go wrong is to revert changed files back to their initial state.

The Arduino SAMD framework files are located separate from the main Arduino folder structure. Technically the SAMD framework is an extension. Adafruit (and Sparkfun) further extend the Arduino SAMD framework.

The root folder for the SAMD framework is found in C:\Users\<yourID>\AppData\Local\Arduino15

You will find a .\packages folder that has folders for arduino and adafruit (and can include others like SparkFun). As you dive down into the adafruit folder you will see .\hardware\samd\<yourVersion>\variants. Each Adafruit SAMD board is a “variant” and has its own collection of system files. For this post I am working with the Feather M0 Express development board, so I drill into the feather_m0_express folder for the files supporting that development board. Almost there. One more level down through the linker_scripts/gcc folders, there you will find 2 linker files with a .ld file extension: flash_with_bootloader.ld and flash_without_bootloader.ld.

In summary (for my ID and installed version of Adafruit SAMD = 1.7.5)… C:\Users\GregTerrell\AppData\Local\Arduino15\packages\adafruit\hardware\samd\1.7.5\variants\feather_m0_express\linker_scripts\gcc

Below I have a segment from the flash_with_bootloader.ld file supplied by Adafruit. The noinit block has been added to create a new memory section that is not loaded (NOLOAD). This suppresses the automatic initialization, which is to “load” variables placed here with their initialized values from your source code. Also the section is aligned before and after to a 4 byte boundary.

The “.” symbol designates the current position in memory the loader is working at. So, “.=ALIGN(4);” instructs the loader to adjust its position, if required and as needed, to be at a 4-byte boundary. The .noinit section is aligned before and after to a 4-byte boundary.

I have placed the .noinit section at the end of data storage after .data and .bss. The .data section contains explicitly initialized variables (you did something like myVar = 17), the values you set are copied down from flash to RAM prior to main() execution. The .bss section uninitialized global and static objects, but prior to main() it is set to all zero values.

	.bss :
	{
		. = ALIGN(4);
		__bss_start__ = .;
		*(.bss*)
		*(COMMON)
		. = ALIGN(4);
		__bss_end__ = .;
	} > RAM

	.noinit (NOLOAD):
	{
		. = ALIGN(4);
		*(.noinit*)
		. = ALIGN(4);
	} > RAM

	.heap (COPY):
	{
		__end__ = .;
		PROVIDE(end = .);
		*(.heap*)
		__HeapLimit = .;
	} > RAM

Decorate the Variables to Protect

Continuing with the diagnostic control type and variable from before, you create a global variable for your struct type and decorate it with a section attribute. This tells the linker to place this variable in that memory section (RAM). The complete line is shown below.

diagnosticControl_t g_diagControl  __attribute__ ((section (".noinit")));

More To Come

When you implement this pattern, you will have to determine how to best initialize your protected variables. After all the whole effort here is to skip initialization when your embedded application starts up. Diagnostics information (like the examples here) can be initialized after a “normal” reset. I’ll be covering capturing diagnostics in couple of the upcoming posts.

 
Next
Next

Building Back Better