demo_project(3avr)

πŸ‘‘ NAME πŸ‘‘

demo_project β€” a simple project. that's it, that's the whole name, she's uncomplicated and we stan πŸ’…


✨ SYNOPSIS ✨

πŸ¦‹ Detailed Description πŸ¦‹

okay so at this point bestie, you should already have the GNU tools configured, built, and installed on your system. no cap. in this chapter we're gonna present a simple example of using the GNU tools in an AVR project. after you read it, you should have a way better feel for how the tools get used and how a Makefile can be configured. 🫢

πŸŽ€ The Project πŸŽ€

this project is gonna use the pulse-width modulator (PWM) to ramp an LED on and off every two seconds. an AT90S2313 processor will be the controller. the circuit for this demonstration is shown in the schematic diagram. if you've got a development kit, you should be able to use that instead of building the circuit for this project. iconic behavior, honestly ✨

Note

meanwhile, the AT90S2313 became obsolete. 😭 either use its successor, the (pin-compatible) ATtiny2313 for the project, or maybe the ATmega8 or one of its successors (ATmega48/88/168) which have become quite popular since the original demo project had been established. for all these more modern devices, it is no longer necessary to use an external crystal for clocking, because they ship with the internal 1 MHz oscillator enabled β€” so C1, C2, and Q1 can be omitted. normally, for this experiment, the external circuitry on /RESET (R1, C3) can be omitted as well, leaving only the AVR, the LED, the bypass capacitor C4, and perhaps R2. for the ATmega8/48/88/168, use PB1 (pin 15 at the DIP-28 package) to connect the LED to. additionally, this demo has been ported to many different other AVRs. the location of the respective OC pin varies between different AVRs, and it is mandated by the AVR hardware. periodt, hardware said so πŸ’€

the source code is given in demo.c. for the sake of this example, create a file called demo.c containing this source code. some of the more important parts of the code are:

Note [1]:

as the AVR microcontroller series has been developed during the past years, new features have been added over time. even though the basic concepts of the timer/counter1 are still the same as they used to be back in early 2001 when this simple demo was written initially, the names of registers and bits have been changed slightly to reflect the new features. also, the port and pin mapping of the output compare match 1A (or 1 for older devices) pin which is used to control the LED varies between different AVRs. the file iocompat.h tries to abstract between all this differences using some preprocessor #ifdef statements, so the actual program itself can operate on a common set of symbolic names. the macros defined by that file are:

  • OCR β€” the name of the OCR register used to control the PWM (usually either OCR1 or OCR1A)
  • DDROC β€” the name of the DDR (data direction register) for the OC output
  • OC1 β€” the pin number of the OC1[A] output within its port
  • TIMER1_TOP β€” the TOP value of the timer used for the PWM (1023 for 10-bit PWMs, 255 for devices that can only handle an 8-bit PWM)
  • TIMER1_PWM_INIT β€” the initialization bits to be set into control register 1A in order to setup 10-bit (or 8-bit) phase and frequency correct PWM mode
  • TIMER1_CLOCKSOURCE β€” the clock bits to set in the respective control register to start the PWM timer; usually the timer runs at full CPU clock for 10-bit PWMs, while it runs on a prescaled clock for 8-bit PWMs

Note [2]:

ISR() is a macro that marks the function as an interrupt routine. in this case, the function will get called when timer 1 overflows. setting up interrupts is explained in greater detail in <avr/interrupt.h>: Interrupts. πŸ’–

Note [3]:

the PWM is being used in 10-bit mode, so we need a 16-bit variable to remember the current value.

Note [4]:

this section determines the new value of the PWM.

Note [5]:

here's where the newly computed value is loaded into the PWM register. since we are in an interrupt routine, it is safe to use a 16-bit assignment to the register. outside of an interrupt, the assignment should only be performed with interrupts disabled if there's a chance that an interrupt routine could also access this register (or another register that uses TEMP) β€” see the appropriate FAQ entry. πŸ™ˆ

