在STM32中从RAM执行代码
Posted
技术标签:
【中文标题】在STM32中从RAM执行代码【英文标题】:Executing code from RAM in STM32 【发布时间】:2017-07-25 12:54:46 【问题描述】:我最近开始在 STM32F4 核板上编程。我刚刚发现只能在有限的时间内对闪存进行编程(虽然不是少数,但它是一个评估板,它将一遍又一遍地编程以开发不同的项目)。之后我在某处读到可以直接编程到 RAM 而不是闪存,但找不到任何有关它的技术信息。
有人知道如何修改链接器/makefile 来编译和链接要从 RAM 的起始地址执行的程序而不是闪存吗?
ps:我使用 STM32CubeMX 为 System workbench 生成的代码和一个为项目生成 makefile 的脚本
【问题讨论】:
更大的问题是您希望代码如何进入 RAM。您必须与另一个在重置后提供代码的设备建立某种连接(或者将代码从闪存加载到 RAM 中,但这会违背目的[尽管您可以通过压缩闪存中的代码来减少闪存写入])。 flash 编程可能有数千甚至数万,你达到了吗? 除了@FreddieChopin 的出色回答,关于在STM32 上从RAM 执行的另外两点; 1) 对于大多数部分,RAM 大小比闪存小得多,因此您会限制您的应用程序大小。 2) 当从闪存运行时,读/写数据和指令访问在不同的总线上,并且闪存有一个加速器,允许完整引用的 1.25DMIPS/MHz 性能。从 ram 运行会导致总线争用数据和指令访问,并大大减慢执行速度。 万一在一些其他灾难发生之前闪光磨损成为问题,例如只是错位电路板或 ESD 损坏,只需购买另一块电路板 - 将其视为消耗品。此外,您认为您将在未来的项目中使用那一代 STM32 多久?技术不断发展,成本下降,供应商淘汰零件,因此您可能会为未来的项目选择不同的处理器。 最大写入次数是多少,新的微控制器和/或评估板的成本是多少?不用担心它,或者更换微控制器,或者如果你到了那个地步,就买一块新板可能更有意义。 【参考方案1】:首先 - 不要想太多节省闪存。当我开始使用微控制器时,我和你有同样的计划,但后来得出结论,这根本没有意义。一个示例 STM32F4 芯片具有保证 最少 10000 次写入/擦除周期的闪存。您必须每天每天对电路板编程 14 次,连续两年才能达到该值。即使在你到达它之后,也没有说闪光灯会立即停止工作。您很可能不应该指望闪存内容会保留 20 年。考虑到耐用性和通常的使用周期,所有这些努力都不值得麻烦(平均而言,您的电路板每天可能会看到几个写入/擦除周期,而且您可能在几年后就不会使用它了)。尤其是当我们谈论便宜的电路板时。
TL;DR:只是不要尝试保存闪存。不值得这么麻烦。
如果您真的想从 RAM 执行代码并且根本不写入闪存,请记住这仅可以使用调试器。否则,您必须将代码写入闪存,并使用一个小例程将其复制到 RAM,然后从那里执行它 - 考虑到您最初保留闪存的想法,这将完全没有意义。无论如何 - 如果你想这样做,它非常简单,你所要做的就是修改链接器脚本。首先从MEMORY
部分完全删除“rom”(或者可能是“flash”或类似的)内存块。现在用 RAM 内存块替换已删除内存的所有用途,因此您应该将所有出现的“rom”替换为“ram”(或者可能将“flash”替换为“sram”或类似的东西)。在这个阶段,它应该真正起作用。您应该做的最后一件事是完全删除代码和功能来执行.data
部分初始化 - 这需要修改链接描述文件(确保此部分的 LMA 与其 VMA 相同),并删除初始化代码来自重置处理程序例程。
请注意,要使此过程生效,您应该:
使用 BOOT0 和 BOOT1 引脚选择“从 SRAM 引导”, 强制 PC 和 SP 使用调试器更正地址。很遗憾,对于您的 Nucleo 板,第一个选项不可用,因为 BOOT1 引脚(在这种情况下应该为高电平)短接到 GND。
但再说一遍 - 只是不要这样做,这不值得麻烦。
【讨论】:
cortex-m 使用向量表,不像全尺寸的手臂,所以入口点是错误的,调试器必须重置或删除向量表,无论哪种方式都必须替换栈指针的初始化。 @old_timer - 您在评论中提出的所有问题都无关紧要,就像调试器一样,您可以(并且 - 在这种情况下 - 应该!)对核心做任何你想做的事情。调试器可以调整 PC 以在您喜欢的任何地方启动。它还可以调整堆栈指针。而STM32如果你想从RAM运行代码,你应该相应地设置BOOTx引脚,这样就可以解决你提到的所有问题。 您在哪里提到了 bootx 引脚?它们在核板上的哪个位置出现? 您的程序只会导致定义的崩溃,需要完成。 有一些在 RAM 中运行的有效用例,主要是关于能够保留 FLASH 内容和速度(比擦除和写入快得多)。我目前正在修改它,它用于初始化和验证 STM32 和只能在它后面访问的外围设备。该解决方案需要使用 STM32 内置 UART 引导加载程序将固件加载到 RAM 中并启动它。这样你就可以自动化测试,当然这肯定不是@Nixmd 需要的。【参考方案2】:如果您最近才开始使用它,那么您还有很长一段时间后闪光灯会耗尽。您可能会遇到驱动器已满错误,只需拔下并重新插入电路板即可。我已经拥有这些东西多年了,还没有磨损闪光灯。并不是说它不能完成,它可以,但你不可能在那里,除非你写了一个快闪程序,把它弄坏了。
您将需要 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
>
全尺寸手臂处理器您的入口点是一条指令,您只需 开始执行。 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
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 所做的那样,他们必须将地址直接推入电脑,和/或为我们做正确的事情。
如果您只修改链接描述文件以用 ram 替换 rom。
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 地址作为入口点(恢复 0x20000041),但您必须先处理堆栈指针。
通过这样做
> reg sp 0x20001000
sp (/32): 0x20001000
> reg sp
sp (/32): 0x20001000
> resume 0x20000041
请注意,论文上的 ram 比 rom 快,并且在您增加时钟频率时不需要等待状态,因此如果您确实增加时钟频率并仅在 ram 中调试,如果您有切换到闪存,它可能会失败不记得设置闪存等待状态...除此之外,如果您愿意,您可以整天在 ram 中开发的程序的空间要小得多。
一个不错的功能是您可以继续暂停和重新加载。我不知道在这个设备/调试器上,如果你打开缓存(如果不是全部,一些 cortex-m4 有一个缓存)你必须小心确保在更改程序时它是关闭的。写入内存是一个数据操作取指令是一个指令提取操作,如果你在 0x20000100 处执行一些指令并且它被缓存在 I 缓存中,它可能会落在指令缓存中。然后你停止使用调试器然后编写一个新程序,包括缓存中的地址(0x20000100),当你运行它时,我的缓存没有被刷新,所以你将混合运行缓存中的先前程序和数据中的新程序,这充其量是一场灾难。因此,要么在以这种方式运行时永远不要打开缓存,要么想出解决此问题的方法(在停止程序之前清除缓存,使用重置按钮在运行之间重置处理器、重启电源等)。
【讨论】:
您确定只有 thumb2 处理器上的andcs
代码吗?
只有 thumb2 这样的东西。 thumb2 只是拇指的扩展。这就是反汇编程序试图从这些 32 位值中得到的,它们是地址。以上是关于在STM32中从RAM执行代码的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Cortex-M3 (STM32) 上从 RAM 执行功能?