You might never heard about ROM Libraries, and you are probably not alone. Some might thing that this refers to the boot ROM modern MCUs have built in, which is kinda close. But the thing here is about to build your own (possibly constant) ROM library, program it to your device of choice, and then use it from the application running on the device.
So the concept is to have a (fixed, stable) part with code and data on your device, which can be used by a (possibly changing) application: Think about a stable LoRaWAN network stack in the ROM, with a changing application using it: Would that not be cool?

This not only adds flexibility, but as well allows smaller updates, as only a part of the program has to be changed or updated.
The question is: how to create and use such a ROM Library with the normal GNU build tools?
Outline
In this article, I show how to create a ROM library, using the GNU tools for ARM Cortex-M. For the example I’m using the NXP LPC845-BRK with the NXP MCUXpresso IDE, but the general steps are very generic and can be applied to other MCUs and and build systems, as long the GNU tools are used. You can find the example projects on GitHub (see the links at the end of the article).
ROM Libraries are very useful to keep a part of the application ‘persistent’, and for example allow easier and smaller upgrade of a system in the field, as long as the ROM library stays the same. So it makes sense to have ‘common’ things or utility functionality inside a ROM library, and let the application use it.
The other point to mention here is: ROM libraries is not the same thing as using Position Independent Code (see Position-Independent Code with GCC for ARM Cortex-M), although they serve similar needs. ROM Libraries are simpler to handle than full Position Independent Code (PIC), so this might be a good alternative to keep an application flexible and small to update. The ROM library sits at a fixed address or memory range, so it is linked as ‘absolute’ binary: this makes it very efficient at runtime and easier to deal with from the application part.
In the next sections, I’ll go through the steps and details about creating and using a ROM library.
ROM Library Memory Map
Because the ROM library remains fixed in a device, the memory map needs to be defined. In the linker file example below I have 4 KByte reserved for the ROM part, and because the ROM library needs some RAM, an extra of 16 bytes in RAM.
/* Memory used for the ROM library.
* Make sure these areas are *not* part of the application
*/
MEMORY
{
PROGRAM_FLASH (rx) : ORIGIN = 0xF000, LENGTH = 0x1000 /* 4K bytes */
SRAM (rwx) : ORIGIN = 0x10000000, LENGTH = 0x10 /* 16 bytes */
}
Keep in mind that this area is reserved from the application point of view, and used by the ROM library.
Creating a ROM Library
In Eclipse, a ROM library can created like any other project. I recommend to create a normal project and then strip off everything you don’t want to have it the library, for example startup code or the usual main(). Below is a simple ROM Library project, just having an interface (RomLib.h) and the implementation (RomLib.c):