Note [6]:

this routine gets called after a reset. it initializes the PWM and enables interrupts.

Note [7]:

the main loop of the program does nothing β€” all the work is done by the interrupt routine! the sleep_mode() puts the processor on sleep until the next interrupt, to conserve power. of course, that probably won't be noticable as we are still driving a LED, it is merely mentioned here to demonstrate the basic principle. πŸ’…

Note [8]:

early AVR devices saturate their outputs at rather low currents when sourcing current, so the LED can be connected directly, and the resulting current through the LED will be about 15 mA. for modern parts (at least for the ATmega 128), however, Atmel has drastically increased the IO source capability, so when operating at 5 V Vcc, R2 is needed. its value should be about 150 Ohms. when operating the circuit at 3 V, it can still be omitted though.


πŸ’œ The Source Code πŸ’œ

/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
 * ----------------------------------------------------------------------------
 *
 * Simple AVR demonstration.  Controls a LED that can be directly
 * connected from OC1/OC1A to GND.  The brightness of the LED is
 * controlled with the PWM.  After each period of the PWM, the PWM
 * value is either incremented or decremented, that's all.
 *
 * $Id$
 */

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

#include "iocompat.h"        /* Note [1] */

enum { UP, DOWN };

ISR (TIMER1_OVF_vect)       /* Note [2] */
{
    static uint16_t pwm;    /* Note [3] */
    static uint8_t direction;

    switch (direction)      /* Note [4] */
    {
        case UP:
            if (++pwm == TIMER1_TOP)
                direction = DOWN;
            break;

        case DOWN:
            if (--pwm == 0)
                direction = UP;
            break;
    }

    OCR = pwm;          /* Note [5] */
}

void
ioinit (void)           /* Note [6] */
{
    /* Timer 1 is 10-bit PWM (8-bit PWM on some ATtinys). */
    TCCR1A = TIMER1_PWM_INIT;
    /*
     * Start timer 1.
     *
     * NB: TCCR1A and TCCR1B could actually be the same register, so
     * take care to not clobber it.
     */
    TCCR1B |= TIMER1_CLOCKSOURCE;
    /*
     * Run any device-dependent timer 1 setup hook if present.
     */
#if defined(TIMER1_SETUP_HOOK)
    TIMER1_SETUP_HOOK();
#endif

    /* Set PWM value to 0. */
    OCR = 0;

    /* Enable OC1 as output. */
    DDROC = _BV (OC1);

    /* Enable timer 1 overflow interrupt. */
    TIMSK = _BV (TOIE1);
    sei ();
}

int
main (void)
{

    ioinit ();

    /* loop forever, the interrupts are doing the rest */

    for (;;)            /* Note [7] */
        sleep_mode();

    return (0);
}

🌈 Compiling and Linking 🌈

the 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=atmega8 -c demo.c

the compilation will create a demo.o file. next we link it into a binary called demo.elf.

$ avr-gcc -g -mmcu=atmega8 -o demo.elf 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. πŸ˜­πŸ’€

✨ Examining the Object File ✨

okay so we have a binary file now πŸŽ€ and like… are we supposed to do anything cute with it besides firing it straight into the processor and praying? YES bestie, thank god. the GNU Binutils suite is literally a whole friend group of tools for messing with the object files that get generated, and one of those icons is avr-objdump, which pulls info out of the object file and shows it off in sooo many useful ways. and if you just type the command by itself with nothing else? she'll list out her options, like a queen introducing herself πŸ’…

so for instance, if you wanna get a feel for how big your application is, -h is right there. the output of this option shows how much space is used in each of the sections β€” and heads up, the .stab and .stabstr sections hold the debugging information and they won't make it into the ROM file, so don't panic when you see them taking up room 🫢

and even MORE useful? -S. this one disassembles the binary file and intersperses the source code right into the output 😭 it's giving director's commentary, honestly. imo this method is so much better than using -S with the compiler, literally because this listing includes routines from the libraries AND the vector table contents. ALSO β€” and this part is important β€” all the "fix-ups" have been satisfied. in other words, the listing this option generates reflects the actual code that the processor will run. no cap, fr fr πŸ”₯

