使用GCC工具链构建两部分固件映像

时间:2016-02-03 17:35:54

标签: gcc linker embedded microcontroller binutils

我有一些使用GCC构建的固件,可以在基于ARM Cortex M0的微控制器上运行。构建当前生成一个二进制映像,可以写入微控制器的程序存储器中。

由于与字段更新有关的原因,我需要将此图像分成两个部分,可以单独更新。我会将这些核心应用称为。

  • 核心:包含中断向量表,main()例程以及各种驱动程序和库例程。它将位于程序存储器的前半部分。

  • 应用:包含特定于应用程序的代码。它将位于程序存储器的后半部分。它将在一个已知地址处有一个入口点,由核心调用以启动应用程序。它将通过已知地址访问核心中的函数和数据。

这里有一些明显的限制,我很清楚:

  • 构建应用时,需要知道核心中符号的地址。因此必须首先构建核心,并且在链接应用程序时必须可用。

  • 应用图片只会与其构建的特定核心图片兼容。

  • 可以在不更新核心的情况下更新应用程序,反之亦然。

所有这一切都没问题。

我的问题很简单,如何使用GCC和GNU binutils构建这些图像?

基本上我想像正常的固件映像一样构建核心,然后构建应用程序映像,应用程序将核心视为库。但是,共享链接(这将需要动态链接机制)或静态链接(将复制用于应用程序二进制文件的核心功能)都不适用于此处。我尝试做的事情实际上要简单得多:使用已知的固定地址链接现有的二进制文件。我不清楚如何使用这些工具。

2 个答案:

答案 0 :(得分:3)

我们现在有这个工作所以我将回答我自己的问题。这是必要的,从正常的单一图像构建开始,将其转换为"核心"然后为" app"。

设置构建
  1. 决定如何将闪存和RAM分成核心和应用程序的单独区域。定义每个区域的起始地址和大小。

  2. 为核心创建链接描述文件。这将与平台的标准链接描述文件相同,只是它必须仅使用为核心保留的区域。这可以通过更改闪存和放大器的ORIGINLENGTH来完成。链接描述文件的MEMORY部分中的RAM条目。

  3. 创建一个头文件,声明应用的入口点。这只需要一个原型,例如:

  4. void app_init(void);

    1. 从核心C代码中包含此标头,并使用核心调用app_init()启动该应用。

    2. 创建一个符号文件,声明入口点的地址,该地址将是应用程序闪存区域的起始地址。我打电话给app.sym。它可以是以下格式的一行:

    3. app_init = 0x00010000;

      1. 使用核心链接描述文件构建核心,并将--just-symbols=app.sym添加到链接器参数,以提供app_init的地址。保留构建中的ELF文件,我将调用core.elf

      2. 为应用创建链接描述文件。这将再次基于该平台的标准链接器脚本,但使用闪存和放大器。 RAM内存范围已更改为为应用程序保留的范围。此外,还需要一个特殊部分,以确保在app_init部分的其余代码之前将.text放置在应用内Flash区域的开头:

      3. SECTIONS
        {
            .text :
            {
                KEEP(*(.app_init))
                *(.text*)
        
        1. 编写app_init函数。这将需要在汇编中,因为它必须在应用程序中的任何C代码被调用之前执行一些低级别的工作。它需要标记为.section .app_init,以便链接器将其放在应用程序闪存区域开头的正确位置。 app_init函数需要:

          1. 使用来自flash的初始值在应用的.data部分填充变量。
          2. 将应用的.bss部分中的变量设置为零。
          3. 拨打该应用的C入口点,我将致电app_start()
        2. 编写启动应用程序的app_start()函数。

        3. 使用app链接描述文件构建应用程序。此链接步骤应传递包含app_initapp_start以及app_start调用的任何尚未包含在核心中的代码的目标文件。链接器参数--just-symbols=core.elf应通过其地址传递给核心中的链接函数。此外,应传递-nostartfiles以省略正常的C运行时启动代码。

        4. 花了一段时间来解决这一切,但它现在运作良好。

答案 1 :(得分:0)

首先......如果仅用于字段更新,则无需依赖核心空间中的中断向量表应用 。我认为 ARM M0部件总是能够移动它。我知道它可以在一些(全部?)STM32Fx的东西上完成,但我相信这是一个ARM M-x的东西,而不是ST的东西。在决定让你的应用程序ISR都是从核心调用的钩子之前,先看看这个。

如果你打算与你的核心进行大量的交互(顺便说一句,我总是把那些在MCU上自我更新的片段称为“bootloader”),这是另一个建议:

Core 将指向功能的struct / table的指针传递到 App 入口点?

这将允许完全分离应用程序与核心的代码,除了共享标头(假设您的ABI不会更改)并防止名称冲突。

它还提供了一种合理的方法来防止GCC优化掉您可能只从 App 调用的任何函数,而不会弄乱您的优化设置或搞砸pragma。

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);
}

下行/成本是一个轻微的运行时间&内存开销和更多代码。