3

首先,我希望我不是在问以前已经问过的东西。我已经尽可能多地搜索了,但我没有找到针对我的具体问题的答案或有用的东西。

我正在开发运行 Cortex M0+ 内核的 FRDM-KL82Z 板。我正在使用 MCUXpresso IDE v10.0.2 和 Segger J-Link 程序员,尽管我认为这与这个问题无关。

该项目将需要一个自定义引导加载程序和由不同开发人员编写的应用程序,每个块都有自己的闪存空间:引导加载程序为 8K,应用程序为 120K(这可能会在未来发生变化,但目前没什么大不了的)。

一旦引导加载程序完成,它将管理到应用程序空间的跳转,应用程序将更改向量表偏移寄存器 (VTOR),以便中断向量表从引导 IVT 更改为应用程序 IVT。这已经成功地测试过了。

我的目标是设置链接器脚本文件,以便应用程序开发人员可以在引导加载程序完成之前在板上构建和调试他们的项目,因为它们将同时被开发。这样做的原因是他们可以使用应用程序空间,因为它将在最终版本中。

我认为重置向量和配置位必须位于它们的默认位置,因为硬件每次需要读取它们时都会转到相同的位置。

我的第一个想法是禁用自动链接器脚本生成并修改MyProject_Debug.ld文件。

脚本自动生成的内容:

INCLUDE "LEDTest_Debug_library.ld"
INCLUDE "LEDTest_Debug_memory.ld"

ENTRY(ResetISR)

