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

时间:2013-11-25 23:55:13

标签: c arm execution spi flash-memory

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

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

2 个答案:

答案 0 :(得分:7)

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

你说你的Flash芯片无法进行内存映射。这是一个非常大的限制,但我们可以使用它。

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

DMA使其更快:

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

MMU使其更简单:

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

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

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

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

没有MMU且没有可用的MPU:

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

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

<强>要点:

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

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

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

编辑1:

提供的补充资料:

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

    1. 理想情况下,我想将程序复制到闪存空间并从那里执行。从理论上讲,这是可能的。是不可能按指令执行程序指令?
    2. 是的,可以通过指令执行程序指令(但是这种方法也存在限制,我将在一秒内完成)。您将首先在内存中分配一个(4字节对齐的)地址,您的单个指令将在该地址中进行。它是32位(4字节)宽,紧随其后,您将放置第二条指令,您永远不会改变它。这个第二条指令将是一个supervisor call (SVC),它会引发一个中断,允许你获取下一条指令,将它放入内存并重新开始。

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

      让您更清楚的是了解C代码的编译方式。即使您使用C ++开始问自己这个,我的设备上的变量和指令编译到哪里

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

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

      编辑2:

      如何使用外设DMA控制器:

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

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

      对可用闪存和RAM的担忧:

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

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

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

      编辑3:

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

      <强>附录

      在继续阅读下面的评论之前,请仔细阅读datasheet

      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 */
      }
      

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

      现在,在我解释你编写的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...
      }
      

答案 1 :(得分:1)

这取决于闪存和/或CPU的种类。 NOR闪存通常映射到内存中,因此您可以直接跳转到内存中。必须将NAND闪存读取(取决于SOC)到本地存储器(SRAM,DRAM( - >需要额外的初始化!))。

编辑:

SPI也无法映射到RAM。您必须编程SOC的SPI控制器和SPI闪存。用于SPI闪存的协议通常在其手册中描述;它很可能是一个通用的协议,所以你可以重用现有的驱动程序。