在STM32中从RAM中执行代码

9
我最近开始在STM32F4 nucleo板上进行编程。我刚发现,将程序编程到闪存中仅可进行有限次数(虽然不少,但这是一个评估板,会反复编程以开发不同的项目)。之后我读到有人说可以直接将程序编程到RAM中,但是没有找到任何技术信息。
有人知道如何修改链接器/Makefile以编译和链接程序,使其从RAM的起始地址而非flash中执行吗?
注:我使用由STM32CubeMX为System Workbench生成的代码以及一个用于为项目生成Makefile的脚本。

更大的问题是你期望代码如何进入RAM。在重置后,你必须有一些连接到另一个设备,以提供代码(或者从闪存中将代码加载到RAM中,但这违背了初衷[尽管你可以通过压缩闪存中的代码来减少闪存写入次数])。 - EOF
Flash编程可能有成千上万,甚至数万行代码,你已经达到了吗? - old_timer
除了@FreddieChopin 的优秀回答之外,在STM32上从RAM执行的另外两个要点是;1)对于大多数部件,RAM大小比闪存小得多,因此您会限制应用程序大小。2)在从Flash运行时,读/写数据和指令访问使用不同的总线,并且Flash有一个加速器,允许全额引用的1.25DMIPS / MHz性能。从RAM中运行会导致数据和指令访问的总线争用,并且会显着减慢执行速度。 - Clifford
1
在不太可能的情况下,如果闪存磨损成为问题,而其他灾难尚未发生,比如仅仅是板子丢失或静电损坏,那么只需购买另一个板子 - 把它视为消耗品。此外,您认为您将在未来的项目中使用该代STM32多长时间?技术不断进步,成本降低,供应商淘汰部件,因此您可能会选择不同的处理器用于未来的项目。 - Clifford
最大写入次数是多少?新的微控制器和/或评估板的成本是多少?如果你到达那个点,最好不要担心它,直接更换微控制器或者获取一个新的开发板。 - user253751
显示剩余3条评论
2个回答

8
如果您最近开始使用它,那么在快闪存储耗尽之前,您有很长的时间。您可能会收到驱动器已满的错误提示,只需拔下板子再插上即可。我已经使用这些东西多年了,但还没有磨损快闪存储。不是说不可能磨损,只是您很少会出现这种情况,除非您编写了一个大量读写快闪存储的程序来使其磨损。
您需要openocd(或其他调试器,也许您的IDE提供该功能,但我不使用那些,无法帮助您)。openocd和gnu工具非常容易获取,因此接下来将介绍如何操作。
从正确的目录中,或通过从openocd复制这些文件来进行操作。
openocd -f stlink-v2-1.cfg -f stm32f4x.cfg

(一个或两个文件可能有其他包含的依赖项,可以将它们引入或采取其他措施。)

应该以类似这样的方式结束,而不是退出到命令行。

Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints

在另一个窗口中。
telnet localhost 4444

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> 

在那个窗口中,您可以暂停处理器。
> halt
stm32f4x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x61000000 pc: 0x080000b2 msp: 0x20000ff0
> 

完整的ARM处理器,您的入口点是一条指令,您只需开始执行。Cortex-M使用向量表,您不能直接跳转到那里。

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang

.thumb_func
reset:
    bl notmain
    b hang

.thumb_func
hang:   b .

你理论上可以分支到复位处理程序地址,但链接脚本需要在闪存中获得该地址,任何位置相关的操作都将无法正常工作。如果你依赖向量表来设置堆栈指针,则可能无法设置堆栈指针。因此,像下面这样的内容将起作用,并且是完整示例的一部分。
sram.s
.cpu cortex-m0
.thumb

.thumb_func
.global _start
_start:
    ldr r0,stacktop
    mov sp,r0
    bl notmain
    b .

.align
stacktop: .word 0x20001000

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

notmain.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );

int notmain ( void )
{
    unsigned int ra;
    ra=GET32(0x20000400);
    PUT32(0x20000404,ra);
    PUT32(0x20000400,ra+1);
    return(0);
}

sram.ld

MEMORY
{    
    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}

SECTIONS
{
    .text : { *(.text*) } > ram
    .rodata : { *(.rodata*) } > ram
    .bss : { *(.bss*) } > ram
}

