|
AVR-LibC
2.3.0
Standard C library for AVR-GCC
|
AVR-LibC Manual |
![]() ![]() |
AVR-LibC Sources |
|||
Main Page |
User Manual |
Library Reference |
FAQ |
Example Projects |
Index |
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.
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:
_BV macro: it turns a bit number into the respective bit mask.ICP, thus the input capture interrupt triggers when the timer reaches its top value.SBI instruction by recognizing that the arguments to the operation are suitable for this optimization.TEMP), see the appropriate FAQ entry.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.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.
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>
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:
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.
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.
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.
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.
Makefile can only be used as input for the GNU version of make.
1.9.6