【问题标题】:Creation and addressing arrays in AVR Assembly (Using the ATMega8535)在 AVR 组装中创建和寻址阵列(使用 ATMega8535)
【发布时间】:2015-03-08 11:09:28
【问题描述】:

我在使用 Atmel ATMega8535 的指令集纯粹在汇编中创建的数组的创建和寻址时遇到问题。

目前我的理解如下:

  • 数组包含长度相等的连续数据。
  • 数组的创建涉及定义数组的开始和结束位置(就像您定义堆栈一样)。
  • 您可以通过添加数组基地址的偏移量来寻址数组中的索引。

我特别想做的是创建一个 8 位整数的一维数组,其中预定义的值在初始化期间填充它,它不必写入,只在需要时处理。问题最终在于无法将逻辑翻译成汇编代码。

我在以下书籍的支持下尝试这样做,但进展甚微:

  • 需要一些汇编:Timothy S Margush 使用 AVR 微控制器进行汇编语言编程
  • 开始使用...AVR 微控制器,作者:Peter Sharpe

我们将不胜感激任何帮助、建议或更多资源。

【问题讨论】:

    标签: arrays assembly avr atmel


    【解决方案1】:

    如果您的数组是只读的,则无需将其复制到 RAM。你可以 将其保存在 Flash 中,并在需要时从那里读取。这将拯救你 宝贵的 RAM,代价是访问速度较慢(从 RAM 读取需要 2 个周期, 从闪存读取是 3 个周期)。

    你可以这样声明你的数组:

    .global my_array
    .type   my_array, @object
    my_array:
        .byte 12, 34, 56, 78
    

    然后,要读取数组的成员,您必须计算:

    adress of member = array base address + member index
    

    如果您的成员超过一个字节,您还必须乘以 索引的大小,但这里不是这种情况。然后,你把 Z 寄存器中所需成员的地址并发出lpm 操作说明。下面是一个实现这个逻辑的函数:

    .global read_data
    ; input:    r24 = array index, r1 = 0
    ; output:   r24 = array value
    ; clobbers: r30, r31
    read_data:
        ldi r30, lo8(my_array)  ; load Z = address of my_array
        ldi r31, hi8(my_array)  ; ...high byte also
        add r30, r24            ; add the array index
        adc r31, r1             ; ...and add 0 to propagate the carry
        lpm r24, Z
        ret
    

    @scottt 建议你先用 C 写,然后看看生成的 部件。我认为这个建议非常好,让我们遵循它:

    #include <stdint.h>
    
    __flash const uint8_t my_array[] = {12, 34, 56, 78};
    
    uint8_t read_data(uint8_t index)
    {
        return my_array[index];
    }
    

    标识“命名地址空间”的 __flash 关键字是嵌入的 C 扩展名supported by gcc。这 生成的程序集与前一个程序集略有不同:相反 计算base_address + index,gcc 做index − (−base_address)

    read_data:
        mov r30, r24                ; load Z = array index
        ldi r31, 0                  ; ...high byte of index is 0
        subi r30, lo8(-(my_array))  ; subtract -(address of my array)
        sbci r31, hi8(-(my_array))  ; ...high byte also
        lpm r24, Z
        ret
    

    这与之前的手卷组件一样高效,除了 它不需要将 r1 寄存器初始化为零。但 无论如何,将 r1 保持为零是 gcc ABI 的一部分,所以它应该不会 区别。

    链接器的作用

    本部分旨在回答评论中的问题:我们如何才能 如果我们不知道它的地址,访问数组?答案是:我们访问 它的名字,就像上面的代码 sn-ps 一样。选择决赛 数组的地址,以及用适当的名称替换名称 地址,是链接器的工作。

    组装(avr-gcc -c)和拆卸(avr-objdump -d) 第一个代码 sn-p 给出了这个:

    my_array.o, section .text:
    00000000 <my_array>:
       0:   0c 22 38 4e        ."8N
    

    如果我们从 C 编译,gcc 会将数组放在 .progmem.data 部分而不是 .text,但差别不大。 数字“0c 22 38 4e”是数组内容,以十六进制表示。那些角色 右边是 ASCII 等价物,“.”是占位符 非打印字符。

    目标文件也带有这个符号表,由avr-nm表示:

    my_array.o:
    00000000 T my_array
    

    表示符号“my_array”已被定义为引用偏移量 0 进入此对象的 .text 部分(由“T”表示)。

    汇编和反汇编第二个代码 sn -p 给出:

    read_data.o, section .text:
    00000000 <read_data>:
       0:   e0 e0        ldi r30, 0x00
       2:   f0 e0        ldi r31, 0x00
       4:   e8 0f        add r30, r24
       6:   f1 1d        adc r31, r1
       8:   84 91        lpm r24, Z
       a:   08 95        ret
    

    反汇编与实际源码对比,可以看出 汇编器将 my_array 的地址替换为 0x00,即 几乎肯定是错的。但它也给链接器留下了一个注释 “搬迁记录”的形式,显示为avr-objdump -r

    read_data.o, RELOCATION RECORDS FOR [.text]:
    OFFSET   TYPE              VALUE 
    00000000 R_AVR_LO8_LDI     my_array
    00000002 R_AVR_HI8_LDI     my_array
    

    这告诉链接器 ldi 指令在偏移量 0x00 和 0x02 用于加载低字节和高字节(分别) my_array 的最终地址。目标文件也带有这个 符号表:

    read_data.o:
             U my_array
    00000000 T read_data
    

    “U”行表示文件使用了一个未定义的符号,名为 “我的数组”。

    使用合适的 main() 将这些部分链接在一起,生成一个二进制文件 包含来自 avr-lbc 的 C 运行时以及我们的代码:

    0000003c <my_array>:
      3c:   0c 22 38 4e        ."8N
    
    00000040 <read_data>:
      40:   ec e3        ldi r30, 0x3C
      42:   f0 e0        ldi r31, 0x00
      44:   e8 0f        add r30, r24
      46:   f1 1d        adc r31, r1
      48:   84 91        lpm r24, Z
      4a:   08 95        ret
    

    需要注意的是,链接器不仅移动了片段 到他们的最终地址,它还修复了ldi 的参数 指令,以便它们现在指向 my_array 的正确地址。

    【讨论】:

    • 确认,数组是只读的,每个成员都是一个字节的长度。理想情况下,我想首先避免用 C 编码,因为虽然实现起来更简单,但由于您所说的差异,我很难通过对反汇编代码进行逆向工程来学习。参考您的第一个代码 sn-p,您将在其中解释如何声明数组。该段中的哪里是数组中基地址的规范?此外,是否可以用十六进制声明成员、地址和偏移量?我假设这些值可以互换使用十进制和十六进制值。
    • @Daniel Dunn:您没有指定数组的基地址,而是让链接器将它放在它认为合适的位置。您确实关联一个符号 (my_array) 并在访问数组时使用相同的符号。然后,链接器将负责将符号用法与其定义相匹配。是的,如果你愿意,你可以用十六进制写数字。
    • 如果为您指定了基地址,您能否举例说明如何访问或引用 my_array 中的值 34。
    • @DanielDunn:我已经提供了示例:它是第二个代码 sn-p 中read_data 的实现。需要注意的重要一点是my_arrayldi 指令中被按名称 引用。无论数组的实际地址如何,汇编器都会生成一个ldi register, 0 指令。链接器稍后将修复此问题。参考文献“链接器的角色”在我的扩展答案中。
    【解决方案2】:

    代码应如下所示:

        .section    .text
        .global main
    main:
        ldi r30,lo8(data)
        ldi r31,hi8(data)
        ldd r24,Z+3
        sts output,r24
        ld r24,Z
        sts output,r24
        ldi r24,0
        ldi r25,0
        ret
        .global data
        .data
    data:
        .byte   1, 2, 3, 4
        .comm   output,1,1
    

    说明

    对于以前使用 GNU 工具链在汇编程序中编程的人,有些课程甚至可以转移到不熟悉的指令集:

    1. 您可以使用汇编指令.byte 1, 2, 3, 4.word 1, 2(对于 AVR,.word 是 16 位)或 .space 100,为数组保留空间。
    2. 学习新指令集时,编写 C 程序并要求 C 编译器生成汇编程序输出。在阅读汇编代码时为指令集找到一个好的汇编编程参考。

    在下面应用这个技巧。

    字节数组.c

    /* volatile our code doesn't get optimized out even when compiler optimization is on */
    volatile char output;
    
    char data[] = { 1, 2, 3, 4 };
    
    int main(void)
    {
        output = data[3];
        output = data[0];
        return 0;
    }
    

    从 C 生成汇编程序

    avr-gcc -mmcu=atmega8 -Wall -Os -S byte-array.c
    

    这将生成汇编文件byte-array.s

    字节数组.s

        .file   "byte-array.c"
    __SP_H__ = 0x3e
    __SP_L__ = 0x3d
    __SREG__ = 0x3f
    __tmp_reg__ = 0
    __zero_reg__ = 1
        .section    .text.startup,"ax",@progbits
    .global main
        .type   main, @function
    main:
    /* prologue: function */
    /* frame size = 0 */
    /* stack size = 0 */
    .L__stack_usage = 0
        ldi r30,lo8(data)
        ldi r31,hi8(data)
        ldd r24,Z+3
        sts output,r24
        ld r24,Z
        sts output,r24
        ldi r24,0
        ldi r25,0
        ret
        .size   main, .-main
    .global data
        .data
        .type   data, @object
        .size   data, 4
    data:
        .byte   1
        .byte   2
        .byte   3
        .byte   4
        .comm   output,1,1
        .ident  "GCC: (Fedora 4.9.2-1.fc21) 4.9.2"
    .global __do_copy_data
    .global __do_clear_bss
    

    阅读此explanation of Pointer Registers,了解AVR 指令集如何使用r30r31 寄存器对作为指针寄存器Z。阅读ldstldilddstsstd 说明。

    实施说明

    如果你链接程序然后反汇编它:

    avr-gcc -mmcu=atmega8 -Os byte-array.c -o byte-array.elf
    avr-objdump -d byte-array.elf
    
    00000000 <__vectors>:
       0:   12 c0           rjmp    .+36        ; 0x26 <__ctors_end>
       2:   2c c0           rjmp    .+88        ; 0x5c <__bad_interrupt>
       4:   2b c0           rjmp    .+86        ; 0x5c <__bad_interrupt>
       6:   2a c0           rjmp    .+84        ; 0x5c <__bad_interrupt>
       8:   29 c0           rjmp    .+82        ; 0x5c <__bad_interrupt>
       a:   28 c0           rjmp    .+80        ; 0x5c <__bad_interrupt>
       c:   27 c0           rjmp    .+78        ; 0x5c <__bad_interrupt>
       e:   26 c0           rjmp    .+76        ; 0x5c <__bad_interrupt>
      10:   25 c0           rjmp    .+74        ; 0x5c <__bad_interrupt>
      12:   24 c0           rjmp    .+72        ; 0x5c <__bad_interrupt>
      14:   23 c0           rjmp    .+70        ; 0x5c <__bad_interrupt>
      16:   22 c0           rjmp    .+68        ; 0x5c <__bad_interrupt>
      18:   21 c0           rjmp    .+66        ; 0x5c <__bad_interrupt>
      1a:   20 c0           rjmp    .+64        ; 0x5c <__bad_interrupt>
      1c:   1f c0           rjmp    .+62        ; 0x5c <__bad_interrupt>
      1e:   1e c0           rjmp    .+60        ; 0x5c <__bad_interrupt>
      20:   1d c0           rjmp    .+58        ; 0x5c <__bad_interrupt>
      22:   1c c0           rjmp    .+56        ; 0x5c <__bad_interrupt>
      24:   1b c0           rjmp    .+54        ; 0x5c <__bad_interrupt>
    
    00000026 <__ctors_end>:
      26:   11 24           eor r1, r1
      28:   1f be           out 0x3f, r1    ; 63
      2a:   cf e5           ldi r28, 0x5F   ; 95
      2c:   d4 e0           ldi r29, 0x04   ; 4
      2e:   de bf           out 0x3e, r29   ; 62
      30:   cd bf           out 0x3d, r28   ; 61
    
    00000032 <__do_copy_data>:
      32:   10 e0           ldi r17, 0x00   ; 0
      34:   a0 e6           ldi r26, 0x60   ; 96
      36:   b0 e0           ldi r27, 0x00   ; 0
      38:   e4 e8           ldi r30, 0x84   ; 132
      3a:   f0 e0           ldi r31, 0x00   ; 0
      3c:   02 c0           rjmp    .+4         ; 0x42 <__SREG__+0x3>
      3e:   05 90           lpm r0, Z+
      40:   0d 92           st  X+, r0
      42:   ac 36           cpi r26, 0x6C   ; 108
      44:   b1 07           cpc r27, r17
      46:   d9 f7           brne    .-10        ; 0x3e <__SP_H__>
    
    00000048 <__do_clear_bss>:
      48:   10 e0           ldi r17, 0x00   ; 0
      4a:   ac e6           ldi r26, 0x6C   ; 108
      4c:   b0 e0           ldi r27, 0x00   ; 0
      4e:   01 c0           rjmp    .+2         ; 0x52 <.do_clear_bss_start>
    
    00000050 <.do_clear_bss_loop>:
      50:   1d 92           st  X+, r1
    
    00000052 <.do_clear_bss_start>:
      52:   ad 36           cpi r26, 0x6D   ; 109
      54:   b1 07           cpc r27, r17
      56:   e1 f7           brne    .-8         ; 0x50 <.do_clear_bss_loop>
      58:   02 d0           rcall   .+4         ; 0x5e <main>
      5a:   12 c0           rjmp    .+36        ; 0x80 <_exit>
    
    0000005c <__bad_interrupt>:
      5c:   d1 cf           rjmp    .-94        ; 0x0 <__vectors>
    
    0000005e <main>: ...
    
    00000080 <_exit>:
      80:   f8 94           cli
    
    00000082 <__stop_program>:
      82:   ff cf           rjmp    .-2         ; 0x82 <__stop_program>
    

    可以看到avr-gcc自动生成启动代码,包括:

    • 中断向量 (__vectors),它使用 rjmp 跳转到中断服务程序。
    • 初始化状态寄存器 SREG 和堆栈指针 SPL/SPH (__ctors_end)
    • 将数据段内容从 FLASH 复制到 RAM 中,用于初始化、可写的全局变量 (__do_copy_data)
    • 清除 BSS 段中未初始化的可写全局变量(__do_clear_bss 等)
    • 调用我们的main()函数
    • 如果main() 返回,则调用_exit()
    • _exit() 只是一个 cli 来禁用中断
    • 和一个无限循环 (__stop_program)

    【讨论】:

    • 启动代码其实来自avr-libc。 __do_copy_data__do_clear_bss 来自 libgcc,仅在需要时才链接到程序中。
    猜你喜欢
    • 2013-08-17
    • 1970-01-01
    • 1970-01-01
    • 2015-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    • 2011-08-19
    相关资源
    最近更新 更多