Search code examples
cgccstructinitializationlinker-scripts

How to initialize an array of structs to a value other than zero like 0xFF?


I have an array of structs that I need to initialize at compile-time (no memset) to 0xFF. This array will be written as part of the program over erased flash. By setting it to 0xFF, it will remain erased after programming, and the app can use it as persistent storage. I've found two ways to do it, one ugly and one a workaround. I'm wondering if there's another way with syntax I haven't found yet. The ugly way is to use a nested initializer setting every field of the struct. However, it's error prone and a little ugly. My workaround is to allocate the struct as an array of bytes and then use a struct-typed pointer to access the data. Linear arrays of bytes are much easier to initialize to a non-zero value.

To aid anyone else doing the same thing, I'm including the gcc attributes used and the linker script portion.

Example struct:

struct BlData_t {
    uint8_t version[3];
    uint8_t reserved;
    uint8_t markers[128];
    struct AppData_t {
        uint8_t version[3];
        uint8_t reserved;
        uint32_t crc;
    } appInfo[512] __attribute__(( packed ));
} __attribute__(( packed ));

Initialize to 0xFF using the best way I know:

// Allocate the space as an array of bytes 
// because it's a simpler syntax to 
// initialize to 0xFF.
__attribute__(( section(".bootloader_data") ))
uint8_t bootloaderDataArray[sizeof(struct BlData_t)] = {
    [0 ... sizeof(struct BlData_t) - 1] = 0xFF
};

// Use a correctly typed pointer set to the
// array of bytes for actual usage
struct BlData_t *bootloaderData = (struct BlData_t *)&bootloaderDataArray;

No initialization necessary because of (NOLOAD):

__attribute__(( section(".bootloader_data") ))
volatile const struct BLData_t bootloader_data;

Addition to linker script:

  .bootloader_data (NOLOAD):
  {
    FILL(0xFF);                  /* Doesn't matter because (NOLOAD) */
    . = ALIGN(512);              /* start on a 512B page boundary */
    __bootloader_data_start = .;
    KEEP (*(.bootloader_data))   /* .bootloader_data sections */
    KEEP (*(.bootloader_data*))  /* .bootloader_data* sections */
    . = ALIGN(512);              /* end on a 512B boundary to support
                                    runtime erasure, if possible */
    __bootloader_data_end = .;
    __bootloader_data_size = ABSOLUTE(. - __bootloader_data_start);
  } >FLASH

How to use the starting address, ending address and size in code:

extern uint32_t __bootloader_data_start;
extern uint32_t __bootloader_data_end;
extern uint32_t __bootloader_data_size;
uint32_t _bootloader_data_start = (uint32_t)&__bootloader_data_start;
uint32_t _bootloader_data_end = (uint32_t)&__bootloader_data_end;
uint32_t _bootloader_data_size = (uint32_t)&__bootloader_data_size;

Update:

  • It turns out that I was asking the wrong question. I didn't know about the (NOLOAD) linker section attribute which tells the program loader not to burn this section into flash. I accepted this answer to help others realize my mistake and possibly theirs. By not even programming the section, I don't have to worry about the initialization at all.
  • I've upvoted the union answers since they seem to be a good solution to the question I asked.

Solution

  • The sensible way to do this is to find the command in the linker script telling it to back off from touching that memory in the first place. Because why would you want it do be erased only to filled up with 0xFF again? That only causes unnecessary flash wear for nothing.

    Something along the lines of this:

    .bootloader_data (NOLOAD) :
    {
      . = ALIGN(512);
      *(.bootloader_data *)
    } >FLASH