Creating programmable look up table on STM32
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"
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... :-)
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:
- Backup page content
- Update backup with changed values
- Erase page
- Write page
- 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.