7
\$\begingroup\$

I am trying to define a look up table that is not a constant on an STM32F103. Basically I want to have a page of flash that acts like a constant lookup table in normal operation, but every once in a while (think days apart) I want to be able to erase that table and write a new one to the page of flash. I believe I understand how to use the HAL to functions to do what I need in my program, but it seems like I would need to declare this block of memory in the linker file and I'm having a lot of trouble finding an example that covers that. The default mem.ld from CubeMX looks like this:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
  RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}

It seems like I need an output sections block after that where I commit a keyword to >FLASH but I don't know what keyword that would be or how I would declare the table in the code to give it the right address.

I have seen the emulated EEPROM application note, but it seems like a lot of extra overhead for memory that will not see enough erase/write cycles to be concerned about the memory's lifetime. Help!

\$\endgroup\$
  • \$\begingroup\$ there is no reason for the linker to be involved at all, at some point you need to specify where the table is, what erase block of the flash. One would assume you only want to update that table not the entire binary, so you really want the table to be a separate binary anyway. can hardcode that address inthe linker script sure but could just as easily of not easier to create a #define somewhere in the code. \$\endgroup\$ – old_timer Apr 23 '18 at 20:33
7
\$\begingroup\$

Note: linking is not a part of the C language standards, so each compiler implements linker files differently. You seem to be using GCC, so I will share some code that works with it.

linker.ld:

MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 8K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 31K
/*(1)*/ FIXED_DATA(rw)  : ORIGIN = 0x8007C00, LENGTH = 1K /* your LUT here*/
}
............
.FIXED_DATA (NOLOAD):/*(2)*/
  {
    *(STATIC_DATA);/*(3)*/
  } >FIXED_DATA /*(4)*/

In C:

__attribute__((section("STATIC_DATA"))) 
             const static volatile statistic_static_data_t staticData;

What this does:

(1) create an area named FIXED_DATA of given location and size. Note that my code is from a different device. Check your datasheet do see what size your blocks are (they may not even be equally sized inside a device!). Make sure to reduce the size of FLASH accordingly, otherwise you will get an error about them not fitting into memory.

(2) create a section named FIXED_DATA. It doesn't have to be called FIXED_DATA actually, but it helps keep track. Attribute NOLOAD tells the linker to not fill the area (see below).

(3) put all variables marked STATIC DATA into this memory section

(4) put everything in this section into the area named FIXED_DATA that we created way above

In the C file, you just have to tag the variables you want put into the area. I recommend calling them const because you don't generally want to write to flash directly. Volatile helps with compiler optimizations that assume const data never changes.

Why go through all this trouble, instead of using the much simpler solutions in other answers? Because it's upgradeable. If, in the future, you want to do a FW update, you may want to keep the stored data in memory. The NOLOAD instruction in the linker file does just that: the linker does not fill the data with 0, as it would normally do if you have a global variable living there.

You can find out more about the arcane ways of the linker if you search for "ld syntax"

\$\endgroup\$
  • 1
    \$\begingroup\$ This is the correct way to do it, in my opinion. Nice answer. \$\endgroup\$ – bitsmack Apr 23 '18 at 16:27
  • \$\begingroup\$ This is what I was looking for thank you so much. One question from having stared at a bunch of linker files lately. Should there be some kind of . = ALIGN(4); statement after the STATIC_DATA attribute is declared or is that implied by the data type. \$\endgroup\$ – Brett K Smith Apr 23 '18 at 18:45
  • \$\begingroup\$ @BrettKSmith I don't think you can even mis-align data. The smallest data type is 8 bits, and the compiler inserts padding into structs and bitfields to ensure proper alignment. If you do need to align to some larger size (e.g. 16 bytes), I suggest you do it in the linker file as well. That well, you get alignment guaranteed when you add a new variable to the section. \$\endgroup\$ – Makotanist Apr 24 '18 at 7:17
  • \$\begingroup\$ Good point about volatile. In the case of STM32 it id unfortunate that the small pages (if non-uniform) are at the beginning of flash so it could be useful to separate the interrupt vector from .text. also that way it is a bit more indepedendent of flash size (if You for example choose a smaller MCU after the prototyping phase) \$\endgroup\$ – Jan Dorniak May 20 '18 at 0:26
4
\$\begingroup\$

This is not that complicated to do.

Basically what you need is a constant global variable which will get placed in flash and define the location using a pragma.

In C++ the header might look something like this:

class FlashLookUpTable
{
    public:
    struct LookUpTable_t
    {
        uint32_t table[100];
    };

    public:
    static LookUpTable_t const * GetLookUpTablePointer();

