Search code examples
gccassemblylinkerbranchavr

How to access the PC pointer (using assembly) in AVR-libc?


I am trying to write some conditional jumps in AVR assembly using AVR-gcc. According to AVR instruction set manual, the brxx instructions take in an operand k, and jumps to PC+k+1. Also, according to the tutorial PDF from http://www.avrbeginners.net/new/tutorials/jumps-calls-and-the-stack/, I should be able to use the PC operand to jump like this:

brne PC+2

However, when I write such test code:

#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

I get this error:

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

Apparently PC is not defined in AVR-libc. Then how am I going to do such condition branch? Thanks!

Update 1

I found this question How can I jump relative to the PC using the gnu assembler for AVR? and found that the syntax for gnu as is breq .+2. However, I get the same error as that question have. When I disassemble using avr-objdump -d main.o, I do get

  74:   01 f0           breq    .+0             ;  0x76

Which is the same symptom as that question. I will try using linker script, but I have no experience in that.

Update 2

Actually I found that if I use even numbers in the breq instruction, like breq .+2 or breq .+4, the objdump shows correct result. However, if I use odd numbers, it will become breq .+0. Can someone explain why?


Solution

  • OK, the answer is totally rewritten now. This is what I understand from the objdump of compiled C codes. Firstly, binutils uses byte addressing, not word addressing, for the program counter, and starts at the instruction right after the current one. This is explained in the following code:

    #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
    

    Apparently, the PC width has nothing to do with relative address. It only affects the maximum PC value, either 0xFF or 0xFFF, so no matter what AVR platform I am compiling for, binutils uses two bytes for an instruction.

    P.S. I think, if the only way I can know how a compiler works is to observe how it works, probably that means poor documentation? Or maybe I just don't know when to start. If someone see this, could you help pointing some useful books about 'this kind of things'? (I don't even know how to describe it) Thanks!