Building a two-part firmware image using GCC toolchain

梦想与她 提交于 2019-12-05 07:21:39

We have this working now so I am going to answer my own question. Here is what was necessary to do this, starting from a normal single image build, turning that into the "core" and then setting up the build for the "app".

  1. Decide how to split up both the flash and the RAM into separate areas for the core and the app. Define the start address and size of each area.

  2. Create a linker script for the core. This will be the same as the standard linker script for the platform except that it must only use the areas reserved for the core. This can be done by changing the ORIGIN and LENGTH of the flash & RAM entries in the MEMORY section of the linker script.

  3. Create a header file declaring the entry point for the app. This just needs a prototype e.g.:

void app_init(void);.

  1. Include this header from the core C code and have the core call app_init() to start the app.

  2. Create a symbol file declaring the address of the entry point, which will be the start address of the flash area for the app. I'll call this app.sym. It can just be one line in the following format:

app_init = 0x00010000;

  1. Build the core, using the core linker script and adding --just-symbols=app.sym to the linker parameters to give the address of app_init. Retain the ELF file from the build, which I'll call core.elf.

  2. Create a linker script for the app. This will again be based on the standard linker script for the platform, but with the flash & RAM memory ranges changed to those reserved for the app. Additionally, it will need a special section to ensure that app_init is placed at the start of the app flash area, before the rest of the code in the .text section:

SECTIONS
{
    .text :
    {
        KEEP(*(.app_init))
        *(.text*)
  1. Write the app_init function. This will need to be in assembly, as it must do some low level work before any C code in the app can be called. It will need to be marked with .section .app_init so that the linker puts it in the correct place at the start of the app flash area. The app_init function needs to:

    1. Populate variables in the app's .data section with initial values from flash.
    2. Set variables in the app's .bss section to zero.
    3. Call the C entry point for the app, which I'll call app_start().
  2. Write the app_start() function that starts the app.

  3. Build the app, using the app linker script. This link step should be passed the object files containing app_init, app_start, and any code called by app_start that is not already in the core. The linker parameter --just-symbols=core.elf should be passed to link functions in the core by their addresses. Additionally, -nostartfiles should be passed to leave out the normal C runtime startup code.

It took a while to figure all this out but it is now working nicely.

First of all... if this is just for field updating, you don't need to rely on the interrupt vector table in the core space for the app. I think ARM M0 parts always have the ability to move it. I know it can be done on some (all?) the STM32Fx stuff, but I believe this is an ARM M-x thing, not an ST thing. Look into this before committing yourself to the decision to make your application ISRs all be hooks called from the core.

If you plan on having a lot of interaction with your core (btw, I always call the piece that does self-updating a "bootloader" on MCUs), here's an alternate suggestion:

Have the Core pass a pointer to a struct / table of functions that describes its capabilities into the App entry point?

This would allow complete separation of the code for the app vs core except for a shared header (assuming your ABI doesn't change) and prevent name collisions.

It also provides a reasonable way to prevent GCC from optimizing away any functions that you might call only from the App without messing up your optimization settings or screwing around with pragmas.

core.h:

struct core_functions
{
    int (*pcore_func1)(int a, int b);
    void (*pcore_func2)(void);
};

core.c:

int core_func1(int a, int b){ return a + b; }
void core_func2(void){ // do something here }

static const struct core_functions cfuncs= 
{
    core_func1,
    core_func2
};

void core_main()
{
   // do setup here
   void (app_entry*)(const struct core_functions *) = ENTRY_POINT;
   app_entry( &cfuncs );
}

app.c

void app_main(const struct core_functions * core)
{
   int res;
   res = core->pcore_func1(20, 30);
}

The downside / cost is a slight runtime & memory overhead and more code.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!