AVR-LibC  2.3.0
Standard C library for AVR-GCC
 

AVR-LibC Manual

AVR-LibC Sources

Main Page

User Manual

Lib­rary Refe­rence

FAQ

Exam­ple Pro­jects

Index

Loading...
Searching...
No Matches
A simple project

At this point, you should have the GNU tools configured, built, and installed on your system. In this chapter, we present a simple example of using the GNU tools in an AVR project. After reading this chapter, you should have a better feel as to how the tools are used and how a Makefile can be configured.

The Project

This project will use pulse-width modulation (PWM) to ramp an LED on and off every two seconds. This project once used to start out with a simple homemade AVR circuit. Meanwhile, in particular the Arduino project has provided a large boost to the use of AVR microcontrollers, and Arduino compatible hardware is ubiquitous. We thus concentrate on something compatible to an Arduino Nano with an ATmega328P (or PB) MCU.

Alas, the only user LED on these Arduino devices is not attached to a GPIO pin that can be directly controlled by the timer hardware to generate the PWM signal. We thus toggle the LED in software.

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]:
It is always a good idea to avoid "magic numbers", and define macros for certain constants at the top of the project (or, in a separate header file). Note the use of the _BV macro: it turns a bit number into the respective bit mask.
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 reaches its top value. Setting up interrupts is explained in greater detail in <avr/interrupt.h>: Interrupts.
As the 16-bit timer 1 is configured to run with a reduced 10 bit resolution, the top value is configured through the "input capture" register ICP, thus the input capture interrupt triggers when the timer reaches its top value.
Note [3]:
Immediately at the start of the interrupt service, handle toggling the LED. As the Arduino Nano LED is attached from PB5 to GND, turn it on here. While the construct of reading the port register, ORing a bit mask into it, and writing it back might look cumbersome – the compiler will actually turn this into an SBI instruction by recognizing that the arguments to the operation are suitable for this optimization.
Note [4]:
The PWM is being used in 10-bit mode, so we need a 16-bit variable to remember the current value.
Note [5]:
This section determines the new value of the PWM.
Note [6]:
Here's where the newly computed value is loaded into the compare 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 [7]:
This interrupt service routine gets called every time the counter reaches its compare/match value. Turn off the LED here.
Note [8]:
This routine configures all the hardware after a reset.
Note [9]:
The main loop of the program does nothing – all the work is done by the interrupt routines! The sleep_mode() puts the processor on sleep until the next interrupt, to conserve power. Of course, that probably won't be noticeable as we are still driving a LED, it is merely mentioned here to demonstrate the basic principle.

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 a pin 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.
*
* Configured to run on an Arduino Nano compatible device with an
* ATmega328P. The board has a LED, which is connected to PB5. Since
* PB5 is not a hardware waveform output from a timer on the
* ATmega328P, the LED is toggled within the timer interrupts.
*/
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
/* Note [1] */
#define TIMER1_TOP 1023
#define LED_PORT PORTB
#define LED_DDR DDRB
#define LED_PIN _BV(5)
enum { UP, DOWN };
ISR(TIMER1_CAPT_vect) // Note [2]
{
// turn on LED
LED_PORT |= LED_PIN; // Note [3]
static uint16_t pwm; // Note [4]
static uint8_t direction;
switch (direction) // Note [5]
{
case UP:
if (++pwm == TIMER1_TOP)
direction = DOWN;
break;
case DOWN:
if (--pwm == 0)
direction = UP;
break;
}
OCR1A = pwm; // Note [6]
}
ISR(TIMER1_COMPA_vect)
{
// turn off LED
LED_PORT &= ~LED_PIN; // Note [7]
}
static void
ioinit(void) // Note [8]
{
/* Timer 1 in CTC mode with 10 bits, CPU/8 speed */
ICR1 = TIMER1_TOP;
TCCR1A = 0;
TCCR1B = _BV(WGM13) | _BV(WGM12) | 2;
/* Set PWM value to 0. */
OCR1A = 1;
/* Enable OC1 as output. */
LED_DDR |= LED_PIN;
/* Enable timer 1 overflow and compare A interrupt. */
TIMSK1 = _BV(ICIE1) | _BV(OCIE1A);
sei ();
}
int
main(void)
{
ioinit ();
/* Loop forever, the interrupts are doing the rest. */
for (;;) // Note [9]
return 0;
}
#define sei()
Definition: interrupt.h:74
#define ISR(vector, attributes)
Definition: interrupt.h:118
#define _BV(bit)
Definition: sfr_defs.h:206
void sleep_mode(void)
unsigned int uint16_t
Definition: stdint.h:96
unsigned char uint8_t
Definition: stdint.h:88

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=atmega328p -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=atmega328p -mrelax -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 AT90S8515 processor environment, which is most certainly what you didn't want.