$ avr-objdump -h -S demo.elf > demo.lst

here's the output as saved in the demo.lst file:

demo.elf:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000000e2  00000000  00000000  000000d4  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .note.package 00000074  000000e4  000000e4  000001b8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000000  00800060  00000158  0000022c  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  3 .bss          00000003  00800060  00800060  0000022c  2**0
                  ALLOC
  4 .comment      00000012  00000000  00000000  0000022c  2**0
                  CONTENTS, READONLY
  5 .note.gnu.avr.deviceinfo 0000003c  00000000  00000000  00000240  2**2
                  CONTENTS, READONLY, OCTETS
  6 .debug_aranges 00000028  00000000  00000000  0000027c  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  7 .debug_info   00000586  00000000  00000000  000002a4  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  8 .debug_abbrev 00000528  00000000  00000000  0000082a  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  9 .debug_line   00000232  00000000  00000000  00000d52  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 10 .debug_frame  00000060  00000000  00000000  00000f84  2**2
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 11 .debug_str    0000027b  00000000  00000000  00000fe4  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 12 .debug_rnglists 0000001a  00000000  00000000  0000125f  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS

Disassembly of section .text:

00000000 <__vectors>:
   0:   12 c0          rjmp .+36      ; 0x26 <__ctors_end>
   2:   61 c0          rjmp .+194     ; 0xc6 <__bad_interrupt>
   4:   60 c0          rjmp .+192     ; 0xc6 <__bad_interrupt>
   6:   5f c0          rjmp .+190     ; 0xc6 <__bad_interrupt>
   8:   5e c0          rjmp .+188     ; 0xc6 <__bad_interrupt>
   a:   5d c0          rjmp .+186     ; 0xc6 <__bad_interrupt>
   c:   5c c0          rjmp .+184     ; 0xc6 <__bad_interrupt>
   e:   5b c0          rjmp .+182     ; 0xc6 <__bad_interrupt>
  10:   1a c0          rjmp .+52      ; 0x46 <__vector_8>
  12:   59 c0          rjmp .+178     ; 0xc6 <__bad_interrupt>
  14:   58 c0          rjmp .+176     ; 0xc6 <__bad_interrupt>
  16:   57 c0          rjmp .+174     ; 0xc6 <__bad_interrupt>
  18:   56 c0          rjmp .+172     ; 0xc6 <__bad_interrupt>
  1a:   55 c0          rjmp .+170     ; 0xc6 <__bad_interrupt>
  1c:   54 c0          rjmp .+168     ; 0xc6 <__bad_interrupt>
  1e:   53 c0          rjmp .+166     ; 0xc6 <__bad_interrupt>
  20:   52 c0          rjmp .+164     ; 0xc6 <__bad_interrupt>
  22:   51 c0          rjmp .+162     ; 0xc6 <__bad_interrupt>
  24:   50 c0          rjmp .+160     ; 0xc6 <__bad_interrupt>

00000026 <__ctors_end>:
  26:   11 24          eor  r1, r1
  28:   1f be          out  0x3f, r1  ; 63
  2a:   cf e5          ldi  r28, 0x5F ; 95
  2c:   d4 e0          ldi  r29, 0x04 ; 4
  2e:   de bf          out  0x3e, r29 ; 62
  30:   cd bf          out  0x3d, r28 ; 61

00000032 <__do_clear_bss>:
  32:   20 e0          ldi  r18, 0x00 ; 0
  34:   a0 e6          ldi  r26, 0x60 ; 96
  36:   b0 e0          ldi  r27, 0x00 ; 0
  38:   01 c0          rjmp .+2       ; 0x3c <.do_clear_bss_start>

0000003a <.do_clear_bss_loop>:
  3a:   1d 92          st   X+, r1