SECTIONS
{
    /* MAIN TEXT SECTION */
    .text : ALIGN(4)
    {
        FILL(0xff)
    __vectors_start__ = ABSOLUTE(.) ;
    KEEP(*(.isr_vector))
    /* Global Section Table */
    . = ALIGN(4) ;
    __section_table_start = .;
    __data_section_table = .;
    LONG(LOADADDR(.data));
    LONG(    ADDR(.data));
    LONG(  SIZEOF(.data));
    LONG(LOADADDR(.data_RAM2));
    LONG(    ADDR(.data_RAM2));
    LONG(  SIZEOF(.data_RAM2));
    __data_section_table_end = .;
    __bss_section_table = .;
    LONG(    ADDR(.bss));
    LONG(  SIZEOF(.bss));
    LONG(    ADDR(.bss_RAM2));
    LONG(  SIZEOF(.bss_RAM2));
    __bss_section_table_end = .;
    __section_table_end = . ;
    /* End of Global Section Table */

    *(.after_vectors*)


    /* Kinetis Flash Configuration data */
    . = 0x400 ;
    PROVIDE(__FLASH_CONFIG_START__ = .) ;
    KEEP(*(.FlashConfig))
    PROVIDE(__FLASH_CONFIG_END__ = .) ;
    ASSERT(!(__FLASH_CONFIG_START__ == __FLASH_CONFIG_END__), "Linker Flash Config Support Enabled, but no .FlashConfig section provided within application");
    /* End of Kinetis Flash Configuration data */


    } >PROGRAM_FLASH

    .text : ALIGN(4)
    {
        *(.text*)
        *(.rodata .rodata.* .constdata .constdata.*)
        . = ALIGN(4);
    } > PROGRAM_FLASH
    /*
     * for exception handling/unwind - some Newlib functions (in common
     * with C++ and STDC++) use this. 
     */
    .ARM.extab : ALIGN(4) 
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > PROGRAM_FLASH
    __exidx_start = .;

    .ARM.exidx : ALIGN(4)
    {
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > PROGRAM_FLASH
    __exidx_end = .;

    _etext = .;


    /* USB_RAM */
    .m_usb_data (NOLOAD) :
    {
        *(m_usb_bdt)
        *(m_usb_global)
    } > USB_RAM
    /* possible MTB section for USB_RAM */
    .mtb_buffer_RAM2 (NOLOAD) :
    {
        KEEP(*(.mtb.$RAM2*))
        KEEP(*(.mtb.$USB_RAM*))
    } > USB_RAM

    /* DATA section for USB_RAM */
    .data_RAM2 : ALIGN(4)
    {
         FILL(0xff)
        PROVIDE(__start_data_RAM2 = .) ;
        *(.ramfunc.$RAM2)
        *(.ramfunc.$USB_RAM)
        *(.data.$RAM2*)
        *(.data.$USB_RAM*)
        . = ALIGN(4) ;
        PROVIDE(__end_data_RAM2 = .) ;
     } > USB_RAM AT>PROGRAM_FLASH

    /* MAIN DATA SECTION */
        /* Default MTB section */
        .mtb_buffer_default (NOLOAD) :
        {
           KEEP(*(.mtb*))
        } > SRAM
    .uninit_RESERVED : ALIGN(4)
    {
        KEEP(*(.bss.$RESERVED*))
        . = ALIGN(4) ;
        _end_uninit_RESERVED = .;
    } > SRAM
    /* Main DATA section (SRAM) */
    .data : ALIGN(4)
    {
       FILL(0xff)
       _data = . ;
       *(vtable)
       *(.ramfunc*)
       *(.data*)
       . = ALIGN(4) ;
       _edata = . ;
    } > SRAM AT>PROGRAM_FLASH
    /* BSS section for USB_RAM */
    .bss_RAM2 : ALIGN(4)
    {
       PROVIDE(__start_bss_RAM2 = .) ;
       *(.bss.$RAM2*)
       *(.bss.$USB_RAM*)
       . = ALIGN (. != 0 ? 4 : 1) ; /* avoid empty segment */
       PROVIDE(__end_bss_RAM2 = .) ;
    } > USB_RAM 
    /* MAIN BSS SECTION */
    .bss : ALIGN(4)
    {
        _bss = .;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4) ;
        _ebss = .;
        PROVIDE(end = .);
    } > SRAM
    /* NOINIT section for USB_RAM */
     .noinit_RAM2 (NOLOAD) : ALIGN(4)
    {
       *(.noinit.$RAM2*)
       *(.noinit.$USB_RAM*)
       . = ALIGN(4) ;
    } > USB_RAM 
    /* DEFAULT NOINIT SECTION */
    .noinit (NOLOAD): ALIGN(4)
    {
        _noinit = .;
        *(.noinit*) 
         . = ALIGN(4) ;
        _end_noinit = .;
     } > SRAM
    .heap :  ALIGN(4)
    {
        _pvHeapStart = .;
        . += 0x1000;
        . = ALIGN(4);
        _pvHeapLimit = .;
    } > SRAM
       .heap2stackfill  : 
    {
        . += 0x1000;
    } > SRAM
     .stack ORIGIN(SRAM) + LENGTH(SRAM) - 0x1000 - 0:  ALIGN(4)
    {
        _vStackBase = .;
        . = ALIGN(4);
        _vStackTop = . + 0x1000;
    } > SRAM
}

我试图在本指南中找到有关 de GNU 链接器的信息,但我的想法到目前为止还没有奏效。我试过的:

  1. 在 Config Words 之后将位置计数器设置为不同的值,并复制在文本部分之前剪切的 ISR_vector 代码:

    ...
    /* End of Kinetis Flash Configuration data */
    
    
    } >PROGRAM_FLASH
    
    .text : ALIGN(4)
    {   
        /*  MODIFIED CODE   */
    
        . = 0x2000;     /*  First position of App code  */
         FILL(0xff)
        __vectors_start__ = ABSOLUTE(.) ;
        KEEP(*(.isr_vector))
    
        /*  END OF MODIFIED CODE    */
    
        *(.text*)
        *(.rodata .rodata.* .constdata .constdata.*)
         . = ALIGN(4);
    } > PROGRAM_FLASH
    ...
    