基本上用 RAM 替换 ROM 引用。(如果您使用的是 GNU 连接器脚本,则可能比此脚本复杂得多,但这样做完全没有问题,可以根据需要添加 .data)。
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mcpu=cortex-m0 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -o notmain.flash.elf -T flash.ld flash.o notmain.o
arm-none-eabi-objdump -D notmain.flash.elf > notmain.flash.list
arm-none-eabi-objcopy notmain.flash.elf notmain.flash.bin -O binary
arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 sram.s -o sram.o
arm-none-eabi-ld -o notmain.sram.elf -T sram.ld sram.o notmain.o
arm-none-eabi-objdump -D notmain.sram.elf > notmain.sram.list
arm-none-eabi-objcopy notmain.sram.elf notmain.sram.hex -O ihex
arm-none-eabi-objcopy notmain.sram.elf notmain.sram.bin -O binary

我制作了程序的flash版本和sram版本。

现在我们已经通过telnet进入了openocd服务器,处理器被暂停,让我们查看一个内存位置并进行更改。

> mdw 0x20000400
0x20000400: 7d7d5889 
> mww 0x20000400 0x12345678
> mdw 0x20000400           
0x20000400: 12345678 

并运行我们基于SRAM的新程序。

> load_image /path/to/notmain.sram.elf
64 bytes written at address 0x20000000
downloaded 64 bytes in 0.008047s (7.767 KiB/s)
> resume 0x20000001

让它运行,脚本速度可能仍然太慢,但肯定需要花时间键入halt命令。

> halt
stm32f4x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x20000008 msp: 0x20001000
> mdw 0x20000400 10
0x20000400: 12345679 12345678 ce879a24 fc4ba5c7 997e5367 9db9a851 40d5083f fbfbcff8 
0x20000420: 035dce6b 65a7f13c 
> 

程序已经运行,程序读取0x20000400并将其保存到0x20000404,然后对0x20000400进行递增并保存,所有这些操作都已完成。

> load_image /path/to/notmain.sram.elf
64 bytes written at address 0x20000000
downloaded 64 bytes in 0.008016s (7.797 KiB/s)
> resume 0x20000000
> halt
stm32f4x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x20000008 msp: 0x20001000
> mdw 0x20000400 10                           
0x20000400: 1234567a 12345679 ce879a24 fc4ba5c7 997e5367 9db9a851 40d5083f fbfbcff8 
0x20000420: 035dce6b 65a7f13c 
> 

所以我们不需要使用BX指令将起始地址设置为1,他们只需将地址直接存入PC寄存器中,或为我们执行正确的操作。

如果您只修改链接脚本以将ROM替换为RAM,则可以实现目标。

20000000 <_start>:
20000000:   20001000
20000004:   20000041
20000008:   20000047
2000000c:   20000047
20000010:   20000047
20000014:   20000047
20000018:   20000047
2000001c:   20000047
20000020:   20000047
20000024:   20000047
20000028:   20000047
2000002c:   20000047
20000030:   20000047
20000034:   20000047
20000038:   20000047
2000003c:   20000047

20000040 <reset>:
20000040:   f000 f806   bl  20000050 <notmain>
20000044:   e7ff        b.n 20000046 <hang>

您可以将0x20000041地址作为程序入口点(使用"resume 0x20000041"命令),但需要先处理好堆栈指针。

可以通过以下方式进行处理:

> reg sp 0x20001000
sp (/32): 0x20001000
> reg sp
sp (/32): 0x20001000
> resume 0x20000041

注意:这些设备的RAM比ROM更快,并且在增加时钟频率时不需要等待状态,因此,如果您仅在RAM中增加时钟频率并进行调试,则可能会导致在切换到Flash时失败,如果您没有记得设置Flash等待状态...除此之外,程序的可用空间显著减少,但如果您想一整天都在RAM中开发程序,那么可以实现。
一个不错的功能是您可以保持暂停并重新加载。我不知道这个设备/调试器上是否有缓存(某些Cortex-M4具有缓存,如果不是全部,则需要小心确保在更改程序时关闭缓存)。写入内存是数据操作,获取指令是指令获取操作,该操作可能会落入指令高速缓存中。如果您在0x20000100处执行某些指令并且它被缓存在I cache中,则您使用调试器停止,然后编写一个包括缓存中地址(0x20000100)的新程序,当您运行它时,I cache未被刷新,因此您将同时运行先前程序和新程序,这最多是灾难性的。因此,在使用此方式运行时,要么永远不要打开缓存,要么找出解决此问题的方法(在停止程序之前清除缓存,在运行之间使用复位按钮复位处理器,断电等)。