0000003c <.do_clear_bss_start>:
  3c:   a3 36          cpi  r26, 0x63 ; 99
  3e:   b2 07          cpc  r27, r18
  40:   e1 f7          brne .-8       ; 0x3a <.do_clear_bss_loop>
  42:   42 d0          rcall     .+132     ; 0xc8 <main>
  44:   4a c0          rjmp .+148     ; 0xda <exit>

00000046 <__vector_8>:
#include "iocompat.h"         /* Note [1] */

enum { UP, DOWN };

ISR (TIMER1_OVF_vect)         /* Note [2] */
{
  46:   1f 92          push r1
  48:   0f 92          push r0
  4a:   0f b6          in   r0, 0x3f  ; 63
  4c:   0f 92          push r0
  4e:   11 24          eor  r1, r1
  50:   2f 93          push r18
  52:   8f 93          push r24
  54:   9f 93          push r25

00000056 <.Loc.1>:
    static uint16_t pwm;     /* Note [3] */
    static uint8_t direction;

    switch (direction)       /* Note [4] */
  56:   20 91 62 00    lds  r18, 0x0062    ; 0x800062 <direction.1>

0000005a <.Loc.4>:
    {
        case UP:
            if (++pwm == TIMER1_TOP)
  5a:   80 91 60 00    lds  r24, 0x0060    ; 0x800060 <pwm.0>
  5e:   90 91 61 00    lds  r25, 0x0061    ; 0x800061 <pwm.0+0x1>

00000062 <.Loc.5>:
    switch (direction)       /* Note [4] */
  62:   21 15          cp   r18, r1
  64:   b1 f0          breq .+44      ; 0x92 <.L2>
  66:   21 30          cpi  r18, 0x01 ; 1
  68:   51 f0          breq .+20      ; 0x7e <.L3>

0000006a <.L4>:
            if (--pwm == 0)
                direction = UP;
            break;
    }

    OCR = pwm;               /* Note [5] */
  6a:   9b bd          out  0x2b, r25 ; 43
  6c:   8a bd          out  0x2a, r24 ; 42

0000006e <.Loc.8>:
}
  6e:   9f 91          pop  r25
  70:   8f 91          pop  r24
  72:   2f 91          pop  r18
  74:   0f 90          pop  r0
  76:   0f be          out  0x3f, r0  ; 63
  78:   0f 90          pop  r0
  7a:   1f 90          pop  r1
  7c:   18 95          reti

0000007e <.L3>:
            if (--pwm == 0)
  7e:   01 97          sbiw r24, 0x01 ; 1

00000080 <.Loc.11>:
  80:   90 93 61 00    sts  0x0061, r25    ; 0x800061 <pwm.0+0x1>
  84:   80 93 60 00    sts  0x0060, r24    ; 0x800060 <pwm.0>
  88:   00 97          sbiw r24, 0x00 ; 0
  8a:   79 f7          brne .-34      ; 0x6a <.L4>

0000008c <.Loc.12>:
                direction = UP;
  8c:   10 92 62 00    sts  0x0062, r1     ; 0x800062 <direction.1>
  90:   ec cf          rjmp .-40      ; 0x6a <.L4>

00000092 <.L2>:
            if (++pwm == TIMER1_TOP)
  92:   01 96          adiw r24, 0x01 ; 1

00000094 <.Loc.16>:
  94:   90 93 61 00    sts  0x0061, r25    ; 0x800061 <pwm.0+0x1>
  98:   80 93 60 00    sts  0x0060, r24    ; 0x800060 <pwm.0>
  9c:   8f 3f          cpi  r24, 0xFF ; 255
  9e:   23 e0          ldi  r18, 0x03 ; 3
  a0:   92 07          cpc  r25, r18
  a2:   19 f7          brne .-58      ; 0x6a <.L4>

000000a4 <.Loc.17>:
                direction = DOWN;
  a4:   21 e0          ldi  r18, 0x01 ; 1
  a6:   20 93 62 00    sts  0x0062, r18    ; 0x800062 <direction.1>
  aa:   df cf          rjmp .-66      ; 0x6a <.L4>

