【问题标题】:Using #defined values before RAM has been initialised在 RAM 初始化之前使用 #defined 值
【发布时间】:2013-03-29 21:54:09
【问题描述】:

我正在为 ARM CPU 编写启动代码。没有内部 RAM,但有 1GB 的 DDRAM 连接到 CPU,在初始化之前无法直接访问。代码存储在闪存中,初始化 RAM,然后将自身和数据段复制到 RAM 并在那里继续执行。我的程序是:

#define REG_BASE_BOOTUP 0xD0000000
#define INTER_REGS_BASE REG_BASE_BOOTUP

#define SDRAM_FTDLL_REG_DEFAULT_LEFT            0x887000

#define DRAM_BASE               0x0
#define SDRAM_FTDLL_CONFIG_LEFT_REG (DRAM_BASE+ 0x1484)
... //a lot of registers

void sdram_init() __attribute__((section(".text_sdram_init")));

void ram_init()
{
  static volatile unsigned int* const sdram_ftdll_config_left_reg = (unsigned int*)(INTER_REGS_BASE + SDRAM_FTDLL_CONFIG_LEFT_REG);
  ... //a lot of registers assignments

  *sdram_ftdll_config_left_reg = SDRAM_FTDLL_REG_DEFAULT_LEFT;
}

目前我的程序无法正常工作,因为寄存器值最终被链接到 RAM,而此时程序尝试访问它们只有闪存可用。

如何更改链接描述文件或程序,以使这些值在闪存中具有地址?有没有办法可以在文本段中包含这些值?

当它们在文件范围内声明时,这些定义的值实际上是全局数据还是静态数据?

编辑:

目标文件与以下链接描述文件链接:

MEMORY 
{                                                                                                             
RAM (rw)    : ORIGIN = 0x00001000, LENGTH = 12M-4K                                
ROM (rx)    : ORIGIN = 0x007f1000, LENGTH = 60K
VECTOR (rx) : ORIGIN = 0x007f0000, LENGTH = 4K   
}
SECTIONS
{
    .startup :
    {
    KEEP((.text.vectors))
    sdram_init.o(.sdram_init)
    } > VECTOR
...
}