The make files are standard, so no magic there. The special thing to remember is that the ROM library is not built as a normal library (usually an archive (*.a), or see How to use Custom Library Names with GNU Linker and Eclipse). Instead, the ROM library is a linked binary, because it sits in a memory area. So the output is not a .a file, but a .elf or .axf one: a normal ELF/Dwarf binary.
The linker files require a bit of special handling, more about this later in this section.
The ROM library can include code, constant and variable data, which then is available to the application. Below is an example interface file for a ROM library:
/*
* Copyright (c) 2022, Erich Styger
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef ROMLIB_H_
#define ROMLIB_H_
/* Below section tags are used to not discard items by the linker. Mark objects you want to keep in the library */
#define ROM_LIB_CODE __attribute__ ((section(".ROM_LIB_CODE"))) /* placed into ROM */
#define ROM_LIB_CONST __attribute__ ((section(".ROM_LIB_CONST"))) /* placed into ROM */
#define ROM_LIB_RAM __attribute__ ((section(".ROM_LIB_RAM"))) /* placed into RAM */
/*!
* \brief Constant lookup table. It is not directly used by the ROM library, so needs to be marked
*/
extern ROM_LIB_CONST const int RomLib_lookupTable[16];
extern ROM_LIB_RAM int RomLib_Variable;
/*! \brief example of a ROM library function */
ROM_LIB_CODE int RomLib_Calculate(int a);
/*! \brief other ROM library example */
ROM_LIB_CODE int RomLib_Count(void);
/*!
* \brief Initializes the ROM library
* \return 0 for success, non-zero otherwise
*/
ROM_LIB_CODE int RomLib_Init(void);
#endif /* ROMLIB_H_ */
Notice that I have specified special linker section names behind macros:
#define ROM_LIB_CODE __attribute__ ((section(".ROM_LIB_CODE"))) /* placed into ROM */
#define ROM_LIB_CONST __attribute__ ((section(".ROM_LIB_CONST"))) /* placed into ROM */
#define ROM_LIB_RAM __attribute__ ((section(".ROM_LIB_RAM"))) /* placed into RAM */
They serve as ‘markers’ I can use later in the linker script to prevent ‘dead-stripping’ unused code and data by the linker. This because some objects in the library are not referenced by the library itself, but from the application.
These macros then are used in the interface (and/or in the implementation), for example:
extern ROM_LIB_CONST const int RomLib_lookupTable[16];
Different macros for code, constant data and variable data are used, so I can have them treated independently in the linker file.
In the linker file, these objects are kept using the KEEP() directive and placed into ROM/FLASH and RAM:
INCLUDE "LPC845_RomLib_Debug_library.ld"
/* Memory used for the ROM library.
* Make sure these areas are *not* part of the application
*/
MEMORY
{
PROGRAM_FLASH (rx) : ORIGIN = 0xF000, LENGTH = 0x1000 /* 4K bytes */
SRAM (rwx) : ORIGIN = 0x10000000, LENGTH = 0x10 /* 16 bytes */
}
ENTRY(RomLib_Init) /* makt it as entry point: everything used by it will not be discarded */
SECTIONS
{
/* MAIN TEXT SECTION: code and constant data */
.text : ALIGN(4)
{
FILL(0xff)
KEEP(*(.ROM_LIB_CODE)) /* do not discard anything marked as ROM Lib code */
KEEP(*(.ROM_LIB_CONST)) /* do not discard anything marked as ROM constant */
*(.text*)
*(.rodata .rodata.* .constdata .constdata.*)
. = ALIGN(4);
} > PROGRAM_FLASH
/* Main DATA section (SRAM): data for initialized variables */
.data : ALIGN(4)
{
FILL(0xff)
KEEP(*(.ROM_LIB_RAM))
*(.data*)
. = ALIGN(4) ;
} > SRAM AT>PROGRAM_FLASH
/* MAIN BSS SECTION for un-initialized variables */
.bss : ALIGN(4)
{
_bss = .;
*(.bss*)
*(COMMON)
. = ALIGN(4) ;
_ebss = .;
} > SRAM AT> SRAM
}
By default, the application startup initializes the global variables/RAM in the startup code. As a ROM library might use its own RAM, this could be covered by the application startup code too. But I have it found easier and more transparent if the ROM library has its own ‘Init()’ function which does all the necessary initialization. Below is an example implementation of a simple ROM library:
/*
* Copyright (c) 2022, Erich Styger
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "RomLib.h" /* own interface */
static int RomLib_Counter; /* initialized in RomLib_Init() */
static int RomLib_InitVar; /* initialized in RomLib_Init() */
int RomLib_Variable; /* not initialized! */
/* example data/constant table in ROM */
const int RomLib_lookupTable[16] = {
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
};
int RomLib_Calculate(int a) {
return a+RomLib_InitVar;
}
int RomLib_Count(void) {
RomLib_Counter++;
return RomLib_Counter;
}
int RomLib_Init(void) {
/* perform initialization, as needed.
* Keep in mind that usually ROM libraries do not get initialized by the normal startup code,
* so all the initialization is done here */
RomLib_Counter = 0x0;
RomLib_InitVar = 0x12345678;
return 0; /* ok */
}
This links the ROM library like any other ELF/Dwarf file.
Because usually you want to flash the ROM library to the target or include it into the application, it is useful to create a derived binary file (Intel Hex, Binary or Motorola S-Record). See S-Record, Intel Hex and Binary Files for details or MCUXpresso IDE: S-Record, Intel Hex and Binary Files how create them with an IDE.
Below a view of the created ROM library with the derived binary (.bin, s19 and Intel Hex) files:

