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

2024-04-12

我有一个 ARM 处理器,能够与外部闪存芯片连接。写入芯片的是为 ARM 架构编译的程序,可供执行。我需要知道如何将这些数据从外部闪存获取到 ARM 处理器上以供执行。

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


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

你说你的Flash芯片不能进行内存映射。这是一个相当大的限制,但我们可以解决它。

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

DMA 使其更快:

如果您有外设 DMA 控制器(例如 Atmel SAM3N 系列上提供的控制器),那么您可以使用 DMA 控制器复制内存块,同时您的处理器执行实际有用的操作。

MMU 使其更简单:

如果您有可用的 MMU,那么您可以轻松地做到这一点,只需选择您想要执行代码的 RAM 区域,将代码复制到其中,并在每次出现页面错误时,将正确的代码重新加载到同一区域中。然而,这已经由 @ensc 提出,所以我还没有添加任何新内容。

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

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

如果没有 MMU,任务会有点棘手,但仍然可以完成。您需要了解编译器如何生成代码并阅读有关位置无关代码 (PIC) http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/。然后你需要在 RAM 中分配一个区域来执行你的外部闪存芯片从中复制代码并将其部分复制到其中(确保从正确的位置开始执行它)。 MPU 需要配置为在任务尝试访问其指定区域之外的内存时生成错误,然后您需要获取正确的内存(这可能会成为一个复杂的过程)、重新加载并继续执行。

没有 MMU 和 MPU 可用:

如果您没有 MMU,这项任务现在就变得非常困难。在这两种情况下,您都对大小有严格的限制外部代码可。基本上,存储在外部闪存芯片上的代码现在必须能够适合exactly在 RAM 中分配的区域内,您将在其中执行它。如果您可以将该代码拆分为彼此不交互的单独任务,那么您就可以做到这一点,否则您就做不到。

