在ARM处理器上执行存储在外部SPI闪存中的程序

Geo*_*gan 2 c arm spi execution flash-memory

我有一个ARM处理器,能够与外部闪存芯片连接.写入芯片的是为ARM架构编译的程序,可以执行.我需要知道的是将外部闪存中的数据传输到ARM处理器上以便执行.

我可以提前运行某种复制例程,将数据复制到可执行存储空间吗?我想我可以,但ARM处理器正在运行一个操作系统,我没有足够的空间在闪存中使用.我还希望能够一次安排两个甚至三个程序的执行,并且一次将多个程序复制到内部闪存是不可行的.操作系统可以用于在可访问的内存空间内启动程序,因此任何需要事先完成的操作都可以.

non*_*kle 6

通过阅读@FiddlingBits和@ensc的现有答案,我认为我可以提供不同的方法.

你说你的Flash芯片不能被内存映射.这是一个非常大的限制,但我们可以使用它.

是的,您可以提前运行复制例程.只要将其放入RAM就可以执行它.

DMA使其更快:

如果你有一个外围DMA控制器(就像Atmel SAM3N系列上的那个),那么你可以使用DMA控制器来复制出内存块,同时你的处理器确实有用.

MMU使其更简单:

如果您有一个可用的MMU,那么您可以通过选择您希望代码执行的RAM区域,将代码复制到其中以及每个页面错误,将正确的代码重新加载到同一区域来轻松完成.但是,@ensc已经提出了这个问题,所以我还没有添加任何新功能.

注意:如果不清楚,MMU与MPU不同

没有MMU解决方案,但MPU可用:

没有MMU,任务有点棘手,但仍有可能.您需要了解编译器如何生成代码并阅读有关位置无关代码(PIC)的信息.然后,您需要在RAM中分配一个区域,您将从中执行外部闪存芯片代码并在其中复制部分内容(确保从正确的位置开始执行它).MPU需要配置为在任务尝试访问其指定区域之外的内存时生成故障,然后您需要获取正确的内存(这可能会成为一个复杂的过程),重新加载并继续执行.

没有MMU,也没有MPU:

如果你没有MMU,这项任务现在变得非常困难.在这两种情况下,您都会严格限制外部代码的大小.基本上,存储在外部闪存芯片上的代码现在必须能够完全适合RAM中的分配区域,您将从中执行它.如果您可以将这些代码拆分为不能彼此交互的单独任务,那么您可以将其拆分.

如果要生成PIC,则可以编译任务并按顺序将它们放入内存中.否则,您将需要使用链接描述文件来控制代码生成,以便将存储在外部闪存中的每个编译任务将从RAM中的相同预定义位置执行(这将要求您了解ld叠加或编译它们分别).

摘要:

为了更完整地回答您的问题,我需要知道您正在使用的芯片和操作系统.可用的RAM数量也有助于我更好地理解您的约束.

但是,您询问是否可以一次加载多个任务来运行.如果您像我建议的那样使用PIC,那么应该可以这样做.如果没有,那么您需要提前决定每个任务的运行时间,以及能够同时加载/运行某些组合的时间.

最后,根据您的系统和芯片,这可能很容易或很难.

编辑1:

提供的其他信息:

  1. 芯片是SAM7S(Atmel)
  2. 它有一个外设DMA控制器.
  3. 它没有MMU或MPU.
  4. 8K内部RAM,这对我们来说是一个限制.
  5. 在安装了自定义编写的操作系统后,它还剩下大约28K的闪存.

提出的其他问题:

  1. 理想情况下,我想将程序复制到闪存空间并从那里执行它们.从理论上讲,这是可能的.是否不可能按指令执行程序指令?

是的,可以通过指令执行程序指令(但是这种方法也存在限制,我将在一秒内完成).您将首先在内存中分配一个(4字节对齐的)地址,您的单个指令将在该地址中进行.它是32位(4字节)宽,紧随其后,您将放置第二条指令,您永远不会改变它.这第二个指令将是一个管理程序调用(SVC) ,将引发一个中断,让您获取下一个指令,将其放置在内存中,然后重新开始.

虽然可能不建议使用,因为你将花费更多的时间进行上下文切换而不是执行代码,你实际上不能使用变量(你需要使用RAM),你不能使用函数调用(除非你手动处理分支说明,哎哟!)和你的闪存将写得太多,以至于它将变得毫无用处.关于最后一个,关于Flash变得无用,我将假设你想要从RAM执行指令.除了所有这些限制之外,您仍然需要为堆栈,堆和全局变量使用一些RAM(有关详细信息,请参阅我的附录).这个区域可以由从外部闪存运行的所有任务共享,但是您需要为此编写自定义链接描述文件,否则您将浪费RAM.

