All members of the EFM32 family provide a single page of flash memory for storing "user" data. This is called, appropriately enough, the user data page, and it is located at address 0x0fe0_0000 on all devices. The size of the user data page varies depending on the underlying flash hard macro block used by a given EFM32 device. These sizes are summarized in the following table:

 

Device User Page Size
(words)
User Page Size
(bytes)
Gecko 128 512
Giant Gecko 1024 4096
Tiny Gecko 128 512
Wonder Gecko 512 2048
Leopard Gecko 512 2048
Zero Gecko 256 1024
Pearl Gecko 512 2048
Jade Gecko 512 2048

 

Because of how it is mapped in memory, the user data page cannot be used to run program code. This makes it ideal for storing unit-specific information, like a serial number, software version, calibration data, network configuration, etc.

 

How, then, can a C program access information in the user data page? Variables are allocated in RAM as needed, and constants, along with program code, are placed in the "text" section defined by the linker and ultimately stored in the main flash array.

 

Information placed in the user data page is like both a variable and a constant. Application code can only determine the value of something in the user data page by reading an underlying memory location (as opposed to constants, effective addresses for which are calculated on the fly by the compiler), and, at the same time, these memory locations effectively hold constant data because they cannot be change by processor register store instructions.

 

For starters, it helps that the CMSIS-compliant peripheral support libraries provided for EFM32 devices include among its different definitions the base address of the user data page. This can be found in the header file for each specific EFM32 part. For example, scrolling through the efm32wg990f256.h file for the Wonder Gecko EFM32WG990F256 device turns up the line...

 

#define USERDATA_BASE     (0x0FE00000UL) /**< User data page base address */

 

...making it a simple matter to provide access to each word in user data space like so:

 

EXAMPLE 1:

#define USERDATA  ((uint32_t *) USERDATA_BASE)

uint32_t a = USERDATA[0];
uint32_t b = USERDATA[1];

// etc

 

Macro definitions for offsets corresponding to specific elements in the user data page can make the code much easier to understand, for example:

 

EXAMPLE 2:

#define USERDATA  ((uint32_t *) USERDATA_BASE)

#define SERIALNUM   0
#define FW_VERSION  1

uint32_t serial_number = USERDATA[SERIALNUM];