你确定在仅支持Thumb2的处理器上使用andcs代码吗? - EOF
没有所谓的thumb2。thumb2只是thumb的扩展。而反汇编器正试图从这些32位值中生成它,它们是地址。 - old_timer
我必须评论并感谢您提供详尽的答案。仅从这个答案中,我就学到了很多东西!您有一种出色的解释事物的方式,表明您拥有丰富的知识和经验。您是否有关于stm32的文章、帖子或书籍?我很想阅读它们 ;) 再次感谢您! - benishor

4
首先,不要过度考虑如何保存闪存。当我刚开始使用微控制器时,我有和你一样的想法,但后来得出结论,在大多数情况下这并没有什么意义。例如,STM32F4芯片的闪存保证最少可写/擦除10,000次。您需要每天连续编程14次,每次写入/擦除,持续两年才能达到该值。即使您达到了这个值,也不能确保闪存立即停止工作。最有可能的是,您不能指望闪存内容在保证的20年内得以保留。与耐久性和通常使用周期相比,所有这些努力都不值得麻烦(平均而言,您的板子每天可能只会进行几次写/擦除循环,并且几年后您可能不再玩它)。特别是如果我们谈论便宜的开发板。
简而言之:不要试图保存闪存。这并不值得所有麻烦。
如果您真的想从RAM中执行代码而不写入闪存,请记住,这仅适用于调试器。否则,您必须将代码写入闪存,并附带一个小程序,将其复制到RAM中,然后从那里执行-这将完全没有意义,因为这与您最初的保存闪存的想法相悖。无论如何 - 如果您要这样做,那么很简单,您只需要修改链接器脚本。首先,在MEMORY部分完全删除“rom”(或者可能是“flash”或类似的)存储块。现在将所有使用已删除存储器的地方替换为RAM存储块,因此您应该将所有出现“rom”的地方都替换为“ram”(或者可能是将“flash”替换为“sram”或类似的)。此时它应该可以正常工作。最后一件事是完全删除执行.data部分初始化的代码和功能-这需要修改链接器脚本(确保该部分的LMA与其VMA相同),并从重置处理程序例程中删除初始化代码。
请注意,为使此过程正常工作,您应该选择以下两种方法之一:
  • 使用BOOT0&AMP;BOOT1引脚选择“从SRAM引导”,
  • 使用调试器强制PC和SP到正确的地址。
对于您的Nucleo板,不幸的是,第一种选项不可用,因为BOOT1引脚(在这种情况下应为高电平)与GND短接。
但再次强调-不要这样做,这并不值得麻烦。

Cortex-M使用向量表,不同于全尺寸的ARM,因此入口点是错误的。使用调试器必须在复位时进入或删除向量表,无论哪种方式都必须替换堆栈指针的初始化。 - old_timer
@老程序员 - 你在评论中提出的所有问题都不重要,因为使用调试器,你可以(而且在那种情况下应该!)随心所欲地处理核心。调试器可以将 PC 调整到任何你想要的位置。它还可以调整堆栈指针。如果你想从 RAM 运行代码,并且使用 STM32,你应该相应地设置 BOOTx 引脚,这样就可以解决你提到的所有问题了。 - Freddie Chopin
你在哪里提到了BootX引脚?它们在Nucleo板上的哪里被分离出来了? - old_timer
你的程序只会导致崩溃,需要完成。 - old_timer
2
有一些有效的用例可以在RAM中运行,主要是为了能够保留FLASH内容和速度(比擦除和写入快得多)。我目前正在尝试它,并且它用于初始化和验证STM32及其后面只能访问的外围设备。该解决方案需要使用STM32内置的UART引导加载程序将固件加载到RAM中并启动它。这样,您就可以自动化测试,尽管这显然不是@Nixmd所需的。 - Norbert Lange

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