让您更清楚的是理解C代码的编译方式.即使您正在使用C++,首先要问自己,我的设备上的变量和指令编译到哪里?

基本上你在尝试之前必须知道的是:

  • 代码执行的地方(Flash/RAM)
  • 如何将此代码链接到其堆栈,堆和全局变量(您将为此任务分配一个单独的堆栈,并为全局变量分隔空间,但您可以共享堆).
  • 这个外部代码的堆栈,堆和全局变量驻留在哪里(我正试图暗示你需要对C代码进行多少控制)

编辑2:

如何使用外设DMA控制器:

对于我正在使用的微控制器,DMA控制器实际上没有连接到嵌入式闪存进行读取或写入.如果您也是这种情况,则无法使用它.但是,您的数据表在这方面还不清楚,我怀疑您需要使用串行端口运行测试以查看它是否真的可以正常工作.

除此之外,我担心由于缓存页面写入,使用DMA控制器时的写入操作可能比手动操作更复杂.您需要确保只在页面内进行DMA传输,并且DMA传输永远不会越过页面边界.另外,我不确定当你告诉DMA控制器从闪存写回同一位置时会发生什么(你可能需要这样做才能确保你只覆盖正确的部分).

对可用闪存和RAM的担忧:

我关注你之前关于一次执行一条指令的问题.如果是这种情况,那么你也可以写一个翻译.如果您没有足够的内存来包含您需要执行的任务的完整代码,那么您将需要将任务编译为PIC,并将全局偏移表(GOT)与所有必需的内存放在一起.任务的全局变量.这是解决整个任务没有足够空间的唯一方法.您还必须为其堆栈分配足够的空间.

如果你没有足够的RAM(我怀疑你不会这样做),你可以在每次需要在外部闪存芯片上的任务之间切换时将RAM内存交换出来并将其转储到Flash中,但我再强烈建议不要写你的闪存很多次.这样你就可以让外部闪存上的任务为它们的全局变量共享一块RAM.

对于所有其他情况,您将编写一名翻译.我甚至做了不可想象的事情,我试图想办法使用你的微控制器内存控制器的中止状态(数据表第18.3.4节中止状态)作为MPU,但却找不到一个远程聪明的方法用它.

编辑3:

我建议阅读数据表中的40.8.2非易失性存储器(NVM)位部分,该部分表明您的闪存最多有10,000次写入/擦除周期(我花了一段时间才找到它).这意味着当你编写和擦除闪存区域时,你将在上下文中切换任务10,000次,那部分Flash将变得无用.

附录

在继续阅读下面的评论之前,请仔细阅读此博客文章.

C变量存在于嵌入式ARM芯片上的位置:

我最好不是从抽象概念中学习,而是从具体的例子中学习,所以我会尝试为你提供代码.基本上所有的魔法都发生在链接器脚本中.如果您阅读并理解它,您将看到代码会发生什么.我们现在剖析一下:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
SEARCH_DIR(.)

/* Memory Spaces Definitions */

MEMORY
{
  /* Here we are defining the memory regions that we will be placing
   * different sections into. Different regions have different properties,
   * for example, Flash is read only (because you need special instructions
   * to write to it and writing is slow), while RAM is read write.
   * In the brackets after the region name:
   *   r - denotes that reads are allowed from this memory region.
   *   w - denotes that writes are allowed to this memory region.
   *   x - means that you can execute code in this region.
   */

  /* We will call Flash rom and RAM ram */
  rom (rx)  : ORIGIN = 0x00400000, LENGTH = 0x00040000 /* flash, 256K */
  ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00006000 /* sram, 24K */
}

/* The stack size used by the application. NOTE: you need to adjust  */
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : 0x800 ;