如果您要生成 PIC,那么您只需编译任务并将它们按顺序放置在内存中即可。否则,您将需要使用链接器脚本来控制代码生成,以便存储在外部闪存中的每个已编译任务将从 RAM 中的相同预定义位置执行(这将要求您了解LD 叠加 ftp://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_22.html或单独编译它们)。

Summary:

为了更完整地回答你的问题,我需要知道你正在使用什么芯片和什么操作系统。有多少可用内存也可以帮助我更好地了解您的限制。

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

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

EDIT 1:

给出的附加信息:

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

提出的其他问题:

  1. 理想情况下,我想将程序复制到闪存空间并从那里执行它们。理论上这是可能的。难道不可能一条一条地执行程序吗?

是的,可以逐条指令执行程序(但这种方法也有一个限制,我将在稍后介绍)。首先,您将在内存中分配单个指令所在的地址(4 字节对齐)。它的宽度为 32 位(4 字节),紧随其后您可以放置​​第二条永远不会更改的指令。这第二条指令将是一个主管呼叫 (SVC) http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0471c/BABFGEFG.html这会引发一个中断,允许您获取下一条指令,将其放入内存中并重新开始。

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

了解 C 代码的编译方式将使您更清楚这一点。即使您使用 C++,首先也要问自己这个问题,我的设备上的变量和指令编译到哪里?

基本上,在尝试此操作之前您必须了解的是:

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

Edit 2:

如何使用外设 DMA 控制器:

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

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

关于可用闪存和 RAM 的问题:

我担心您之前关于一次执行一条指令的问题。如果是这样的话,那么你不妨写一个解释器。 如果您没有足够的内存来包含需要执行的任务的完整代码,那么您需要将任务编译为 PIC,并将全局偏移表 (GOT) 与该任务所需的所有内存一起放置在 ram 中任务的全局变量。这是解决没有足够空间来完成整个任务的唯一方法。您还必须为其堆栈分配足够的空间。

如果您没有足够的 RAM(我怀疑您不会),则每次您需要在外部闪存芯片上的任务之间进行更改时,您可以将 RAM 内存换出并将其转储到闪存中,但我再次强烈建议您不要编写多次到您的闪存。这样,您就可以使外部闪存上的任务为其全局变量共享一块 RAM。

对于所有其他情况,您将编写一个解释器。我什至做了不可想象的事情,我试图想出一种方法来使用微控制器内存控制器的中止状态(第 18.3.4 节中止状态)数据表 http://www.atmel.com/Images/doc6175.pdf)作为 MPU,但未能找到一种甚至远程聪明的方法来使用它。

Edit 3:

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

APPENDIX

请简短阅读这个博客条目 http://www.geeksforgeeks.org/memory-layout-of-c-program/在继续阅读下面我的评论之前。

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 内核读取存储在闪存向量表中的地址,该地址指向您的重置向量。重置向量只是一个函数,对我来说它也是由 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...
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 4 x 3 锁图案

    我遇到了这个 它要求计算在 4x3 网格中可以制作特定长度的锁定图案的方式数 并遵循规则 可能有些点不能包含在路径中 有效的模式具有以下属性 图案可以使用第一次接触的点序列来表示 与绘制图案的顺序相同 从 1 1 到 2 2 的图案与图案不
  • 不允许从函数返回函数。我怎么能?

    8 3 5 8 Functions dcl fct says 函数的返回类型不得为 类型数组或function 尽管它们可能具有指针类型的返回类型或对此类事物的引用 为什么规则这么明确 是否有某种语法甚至允许返回函数而不是函数指针 我是否误
  • 如何通过MFC将应用程序设置保存到注册表中?

    我有一个由 MFC 项目向导创建的 MFC 应用程序 我想在注册表中保存 读取应用程序设置 所以问了这个question https stackoverflow com questions 1880275 good c registry w
  • 更改 WinForms 按钮突出显示颜色

    I found 这一页 https stackoverflow com questions 9260303 how to change menu hover color winforms 其中概述了如何更改 MenuStrip 及其项目的呈
  • 了解子表单何时关闭

    我有一个带有按钮的 Form1 当您单击按钮时 将执行以下代码块 Form2 frm new Form2 frm Name Form musteriNumarasi ToString frm Text Kullan c musteriNum
  • 为什么 C++ Concepts TS 中同时存在变量和函数概念?

    我一直在看 C 1zN4377 http www open std org jtc1 sc22 wg21 docs papers 2015 n4377 pdfGCC 6 中正在实现的概念 TS 草案 我不明白拥有两种不同概念的目的 变量概念
  • 验证码怎么写?

    我正在开发一个注册表 我想放置验证码 我生成一个随机字符串 但如何将其转换为图像 否则我如何开发验证码或任何参考 谢谢 Try out 验证码 http recaptcha net plugins aspnet 或查看博客文章 使用 Asp
  • 如何在 ASP.NET 5/vNext/Core 中使用 Elmah?

    我对如何在 ASP NET 5 MVC 6 项目中使用 Elmah 有点困惑 我从 nuget 得到了包 它添加了 Elmah Mvc 2 1 2 到project json 中的依赖项 我不知道从这里到哪里去 以前 nuget 会向 we
  • 在 Eclipse 4.4.2 中使用 C 代码中的构建变量

    我有一个之前使用 Eclipse 3 5 2 创建的项目 在其中 我能够在项目属性中设置构建变量 在这种情况下 假设我设置了SW VERSION是 4403 现在这应该是一个十六进制数字 所以在构建设置中 我添加了一个符号 VERSION
  • Mono 和 WebRequest 速度 - 测试

    在 mono 4 6 2 linux 中 我注意到 wget 下载文件的速度与webclient DownloadString 所以我做了一个小测试来调查 为什么 wget 明显比 C 快 根据我自己的实验 使用 wget 下载 手动读取文
  • 单击按钮本地化应用程序

    我在我的项目 mainMaster 页面中找到了 imageButtons
  • 如何获得字符串的所有字谜

    我试图找到一个字符串的所有可能的字谜并仅使用递归将它们存储在数组中 我被困住了 这就是我所拥有的一切 int main const int MAX 10 string a ABCD string arr 10 permute arr a 0
  • 无法将方法组分配给 asp.net、linq、c# 中的隐式类型局部变量

    public void selectqueryasso CustomerOrderResult cso new CustomerOrderResult var a from as1 in ds orders from as2 in ds o
  • WinForms TreeView - 如何手动“突出显示”节点(就像被单击一样)

    我需要知道如何让以编程方式选择的节点以图形方式处于 选定 状态 就像用户单击它一样 SelectedNode 仅使这一节点在内部被选中 非常感谢 它没有显示为突出显示的原因是由于树视图没有焦点 这是我的测试表单上的按钮单击事件 TreeVi
  • C memcpy 二维数组

    我正在尝试使用将一个二维数组复制到另一个memcpy 我的代码 include
  • 为什么 `boost::any` 比 `void*` 更好?

    有什么先天优势boost any and boost any cast提供超过使用void and dynamic cast 优点是boost any比类型安全得多void E g int i 5 void p i static cast
  • 同时重新排序和旋转图像的高效方法

    为了快速加载 jpeg 我为turbojpeg 实现了一个 mex wrapper 以有效地将 大 jpeg 读入 MATLAB 对于 4000x3000px 的图像 实际解码只需要大约 120 毫秒 而不是 5 毫秒 然而 像素顺序是 R
  • 在C中更改函数内的数组

    我正在学习 C 并且很困惑为什么在 main 中创建的数组不会在函数内部更改 我假设传递的数组是一个指针 并且更改指针应该更改数组 对吧 有人可以解释这种情况下发生了什么吗 谢谢你的帮助 int main int i length 10 i
  • Bazel:为 cc_binary/cc_test 设置运行时环境变量和配置文件位置

    我正在尝试在 Linux 上的 C 应用程序中使用 odbc 以下构建文件用于将库作为外部依赖项包含在内 licenses notice cc library name lib srcs lib libodbc so lib64 libod
  • 计算 .NET Core 项目的代码指标?

    我正在研究 ASP NET Core 和 NET Core 项目 对于经典的 C 项目 Visual Studio 2015 具有计算代码指标的功能 对于 NET Core 预览版 2 工具中缺少支持 在工具更加完整之前 有人知道解决方法吗

随机推荐