Here is an easy way to store and use configuration data in the flash memory of a microcontroller.
This places the configuration data at a specific absolute memory address and uses no special pragmas to enable read access.
This is a slight reworking and consolidation of what MCU on Eclipse suggests, and slightly easier than marking variables with __attribute__((section(".etwas")))
all the time, though that is useful in other cases. Also, the SEGGER J-Link debugger on my development kit was always erasing the configuration data (as I’d placed it between other code and data, at a pretty low flash address), which was the opposite of the problem MCU on Eclipse was having. So, I needed a way to protect specific flash regions from getting erased, as Kinetis Design Studio has no easy options for this.
The Problem
Let’s say you want to configure a microcontroller at runtime using some values in flash memory, and let’s say you have a C structure that looks like this:
1
2
3
4
|
typedef struct
{
uint32_t deviceType;
} ConfigBlock;
|
You want the data associated with a single instance of ConfigBlock
to be located somewhere specific.
The Solution
- Create a configuration block in the
.ld
linker script. - Make sure the configuration block is sector-aligned, and uses a full flash sector.
- Put the configuration block at the end of flash memory, or beyond the highest code address that will be written.
- Define a linker symbol to refer to the configuration block.
- Declare a C symbol to refer to the configuration block.
- Write config values to flash memory using J-Link.
- Use the C symbol.
- Profit!
Create a configuration block in the linker script
Easy. Add a block definition to the MEMORY
declaration in your linker script:
MEMORY { m_config_block (R) : ORIGIN = 126K, LENGTH = 2K }
The block is marked read-only, as flash memory is usually not memory mapped for write access.
For writing, you will have to use microcontroller-specific flash APIs provided by the manufacturer to clear + to set specific bytes in the flash memory.
This is what I want anyways, as I would only modify the configuration data during development or during the manufacturing process (i.e. when an SWD programmer is pogo-pinned onto the device).
Make sure configuration block is sector-aligned, and uses a full flash sector
The user guide for the microcontroller I’m using says the following:
Making sure the configuration block is sector-aligned and uses a full flash sector will save you from some headaches associated with the way flash memory is programmed. If you don’t align the config block, or if the config block straddles the boundary with program or data blocks, then the configuration block will likely get wiped out when the surrounding blocks are erased + written. This will violate the principle of least surprise, when time after time your config block is erased.
Using a full, sector-aligned block keeps other data out and prevents accidental overwrites / erasures.
This will not be the most efficient way to do things, if the device has very limited flash. However, limited-flash devices often have smaller sector sizes, so this should still work effectively.
Put the configuration block at the end of flash memory
When writing a program to my microcontroller, Eclipse + SEGGER J-Link seems to erase all of the flash sectors that contain code or initialized data.
In a previous version of my linker script, I had placed the configuration block between code and data, and the block straddled a flash sector boundary.
So what happened? J-Link wiped out the configuration block each time I tried to debug a newly-compiled program, because it had no sense that anything in my configuration block was data worth protecting, and it needed to wipe surrounding sectors in order to write the new program bytes.
To fix this, I pushed the configuration block to the end of flash memory, i.e. "ORIGIN = 126K, LENGTH = 2K"
, to get it out of harm’s way.
Because the configuration block now lives at the end of flash memory, J-Link happily ignores it completely when reflashing the chip.
Define a linker symbol to refer to the configuration block
Somewhere below the MEMORY
declarations in the linker script, add the following:
/* Add symbol to provide location of the ConfigBlock data. */ configBlock = ORIGIN(m_config_block);
This tells the linker: “If the program uses a symbol called configBlock
, it is placed at the memory location defined by the starting address of m_config_block
“.
Declare a C symbol to refer to the configuration block
1 |
extern const ConfigBlock configBlock; // Provided by linker.
|
Write config values to flash memory using J-Link
Start up JLink.exe
and use the mem32
and w4
commands to set configuration values into the config data block in flash memory:
SEGGER J-Link Commander V6.12a (Compiled Dec 2 2016 16:44:55) DLL version V6.12a, compiled Dec 2 2016 16:44:26 Connecting to J-Link via USB...O.K. Firmware: J-Link OpenSDA 2 compiled May 6 2016 11:04:17 Hardware version: V1.00 S/N: 621000000 VTref = 3.300V Type "connect" to establish a target connection, '?' for help J-Link> Unknown command. '?' for help. J-Link>connect Please specify device / core.: LPC11E67 Type '?' for selection dialog Device> Please specify target interface: J) JTAG (Default) S) SWD TIF>S Specify target interface speed [kHz]. : 4000 kHz Speed> Device "LPC11E67" selected. Found SWD-DP with ID 0x0BC11477 Found SWD-DP with ID 0x0BC11477 AP-IDR: 0x04770031, Type: AHB-AP Found Cortex-M0 r0p1, Little endian. FPUnit: 2 code (BP) slots and 0 literal slots CoreSight components: ROMTbl 0 @ F0002000 ROMTbl 0 [0]: FFFFE000, CID: B105900D, PID: 001BB932 MTB-M0+ ROMTbl 0 [1]: FFFFF000, CID: B105900D, PID: 0008E000 MTBDWT ROMTbl 0 [2]: F00FD000, CID: B105100D, PID: 000BB4C0 ROM Table ROMTbl 1 @ E00FF000 ROMTbl 1 [0]: FFF0F000, CID: B105E00D, PID: 000BB008 SCS ROMTbl 1 [1]: FFF02000, CID: B105E00D, PID: 000BB00A DWT ROMTbl 1 [2]: FFF03000, CID: B105E00D, PID: 000BB00B FPB Cortex-M0 identified. J-Link>mem32 0x1f800,4 0001F800 = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF J-Link>w4 0x1f800,0x0102aa55 Writing 0102AA55 -> 0001F800 J-Link>q J-Link: Flash download: Flash programming performed for 1 range (2048 bytes) J-Link: Flash download: Total time needed: 0.256s (Prepare: 0.166s, Compare: 0.009s, Erase: 0.014s, Program: 0.049s, Verify: 0.008s, Restore: 0.007s)
Use the C symbol
Using the C symbol causes it to be linked to the address provided by the same-named linker symbol.
In other words, the following code:
1
2
3
|
PRINTF("ld-provided configBlock: %p\r\n", &configBlock);
PRINTF(" deviceType: %08x\r\n", configBlock.deviceType);
PRINTF("\r\n");
|
Will print:
ld-provided configBlock: 1F800 deviceType: 0102aa55
All data from the ConfigBlock
structure is being read from flash memory starting at address 0x1F800
.
The deviceType
value can now be set permanently in flash, and used across Debug sessions!
Another Example
linker.ld Script
MEMORY { FLASH (rx) : ORIGIN = 0x1C000, LENGTH = 0x63000 CONFIG (r) : ORIGIN = 0x7f000, LENGTH = 0x1000 RAM (rwx) : ORIGIN = 0x20002ef8, LENGTH = 0xd108 } SECTIONS { /* Factory configuration data */ .config : { . = ALIGN(4); SERIAL_NUMBER = .; . += 16; PROVIDE(SOMETHING_ELSE = .); } > CONFIG }
Using PROVIDE
in the linker script causes the linker to emit the SOMETHING_ELSE
symbol only when it is actually used in the C program.
C file
1
2
3
4
5
6
7
8
9
10
11
|
#ifdef __cplusplus
extern "C" {
#endif
// Factory-programmed flash data.
extern const unsigned char SERIAL_NUMBER[SERIAL_NUMBER_LENGTH_BYTES];
extern const unsigned char SOMETHING_ELSE;
#ifdef __cplusplus
}
#endif
|
nm output
20003a74 D __data_end__ 20002ef8 D __data_start__ w __deregister_frame_info ... 0007f000 B SERIAL_NUMBER 00068334 T sniprintf 0005cc04 T snprintf 0007f010 B SOMETHING_ELSE
Notes
The above process seems to work pretty well using Kinetis Design Studio 3.2.0 + the following SEGGER J-Link versions:
SEGGER J-Link Commander V6.12a (Compiled Dec 2 2016 16:44:55) DLL version V6.12a, compiled Dec 2 2016 16:44:26 Connecting to J-Link via USB...O.K. Firmware: J-Link OpenSDA 2 compiled May 6 2016 11:04:17
As always, Your Mileage May Vary, if you’re using a different kind of chip or programmer setup (or Eclipse may always just wipe out the entire flash…)
More Notes
The above process will not work if you are using the mbed USB Mass Storage Device method of programming your device.
i.e. If you copy and paste a .hex
file to an mbed-enabled board that exposes a USB Mass Storage Device, it will wipe all of flash.
You must use the loadfile
command in JLink.exe
, which will carefully avoid overwriting parts of flash that haven’t changed and should ignore the CONFIG block entirely (as there is no data at those addresses in the .hex
file)