/* Section Definitions */
SECTIONS
{
    .text :
    {
        . = ALIGN(4);
        _sfixed = .;
        KEEP(*(.vectors .vectors.*))
        *(.text .text.* .gnu.linkonce.t.*)
        *(.glue_7t) *(.glue_7)
        *(.rodata .rodata* .gnu.linkonce.r.*)  /* This is important, .rodata is in Flash */
        *(.ARM.extab* .gnu.linkonce.armextab.*)

        /* Support C constructors, and C destructors in both user code
           and the C library. This also provides support for C++ code. */
        . = ALIGN(4);
        KEEP(*(.init))
        . = ALIGN(4);
        __preinit_array_start = .;
        KEEP (*(.preinit_array))
        __preinit_array_end = .;

        . = ALIGN(4);
        __init_array_start = .;
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array))
        __init_array_end = .;

        . = ALIGN(0x4);
        KEEP (*crtbegin.o(.ctors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
        KEEP (*(SORT(.ctors.*)))
        KEEP (*crtend.o(.ctors))

        . = ALIGN(4);
        KEEP(*(.fini))

        . = ALIGN(4);
        __fini_array_start = .;
        KEEP (*(.fini_array))
        KEEP (*(SORT(.fini_array.*)))
        __fini_array_end = .;

        KEEP (*crtbegin.o(.dtors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
        KEEP (*(SORT(.dtors.*)))
        KEEP (*crtend.o(.dtors))

        . = ALIGN(4);
        _efixed = .;            /* End of text section */
    } > rom /* All the sections in the preceding curly braces are going to Flash in the order that they were specified */

    /* .ARM.exidx is sorted, so has to go in its own output section.  */
    PROVIDE_HIDDEN (__exidx_start = .);
    .ARM.exidx :
    {
      *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > rom
    PROVIDE_HIDDEN (__exidx_end = .);

    . = ALIGN(4);
    _etext = .;

    /* Here is the .relocate section please pay special attention to it */
    .relocate : AT (_etext)
    {
        . = ALIGN(4);
        _srelocate = .;
        *(.ramfunc .ramfunc.*);
        *(.data .data.*);
        . = ALIGN(4);
        _erelocate = .;
    } > ram  /* All the sections in the preceding curly braces are going to RAM in the order that they were specified */

    /* .bss section which is used for uninitialized but zeroed data */
    /* Please note the NOLOAD flag, this means that when you compile the code this section won't be in your .hex, .bin or .o files but will be just assumed to have been allocated */
    .bss (NOLOAD) :
    {
        . = ALIGN(4);
        _sbss = . ;
        _szero = .;
        *(.bss .bss.*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = . ;
        _ezero = .;
    } > ram

    /* stack section */
    .stack (NOLOAD):
    {
        . = ALIGN(8);
        _sstack = .;
        . = . + STACK_SIZE;
        . = ALIGN(8);
        _estack = .;
    } > ram

    . = ALIGN(4);
    _end = . ;

    /* heap extends from here to end of memory */
}
Run Code Online (Sandbox Code Playgroud)

这是SAM3N自动生成的链接描述文件(您的链接描述文件应仅在内存区域定义上有所不同).现在,让我们来看看设备在关机后启动时会发生什么.

首先发生的事情是ARM内核读取存储在FLASH存储器向量表中的地址,该地址指向您的复位向量.重置向量只是一个函数,对我来说它也是由Atmel Studio自动生成的.这里是:

void Reset_Handler(void)
{
    uint32_t *pSrc, *pDest;

    /* Initialize the relocate segment */
    pSrc = &_etext;
    pDest = &_srelocate;

    /* This code copyes all of the memory for "initialised globals" from Flash to RAM */
    if (pSrc != pDest) {
        for (; pDest < &_erelocate;) {
            *pDest++ = *pSrc++;
        }
    }

    /* Clear the zero segment (.bss). Since it in RAM it could be anything after a reset so zero it. */
    for (pDest = &_szero; pDest < &_ezero;) {
        *pDest++ = 0;
    }

    /* Set the vector table base address */
    pSrc = (uint32_t *) & _sfixed;
    SCB->VTOR = ((uint32_t) pSrc & SCB_VTOR_TBLOFF_Msk);

    if (((uint32_t) pSrc >= IRAM_ADDR) && ((uint32_t) pSrc < IRAM_ADDR + IRAM_SIZE)) {
        SCB->VTOR |= 1 << SCB_VTOR_TBLBASE_Pos;
    }

    /* Initialize the C library */
    __libc_init_array();

    /* Branch to main function */
    main();

    /* Infinite loop */
    while (1);
}
Run Code Online (Sandbox Code Playgroud)

现在,请耐心等待一段时间,同时我解释一下你编写的C代码是如何适合所有这些的.

请考虑以下代码示例:

int UninitializedGlobal; // Goes to the .bss segment (RAM)
int ZeroedGlobal[10] = { 0 }; // Goes to the .bss segment (RAM)
int InitializedGlobal[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 11 }; // Goes to the .relocate segment (RAM and FLASH)
const int ConstInitializedGlobal[10] = { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }; // Goes to the .rodata segment (FLASH)

void function(int parameter)
{
    static int UninitializedStatic; // Same as UninitializedGlobal above.
    static int ZeroedStatic = 0; // Same as ZeroedGlobal above.
    static int InitializedStatic = 7; // Same as InitializedGlobal above.
    static const int ConstStatic = 18; // Same as ConstInitializedGlobal above. Might get optimized away though, lets assume it doesn't.

    int UninitializedLocal; // Stacked. (RAM)
    int ZeroedLocal = 0; // Stacked and then initialized (RAM)
    int InitializedLocal = 7; // Stacked and then initialized (RAM)
    const int ConstLocal = 91; // Not actually sure where this one goes. I assume optimized away.

    // Do something with all those lovely variables...
}
Run Code Online (Sandbox Code Playgroud)