在 AVR 组件中创建和寻址数组(使用 ATMega8535)

Creation and addressing arrays in AVR Assembly (Using the ATMega8535)

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

目前我的理解如下:

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

我尝试使用以下书籍的支持来做到这一点,但进展甚微:

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

代码应如下所示:

    .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.word 是 AVR 的 16 位)或 .space 100 的数组保留 space。 =81=]
  2. 在学习新的指令集时,编写C程序并要求C编译器生成汇编输出。在阅读汇编程序代码时,为指令集找到一个好的汇编程序编程参考。

在下面应用这个技巧。

byte-array.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

byte-array.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 指令。

实施说明

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

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()函数
  • 调用 _exit() 如果 main() 曾经 returns
  • _exit()只是一个cli来禁用中断
  • 和无限循环 (__stop_program)

如果您的数组是只读的,则不需要将其复制到 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

@scott 建议你先用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];
}

标识“命名地址space”的__flash关键字是一个嵌入式 C 扩展 支持 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 的一部分,所以它应该不 区别。

链接器的作用

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

组装(使用avr-gcc -c)和拆卸(使用avr-objdump -d) 第一个代码片段给出了这个:

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

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

目标文件也带有这个符号table,显示为avr-nm:

my_array.o:
00000000 T my_array

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

组装和反汇编第二个代码片段给出了这个:

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 的最终地址。目标文件也带有这个 符号 table:

read_data.o:
         U my_array
00000000 T read_data

其中“U”行表示文件使用了一个名为 “my_array”。

使用 suitable 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.

的正确地址