在 STM32 中从 RAM 执行代码

Nix*_*xmd 7 c linker gcc arm stm32

我最近开始在 STM32F4 核板上编程。我刚刚发现只能在有限的时间内对闪存进行编程(虽然不是少数,但它是一个评估板,它将被一遍又一遍地编程以开发不同的项目)。之后我在某处读到可以直接编程到 RAM 而不是闪存中,但找不到任何关于它的技术信息。

有谁知道如何修改链接器/makefile 来编译和链接要从 RAM 的起始地址而不是闪存执行的程序?

ps:我使用STM32CubeMX为系统工作台生成的代码和一个脚本来为项目生成makefile

old*_*mer 7

如果您最近开始使用它,那么在闪光灯磨损之前您还有很长时间。您可能会遇到驱动器已满错误,只需拔下并重新插入电路板即可。这些东西我已经有很多年了,闪光灯还没有磨损。并不是说它不能完成,它可以,但除非您编写了一个磨损它的闪存抖动程序,否则您不太可能在那里。

您将需要 openocd(或其他一些调试器,也许您的 IDE 提供了,我不使用那些,因此无法提供帮助)。openocd 和 gnu 工具是微不足道的,所以要走一遍。

从正确的目录,或通过从 openocd 复制这些文件

openocd -f stlink-v2-1.cfg -f stm32f4x.cfg
Run Code Online (Sandbox Code Playgroud)

(其中一个或两个可能具有依赖项,它们包含的其他文件,可以将它们拉入或需要任何其他文件)。

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

Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
Run Code Online (Sandbox Code Playgroud)

在另一个窗口

telnet localhost 4444

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> 
Run Code Online (Sandbox Code Playgroud)

在那个窗口中你可以停止处理器

> halt
stm32f4x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x61000000 pc: 0x080000b2 msp: 0x20000ff0
> 
Run Code Online (Sandbox Code Playgroud)

全尺寸手臂处理器您的入口点是一条指令,您只需开始执行。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 .
Run Code Online (Sandbox Code Playgroud)

理论上,您可以分支到重置处理程序地址,但链接描述文件将希望在闪存中使用该地址,任何与位置相关的内容都将不起作用。如果您依靠向量表来执行此操作,则可能不会设置您的堆栈指针。所以这样的事情会起作用,这是完整示例的一部分

SRAM

.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
Run Code Online (Sandbox Code Playgroud)

不是main.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);
}
Run Code Online (Sandbox Code Playgroud)

文件

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

SECTIONS
{
    .text : { *(.text*) } > ram
    .rodata : { *(.rodata*) } > ram
    .bss : { *(.bss*) } > ram
}
Run Code Online (Sandbox Code Playgroud)

基本上用 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
Run Code Online (Sandbox Code Playgroud)

我的程序的 flash 版本和 sram 版本的构建。

所以现在我们有我们的 telnet 进入 openocd 服务器,处理器停止,让我们看看内存位置并更改它

> mdw 0x20000400
0x20000400: 7d7d5889 
> mww 0x20000400 0x12345678
> mdw 0x20000400           
0x20000400: 12345678 
Run Code Online (Sandbox Code Playgroud)

并运行我们新的基于 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
Run Code Online (Sandbox Code Playgroud)

让它运行,脚本速度可能仍然很慢,但肯定花时间输入停止命令是足够的。

> 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 
> 
Run Code Online (Sandbox Code Playgroud)

所以程序运行,程序读取 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 
> 
Run Code Online (Sandbox Code Playgroud)

所以我们不需要或用一个开始地址,你用 BX 做的,他们必须把地址直接推到电脑里,和/或为我们做正确的事情。

如果您只想修改链接器脚本以将 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>
Run Code Online (Sandbox Code Playgroud)

您可以使用 0x20000041 地址作为您的入口点(恢复 0x20000041),但您必须首先处理堆栈指针。

通过做这样的事情

> reg sp 0x20001000
sp (/32): 0x20001000
> reg sp
sp (/32): 0x20001000
> resume 0x20000041
Run Code Online (Sandbox Code Playgroud)

请注意,论文中的 ram 比 rom 快,并且在增加时钟频率时不需要等待状态,因此如果您确实增加了时钟频率并仅在 ram 中调试,如果您不记得切换到闪存时它可能会失败设置闪存等待状态...除此之外,如果您愿意,您可以整天在 ram 中开发程序的空间显着减少。

一个不错的功能是您可以保持暂停和重新加载。我不知道在这个设备/调试器上,如果你打开缓存(一些 cortex-m4s 有缓存,如果不是全部)你必须小心确保在你更改程序时关闭。写入内存是一种数据操作,获取指令是一种指令获取操作,如果您在 0x20000100 处执行某条指令并将其缓存在 I 缓存中,则该操作可能会进入指令缓存。然后您停止使用调试器,然后编写一个新程序,其中包括缓存中的地址(0x20000100),当您运行它时,I 缓存尚未刷新,因此您将混合运行缓存中的先前程序和数据中的新程序,其中充其量是一场灾难。


Fre*_*pin 4

首先——不要过多考虑节省闪存。当我开始使用微控制器时,我有和你一样的计划,但后来得出的结论是,它根本没有意义。示例 STM32F4 芯片具有保证至少10000 次写入/擦除周期的闪存。您必须连续两年每天对电路板进行 14 次编程才能达到该值即使到达该位置后,闪光灯也不会立即停止工作。您很可能不应该指望闪存内容会保留 20 年保证。考虑到耐用性和通常的使用周期,所有这些努力都是不值得的(平均而言,您的主板每天可能会看到几个写入/擦除周期,无论如何您可能不会在几年后使用它)。尤其是当我们谈论廉价的主板时。

TL;DR:只是不要尝试保存闪存。不值得这么麻烦。

如果您确实想从 RAM 执行代码而不写入闪存,请记住这只能通过调试器实现。否则,您必须将代码写入闪存,并使用一个小例程将其复制到 RAM,然后从那里执行它 - 考虑到您保留闪存的最初想法,这将完全没有意义。不管怎样 - 如果你想这样做,这非常简单,你所要做的就是修改链接器脚本。首先从部分完全删除“rom”(或者可能是“flash”或类似的东西)内存块MEMORY。现在将所有已删除内存的使用替换为 RAM 内存块,因此您可能应该将所有出现的“rom”替换为“ram”(或者可能将“flash”替换为“sram”或​​类似的东西)。在这个阶段它应该真正起作用。您应该做的最后一件事是完全删除执行节初始化的代码和功能.data- 这将需要修改链接器脚本(确保该节的 LMA 与其 VMA 相同),并从 Reset 处理程序中删除初始化代码常规。

请注意,要使此过程有效,您应该:

  • 使用 BOOT0 和 BOOT1 引脚选择“从 SRAM 启动”,
  • 使用调试器强制 PC 和 SP 更正地址。

不幸的是,对于您的 Nucleo 板,第一个选项不可用,因为 BOOT1 引脚(在这种情况下应该为高电平)对 GND 短路。

但再次强调——不要这样做,不值得这么麻烦。

  • 在 RAM 中运行有有效的用例,主要是能够保留闪存内容和速度(比擦除和写入快得多)。我目前正在修改它,它用于初始化和验证 STM32 以及只能在其后面访问的外设。该解决方案需要使用 STM32 内置 UART 引导加载程序将固件加载到 RAM 中并启动它。这样你就可以自动化测试,当然这肯定不是@Nixmd 所需要的。 (2认同)