从寄存器分配反汇编:

  *sdram_ftdll_config_left_reg = SDRAM_FTDLL_REG_DEFAULT_LEFT;
  7f0068:       e59f3204        ldr     r3, [pc, #516]  ; 7f0274 <sdram_init+0x254>
  7f006c:       e5932000        ldr     r2, [r3]
  7f0070:       e59f3200        ldr     r3, [pc, #512]  ; 7f0278 <sdram_init+0x258>
  7f0074:       e5823000        str     r3, [r2]
  ...
  7f0274:       007f2304        .word   0x007f2304
  7f0278:       00887000        .word   0x00887000

【问题讨论】:

  • __attribute__((progmem)) 什么的?
  • 您确定这是您的实际代码吗?我怀疑这会编译...
  • @Étienne 这不是关于隐式返回值,而是关于如何尝试将一个数字文字分配给另一个。您当前的代码扩展为0xd0010000 = 0x800f800f;,这没有意义。您不是想将这些地址定义为硬编码指针吗? #define register1 *(uint32_t *)0xd0010000
  • @Étienne (没有隐式转换! 有显式类型转换的转换,有隐式的提升类型转换。)仍然不好。您不能取消引用int,需要将其转换为非void 数据指针类型。但重点是,你按照我的建议做了吗?很可能在 GCC 的手册中有一些关于属性的内容。
  • @Étienne 既然我们有了真正的代码,我已经更新了我的答案。我相信它有一些有用的建议,即使您已接受 duskwuff 的 回答。

标签: c arm c-preprocessor linker-scripts


【解决方案1】:

直接回答您的问题——#defined 值不会存储在程序的任何地方(可能在调试部分除外)。宏会在编译时展开,就像您在函数中输入它们一样,例如:

*((unsigned int *) 0xd0010000) = 0x800f800f;

这些值最终会出现在文本段中,作为编译代码的一部分。

这里更有可能的是你做错了什么。在我的脑海中,我的第一个猜测是您的堆栈未正确初始化,或者位于尚不可用的内存区域中。

【讨论】:

  • 初始化期间我根本没有使用堆栈,因为当时没有可用的内存。
  • 您几乎可以肯定正在使用堆栈。如果您正在调用一个函数,调用者会将一些寄存器保存到堆栈中,并且编译器可能会将各种其他本地变量溢出到堆栈中。如果没有堆栈,您将无法可靠地运行已编译的 C 代码——您要么需要在启动期间为堆栈使用片上内存,要么只使用汇编。
  • 我通过从程序集分支到定义在其上的标签来调用该函数,并且在此 RAM 初始化之后手动设置堆栈指针。
  • 没关系 - 编译器仍然会假设堆栈已初始化,并且最终可能会尝试使用它。如果你不相信我,请尝试反汇编编译的文件。
  • @Étienne 您的错误实际上正如我在消息的最后一条评论中所描述的那样(回复 duskwuff);您已经使用定义分配了一个静态变量指针。我还认为您不会遇到堆栈问题,因为 gcc 处理 ARM 叶函数的方式,但无论如何这是一个好点,因为它是一个定时炸弹。
【解决方案2】:

有几个选项可以解决这个问题。

  1. 使用PC 相对数据访问。
  2. 使用自定义linker script
  3. 使用汇编程序。

使用PC相对数据访问

使用此方法的问题是您必须了解编译器如何生成代码的详细信息。 #define register1 (volatile unsigned int *)0xd0010000UL 是作为静态变量存储的,该变量从链接的 SDRAM 地址加载。

  7f0068:      ldr     r3, [pc, #516]  ; 7f0274 <sdram_init+0x254>
  7f006c:      ldr     r2, [r3]  ; !! This is a problem !!
  7f0070:      ldr     r3, [pc, #512]  ; 7f0278 <sdram_init+0x258>
  7f0074:      str     r3, [r2]
  ...
  7f0274:     .word   0x007f2304  ; !! This memory doesn't exist.
  7f0278:     .word   0x00887000

你必须这样做,

 void ram_init()
 {
     /* NO 'static', you can not do that. */
     /* static */ volatile unsigned int* const sdram_reg = 
         (unsigned int*)(INTER_REGS_BASE + SDRAM_FTDLL_CONFIG_LEFT_REG);
     *sdram_ftdll_config_left_reg = SDRAM_FTDLL_REG_DEFAULT_LEFT;
 }

或者您可能更喜欢在汇编程序中实现它,因为您在这里可以做什么和不能做什么可能很迟钝。上面C代码的主要作用是每件事都是计算出来的或者PC相对的。如果您选择使用链接描述文件,则必须如此。正如Duskwuff 指出的那样,您也可能遇到堆栈问题。如果您没有可用作临时堆栈的 ETB 内存等,那么最好在汇编程序中对其进行编码。

链接器脚本

请参阅gnu linker map... 和许多其他有关在这种情况下使用链接器脚本的问题。如果您需要详细信息,则需要提供处理器使用的实际地址。使用此选项,您可以annotate your function 指定它将位于哪个部分。例如,

void ram_init() __attribute__((section("FLASH")));

在这种情况下,您将使用 Gnu Linkers MEMORY 语句和 AT 语句将此代码放在您希望运行它的 闪存地址

使用汇编程序

Assembler 让您可以完全控制内存使用。您可以保证不使用堆栈,不生成非PC 相关代码,并且它可能会比boot 更快。这是一些 table 驱动的 ARM 汇编程序,我在您描述的案例中使用过,初始化 SDRAM 控制器。

 /* Macro for table of register writes. */
 .macro DCDGEN,type,addr,data
 .long \type
 .long \addr
 .long \data
 .endm
 .set FTDLL_CONFIG_LEFT, 0xD0001484

 sdram_init:
 DCDGEN 4, FTDLL_CONFIG_LEFT, 0x887000
 1:

 init_sdram_bank:
         adr     r0,sdram_init
         adr     r1,1b
 1:
         /* Delay. */
         mov     r5,#0x100
 2:      subs    r5,r5,#1
         bne     2b

         ldmia   r0!, {r2,r3,r4} /* Load DCD entry. */
         cmp     r2,#1           /* byte? */
         streqb  r4,[r3]         /* Store byte... */
         strne   r4,[r3]         /* Store word. */

         cmp     r0,r1           /* table done? */
         blo     1b

         bx lr

         /* Dump literal pool. */
        .ltorg

汇编程序有很多好处。您还可以清除bss 部分并使用简单的例程设置stack。互联网上有很多,我想您可能可以自己编写一个。 gnu ld 脚本对汇编程序也很有用,因为您可以确保像 bss 这样的部分是对齐的并且是 4,8 的倍数等。这样清算程序就不需要特殊情况了。此外,您必须在初始化后将codeflash 复制到 SDRAM。这是一个相当昂贵/长时间运行的任务,您可以使用一些简短的汇编程序来加速它。

【讨论】:

  • #defines 在内存中的任何地方都不存在。
  • @duskwuff 确实如此。但是对于 ARM,8 位常量和移位可以立即编码。像 Etienne 这样的#define 将被编码为从固定地址的内存加载的.long;至少这是我第一部分的解释,这可能是不正确的。所以#define的使用存在于内存中。
  • 不,这不是我见过的任何编译器的工作方式。最多,常量可能会被放在函数的末尾并使用 PC 相对引用加载 - 但是,如果函数已经在闪存中,那么常量也是如此。
  • @duskwuff 我认为我们都需要查看真实代码才能知道这一点。例如,我看到人们将#define 分配给一个指针变量,然后使用该变量。我也同意,正如我在答案的那部分所说,代码需要与 PC 相关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-02
  • 1970-01-01
相关资源
最近更新 更多