如何在Cortex-M3 (STM32)上从RAM执行函数?

7
我正在尝试在Cortex-M3处理器(STM32)上从RAM执行一个函数。该函数擦除并重写内部闪存,因此它必须位于RAM中,但我该如何做呢?
我尝试的方法是:使用memcpy将函数复制到RAM中的字节数组中(检查其是否正确对齐),设置函数指针指向该字节数组,然后调用函数(指针)。
这对于大约10条指令(我可以通过调试器跟踪执行)运行良好,但是然后我会收到总线错误并且处理器会重置。总线错误发生在循环的第二次通过时,因此代码应该没问题(因为第一次通过时它能够正常工作)。我认为更快的RAM访问以某种方式混乱了总线时序...
无论如何,有没有正确的方法来做这件事?如何编写一个scatter文件以自动将函数放置在RAM中(我正在使用Keil uVision for Cortex-M3)?
编辑:更多信息: 工具链:RealView MDK-ARM V 4.10 编译器:Armcc v4.0.0.728 汇编器:Armasm v4.0.0.728 链接器:ArmLink v4.0.0.728 处理器:STM32F103ZE
当发生重置时,总线故障寄存器中的IMPRECISERR位被设置。

嗯,我认为你应该将这个问题发布到制造商的论坛上,因为你正在使用特定的硬件。不过,你能否提供更多细节?你正在使用哪个编译器?你是如何调试它的?有没有代码示例?也许只有一个人知道答案。除此之外…… - t0mm13b
1
@tommieb75:但我更喜欢你们! - c0m4
4个回答

8
循环迭代中的崩溃可能是因为函数分支到一个绝对地址,并且不相对于RAM中新函数位置。那么在该点访问原始代码位置会因闪存擦除操作而导致总线错误吗?
我相信通过在函数定义中附加__ram指令,您可以使用CARM正确地将函数编译并复制到RAM中。有关如何在RealView编译器中执行相同操作的说明,请参见“在RAM中执行函数”技术支持文章:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka11306.html µVision允许您将模块定位到特定的内存区域,这些区域在“项目 - 选项 - 目标”对话框中输入。要这样做,请右键单击源文件(或文件组)并打开“选项 - 属性”对话框。然后,在“内存分配”下选择内存区域。
此外,在“ARMExamplesRAM_Function”文件夹中有一个示例。
这应该生成启动代码来负责将函数复制到RAM并正确链接调用该位置。否则,如果您需要动态地将任意函数复制到RAM,则请查看使用RealView编译位置无关代码(PIC)

我有同样的问题,但我没有使用RTX或任何库,因此我不包括编译器的代码,这些代码会自动将RAM函数加载到RAM中。我希望在RAM中有一个加载区域或执行区域,我的代码实际上将链接到该区域,但我需要JTAG编程器将代码加载到另一个地址(在闪存中)。理想情况下,在.sct文件中(我正在使用Keil MDK),我将指定存储和链接地址的部分。但我还没有弄清如何做到这一点。 - Captain NedD
@Captain:您可能需要考虑使用您的具体情况开启一个新问题。然而,我相信上面的解决方案在您的情况下同样适用。 - Judge Maygarden

2

由于ARM有限的立即数载入能力,为ARM生成代码的工具经常将代码和数据并置。例如,像这样的语句

void myRoutine(void)
{
  myVar1=0x12345678;
  myVar2=0x87654321;
}

可能最终会变成类似以下内容:
myRoutine:        
    ldr r0,=myVar1; Load the address of _myVar
    ldr r1,=0x12345678
    str r1,[r0]
    ldr r0,=myVar1; Load the address of _myVar
    ldr r1,=0x87654321
    str r1,[r0]
    bx  lr

which would get translated into:
    ldr r0,dat1
    ldr r1,dat2
    str r1,[r0]
    ldr r0,dat3
    ldr r1,dat4
    str r1,[r0]
    bx  lr
... followed some time later by
dat1 dcd _myVar1
dat2 dcd 0x12345678
dat3 dcd _myVar2
dat4 dcd 0x12345678

or perhaps even something like:
    mar  r0,dat1
    ldrm r0,[r1,r2,r3,r4]
    str r2,[r1]
    str r4,[r3]
    bx  lr
... followed some time later by
dat1 dcd _myVar1
dat2 dcd 0x12345678
dat3 dcd _myVar2
dat4 dcd 0x12345678

请注意,_myVar 和 0x12345678 可能会直接放在它们所属的例程代码之后;如果您尝试使用跟随最后一条指令的标签来确定例程的长度,则该长度将不包括补充数据。
另一个需要注意的 ARM 特点是,由于历史原因,代码地址通常会设置其最低有效位,即使代码实际上从半字边界开始。因此,地址为 0x12345679 的指令将从 0x12345678 开始占用两个或四个字节。这可能会使诸如 memcpy 等地址计算变得复杂。
我的建议是编写一个小型汇编语言例程来完成您需要的工作。它只有几条指令,您可以确切地知道代码正在做什么以及它可能具有的地址依赖性,并且您不必担心未来编译器版本改变您的代码,从而导致某些问题(例如,即使 dat1 落在奇数半字边界上,上面代码的第三个版本也不会有问题,因为 M3 的 LDR 指令可以处理非对齐读取,但使用 LDRM 的第四个(稍快且更紧凑)版本将在这种情况下失败;即使今天的编译器版本使用四个 LDR 指令,未来版本也可能使用 LDRM)。保留HTML标签。

2

不了解你的情况,我只能提出一些常规建议... 确保该函数有一个有效的堆栈(或避免在函数中进行所有堆栈操作),禁用中断,并且系统向量表中的任何向量都不指向在擦除 Flash 时消失的代码。最后,请确保你的函数被链接到你放置它的地址运行... 该代码可能不可重定位并可能跳转到其旧位置中的某个位置。


1

使用IAR编译器(我知道你的问题是关于Keil的,但我没有它可以使用)你可以将整个项目或单个文件标记为“位置无关代码”。在其他处理器中使用过此功能后,意味着您可以将其“移动到任何地方”,它仍将正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接