My application has a number of modules that each require some variables to be stored in off-chip non-volatile memory. To make the reading and writing of these easier, I'm trying to collect them together into a contiguous region of RAM, to that the NVM driver can address a single block of memory when communicating with the NVM device.
To achieve this, I have created a custom linker script containing the following section definition.
.nvm_fram :
{
/* Include the "nvm_header" input section first. */
*(.nvm_header)
/* Include all other input sections prefixed with "nvm_" from all modules. */
*(.nvm_*)
/* Allocate a 16 bit variable at the end of the section to hold the CRC. */
. = ALIGN(2);
_gld_NvmFramCrc = .;
LONG(0);
} > data
_GLD_NVM_FRAM_SIZE = SIZEOF(.nvm_fram);
The data
region is defined in the MEMORY section using the standard definition provided by Microchip for the target device.
data (a!xr) : ORIGIN = 0x1000, LENGTH = 0xD000
One example of a C source file which attempts to place its variables in this section is the NVM driver itself. The driver saves a short header structure at teh beginning of the NVM section so that it can verify the content of the NVM device before loading it into RAM. No linker error reported for this variable.
// Locate the NVM configuration in the non-volatile RAM section.
nvm_header_t _nvmHeader __attribute__((section(".nvm_header")));
Another module that has variables to store in the .nvm_fram section is the communications (CANopen) stack. This saves the Module ID and bitrate in NVM.
// Locate LSS Slave configuration in the non-volatile RAM section.
LSS_slave_config_t _slaveConfig __attribute__((section(".nvm_canopen"))) =
{ .BitRate = DEFAULT_BITRATE, .ModuleId = DEFAULT_MODULEID };
Everything compiles nicely, but when the linker runs, the following error stops the build.
elf-ld.exe: Link Error: attributes for input section '.nvm_canopen' conflict with
output section '.nvm_fram'
It's important that the variables can be initialised with values by the crt startup, as shown by the _slaveConfig
declaration above, in case the NVM driver cannot load them from the NVM device (it's blank, or the software version has changed, etc.). Is this what's causing the attributes mismatch?
There are several questions here and on the Microchip forums, which relate to accessing symbols that are defined in the linker script from C. Most of these concern values in the program Flash memory and how to access them from C; I know how to do this. There is a similar question, but this doesn't appear to address the attributes issue, and is a little confusing due to being specific to a linker for a different target processor.
I've read the Microchip linker manual and various GCC linker documents online, but can't find the relevant sections because I don't really understand what the error means and how it relates to my code. What are the 'input and output section attributes', where are they specified in my code, and how do I get them to match eachother?
The problem is due to the _nvmHeader
variable not having an initial value assigned to it in the C source, but the _slaveConfig
variable does.
This results in the linker deducing that the .nvm_fram
output section is uninitialised (nbss) from the .nvm_header
input section attributes. So, when it enconters initialised data in the .nvm_canopen
input section from the _slaveConfig
variable, there is a mismatch in the input section attributes: .nvm_fram
is for uninitialised data, but .nvm_canopen
contains initialised data.
The solution is to ensure that all variables that are to be placed in the .nvm_fram
output section are given initial values in the C source.
// Type used to hold metadata for the content of the NVM.
typedef struct
{
void* NvmBase; // The original RAM address.
uint16_t NvmSize; // The original NVM section size.
} nvm_header_t;
// The linker supplies the gld_NVM_FRAM_SIZE symbol as a 'number'.
// This is represented as the address of an array of unspecified
// length, so that it cannot be implicitly dereferenced, and cast
// to the correct type when used.
extern char GLD_NVM_FRAM_SIZE[];
// The following defines are used to convert linker symbols into values that
// can be used to initialise the _nvmHeader structure below.
#define NVM_FRAM_BASE ((void*)&_nvmHeader)
#define NVM_FRAM_SIZE ((uint16_t)GLD_NVM_FRAM_SIZE)
// Locate the NVM configuration in the non-volatile RAM section.
nvm_header_t _nvmHeader __attribute__((section(".nvm_header"))) =
{
.NvmBase = NVM_FRAM_BASE, .NvmSize = NVM_FRAM_SIZE
};
The answer is therefore that the output section attributes may be determined partly by the memory region in which the section is to be located and also by the attributes of the first input section assigned to it. Initialised and uninitialised C variables have different input section attributes, and therefore cannot be located within the same output section.