This completes the part of creating a ROM libary. Next, we are going to use it in an application project.
Using a ROM Library
From the application point of view, the ROM library is just something it references, but not actually has to link with.
It is a good idea, to reference the ROM library from a (Eclipse) project point of view: if the ROM library changes, to build the ROM library first and then build the application with it. See Go Reference! Or: Subprojects in eclipse how to reference other projects like below:

As the ROM library uses FLASH and potentially RAM, make sure in the application linker file that the ROM library area is excluded and not used. Because in our example the ROM library is using 4 K of FLASH at the end of the FLASH area and 16 bytes of RAM at the start of the RAM area, the memory map needs to be adjusted for this:

Within the application, use the interface header file to the ROM library, initialize it and use it like usual:

Now the last piece, which is the ‘magic’ thing: we have to tell now the linker to link against the ROM library. It means that the linker needs to know about the ROM library symbols and addresses, but only references to it, but does not include the data/code of it. This is done with the -R linker option.
One way is to pass it in the following way to the linker command line, for example:
-Wl,-R"../../LPC845_RomLib/Debug/LPC845_RomLib.axf"
From the GNU ld manual:
-R filename--just-symbols=filename
Read symbol names and their addresses from filename, but do not relocate it or include it in the output. This allows your output file to refer symbolically to absolute locations of memory defined in other programs. You may use this option more than once. For compatibility with other ELF linkers, if the-R
option is followed by a directory name, rather than a file name, it is treated as the-rpath
option.
In the NXP MCUXpresso IDE, that option can be specified here:

From the linker .map file, you can see that it uses the symbols from the ROM library file:

Placing the ROM Library inside the Application
By default, the ROM library is only referenced, but not linked with the application. However during testing, it easily can be included in to the application: you can include the binary file into the application file, see Include .bin Binary Files in a GNU Linker File how to do this.
Debugging the ROM Library
Because the source and symbol information of the ROM library is in another ELF/Dwarf file, gdb does not load that information automatically. One way to debug the ROM library on source level is to use the gdb ‘file’ command in the GDB console, for example in Eclipse:
file ../LPC845_RomLib/Debug/LPC845_RomLib.axf

Another way is to use the ‘Other Symbols’ setting in the Eclipse launch configuration:

See Debugging Bootloader and Application with one Debug Session for details, as it is the same approach with a ROM library.
With this, I can build, use and debug ROM Libraries 🙂

Summary
ROM libraries are very useful: they can stay in ROM or FLASH and be used by the application. This makes it ideal for any application which might be updated later on, while keeping the ROM library the same. This makes the concept of a ROM library attractive for cases, where you need to certify or test an application: keeping the ROM library ‘invariant’ simplifies the process or test procedures. Another way using ROM libraries is to use it with ‘changing’ applications: new ‘apps’ can be loaded in an easy way: they are small because they rely on the ROM part, and any kind of loader easily can load the app, because the GNU linker has already resolved all dependencies. This makes it an attractive way of application updates over low-bandwidth networks like LoRaWAN or TheThingsNetwork too.
Happy ROMing 🙂
Links
- Include .bin Binary Files in a GNU Linker File
- Example project to build a ROM Library: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/LPC845-BRK/LPC845_RomLib
- Example project using a ROM Library: https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/MCUXpresso/LPC845-BRK/LPC845_UseRomLib
- GNU Linker (ld) Manual: https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html