The -mrelax option instructs the linker to replace CALL instruction with faster and smaller RCALL instructions if possible.

Examining the Object File

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. The output of this option shows how much space is used in each of the output sections. The .comment, .note and .debug sections hold additional (debug) information and won't make it into the ROM file.

An overview of the program space and consumed by the program, and of the data in static storage, can be obtained with

$ avr-objdump -P mem-usage demo.elf

Since non-zero data in static storage consumes RAM, and also needs initialization values stored in non-volatile memory, the .data ouput section contributes both to the text segment (called Program below) and also to the data segment (called Data). For our program, the output reads:

demo.elf:     file format elf32-avr
AVR Memory Usage
----------------
Device: atmega328p

Program:     320 bytes (1.0% Full)
(.text + .data + .rodata + .bootloader)

Data:          3 bytes (0.1% Full)
(.data + .bss + .noinit)

An even more useful option is -S. This option disassembles the binary file and intersperses the source code in the output! Such a listing includes routines pulled in 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 -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 .data         00000000  00800100  00000140  000001d4  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  1 .text         00000140  00000000  00000000  00000094  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .bss          00000003  00800100  00800100  000001d4  2**0
                  ALLOC
  3 .comment      0000001a  00000000  00000000  000001d4  2**0
                  CONTENTS, READONLY
  4 .note.gnu.avr.deviceinfo 00000040  00000000  00000000  000001f0  2**2
                  CONTENTS, READONLY, OCTETS
  5 .debug_aranges 00000068  00000000  00000000  00000230  2**3
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  6 .debug_info   00000760  00000000  00000000  00000298  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  7 .debug_abbrev 000006c3  00000000  00000000  000009f8  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  8 .debug_line   00000388  00000000  00000000  000010bb  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
  9 .debug_frame  00000050  00000000  00000000  00001444  2**2
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 10 .debug_str    0000038f  00000000  00000000  00001494  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 11 .debug_ranges 00000018  00000000  00000000  00001823  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS

Disassembly of section .text:

00000000 <__vectors>:
   0:	33 c0       	rjmp	.+102    	; 0x68 <__ctors_end>
   2:	00 00       	nop
   4:	7a c0       	rjmp	.+244    	; 0xfa <__bad_interrupt>
   6:	00 00       	nop
   8:	78 c0       	rjmp	.+240    	; 0xfa <__bad_interrupt>
   a:	00 00       	nop
   c:	76 c0       	rjmp	.+236    	; 0xfa <__bad_interrupt>
   e:	00 00       	nop
  10:	74 c0       	rjmp	.+232    	; 0xfa <__bad_interrupt>
  12:	00 00       	nop
  14:	72 c0       	rjmp	.+228    	; 0xfa <__bad_interrupt>
  16:	00 00       	nop
  18:	70 c0       	rjmp	.+224    	; 0xfa <__bad_interrupt>
  1a:	00 00       	nop
  1c:	6e c0       	rjmp	.+220    	; 0xfa <__bad_interrupt>
  1e:	00 00       	nop
  20:	6c c0       	rjmp	.+216    	; 0xfa <__bad_interrupt>
  22:	00 00       	nop
  24:	6a c0       	rjmp	.+212    	; 0xfa <__bad_interrupt>
  26:	00 00       	nop
  28:	2f c0       	rjmp	.+94     	; 0x88 <__vector_10>
  2a:	00 00       	nop
  2c:	64 c0       	rjmp	.+200    	; 0xf6 <__vector_11>
  2e:	00 00       	nop
  30:	64 c0       	rjmp	.+200    	; 0xfa <__bad_interrupt>
  32:	00 00       	nop
  34:	62 c0       	rjmp	.+196    	; 0xfa <__bad_interrupt>
  36:	00 00       	nop
  38:	60 c0       	rjmp	.+192    	; 0xfa <__bad_interrupt>
  3a:	00 00       	nop
  3c:	5e c0       	rjmp	.+188    	; 0xfa <__bad_interrupt>
  3e:	00 00       	nop
  40:	5c c0       	rjmp	.+184    	; 0xfa <__bad_interrupt>
  42:	00 00       	nop
  44:	5a c0       	rjmp	.+180    	; 0xfa <__bad_interrupt>
  46:	00 00       	nop
  48:	58 c0       	rjmp	.+176    	; 0xfa <__bad_interrupt>
  4a:	00 00       	nop
  4c:	56 c0       	rjmp	.+172    	; 0xfa <__bad_interrupt>
  4e:	00 00       	nop
  50:	54 c0       	rjmp	.+168    	; 0xfa <__bad_interrupt>
  52:	00 00       	nop
  54:	52 c0       	rjmp	.+164    	; 0xfa <__bad_interrupt>
  56:	00 00       	nop
  58:	50 c0       	rjmp	.+160    	; 0xfa <__bad_interrupt>
  5a:	00 00       	nop
  5c:	4e c0       	rjmp	.+156    	; 0xfa <__bad_interrupt>
  5e:	00 00       	nop
  60:	4c c0       	rjmp	.+152    	; 0xfa <__bad_interrupt>
  62:	00 00       	nop
  64:	4a c0       	rjmp	.+148    	; 0xfa <__bad_interrupt>
	...

00000068 <__ctors_end>:
  68:	11 24       	eor	r1, r1
  6a:	1f be       	out	0x3f, r1	; 63

0000006c <__init_sp>:
  6c:	cf ef       	ldi	r28, 0xFF	; 255
  6e:	d8 e0       	ldi	r29, 0x08	; 8
  70:	de bf       	out	0x3e, r29	; 62
  72:	cd bf       	out	0x3d, r28	; 61

00000074 <__do_clear_bss>:
/* __do_clear_bss is only necessary if there is anything in .bss section.  */

#ifdef L_clear_bss
	.section .init4,"ax",@progbits
DEFUN __do_clear_bss
	ldi	r18, hi8(__bss_end)
  74:	21 e0       	ldi	r18, 0x01	; 1

00000076 <.Loc.1>:
	ldi	r26, lo8(__bss_start)
  76:	a0 e0       	ldi	r26, 0x00	; 0

00000078 <.Loc.2>:
	ldi	r27, hi8(__bss_start)
  78:	b1 e0       	ldi	r27, 0x01	; 1

0000007a <.Loc.3>:
	rjmp	.do_clear_bss_start
  7a:	01 c0       	rjmp	.+2      	; 0x7e <.Loc.5>

0000007c <.Loc.4>:
.do_clear_bss_loop:
	st	X+, __zero_reg__
  7c:	1d 92       	st	X+, r1

0000007e <.Loc.5>:
.do_clear_bss_start:
	cpi	r26, lo8(__bss_end)
  7e:	a3 30       	cpi	r26, 0x03	; 3

00000080 <.Loc.6>:
	cpc	r27, r18
  80:	b2 07       	cpc	r27, r18

00000082 <.Loc.7>:
	brne	.do_clear_bss_loop
  82:	e1 f7       	brne	.-8      	; 0x7c <.Loc.4>

00000084 <__call_main>:
  84:	3b d0       	rcall	.+118    	; 0xfc <main>
  86:	58 c0       	rjmp	.+176    	; 0x138 <exit>

00000088 <__vector_10>:
#define LED_PIN  _BV(5)

enum { UP, DOWN };

ISR(TIMER1_CAPT_vect)           // Note [2]
{
  88:	1f 92       	push	r1
  8a:	1f b6       	in	r1, 0x3f	; 63
  8c:	1f 92       	push	r1
  8e:	11 24       	eor	r1, r1
  90:	2f 93       	push	r18
  92:	8f 93       	push	r24
  94:	9f 93       	push	r25

00000096 <.Loc.1>:
    // turn on LED
    LED_PORT |= LED_PIN;        // Note [3]
  96:	2d 9a       	sbi	0x05, 5	; 5

00000098 <.Loc.3>:

    static uint16_t pwm;        // Note [4]
    static uint8_t direction;

    switch (direction)          // Note [5]
  98:	20 91 02 01 	lds	r18, 0x0102	; 0x800102 <direction.1561>
  9c:	80 91 00 01 	lds	r24, 0x0100	; 0x800100 <pwm.1560>
  a0:	90 91 01 01 	lds	r25, 0x0101	; 0x800101 <pwm.1560+0x1>
  a4:	22 23       	and	r18, r18
  a6:	89 f0       	breq	.+34     	; 0xca <.L2>
  a8:	21 30       	cpi	r18, 0x01	; 1
  aa:	d9 f0       	breq	.+54     	; 0xe2 <.L3>

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

    OCR1A = pwm;                // Note [6]
  ac:	80 91 00 01 	lds	r24, 0x0100	; 0x800100 <pwm.1560>
  b0:	90 91 01 01 	lds	r25, 0x0101	; 0x800101 <pwm.1560+0x1>
  b4:	90 93 89 00 	sts	0x0089, r25	; 0x800089 <__TEXT_REGION_LENGTH__+0x7f8089>
  b8:	80 93 88 00 	sts	0x0088, r24	; 0x800088 <__TEXT_REGION_LENGTH__+0x7f8088>

000000bc <.Loc.8>:
}
  bc:	9f 91       	pop	r25
  be:	8f 91       	pop	r24
  c0:	2f 91       	pop	r18
  c2:	1f 90       	pop	r1
  c4:	1f be       	out	0x3f, r1	; 63
  c6:	1f 90       	pop	r1
  c8:	18 95       	reti

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

000000cc <.Loc.11>:
  cc:	90 93 01 01 	sts	0x0101, r25	; 0x800101 <pwm.1560+0x1>
  d0:	80 93 00 01 	sts	0x0100, r24	; 0x800100 <pwm.1560>
  d4:	8f 3f       	cpi	r24, 0xFF	; 255
  d6:	93 40       	sbci	r25, 0x03	; 3
  d8:	49 f7       	brne	.-46     	; 0xac <.L4>

000000da <.Loc.12>:
                direction = DOWN;
  da:	81 e0       	ldi	r24, 0x01	; 1
  dc:	80 93 02 01 	sts	0x0102, r24	; 0x800102 <direction.1561>
  e0:	e5 cf       	rjmp	.-54     	; 0xac <.L4>

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

000000e4 <.Loc.16>:
  e4:	90 93 01 01 	sts	0x0101, r25	; 0x800101 <pwm.1560+0x1>
  e8:	80 93 00 01 	sts	0x0100, r24	; 0x800100 <pwm.1560>
  ec:	89 2b       	or	r24, r25
  ee:	f1 f6       	brne	.-68     	; 0xac <.L4>

000000f0 <.Loc.17>:
                direction = UP;
  f0:	10 92 02 01 	sts	0x0102, r1	; 0x800102 <direction.1561>
  f4:	db cf       	rjmp	.-74     	; 0xac <.L4>

000000f6 <__vector_11>:

ISR(TIMER1_COMPA_vect)
{
    // turn off LED
    LED_PORT &= ~LED_PIN;       // Note [7]
  f6:	2d 98       	cbi	0x05, 5	; 5

000000f8 <.Loc.22>:
}
  f8:	18 95       	reti

000000fa <__bad_interrupt>:
  fa:	82 cf       	rjmp	.-252    	; 0x0 <__vectors>

000000fc <main>:

static void
ioinit(void)                    // Note [8]
{
    /* Timer 1 in CTC mode with 10 bits, CPU/8 speed */
    ICR1 = TIMER1_TOP;
  fc:	8f ef       	ldi	r24, 0xFF	; 255
  fe:	93 e0       	ldi	r25, 0x03	; 3
 100:	90 93 87 00 	sts	0x0087, r25	; 0x800087 <__TEXT_REGION_LENGTH__+0x7f8087>
 104:	80 93 86 00 	sts	0x0086, r24	; 0x800086 <__TEXT_REGION_LENGTH__+0x7f8086>

00000108 <.Loc.28>:
    TCCR1A = 0;
 108:	10 92 80 00 	sts	0x0080, r1	; 0x800080 <__TEXT_REGION_LENGTH__+0x7f8080>

0000010c <.Loc.30>:
    TCCR1B = _BV(WGM13) | _BV(WGM12) | 2;
 10c:	8a e1       	ldi	r24, 0x1A	; 26
 10e:	80 93 81 00 	sts	0x0081, r24	; 0x800081 <__TEXT_REGION_LENGTH__+0x7f8081>

00000112 <.Loc.32>:

    /* Set PWM value to 0. */
    OCR1A = 1;
 112:	81 e0       	ldi	r24, 0x01	; 1
 114:	90 e0       	ldi	r25, 0x00	; 0
 116:	90 93 89 00 	sts	0x0089, r25	; 0x800089 <__TEXT_REGION_LENGTH__+0x7f8089>
 11a:	80 93 88 00 	sts	0x0088, r24	; 0x800088 <__TEXT_REGION_LENGTH__+0x7f8088>

0000011e <.Loc.34>:

    /* Enable OC1 as output. */
    LED_DDR |= LED_PIN;
 11e:	25 9a       	sbi	0x04, 5	; 4

00000120 <.Loc.36>:

    /* Enable timer 1 overflow and compare A interrupt. */
    TIMSK1 = _BV(ICIE1) | _BV(OCIE1A);
 120:	82 e2       	ldi	r24, 0x22	; 34
 122:	80 93 6f 00 	sts	0x006F, r24	; 0x80006f <__TEXT_REGION_LENGTH__+0x7f806f>

00000126 <.Loc.38>:
    sei ();
 126:	78 94       	sei

00000128 <.L9>:
    ioinit ();

    /* Loop forever, the interrupts are doing the rest.  */

    for (;;)                    // Note [9]
        sleep_mode();
 128:	83 b7       	in	r24, 0x33	; 51
 12a:	81 60       	ori	r24, 0x01	; 1
 12c:	83 bf       	out	0x33, r24	; 51

0000012e <.Loc.43>:
 12e:	88 95       	sleep

00000130 <.Loc.46>:
 130:	83 b7       	in	r24, 0x33	; 51
 132:	8e 7f       	andi	r24, 0xFE	; 254
 134:	83 bf       	out	0x33, r24	; 51

00000136 <.Loc.49>:
    for (;;)                    // Note [9]
 136:	f8 cf       	rjmp	.-16     	; 0x128 <.L9>

00000138 <exit>:
 138:	f8 94       	cli
 13a:	00 c0       	rjmp	.+0      	; 0x13c <_exit>

0000013c <_exit>:
	cli
 13c:	f8 94       	cli

0000013e <__stop_program>:
	rjmp	__stop_program
 13e:	ff cf       	rjmp	.-2      	; 0x13e <__stop_program>

Linker Map Files

avr-objdump is very useful, but sometimes it's necessary to see information about the link that can only be generated by the linker. A map file contains this information. A map file is useful for monitoring the sizes of your code and data. It also shows where modules are loaded and which modules were loaded from libraries. It is yet another view of your application. To get a map file, I usually add -Wl,-Map,demo.map to my link command. Relink the application using the following command to generate demo.map (a portion of which is shown below). The linker will create a map file even in the case when some memory region overflows and the linker fails with an error. So you can collect valuable information about the cause of the overflow.

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

Some points of interest in the demo.map file are:

.rela.plt
*(.rela.plt)
.text 0x00000000 0x140
*(.vectors)
.vectors 0x00000000 0x68 /home/john/gnu/build/avr-libc-onlinedocs/avr/devices/atmega328p/crtatmega328p.o
0x00000000 __vectors
0x00000000 __vector_default
*(.vectors)
*(.progmem.gcc*)
0x00000068 . = ALIGN (0x2)
0x00000068 __trampolines_start = .
*(.trampolines)
.trampolines 0x00000068 0x0 linker stubs
*(.trampolines*)
0x00000068 __trampolines_end = .
*libprintf_flt.a:*(.progmem.data)
*libc.a:*(.progmem.data)
*(.progmem.*)
0x00000068 . = ALIGN (0x2)
*(.lowtext)
*(.lowtext*)
0x00000068 __ctors_start = .

The .text segment (where program instructions are stored) starts at location 0x0 and occupies 0x140 = 320 bytes. 0x68 = 104 bytes of that are allocated to interrupt vectors in .vectors, which is basically defined by the MCU hardware.

*(.fini2)
*(.fini2)
*(.fini1)
*(.fini1)
*(.fini0)
.fini0 0x0000013c 0x4 /home/DATA/gnu/install/gcc-8.5-avr/bin/../lib/gcc/avr/8.5.1/avr5/libgcc.a(_exit.o)
*(.fini0)
*(.hightext)
*(.hightext*)
*(.progmemx.*)
0x00000140 . = ALIGN (0x2)
*(.jumptables)
*(.jumptables*)
0x00000140 _etext = .
.data 0x00800100 0x0 load address 0x00000140
[!provide] PROVIDE (__data_start = .)
*(.data)
.data 0x00800100 0x0 demo.o
.data 0x00800100 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/devices/atmega328p/crtatmega328p.o
.data 0x00800100 0x0 /home/DATA/gnu/install/gcc-8.5-avr/bin/../lib/gcc/avr/8.5.1/avr5/libgcc.a(_clear_bss.o)
.data 0x00800100 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/devices/atmega328p/libatmega328p.a(init_sp.o)
.data 0x00800100 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/devices/atmega328p/libatmega328p.a(call_main.o)
.data 0x00800100 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/lib/avr5/libc.a(exit.o)
.data 0x00800100 0x0 /home/DATA/gnu/install/gcc-8.5-avr/bin/../lib/gcc/avr/8.5.1/avr5/libgcc.a(_exit.o)
*(.data*)
*(.gnu.linkonce.d*)
*(.rodata)
*(.rodata*)
*(.gnu.linkonce.r*)
0x00800100 . = ALIGN (0x2)
0x00800100 _edata = .
[!provide] PROVIDE (__data_end = .)
.bss 0x00800100 0x3
0x00800100 PROVIDE (__bss_start = .)
*(.bss)
.bss 0x00800100 0x3 demo.o
.bss 0x00800103 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/devices/atmega328p/crtatmega328p.o
.bss 0x00800103 0x0 /home/DATA/gnu/install/gcc-8.5-avr/bin/../lib/gcc/avr/8.5.1/avr5/libgcc.a(_clear_bss.o)
.bss 0x00800103 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/devices/atmega328p/libatmega328p.a(init_sp.o)
.bss 0x00800103 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/devices/atmega328p/libatmega328p.a(call_main.o)
.bss 0x00800103 0x0 /home/john/gnu/build/avr-libc-onlinedocs/avr/lib/avr5/libc.a(exit.o)
.bss 0x00800103 0x0 /home/DATA/gnu/install/gcc-8.5-avr/bin/../lib/gcc/avr/8.5.1/avr5/libgcc.a(_exit.o)
*(.bss*)
*(COMMON)
0x00800103 PROVIDE (__bss_end = .)
0x00000140 __data_load_start = LOADADDR (.data)
0x00000140 __data_load_end = (__data_load_start + SIZEOF (.data))
.noinit 0x00800103 0x0
[!provide] PROVIDE (__noinit_start = .)
*(.noinit .noinit.* .gnu.linkonce.n.*)
[!provide] PROVIDE (__noinit_end = .)
0x00800103 _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 = .

The address one byte past the last address in the .text segment is denoted by _etext.

The .data segment (where initialized static variables are stored) starts at location 0x100, which is the first address after the IO registers on an ATmega328P processor. The segment is mapped to 0x800000 to simulate a flat address space.

The next available address in the .data segment is also location 0x100, so the application has no non-zero initialized data.

The .bss segment (where zero-initialized data is stored) starts at location 0x100.

The next available address in the .bss segment is location 0x103, so the application uses a total of 3 bytes of zero-initialized data, all from the demo.o module. These are the pwm (2 bytes) and direction (1 byte) static variables of the TIMER1_CAPT_vect ISR.

The range from __data_load_start to __data_load_end holds the initialization data for the .data segment, which is read by the startup code to initialize .data before main() starts. As the two symbols have the same value, there is nothing to copy to .data (since there are no variables in the .data input sections).

The .eeprom segment (where EEPROM variables are stored) starts at location 0x0, mapped to 0x810000 to simulate a flat address space.

The next available address in the .eeprom segment is also location 0x0, so there aren't any EEPROM variables.

Generating Intel Hex Files

We have a binary of the application, but how do we get it into the processor?

Many programming applications like AVRDUDE now accept the final ELF file as input, so no further step is needed there.

Some programmers require a specific kind of load file, like "Intel Hex" or "Motorola SRecord" format. For them, portions of the binary need to be extracted. The GNU utility that does this is called avr-objcopy.

The ROM contents can be pulled from our project's binary and put into the file demo.hex using the following command:

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

The -j option indicates that we want the information from the .text and .data segment extracted. Note that the .data segment must be included as it contains the initialization values for the data segment, which the startup code expects to be placed immediately behind the instruction code (the .text segment) in flash. The startup code then copies them over to RAM before calling main().

On devices with a .rodata segment, -j .rodata must be added to the options. (The ATmega328P doesn't have a .rodata segment since its .rodata input sections are allocated to the .data segment. See the FAQ for an explanation.)

The resulting demo.hex file contains:

:1000000033C000007AC0000078C0000076C0000055
:1000100074C0000072C0000070C000006EC000001C
:100020006CC000006AC000002FC0000064C0000067
:1000300064C0000062C0000060C000005EC000003C
:100040005CC000005AC0000058C0000056C000004C
:1000500054C0000052C0000050C000004EC000005C
:100060004CC000004AC0000011241FBECFEFD8E0F2
:10007000DEBFCDBF21E0A0E0B1E001C01D92A33002
:10008000B207E1F73BD058C01F921FB61F92112450
:100090002F938F939F932D9A2091020180910001BD
:1000A00090910101222389F02130D9F08091000143
:1000B0009091010190938900809388009F918F9186
:1000C0002F911F901FBE1F901895019690930101CC
:1000D000809300018F3F934049F781E080930201B4
:1000E000E5CF01979093010180930001892BF1F6F0
:1000F00010920201DBCF2D98189582CF8FEF93E0FD
:100100009093870080938600109280008AE180930C
:10011000810081E090E09093890080938800259A87
:1001200082E280936F00789483B7816083BF889563
:1001300083B78E7F83BFF8CFF89400C0F894FFCFC9
:00000001FF

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.

Letting Make Build the Project

Rather than type these commands over and over, they can all be placed in a make file. To build the demo project using GNU make, save the following in a file called Makefile.

This also adds a "program" target, so in order to download the image to the device's flash, it can simply called as

$ make program

The actual port (serial device) to talk to when programming can be specified in the environment variable AVRDUDE_PORT. There is some guesswork about possible ports (OS dependant) inside the Makefile if this variable does not exist.

Note
This Makefile can only be used as input for the GNU version of make.
PRG = demo
OBJ = demo.o
MCU_TARGET = atmega328p
OPTIMIZE = -Os
WARN = -Wall -Wextra -Werror=missing-prototypes -Werror=strict-prototypes
DEFS =
LIBS =
# AVRDUDE configuration
# override that from the environment if needed
ifndef AVRDUDE_PORT
ifeq ($(OS),Windows_NT)
AVRDUDE_PORT = COM3
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
AVRDUDE_PORT = /dev/ttyUSB0
endif
ifeq ($(UNAME_S),Darwin)
AVRDUDE_PORT = /dev/cu.usbserial-10
endif
ifeq ($(UNAME_S),FreeBSD)
AVRDUDE_PORT = /dev/cuaU0
endif
endif
endif
ifndef AVRDUDE_PROGRAMMER
AVRDUDE_PROGRAMMER = arduino
endif
ifndef AVRDUDE_ADDITIONAL
AVRDUDE_ADDITIONAL = -b 57600 # for Arduino Nano compat device
endif
ifndef AVRDUDE
AVRDUDE = avrdude # search along $PATH
endif
# You should not have to change anything below here.
CC = avr-gcc
CFLAGS = -g $(WARN) $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS)
LDFLAGS = -Wl,-Map,$(PRG).map -mrelax
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
all: $(PRG).elf lst text eeprom
$(PRG).elf: $(OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
# dependency:
demo.o: demo.c
clean:
rm -f -- *.o $(PRG).elf
rm -f -- *.lst *.map $(EXTRA_CLEAN_FILES)
lst: $(PRG).lst
%.lst: %.elf
$(OBJDUMP) -h -S $< > $@
# device programming
program: $(PRG).elf
$(AVRDUDE) -c $(AVRDUDE_PROGRAMMER) \
-p $(MCU_TARGET) -P $(AVRDUDE_PORT) \
$(AVRDUDE_ADDITIONAL) \
-U $(PRG).elf
.PHONY: all clean lst program
# Rules for building the .text rom images
text: hex bin srec
hex: $(PRG).hex
bin: $(PRG).bin
srec: $(PRG).srec
%.hex: %.elf
$(OBJCOPY) -j .text -j .data -O ihex $< $@
%.srec: %.elf
$(OBJCOPY) -j .text -j .data -O srec $< $@
%.bin: %.elf
$(OBJCOPY) -j .text -j .data -O binary $< $@
.PHONY: text hex bin srec
# Rules for building the .eeprom rom images
eeprom: ehex ebin esrec
ehex: $(PRG)_eeprom.hex
ebin: $(PRG)_eeprom.bin
esrec: $(PRG)_eeprom.srec
%_eeprom.hex: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O ihex $< $@ \
|| { echo empty $@ not generated; exit 0; }
%_eeprom.srec: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O srec $< $@ \
|| { echo empty $@ not generated; exit 0; }
%_eeprom.bin: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O binary $< $@ \
|| { echo empty $@ not generated; exit 0; }
EXTRA_CLEAN_FILES += *.hex *.bin *.srec
.PHONY: eeprom ehex ebin esrec
void exit(int __status)

Reference to the source code