    private:
    static const uint32_t FLASH_PAGE_SIZE = 1024U; // or whatever the flash smallest deletable size is
    // This variable contains the number of bytes needed to store the structure in complete flash pages
    static const uint32_t ARRAY_SIZE = (sizeof(LookUpTable_t)/FLASH_PAGE_SIZE) + FLASH_PAGE_SIZE;

    union FlashPageSizedStructure
    {
        LookUpTable_t t;
        uint8_t flashpage[ARRAY_SIZE];
    }

    static const FlashPageSizedStructure tableInFlash;

};

And this is how the implementation looks:

// the exact pragma depends on the compiler used, this one works for IAR
// the location should be at the start of a page boundary, especially when using this union approach
#pragma location=0x800FC00U
const FlashLookUpTable::FlashPageSizedStructure FlashLookUpTable::tableInFlash = 
{
    // initialize values here
}

FlashLookUpTable::LookUpTable_t const * FlashLookUpTable::GetLookUpTablePointer(void) const 
{
    return &tableInFlash.t;
}

To write that page in flash, you need a buffer (either in RAM or in flash) with the same size of a flash page because you have to erase the page before you write it again, so an in place change of a single value is not possible.

Depending on how exactly you use it, you might need to declare the structure as volatile. This happens especially if you access the table directly (not with a pointer like in this case).

Some compilers optimize the code in such a way, that they take the constant out of the table directly into the code. Result is, that if you change the value in your table, the value is not taken into account in the code.

The IAR compiler had some trouble (it is fixed in the current version) handling a static volatile const so I switched to using a pointer.


If you want to change the values, you need some sort of flash algorithm.

Writing flash always consists of:

  1. Backup page content
  2. Update backup with changed values
  3. Erase page
  4. Write page
  5. for safety: compare written page with backup

Note for the advanced: In some cases you can exploit that you can writes zeros to positions where a one was, so you could change a 0x7F to 0x3F, but not the other way round. In that case you don't need to do a page erase. Some controllers might not support this.

\$\endgroup\$
  • \$\begingroup\$ If it's declared as a const will I be able to overwrite the table from within the program or wouldn't the const property prevent that? \$\endgroup\$ – Brett K Smith Apr 23 '18 at 7:14
  • 1
    \$\begingroup\$ @Arsenal how do you ensure that the linker doesn't put unrelated data in the same flash page, which would be destroyed with the flash erase operation? \$\endgroup\$ – berendi Apr 23 '18 at 8:14
  • 1
    \$\begingroup\$ @BrettKSmith you have to use a flash algorithm to write to the values. The const will prevent you from assigning values accidentally leading to a fault exception. \$\endgroup\$ – Arsenal Apr 23 '18 at 11:09
  • \$\begingroup\$ @berendi while it is up to the linker where to place stuff, it tends to fill the flash from beginning to end. So I place these variables at the end of the flash. If you are really worried, you can create a union with an array of bytes with the size of the flash page, then there is no space for the linker to place something else. \$\endgroup\$ – Arsenal Apr 23 '18 at 11:11
  • \$\begingroup\$ @berendi I have updated the code with a union, to show what I mean with my comment. It probably is a better approach, thanks for the input. \$\endgroup\$ – Arsenal Apr 23 '18 at 13:35
3
\$\begingroup\$

Yes, it would be cleanest to declare a section for your table.

But the easiest way is to just reduce the FLASH section by the page size and then:

int *table = (int *) (0x0800000 +0x20000 - PAGE_SIZE)

Here the "0x20000" is the flash memory size. In /my/ code I use a define MY_FLASH_SIZE defined as: #define MY_FLASH_SIZE (( *(short *)0x1ffff7cc) << 10) which works for the F0 series, maybe yours too. I have moved from 128k devices in development (only a few nickles more expensive than the 64k ones) to 64k devices in production without realizing this changed the location of the flash area... :-)

\$\endgroup\$
  • \$\begingroup\$ So the linker file changes to: FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 127K and that int pointer decliration would go in the body of my code? \$\endgroup\$ – Brett K Smith Apr 23 '18 at 4:22
1
\$\begingroup\$

If you have flash to spare, you could declare a global const variable twice the size of a flash page, and round its start address up to a multiple of the flash page size. A bit wastefull, but no need to do linkerscript magic.

\$\endgroup\$
  • 1
    \$\begingroup\$ Why would this be stored in flash and not RAM? I don't understand. \$\endgroup\$ – Brett K Smith Apr 23 '18 at 5:47
  • \$\begingroup\$ Sorry, I forgot: a const variable. In modern C++, constexpr. \$\endgroup\$ – Wouter van Ooijen Apr 23 '18 at 16:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.