当我这样做并打开 .hex 文件时,配置字 (0x400) 和应用程序空间 (0x2000) 的开头之间的空间实际上是空的(充满 0xFF),但 0x2000 之后的代码与 IVT 表完全不同.

  1. 如果我在 IVT 代码行之前将位置计数器移动到 0x2000 ,它会有效地将 IVT 地址移动到 0x2000 位置。为此,我将 Config Words 部分移到 IVT 部分之前,因为定位计数器不能向后移动。

  2. 我已经尝试在内存映射中创建一个 Bootloader 部分,具有正确的起始和长度位置,并将默认情况下放置在 PROGRAM_FLASH 部分中的每一行复制到一个新的去 BOOTLOADER(与 ">BOOTLOADER 相同的代码“ 在最后)。在这种情况下,de IVT 仅出现在引导空间中。

链接描述文件是否有可能仅将 de IVT 放置在指示它的第一个位置,然后忽略所有其他调用?我究竟做错了什么?我应该尝试另一种方法来实现这一目标吗?

非常感谢,我知道它很长!

4

3 回答 3

3

我认为仅使用链接器恶作剧来制作向量表的副本是不可能的。链接描述文件不会让您多次匹配同一个符号,以便您可以输出两次。

binutils 2.29 手册

如果文件名与多个通配符模式匹配,或者如果文件名显式出现并且还与通配符模式匹配,则链接器将使用链接描述文件中的第一个匹配项。

我在没有使用任何通配符模式的情况下对其进行了测试,结果相似,因此我认为链接器不会让您两次输出相同的符号。

我还尝试使用 objcopy 创建可以从链接器脚本引用的向量表的重命名副本,但该表最终全为零,整个方法相当复杂,所以我认为这不值得追求。

如果您想在现在和引导加载程序完成之间保持应用程序代码尽可能相似,我建议采用不同的方法:

利用__vectors_start__现有链接描述文件提供的符号,以便您的代码始终知道向量表的放置位置,即使您对链接描述文件进行了更改。

void relocate_vector_table(void) {
    extern unsigned __vectors_start__;
    SCB->VTOR = (unsigned)&__vectors_start__;
}

这将允许相同的代码与您当前的配置(无引导加载程序,ROM 从 0x0 开始)和您最终的引导加载程序配置(ROM 从 0x2000 开始)一起工作。

于 2017-09-14T02:29:12.460 回答
3

我对 M4 应用程序和引导加载程序的经验表明,将 Flash 启动设置在某个偏移地址就足够了,然后在应用程序中将 VTOR 初始化为该地址。

从链接器脚本:

#

/* 指定内存区域 */

MEMORY
{
CLASSBRAM (rw)   : ORIGIN = 0x20000000, LENGTH = 0x80
/*RAM length = 192K - CLASSBRAM-length */
RAM (xrw)      : ORIGIN = 0x20000080, LENGTH = 0x2FF80 
CCMRAM (rw)      : ORIGIN = 0x10000000, LENGTH = 64K
/* FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 512K */
FLASH (rx)      : ORIGIN = 0x08010000, LENGTH = 448K  /*in case of 64K for Bootloader*/
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    PROVIDE( _Rom_Start = . );
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

......

#

代码:

extern const uint32_t _Rom_Start;
....
#define ROM_START ((uint32_t *)&_Rom_Start)
...
SCB->VTOR = (uint32_t)ROM_START;
于 2017-11-15T16:27:23.427 回答
1

我的 MCU 项目通常具有 Makefile 目标,这些目标实际上会刷新芯片(当然,构建作为依赖项),所以我为此做了一个特殊的目标,它可以“单独”刷新主固件。

我的 openocd 驱动的程序员可以刷新平面二进制文件,而不仅仅是 hex 文件,所以我可以通过dd将矢量表从主固件二进制文件的开头复制到它自己的文件中来做到这一点。然后我将其写入闪存的开头,并将主固件写入其通常的位置,分别进行操作。芯片启动,从复制的向量表中获取复位和堆栈地址,启动主固件,然后将向量表地址重新指向它自己的更高地址的副本。

如果您的程序员不支持平面二进制文件,您可以使用objdump或其他工具将平面二进制文件转换回十六进制文件,或者可能更改十六进制文件/片段的基地址。

于 2017-09-14T03:00:02.537 回答