This first thing that needs to be done is compile the source. When compiling, the compiler needs to know the processor type so the -mmcu option is specified. The -Os option will tell the compiler to optimize the code for efficient space usage (at the possible expense of code execution speed.) The -g is used to embed debug info. The debug info is useful for disassemblies and doesn't end up in the .hex files, so I usually specify it. Finally, the -c tells the compiler to compile and stop -- don't link. This demo is small enough that we could compile and link in one step. However, real-world projects will have several modules and will typically need to break up the building of the project into several compiles and one link.
% avr-gcc -g -Os -mmcu=at90s2313 -c demo.c
The compilation will create a demo.o file. Next we link it into a binary called demo.out.
% avr-gcc -g -mmcu=at90s2313 -o demo.out demo.o
It is important to specify the MCU type when linking. The compiler uses the -mmcu option to choose start-up files and run-time libraries that get linked together. If this option isn't specified, the compiler defaults to the 8515 processor environment, which is most certainly what you didn't want.
Now we have a binary file. Can we do anything useful with it (besides put it into the processor?) The GNU Binutils suite is made up of many useful tools for manipulating object files that get generated. One tool is avr-objdump, which takes information from the object file and displays it in many useful ways. Typing the command by itself will cause it to list out its options.
For instance, to get a feel of the application's size, the -h option can be used:
% avr-objdump -h demo.out demo.out: file format elf32-avr Sections: Idx Name Size VMA LMA File off Algn 0 .text 000000ec 00000000 00000000 00000094 2**0 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000000 00800060 000000ec 00000180 2**0 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000004 00800060 00800060 00000180 2**0 ALLOC 3 .eeprom 00000000 00810000 00810000 00000180 2**0 CONTENTS 4 .stab 00000690 00000000 00000000 00000180 2**2 CONTENTS, READONLY, DEBUGGING 5 .stabstr 00000637 00000000 00000000 00000810 2**0 CONTENTS, READONLY, DEBUGGING
The output of this command shows how much space is used in each of the sections (the .stab and .stabstr sections hold the debugging information and won't make it into the ROM file.)
An even more useful option is -S. This option disassembles the binary file and intersperses the source code in the output! This method is much better, in my opinion, than using the -S with the compiler because this listing includes routines from the libraries and the vector table contents. Also, all the "fix-ups" have been satisfied. In other words, the listing generated by this option reflects the actual code that the processor will run.
% avr-objdump -S demo.out
This command generates the output shown in Example 2-2.
Example 2-2. Disassembly of Demo Application
demo.out: file format elf32-avr Disassembly of section .text: 00000000 <.__start_of_init__>: 0: 0a c0 rjmp .+20 ; 0x16 2: 21 c0 rjmp .+66 ; 0x46 4: 20 c0 rjmp .+64 ; 0x46 6: 1f c0 rjmp .+62 ; 0x46 8: 1e c0 rjmp .+60 ; 0x46 a: 1f c0 rjmp .+62 ; 0x4a c: 1c c0 rjmp .+56 ; 0x46 e: 1b c0 rjmp .+54 ; 0x46 10: 1a c0 rjmp .+52 ; 0x46 12: 19 c0 rjmp .+50 ; 0x46 14: 18 c0 rjmp .+48 ; 0x46 00000016 <_real_init_>: 16: 11 24 eor r1, r1 18: 1f be out 0x3f, r1 ; 63 1a: 20 e0 ldi r18, 0x00 ; 0 1c: a8 95 wdr 1e: 21 bd out 0x21, r18 ; 33 20: 20 e0 ldi r18, 0x00 ; 0 22: 25 bf out 0x35, r18 ; 53 24: ec ee ldi r30, 0xEC ; 236 26: f0 e0 ldi r31, 0x00 ; 0 28: a0 e6 ldi r26, 0x60 ; 96 2a: b0 e0 ldi r27, 0x00 ; 0 2c: 03 c0 rjmp .+6 ; 0x34 0000002e <.copy_data_loop>: 2e: c8 95 lpm 30: 31 96 adiw r30, 0x01 ; 1 32: 0d 92 st X+, r0 00000034 <.copy_data_start>: 34: a0 36 cpi r26, 0x60 ; 96 36: d9 f7 brne .-10 ; 0x2e 38: a0 e6 ldi r26, 0x60 ; 96 3a: b0 e0 ldi r27, 0x00 ; 0 3c: 01 c0 rjmp .+2 ; 0x40 0000003e <.zero_bss_loop>: 3e: 1d 92 st X+, r1 00000040 <.zero_bss_start>: 40: a4 36 cpi r26, 0x64 ; 100 42: e9 f7 brne .-6 ; 0x3e 44: 4c c0 rjmp .+152 ; 0xde 00000046 <_comparator_>: 46: 00 c0 rjmp .+0 ; 0x48 00000048 <_unexpected_>: 48: 18 95 reti 0000004a <_overflow1_>: static uint16_t pwm; enum {UP, DOWN} direction; SIGNAL(SIG_OVERFLOW1) { 4a: 1f 92 push r1 4c: 0f 92 push r0 4e: 0f b6 in r0, 0x3f ; 63 50: 0f 92 push r0 52: 11 24 eor r1, r1 54: 2f 93 push r18 56: 8f 93 push r24 58: 9f 93 push r25 switch (direction) { 5a: 80 91 62 00 lds r24, 0x0062 5e: 90 91 63 00 lds r25, 0x0063 62: 00 97 sbiw r24, 0x00 ; 0 64: 71 f0 breq .+28 ; 0x82 66: 01 97 sbiw r24, 0x01 ; 1 68: f1 f4 brne .+60 ; 0xa6 case UP: if (++pwm == 1023) direction = DOWN; break; case DOWN: if (--pwm == 0) 6a: 80 91 60 00 lds r24, 0x0060 6e: 90 91 61 00 lds r25, 0x0061 72: 01 97 sbiw r24, 0x01 ; 1 74: 90 93 61 00 sts 0x0061, r25 78: 80 93 60 00 sts 0x0060, r24 7c: 00 97 sbiw r24, 0x00 ; 0 7e: 99 f4 brne .+38 ; 0xa6 direction = UP; 80: 0e c0 rjmp .+28 ; 0x9e 82: 80 91 60 00 lds r24, 0x0060 86: 90 91 61 00 lds r25, 0x0061 8a: 01 96 adiw r24, 0x01 ; 1 8c: 90 93 61 00 sts 0x0061, r25 90: 80 93 60 00 sts 0x0060, r24 94: 8f 5f subi r24, 0xFF ; 255 96: 93 40 sbci r25, 0x03 ; 3 98: 31 f4 brne .+12 ; 0xa6 9a: 81 e0 ldi r24, 0x01 ; 1 9c: 90 e0 ldi r25, 0x00 ; 0 9e: 90 93 63 00 sts 0x0063, r25 a2: 80 93 62 00 sts 0x0062, r24 break; } __outw(pwm, OCR1L); a6: 80 91 60 00 lds r24, 0x0060 aa: 90 91 61 00 lds r25, 0x0061 ae: 9b bd out 0x2b, r25 ; 43 b0: 8a bd out 0x2a, r24 ; 42 b2: 9f 91 pop r25 b4: 8f 91 pop r24 b6: 2f 91 pop r18 b8: 0f 90 pop r0 ba: 0f be out 0x3f, r0 ; 63 bc: 0f 90 pop r0 be: 1f 90 pop r1 c0: 18 95 reti 000000c2 <ioinit>: } void ioinit(void) { #if defined(COM11) outp(BV(PWM10)|BV(PWM11)|BV(COM11), TCCR1A); /* tmr1 is 10-bit PWM */ #elif defined(COM1A1) outp(BV(PWM10)|BV(PWM11)|BV(COM1A1), TCCR1A); /* tmr1 is 10-bit PWM */ c2: 83 e8 ldi r24, 0x83 ; 131 c4: 8f bd out 0x2f, r24 ; 47 #else # error "need either COM1A1 or COM11" #endif outp(BV(CS10), TCCR1B); /* tmr1 running on full MCU clock */ c6: 81 e0 ldi r24, 0x01 ; 1 c8: 8e bd out 0x2e, r24 ; 46 __outw(0, OCR1L); /* set PWM value to 0 */ ca: 80 e0 ldi r24, 0x00 ; 0 cc: 90 e0 ldi r25, 0x00 ; 0 ce: 9b bd out 0x2b, r25 ; 43 d0: 8a bd out 0x2a, r24 ; 42 outp(BV(OC1), DDRB); /* enable OC1 and PB2 as output */ d2: 88 e0 ldi r24, 0x08 ; 8 d4: 87 bb out 0x17, r24 ; 23 #endif } extern inline void timer_enable_int (unsigned char ints) { d6: 80 e8 ldi r24, 0x80 ; 128 #ifdef TIMSK outp (ints, TIMSK); d8: 89 bf out 0x39, r24 ; 57 timer_enable_int(BV(TOIE1)); sei(); /* enable interrupts */ da: 78 94 sei } dc: 08 95 ret 000000de <main>: int main(void) { de: cf ed ldi r28, 0xDF ; 223 e0: d0 e0 ldi r29, 0x00 ; 0 e2: de bf out 0x3e, r29 ; 62 e4: cd bf out 0x3d, r28 ; 61 ioinit(); e6: ed df rcall .-38 ; 0xc2 for (;;) e8: ff cf rjmp .-2 ; 0xe8 000000ea <__stop_progIi__>: ea: ff cf rjmp .-2 ; 0xea