如何访问AVR libc中的PC指针(使用汇编)



我正在尝试使用AVR gcc在AVR程序集中编写一些条件跳转。根据AVR指令集手册,brxx指令接收操作数k,并跳到PC+k+1。此外,根据教程PDFhttp://www.avrbeginners.net/new/tutorials/jumps-calls-and-the-stack/,我应该能够使用PC操作数像这样跳转:

brne PC+2

然而,当我写这样的测试代码时:

#include <avr/io.h>
.section .text
.global main ; Note [5]
main:
    sbi _SFR_IO_ADDR(DDRA), PA0
    sbi _SFR_IO_ADDR(PORTA), PA0
    ldi 16, 0xFF
    cpi 16, 0xFF
    breq PC + 2
    cbi _SFR_IO_ADDR(PORTA), PA0
    rjmp end
end:
    rjmp end

我得到这个错误:

avr-gcc -mmcu="atmega16" -DF_CPU="16000000UL" -O0 main.S -o main.o
/tmp/ccAa2ySf.o: In function `main':
(.text+0x8): undefined reference to `PC'
collect2: ld returned 1 exit status
make: *** [main.o] Error 1

显然,AVR libc中没有定义PC。那么我该如何做这样的条件分支呢?谢谢

更新1

我发现了这样一个问题:如何使用AVR的gnu汇编程序相对于PC进行跳转?发现gnu as的语法为CCD_ 5。然而,我得到了和那个问题一样的错误。当我使用avr-objdump -d main.o进行拆解时,我确实会得到

  74:   01 f0           breq    .+0             ;  0x76

这和那个问题的症状是一样的。我会尝试使用链接器脚本,但我没有这方面的经验。

更新2

事实上,我发现如果我在breq指令中使用偶数,比如breq .+2breq .+4,objdump会显示正确的结果。但是,如果我使用奇数,它将变成breq .+0。有人能解释一下原因吗?

好的,现在完全重写了答案。这是我从编译的C代码的objdump中所理解的。首先,binutils对程序计数器使用字节寻址,而不是字寻址,并从当前指令之后的指令开始。以下代码对此进行了解释:

#include <avr/io.h>
.section .text
.global main
main:
    sbi _SFR_IO_ADDR(DDRA), PA0
    sbi _SFR_IO_ADDR(PORTA), PA0
    ldi 16, 0xFF
    cpi 16, 0xFF
    breq .+4  ;; If we are executing here
    cbi _SFR_IO_ADDR(PORTA), PA0  ;; This is .+0, will be skipped
    cbi _SFR_IO_ADDR(PORTA), PA0  ;; This is .+2, will be skipped
    cbi _SFR_IO_ADDR(PORTA), PA0  ;; This is .+4, which will be executed
    rjmp end
end:
    rjmp end

显然,PC的宽度与相对地址无关。它只影响最大PC值,0xFF或0xFFF,因此无论我为哪个AVR平台编译,binutils都会使用两个字节作为指令。

附言:我想,如果我能知道编译器如何工作的唯一方法是观察它是如何工作的,那可能意味着糟糕的文档?或者我只是不知道什么时候开始。如果有人看到这一点,你能帮我指出一些关于"这类事情"的有用书籍吗?(我甚至不知道该怎么形容)谢谢!

8位MCU并不意味着汇编指令被编码为8位操作码。来自ATmega16规范

大多数AVR指令都有一个单独的16位字格式

相反,即使ATmega是8位MCU,使用的指令也被编码为16位操作码。查看"AVR指令集"。这就是程序计数器(PC)如此工作的原因(仅分配给16位/2字节对齐的地址)。如果它能够设置为一个8位/1字节对齐的地址,它将尝试执行一个无效的操作码

有一件事要做。将上面的示例编译为对象文件。然后反汇编文件(使用objdump-D)并查看生成的反汇编。指令的偏移量应该是16位对齐的。

那么我该如何做这样的条件分支呢?

只需定义一个标签并分支到它。汇编程序将为您计算偏移量!

    brne some_label2
    ; code1
some_label2:
    ; code2

在分支目标遥不可及的情况下,在相反的条件下进行跳跃:

    breq some_label1
    [r]jmp some_labe2
some_label1:
    ; code1
some_label2:
    ; code2

GNU汇编程序还支持一种特殊的标签,它只是一些数字,并且您可以多次使用同一标签。跳跃目标分别是在CCD_ 13正向上首次发现的。b反向:

1:
    ; code 1
    brne 1b ; jump to label 1 above (backwards)
    brcc 1f ; jump to label 1 below (forwards)
    ; code 2
1:

在编写包含本地标签的程序集宏时,这可能很有用。

特别是在汇编宏中使用的伪变量@,它随着每次宏的使用而增加,因此也可以用于声明没有冲突的标签:

.macro loop reg
    .Lloop@:
        dec reg
        brne .Lloop@
.endm
    loop r16
    loop r16

如何访问PC指针

如果你真的因为一些模糊的原因需要程序计数器的值,你可以

    rcall .
#ifdef __AVR_3_BYTE_PC__
    pop  r18
#endif 
    pop  r17
    pop  r16

并且代码位置的字地址就在rcall之后。符号CCD_ 17是汇编程序的"em";当前位置"。

根据具体情况,可能更容易定义一个标签并获取其地址:

main:
    ldi r16,lo8(main)     ; Byte-address, low byte
    ldi r17,hi8(main)     ; Byte-address, high byte
    ldi r18,hh8(main)     ; Byte-address, highest byte
    ldi r19,pm_lo8(main)  ; Word-address, low byte
    ldi r20,pm_hi8(main)  ; Word-address, high byte
    ldi r21,pm_hh8(main)  ; Word-address, highest byte
    ldi r22,lo8(gs(main)) ; Word-address where the linker will 
    ldi r23,hi8(gs(main)) ; generate a stub as needed.

最新更新