if (version_check() > USERDATA[FW_VERSION])
{
  if (firmware_update() != FW_UPDATE_SUCCESS)
  {

// etc

 

This particular way to access the user data page does impose one rather stiff limitation: All data must be organized along 32-bit boundaries. Data that does not fit neatly into unsigned (or signed if cast accordingly) integers generally must have corresponding unpacking functions that extract and cast into the relevant type the desired data. For smaller data types, like characters and short (16-bit) integers, the necessary unpacking and casting may not impose much of a performance penalty, but there is an associated risk of programming error if the endianness of the ARM processor core is not handled properly.

 

While there's some pain in accessing data that isn't explicitly 32-bit, this method of accessing the user data page is particularly advantageous if updates are required. Recall that the user data page is specifically one page of flash memory, so it must be completely erased in order to rewrite just one value. This might be highly inefficient for a single item, but the flash must be written in word-oriented fashion, which means 32 bits at a time. Knowing this, the update process can be made fairly efficient by grouping all updates and reprogramming the entire flash page in one fell swoop as shown below:

 

EXAMPLE 3:

#define USERDATA  ((uint32_t *) USERDATA_BASE)

#define USER_PAGE_WORDS 128

#define CAL_DATA    4
#define NET_PARAMS  16

#define UD_SUCCESS  0xFFFF

// etc

uint32_t user_page_update(void);
uint32_t update_calibration(uint32_t *);
int main(void) { // Update user data page if (user_page_update() != UD_SUCCESS) {
// etc
}
// etc } uint32_t user_page_update(void) { uint32_t user_page_copy[USER_PAGE_WORDS], i; // Make copy of user data page contents for (i = 0; i < USER_PAGE_WORDS; i++) user_page_copy[i] = USERDATA[i]; // Get updated calibration data if (update_calibration(user_page_copy) != UD_SUCCESS)
{
// etc }

// Erase user data page
  MSC_ErasePage(USERDATA);

// Rewrite user page with new data
  if (MSC_WriteWord(USERDATA, user_page_copy, USER_PAGE_WORDS) != mscReturnOk)
  {
// etc
}

return UD_SUCCESS;
} uint32_t update_calibration(uint32_t *tempdata) {
// Get new calibration parameters
tempdata[CAL_DATA] = run_cal1(); tempdata[CAL_DATA + 1] = run_cal2();

// etc
}

 

Of course, C has a way of accessing the same data using two or more different types. This is done by means of a union. Consider...

 

EXAMPLE 4:

#define USER_PAGE_WORDS 128

typedef struct
{
  __IM uint32_t SERIAL;
  __IM uint8_t  FW_MAJ;
  __IM uint8_t  FW_MIN;
  __IM uint8_t  FW_FIX;
__IM uint64_t CALVAL;
__IM char PRODNAME[32];
//etc

} USERDATA_Typedef; typedef union { USERDATA_Typedef VALUE; uint32_t WORDS[USER_PAGE_WORDS]; } USERDATA_Union; #define USERDATA ((USERDATA_Union *) USERDATA_BASE) int main(void) { uint32_t serial; uint8_t firmware_major, firmware_minor, firmware_fix; serial = USERDATA->VALUE.SERIAL; firmware_major = USERDATA->VALUE.FW_MAJ; firmware_minor = USERDATA->VALUE.FW_MIN; firmware_fix = USERDATA->VALUE.FW_FIX;
// etc }

 

Better still, use of the union requires only minimal changes to user data page update code above. First, the RAM copy of the flash contents would be made as follows:

 

uint32_t user_page_update(void)
{
  USERDATA_Union user_page_copy;
uint32_t i; // Make copy of user data page contents for (i = 0; i < USER_PAGE_WORDS; i++) user_page_copy.WORDS[i] = USERDATA->WORDS[i];

 

Second, the calibration update function might be modified to return a new 64-bit value, which could be sanity-checked as needed. Finally, the call to the page write function would to accommodate use of the union. Note that regardless of the structure of the underlying data, the page erase function is called in the same way because it always takes the address of (or an address within) the page in question.

 

  // Rewrite user page with new data
  if (MSC_WriteWord(USERDATA->WORDS, user_page_copy.WORDS, USER_PAGE_WORDS) != mscReturnOk)
  {
// etc
}

 

Experienced C developers will look at this use of the union and note that it's more cumbersome than necessary. USERDATA_Typedef does not need to be declared separately. The ISO C11 standard allows unnamed or anonymous structures to be included in unions, and the versions of GCC in recent releases of Simplicity Studio support this. Cleaning up Example 3 to take advantage of this results in...

 

EXAMPLE 5:

#define USER_PAGE_WORDS 128

typedef union
{
  struct
  {
    __IM uint32_t SERIAL;
    __IM uint8_t  FW_MAJ;
    __IM uint8_t  FW_MIN;
    __IM uint8_t  FW_FIX;
    __IM uint64_t CAL1;
    __IM char     PRODNAME[32];

//etc

  };
  uint32_t WORDS[USER_PAGE_WORDS];
} USERDATA_Union;

#define USERDATA ((USERDATA_Union *) USERDATA_BASE) #define UD_SUCCESS 0xFFFF
// etc

uint32_t user_page_update(void);
int main(void) {
  uint32_t serial;
  uint8_t firmware_major, firmware_minor, firmware_fix;

  serial = USERDATA->SERIAL;

  firmware_major = USERDATA->FW_MAJ;
  firmware_minor = USERDATA->FW_MIN;
  firmware_fix   = USERDATA->FW_FIX;

// etc
// Update user data page if (user_page_update() != UD_SUCCESS) {
// etc
}
// etc } uint32_t user_page_update(void) {   USERDATA_Union user_page_copy;
  uint32_t i;

  // Make copy of user data page contents
  for (i = 0; i < USER_PAGE_WORDS; i++)
    user_page_copy.WORDS[i] = USERDATA->WORDS[i];

// Get updated calibration data if (update_calibration(&user_page_copy->CAL1) != UD_SUCCESS)
{
// etc }

// Erase user data page
  MSC_ErasePage(USERDATA);

// Rewrite user page with new data
  if (MSC_WriteWord(USERDATA->WORDS, user_page_copy.WORDS, USER_PAGE_WORDS) != mscReturnOk)
  {
// etc
}

return UD_SUCCESS;
} uint32_t update_calibration(uint64_t *tempdata) {
// Get new calibration parameters
*tempdata = run_calibrate();
// etc
}

 

  • Knowledge Base Articles
  • 32-bit MCUs
  • This FOR loop seem rather cumbersome as well.

    // Make copy of user data page contents
      for (i = 0; i < USER_PAGE_WORDS; i++)
        user_page_copy.WORDS[i] = USERDATA->WORDS[i];

    Instead you could just use a struct/union assignment. Chances are the compiler can generate better code for this as well.

    // Make copy of user data page contents
      user_page_copy = *USERDATA;

     

    And since MSC_WriteWord() expects a void* parameter instead of uint32_t* you could drop the whole union and WORDS from the mix.

    // Rewrite user page with new data
    if (MSC_WriteWord(USERDATA_BASE, &user_page_copy, sizeof(user_page_copy)) != mscReturnOk)
    {
    // etc
    }

     

    NB: MSC_WriteWord() expects the number of bytes as last parameter, not the number of words! This is a bug in the original code above.

     

     

    0