I wrote the following program to turn an LED on and off at 1-second intervals:
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = 0b00100000;
for (;;) {
PORTB = 0b00100000;
_delay_ms(1000);
PORTB = 0b00000000;
_delay_ms(1000);
}
return 0;
}
I tried disassembling the binary file of this code using the avr-objdump
of which the output is:
00000080 <main>:
80: 80 e2 ldi r24, 0x20 ; 32
82: 84 b9 out 0x04, r24 ; 4
00000084 <.L2>:
84: 85 b9 out 0x05, r24 ; 5
86: 2f ef ldi r18, 0xFF ; 255
88: 33 ed ldi r19, 0xD3 ; 211
8a: 90 e3 ldi r25, 0x30 ; 48
0000008c <.L1^B1>:
8c: 21 50 subi r18, 0x01 ; 1
8e: 30 40 sbci r19, 0x00 ; 0
90: 90 40 sbci r25, 0x00 ; 0
92: e1 f7 brne .-8 ; 0x8c <.L1^B1>
94: 00 c0 rjmp .+0 ; 0x96 <L0^A>
00000096 <L0^A>:
96: 00 00 nop
98: 15 b8 out 0x05, r1 ; 5
9a: 2f ef ldi r18, 0xFF ; 255
9c: 33 ed ldi r19, 0xD3 ; 211
9e: 90 e3 ldi r25, 0x30 ; 48
000000a0 <.L1^B2>:
a0: 21 50 subi r18, 0x01 ; 1
a2: 30 40 sbci r19, 0x00 ; 0
a4: 90 40 sbci r25, 0x00 ; 0
a6: e1 f7 brne .-8 ; 0xa0 <.L1^B2>
a8: 00 c0 rjmp .+0 ; 0xaa <L0^A>
000000aa <L0^A>:
aa: 00 00 nop
ac: eb cf rjmp .-42 ; 0x84 <.L2>
000000ae <_exit>:
ae: f8 94 cli
000000b0 <__stop_program>:
b0: ff cf rjmp .-2 ; 0xb0 <__stop_program>
My understanding of the above assembly code is:
- the two lines under
00000080 <main>:
sets the data direction of PB5 to output. 00000084 <.L2>:
section turns the LED on.0000008c <.L1^B1>:
waits for 1 second (first 4 lines) which then jumps to00000096 <L0^A>:
00000096 <L0^A>:
turns the LED off by writing the contents ofr1
(which is currently 0s) to PB5- then
000000a0 <.L1^B2>:
waits for 1 second and jumps to000000aa <L0^A>:
000000aa <L0^A>:
spends one clock cycle and jumps to00000084 <.L2>:
which turns the LED on. So on and so forth.
Questions:
- Is my understanding of the assembly code correct?
- What is the purpose of a
nop
sometimes as the first instruction?
1 Answer 1
In general, yes, but disassembling the raw binary may not be very fruitful, as you could just look at the compiler output listing which includes the C source code and generated assembly opcodes.
You ask for 1000 millisecond delay, which is done by looping 3199999 times. As the loop will be a few clock cycles less than 1000 milliseconds, it adds extra useless RJMP and NOP opcodes to waste a few clock cycles to end up closer to 1000 milliseconds.
-
\$\begingroup\$ For 1., how do I get this? \$\endgroup\$kovac– kovac2023年02月22日 13:40:03 +00:00Commented Feb 22, 2023 at 13:40
-
1\$\begingroup\$ Most C compilers support
-S
for making the assmbler file filename.s from filename.c. If you're using the avr toolset,avr-gcc -S -Os -mmcu=atmega328 -c filename.c
is likely to give you useful output. Also look at assembler listing output:avr-as -mmcu=atmega328 -o filename.o -a=filename.lst filename.s
\$\endgroup\$jonathanjo– jonathanjo2023年02月22日 13:50:21 +00:00Commented Feb 22, 2023 at 13:50
r24
is often used as a temporary register. Data direction register B is at IO address 4, ie, when usingOUT
. (And also at memory address 0x24, for when using ST.) It's a pecularity of this CPU that some registers appear in both IO and memory address spaces, but with different addresses. \$\endgroup\$