2.2. Compiling and Linking

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