000000ac <ioinit>:

void
ioinit (void)            /* Note [6] */
{
    /* Timer 1 is 10-bit PWM (8-bit PWM on some ATtinys). */
    TCCR1A = TIMER1_PWM_INIT;
  ac:   83 e8          ldi  r24, 0x83 ; 131
  ae:   8f bd          out  0x2f, r24 ; 47

000000b0 <.Loc.22>:
     * Start timer 1.
     *
     * NB: TCCR1A and TCCR1B could actually be the same register, so
     * take care to not clobber it.
     */
    TCCR1B |= TIMER1_CLOCKSOURCE;
  b0:   8e b5          in   r24, 0x2e ; 46

000000b2 <.Loc.23>:
  b2:   81 60          ori  r24, 0x01 ; 1
  b4:   8e bd          out  0x2e, r24 ; 46

000000b6 <.Loc.24>:
#if defined(TIMER1_SETUP_HOOK)
    TIMER1_SETUP_HOOK();
#endif

    /* Set PWM value to 0. */
    OCR = 0;
  b6:   1b bc          out  0x2b, r1  ; 43
  b8:   1a bc          out  0x2a, r1  ; 42

000000ba <.Loc.26>:

    /* Enable OC1 as output. */
    DDROC = _BV (OC1);
  ba:   82 e0          ldi  r24, 0x02 ; 2
  bc:   87 bb          out  0x17, r24 ; 23

000000be <.Loc.28>:

    /* Enable timer 1 overflow interrupt. */
    TIMSK = _BV (TOIE1);
  be:   84 e0          ldi  r24, 0x04 ; 4
  c0:   89 bf          out  0x39, r24 ; 57

000000c2 <.Loc.30>:
    sei ();
  c2:   78 94          sei

000000c4 <.Loc.31>:
}
  c4:   08 95          ret

000000c6 <__bad_interrupt>:
  c6:   9c cf          rjmp .-200     ; 0x0 <__vectors>

000000c8 <main>:

int
main (void)
{

    ioinit ();
  c8:   f1 df          rcall     .-30      ; 0xac <ioinit>

000000ca <.L10>:

    /* loop forever, the interrupts are doing the rest */

    for (;;)            /* Note [7] */
        sleep_mode();
  ca:   85 b7          in   r24, 0x35 ; 53
  cc:   80 68          ori  r24, 0x80 ; 128
  ce:   85 bf          out  0x35, r24 ; 53

000000d0 <.Loc.38>:
  d0:   88 95          sleep

000000d2 <.Loc.41>:
  d2:   85 b7          in   r24, 0x35 ; 53
  d4:   8f 77          andi r24, 0x7F ; 127
  d6:   85 bf          out  0x35, r24 ; 53

000000d8 <.Loc.44>:
    for (;;)            /* Note [7] */
  d8:   f8 cf          rjmp .-16      ; 0xca <.L10>

000000da <exit>:
  da:   f8 94          cli
  dc:  00 c0          rjmp .+0       ; 0xde <_exit>

000000de <_exit>:
  de:   f8 94          cli

000000e0 <__stop_program>:
  e0:   ff cf          rjmp .-2       ; 0xe0 <__stop_program>

πŸ—ΊοΈ Linker Map Files πŸ’–

avr-objdump is sooo useful bestie, but sometimes you gotta see stuff about the link that only the linker itself can spit out. That's literally what a map file is for. It's the moment for monitoring the sizes of your code and your data, it shows you where your modules get loaded and which modules got loaded from libraries, and honestly? It's yet another whole view of your application. iconic.

So to get a map file, I usually just slap -Wl,-Map,demo.map onto my link command. Relink the application with this and you'll generate demo.map β€” a portion of it is shown below:

$ avr-gcc -g -mmcu=atmega8 -Wl,-Map,demo.map -o demo.elf demo.o

K so some points of interest in the demo.map file, let's go 🫢:

.rela.plt
 *(.rela.plt)

.text           0x00000000       0xe2
 *(.vectors)
 .vectors       0x00000000       0x26 /build/avr-libc-YrCtbu/avr-libc-2.2.1/avr/devices/atmega8/crtatmega8.o
                0x00000000                __vectors
                0x00000000                __vector_default
 *(.vectors)
 *(.progmem.gcc*)
                0x00000026                        . = ALIGN (0x2)
                0x00000026                        __trampolines_start = .
 *(.trampolines)
 .trampolines   0x00000026        0x0 linker stubs
 *(.trampolines*)
                0x00000026                        __trampolines_end = .
 *libprintf_flt.a:*(.progmem.data)
 *libc.a:*(.progmem.data)
 *(.progmem.*)
                0x00000026                        . = ALIGN (0x2)
 *(.lowtext)
 *(.lowtext*)
                0x00000026                        __ctors_start = .

The .text segment β€” that's where your program instructions live, bestie β€” starts at location 0x0.

 *(.fini2)
 *(.fini2)
 *(.fini1)
 *(.fini1)
 *(.fini0)
 .fini0         0x000000de        0x4 /usr/lib/gcc/avr/14.2.0/avr4/libgcc.a(_exit.o)
 *(.fini0)
 *(.hightext)
 *(.hightext*)
 *(.progmemx.*)
                0x000000e2                        . = ALIGN (0x2)
 *(.jumptables)
 *(.jumptables*)
                0x000000e2                        _etext = .

.note.package   0x000000e4       0x74
 .note.package  0x000000e4       0x74 linker stubs

.data           0x00800060        0x0 load address 0x00000158
                [!provide]                        PROVIDE (__data_start = .)
 *(.data)
 .data          0x00800060        0x0 demo.o
 .data          0x00800060        0x0 /build/avr-libc-YrCtbu/avr-libc-2.2.1/avr/lib/avr4/exit.o
 .data          0x00800060        0x0 /build/avr-libc-YrCtbu/avr-libc-2.2.1/avr/devices/atmega8/crtatmega8.o
 .data          0x00800060        0x0 /usr/lib/gcc/avr/14.2.0/avr4/libgcc.a(_exit.o)
 .data          0x00800060        0x0 /usr/lib/gcc/avr/14.2.0/avr4/libgcc.a(_clear_bss.o)
 *(.data*)
 *(.gnu.linkonce.d*)
 *(.rodata)
 *(.rodata*)
 *(.gnu.linkonce.r*)
                0x00800060                        . = ALIGN (0x2)
                0x00800060                        _edata = .
                [!provide]                        PROVIDE (__data_end = .)

.bss            0x00800060        0x3
                0x00800060                        PROVIDE (__bss_start = .)
 *(.bss)
 .bss           0x00800060        0x3 demo.o
 .bss           0x00800063        0x0 /build/avr-libc-YrCtbu/avr-libc-2.2.1/avr/lib/avr4/exit.o
 .bss           0x00800063        0x0 /build/avr-libc-YrCtbu/avr-libc-2.2.1/avr/devices/atmega8/crtatmega8.o
 .bss           0x00800063        0x0 /usr/lib/gcc/avr/14.2.0/avr4/libgcc.a(_exit.o)
 .bss           0x00800063        0x0 /usr/lib/gcc/avr/14.2.0/avr4/libgcc.a(_clear_bss.o)
 *(.bss*)
 *(COMMON)
                0x00800063                        PROVIDE (__bss_end = .)
                0x00000158                        __data_load_start = LOADADDR (.data)
                0x00000158                        __data_load_end = (__data_load_start + SIZEOF (.data))

.noinit         0x00800063        0x0
                [!provide]                        PROVIDE (__noinit_start = .)
 *(.noinit .noinit.* .gnu.linkonce.n.*)
                [!provide]                        PROVIDE (__noinit_end = .)
                0x00800063                        _end = .
                [!provide]                        PROVIDE (__heap_start = .)
                0x00000000                        __flmap_init_label = DEFINED (__flmap_noinit_start)?__flmap_noinit_start:0x0
                0x00000000                        __flmap = DEFINED (__flmap)?__flmap:0x0

.eeprom         0x00810000        0x0
 *(.eeprom*)
                0x00810000                        __eeprom_end = .

okay now let's decode this lil masterpiece ✨

The last address in the .text segment is location 0x114 (denoted by _etext), so your instructions use up 276 bytes of FLASH.

The .data segment β€” that's where initialized static variables live β€” starts at location 0x60, which is the first address after the register bank on an ATmega8 processor.

The next available address in the .data segment is also location 0x60, so congrats bestie, the application has no initialized data. periodt.

The .bss segment β€” where uninitialized data is stored β€” starts at location 0x60.

The next available address in the .bss segment is location 0x63, so the application uses 3 bytes of uninitialized data. slay.

The .eeprom segment β€” where EEPROM variables are stored β€” starts at location 0x0.

And the next available address in the .eeprom segment is also location 0x0, so there aren't any EEPROM variables at all. πŸ’€


πŸ”₯ Generating Intel Hex Files πŸ”₯

So we have a binary of the application... but like, how do we actually get it into the processor?? Most (if not all) programmers will not accept a GNU executable as an input file, so we gotta do a lil more processing. The next step is extracting portions of the binary and saving that info into .hex files. The GNU utility that does this is called avr-objcopy. πŸ’…

The ROM contents can be pulled from our project's binary and dropped into the file demo.hex with this:

$ avr-objcopy -j .text -j .data -O ihex demo.elf demo.hex

And the resulting demo.hex file contains:

:1000000012C061C060C05FC05EC05DC05CC05BC04C
:100010001AC059C058C057C056C055C054C053C06C
:1000200052C051C050C011241FBECFE5D4E0DEBF86
:10003000CDBF20E0A0E6B0E001C01D92A336B2071C
:10004000E1F742D04AC01F920F920FB60F921124CF
:100050002F938F939F9320916200809160009091E5
:1000600061002115B1F0213051F09BBD8ABD9F91F7
:100070008F912F910F900FBE0F901F9018950197A1
:100080009093610080936000009779F7109262006E
:10009000ECCF019690936100809360008F3F23E046
:1000A000920719F721E020936200DFCF83E88FBD2C
:1000B0008EB581608EBD1BBC1ABC82E087BB84E01C
:1000C00089BF789408959CCFF1DF85B7806885BF9C
:1000D000889585B78F7785BFF8CFF89400C0F894DE
:0200E000FFCF50
:00000001FF

The -j option is saying "yeah I want the info from the .text and .data segment extracted". And if we specify the EEPROM segment, we can generate a .hex file that can be used to program the EEPROM:

$ avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex demo.elf demo_eeprom.hex

There is no demo_eeprom.hex file written, as that file would be empty. πŸ™ˆ

Okay so heads up β€” starting with version 2.17 of the GNU binutils, the avr-objcopy command that used to generate the empty EEPROM files now aborts, because of the empty input section .eeprom. So those empty files just don't get generated anymore. It also signals an error to the Makefile, which will be caught there, and makes it print a message about the empty file not being generated.

πŸŽ€ Letting Make Build the Project πŸŽ€

Instead of typing these commands over and over (no thanks, not the vibe), they can all be placed in a make file. To build the demo project using make, save the following in a file called Makefile.

Note πŸ“Œ This Makefile can only be used as input for the GNU version of make.

πŸ“– Reference to the source code πŸ“–

πŸ‘‘ Author πŸ‘‘

Generated automatically by Doxygen for AVR-LibC from the source code.


And that's the whole page bestie, we ate and left absolutely no crumbs πŸ“βœ¨ This was AVR-LibC, Version 2.2.1, demo_project(3avr) β€” go forth and link something iconic πŸ’–πŸ¦‹

πŸ“¦ where this page comes from