I figured out how to assemble without error messages.
The error messages you see are caused by the fact that avr-as
does not invoke the C-preprocessor and therefore the #include
lines are read as regular comments.
Create a file ledON.S
and notice the CAPITAL S
in the filename. The capital S
indicates that the C-preprocessor must be invoked first. Create the file with the following content:
#include <avr/io.h>
init: sbi _SFR_IO_ADDR(DDRB),0x05 ; Configure port B pin 5 as output
ret
.global main
main:
call init
loop:
sbi _SFR_IO_ADDR(PORTB),0x05 ; Toggle output pin HIGH
cbi _SFR_IO_ADDR(PORTB),0x05 ; Toggle output pin LOW
rjmp loop
The main
part is required in the source code, this is where usually the main program is located. If you remove it, no real code is executed ever and thus the compiler will complain about that. Code in a routine called init
should be explicitly called from main
. Although the pin toggles, in practice this will be too fast to see with the bare eye. If you check with an oscilloscope you'll see a square wave (I estimate at 25% duty cycle).
Then assemble the source code with:
avr-gcc -mmcu=atmega168a ledON.S -o ledON.o
Again avr-gcc
is required to invoke the C-preprocessor.
To check the result of the assembled program run the following command:
avr-objdump -C -d ./ledON.o
And the resulting disassembly listing looks like this:
./ledON.o: file format elf32-avr
Disassembly of section .text:
00000000 <__vectors>:
0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
10: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
14: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
18: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
1c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
20: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
24: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
28: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
2c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
30: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
34: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
38: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
3c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
40: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
44: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
48: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
4c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
50: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
54: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
58: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
5c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
60: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
00000068 <__ctors_end>:
68: 11 24 eor r1, r1
6a: 1f be out 0x3f, r1 ; 63
6c: cf ef ldi r28, 0xFF ; 255
6e: d4 e0 ldi r29, 0x04 ; 4
70: de bf out 0x3e, r29 ; 62
72: cd bf out 0x3d, r28 ; 61
74: 0e 94 42 00 call 0x84 ; 0x84 <main>
78: 0c 94 47 00 jmp 0x8e ; 0x8e <_exit>
0000007c <__bad_interrupt>:
7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
00000080 <init>:
80: 25 9a sbi 0x04, 5 ; 4
82: 08 95 ret
00000084 <main>:
84: 0e 94 40 00 call 0x80 ; 0x80 <init>
00000088 <loop>:
88: 2d 9a sbi 0x05, 5 ; 5
8a: 2d 98 cbi 0x05, 5 ; 5
8c: fd cf rjmp .-6 ; 0x88 <loop>
0000008e <_exit>:
8e: f8 94 cli
00000090 <__stop_program>:
90: ff cf rjmp .-2 ; 0x90 <__stop_program>
INTERMEZZO
You'll notice the assembler will automatically initialize the stack pointer
and status register in __ctors_end
.
Also it will automatically add a rjmp
at the end of the code.
Default behaviour of a program assembled by gcc-avr when it ends is:
- turn off interrupts (
_exit
, cli
)
- infinite empty loop that does nothing (
__stop_program
, rjmp .-2
)
The opcode will have most of its bits set aside to hold an address. The number of bits can vary because sometimes the address is absolute, and sometimes it is relative (relative addresses are offsets and therefore smaller numbers). The actual value that's placed in these bits is a calculation based on the value of the label that is used. For absolute addressing it is just the label's value. For relative addressing, the value for the operand field is calculated by subtracting the current program counter (where the jump instruction appears) from the address referred to in the label. Those are two different locations.
When the assembler sees a label, it uses its value just as if you had specified the number instead. Part of the usefulness of an assembler is that it keeps track of the PC as the program is compiled, and therefore can assign the correct address to a label when it appears.
This answer is somewhat generic, as I haven't worked with AVR assembly code.
Update: I searched on "AVR Assembler User Guide" to find the reference in your question. At the same time if found "AVR Assembler - - Atmel Corporation" which links to a document that shows how the instructions are put together. See the section called "Instructions".
Best Answer
Change
.DEF
with.EQU
: