Makefile can be configured.PWM ) to ramp an LED on and off every two seconds. An AT90S2313 processor will be used as the controller. The circuit for this demonstration is shown in the schematic diagram. If you have a development kit, you should be able to use it, rather than build the circuit, for this project.
Schematic of circuit for demo project
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:
PWM is being used in 10-bit mode, so we need a 16-bit variable to remember the current value.PWM.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.PWM and enables interrupts.SLEEP instruction in this loop to conserve power./* * ---------------------------------------------------------------------------- * "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: demo.c,v 1.1 2002/09/30 18:16:07 troth Exp $ */ #include <inttypes.h> #include <avr/io.h> #include <avr/interrupt.h> #include <avr/signal.h> #if defined(__AVR_AT90S2313__) # define OC1 PB3 # define OCR OCR1 # define DDROC DDRB #elif defined(__AVR_AT90S2333__) || defined(__AVR_AT90S4433__) # define OC1 PB1 # define DDROC DDRB # define OCR OCR1 #elif defined(__AVR_AT90S4414__) || defined(__AVR_AT90S8515__) || \ defined(__AVR_AT90S4434__) || defined(__AVR_AT90S8535__) || \ defined(__AVR_ATmega163__) # define OC1 PD5 # define DDROC DDRD # define OCR OCR1A #else # error "Don't know what kind of MCU you are compiling for" #endif #if defined(COM11) # define XCOM11 COM11 #elif defined(COM1A1) # define XCOM11 COM1A1 #else # error "need either COM1A1 or COM11" #endif enum { UP, DOWN }; volatile uint16_t pwm; /* Note [1] */ volatile uint8_t direction; SIGNAL (SIG_OVERFLOW1) /* Note [2] */ { switch (direction) /* Note [3] */ { case UP: if (++pwm == 1023) direction = DOWN; break; case DOWN: if (--pwm == 0) direction = UP; break; } OCR = pwm; /* Note [4] */ } void ioinit (void) /* Note [5] */ { /* tmr1 is 10-bit PWM */ TCCR1A = _BV (PWM10) | _BV (PWM11) | _BV (XCOM11); /* tmr1 running on full MCU clock */ TCCR1B = _BV (CS10); /* set PWM value to 0 */ OCR = 0; /* enable OC1 and PB2 as output */ DDROC = _BV (OC1); timer_enable_int (_BV (TOIE1)); /* enable interrupts */ sei (); } int main (void) { ioinit (); /* loop forever, the interrupts are doing the rest */ for (;;) /* Note [6] */ ; return (0); }
-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=at90s2333 -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=at90s2333 -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.
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 (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 -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 000000ca 00000000 00000000 00000094 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .data 00000000 00800060 000000ca 0000015e 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000003 00800060 00800060 0000015e 2**0
ALLOC
3 .noinit 00000000 00800063 00800063 0000015e 2**0
CONTENTS
4 .eeprom 00000000 00810000 00810000 0000015e 2**0
CONTENTS
5 .stab 0000066c 00000000 00000000 00000160 2**2
CONTENTS, READONLY, DEBUGGING
6 .stabstr 00000612 00000000 00000000 000007cc 2**0
CONTENTS, READONLY, DEBUGGING
Disassembly of section .text:
00000000 <__vectors>:
0: 0a c0 rjmp .+20 ; 0x16
2: 62 c0 rjmp .+196 ; 0xc8
4: 61 c0 rjmp .+194 ; 0xc8
6: 60 c0 rjmp .+192 ; 0xc8
8: 5f c0 rjmp .+190 ; 0xc8
a: 0a c0 rjmp .+20 ; 0x20
c: 5d c0 rjmp .+186 ; 0xc8
e: 5c c0 rjmp .+184 ; 0xc8
10: 5b c0 rjmp .+182 ; 0xc8
12: 5a c0 rjmp .+180 ; 0xc8
14: 59 c0 rjmp .+178 ; 0xc8
00000016 <__ctors_end>:
16: 11 24 eor r1, r1
18: 1f be out 0x3f, r1 ; 63
1a: cf ed ldi r28, 0xDF ; 223
1c: cd bf out 0x3d, r28 ; 61
1e: 4e c0 rjmp .+156 ; 0xbc
00000020 <__vector_5>:
volatile uint16_t pwm; /* Note [1] */
volatile uint8_t direction;
SIGNAL (SIG_OVERFLOW1) /* Note [2] */
{
20: 1f 92 push r1
22: 0f 92 push r0
24: 0f b6 in r0, 0x3f ; 63
26: 0f 92 push r0
28: 11 24 eor r1, r1
2a: 2f 93 push r18
2c: 8f 93 push r24
2e: 9f 93 push r25
switch (direction) /* Note [3] */
30: 80 91 60 00 lds r24, 0x0060
34: 99 27 eor r25, r25
36: 00 97 sbiw r24, 0x00 ; 0
38: a1 f0 breq .+40 ; 0x62
3a: 01 97 sbiw r24, 0x01 ; 1
3c: 29 f5 brne .+74 ; 0x88
{
case UP:
if (++pwm == 1023)
direction = DOWN;
break;
case DOWN:
if (--pwm == 0)
3e: 80 91 61 00 lds r24, 0x0061
42: 90 91 62 00 lds r25, 0x0062
46: 01 97 sbiw r24, 0x01 ; 1
48: 90 93 62 00 sts 0x0062, r25
4c: 80 93 61 00 sts 0x0061, r24
50: 80 91 61 00 lds r24, 0x0061
54: 90 91 62 00 lds r25, 0x0062
58: 89 2b or r24, r25
5a: b1 f4 brne .+44 ; 0x88
direction = UP;
5c: 10 92 60 00 sts 0x0060, r1
60: 13 c0 rjmp .+38 ; 0x88
62: 80 91 61 00 lds r24, 0x0061
66: 90 91 62 00 lds r25, 0x0062
6a: 01 96 adiw r24, 0x01 ; 1
6c: 90 93 62 00 sts 0x0062, r25
70: 80 93 61 00 sts 0x0061, r24
74: 80 91 61 00 lds r24, 0x0061
78: 90 91 62 00 lds r25, 0x0062
7c: 8f 5f subi r24, 0xFF ; 255
7e: 93 40 sbci r25, 0x03 ; 3
80: 19 f4 brne .+6 ; 0x88
82: 81 e0 ldi r24, 0x01 ; 1
84: 80 93 60 00 sts 0x0060, r24
break;
}
OCR = pwm; /* Note [4] */
88: 80 91 61 00 lds r24, 0x0061
8c: 90 91 62 00 lds r25, 0x0062
90: 9b bd out 0x2b, r25 ; 43
92: 8a bd out 0x2a, r24 ; 42
}
94: 9f 91 pop r25
96: 8f 91 pop r24
98: 2f 91 pop r18
9a: 0f 90 pop r0
9c: 0f be out 0x3f, r0 ; 63
9e: 0f 90 pop r0
a0: 1f 90 pop r1
a2: 18 95 reti
000000a4 <ioinit>:
void
ioinit (void) /* Note [5] */
{
/* tmr1 is 10-bit PWM */
TCCR1A = _BV (PWM10) | _BV (PWM11) | _BV (XCOM11);
a4: 83 e8 ldi r24, 0x83 ; 131
a6: 8f bd out 0x2f, r24 ; 47
/* tmr1 running on full MCU clock */
TCCR1B = _BV (CS10);
a8: 81 e0 ldi r24, 0x01 ; 1
aa: 8e bd out 0x2e, r24 ; 46
/* set PWM value to 0 */
OCR = 0;
ac: 1b bc out 0x2b, r1 ; 43
ae: 1a bc out 0x2a, r1 ; 42
/* enable OC1 and PB2 as output */
DDROC = _BV (OC1);
b0: 88 e0 ldi r24, 0x08 ; 8
b2: 87 bb out 0x17, r24 ; 23
extern inline void timer_enable_int (unsigned char ints)
{
#ifdef TIMSK
TIMSK = ints;
b4: 80 e8 ldi r24, 0x80 ; 128
b6: 89 bf out 0x39, r24 ; 57
timer_enable_int (_BV (TOIE1));
/* enable interrupts */
sei ();
b8: 78 94 sei
}
ba: 08 95 ret
000000bc <main>:
int
main (void)
{
bc: cf ed ldi r28, 0xDF ; 223
be: d0 e0 ldi r29, 0x00 ; 0
c0: de bf out 0x3e, r29 ; 62
c2: cd bf out 0x3d, r28 ; 61
ioinit ();
c4: ef df rcall .-34 ; 0xa4
/* loop forever, the interrupts are doing the rest */
for (;;) /* Note [6] */
c6: ff cf rjmp .-2 ; 0xc6
000000c8 <__bad_interrupt>:
c8: 9b cf rjmp .-202 ; 0x0
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).
$ avr-gcc -g -mmcu=at90s2313 -Wl,-Map,demo.map -o demo.elf demo.o
Some points of interest in the demo.map file are:
.rela.plt
*(.rela.plt)
.text 0x00000000 0xca
*(.vectors)
.vectors 0x00000000 0x16 ../../../obj-i586-alt-linux/crt1/crts2313.o
0x00000000 __vectors
0x00000000 __vector_default
0x00000016 __ctors_start = .
The .text segment (where program instructions are stored) starts at location 0x0.
*(.fini2)
*(.fini1)
*(.fini0)
0x000000ca _etext = .
.data 0x00800060 0x0 load address 0x000000ca
0x00800060 PROVIDE (__data_start, .)
*(.data)
*(.gnu.linkonce.d*)
0x00800060 . = ALIGN (0x2)
0x00800060 _edata = .
0x00800060 PROVIDE (__data_end, .)
.bss 0x00800060 0x3
0x00800060 PROVIDE (__bss_start, .)
*(.bss)
*(COMMON)
COMMON 0x00800060 0x3 demo.o
0x0 (size before relaxing)
0x00800060 direction
0x00800061 pwm
0x00800063 PROVIDE (__bss_end, .)
0x000000ca __data_load_start = LOADADDR (.data)
0x000000ca __data_load_end = (__data_load_start + SIZEOF (.data))
.noinit 0x00800063 0x0
0x00800063 PROVIDE (__noinit_start, .)
*(.noinit*)
0x00800063 PROVIDE (__noinit_end, .)
0x00800063 _end = .
0x00800063 PROVIDE (__heap_start, .)
.eeprom 0x00810000 0x0 load address 0x000000ca
*(.eeprom*)
0x00810000 __eeprom_end = .
The last address in the .text segment is location 0xf2 ( denoted by _etext ), so the instructions use up 242 bytes of FLASH.
The .data segment (where initialized static variables are stored) starts at location 0x60, which is the first address after the register bank on a 2313 processor.
The next available address in the .data segment is also location 0x60, so the application has no initialized data.
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.
The .eeprom segment (where EEPROM variables are stored) starts at location 0x0.
The next available address in the .eeprom segment is also location 0x0, so there aren't any EEPROM variables.
.hex files. 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 resulting demo.hex file contains:
:100000000AC062C061C060C05FC00AC05DC05CC0A1 :100010005BC05AC059C011241FBECFEDCDBF4EC02A :100020001F920F920FB60F9211242F938F939F93CD :100030008091600099270097A1F0019729F58091A0 :10004000610090916200019790936200809361003B :100050008091610090916200892BB1F41092600050 :1000600013C08091610090916200019690936200AC :100070008093610080916100909162008F5F934056 :1000800019F481E08093600080916100909162009A :100090009BBD8ABD9F918F912F910F900FBE0F90A6 :1000A0001F90189583E88FBD81E08EBD1BBC1ABCE4 :1000B00088E087BB80E889BF78940895CFEDD0E0D1 :0A00C000DEBFCDBFEFDFFFCF9BCF07 :00000001FF
The -j option indicates that we want the information from the .text and .data segment extracted. 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
The resulting demo_eeprom.hex file contains:
:00000001FF
which is an empty .hex file (which is expected, since we didn't define any EEPROM variables).
make, save the following in a file called Makefile.
Makefile can only be used as input for the GNU version of make.PRG = demo OBJ = demo.o MCU_TARGET = at90s2313 OPTIMIZE = -O2 DEFS = LIBS = # You should not have to change anything below here. CC = avr-gcc # Override is only needed by avr-lib build system. override CFLAGS = -g -Wall $(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS) override LDFLAGS = -Wl,-Map,$(PRG).map OBJCOPY = avr-objcopy OBJDUMP = avr-objdump all: $(PRG).elf lst text eeprom $(PRG).elf: $(OBJ) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) clean: rm -rf *.o $(PRG).elf *.eps *.png *.pdf *.bak rm -rf *.lst *.map $(EXTRA_CLEAN_FILES) lst: $(PRG).lst %.lst: %.elf $(OBJDUMP) -h -S $< > $@ # 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 $< $@ # 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 $< $@ %_eeprom.srec: %.elf $(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O srec $< $@ %_eeprom.bin: %.elf $(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O binary $< $@ # Every thing below here is used by avr-libc's build system and can be ignored # by the casual user. FIG2DEV = fig2dev EXTRA_CLEAN_FILES = *.hex *.bin *.srec dox: eps png pdf eps: $(PRG).eps png: $(PRG).png pdf: $(PRG).pdf %.eps: %.fig $(FIG2DEV) -L eps $< $@ %.pdf: %.fig $(FIG2DEV) -L pdf $< $@ %.png: %.fig $(FIG2DEV) -L png $< $@