AVR Cross Compiler gcc: allgemeine Betrachtungen
Mit dem AVR-Controller zum Mond?
Der AVR-Cross-Compiler gcc - wer effizient Programme für die AVR-Familie erstellen will, wird nicht umhin kommen, sich ein wenig mit dem erzeugten Assembler-Code zu befassen. Hier betrachten wir den Startup-Code und eine Standard-Verzögerungsschleife.
Was erzeugt der gcc?
Grundsätzlich empfiehlt es sich, ein allgemeines Makefile zu schreiben, dass man für sämtliche Projekte einsetzen kann. Danach kompilieren wir ein einfaches Test-Programm, disassemblieren und betrachten es.
Es wird neben einer AVR-Cross-Compiler-Suite der AVR-Disassembler
vavrdisasm
benötigt.
Makefile
# ---
# device family, clock rate
DEVICE = attiny85
CLOCK = 16500000 # 16.5MHz
# source files
OBJ = main.o
# Optimization level, can be [0, 1, 2, 3, s].
# 0 = turn off optimization. s = optimize for size.
# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
#OPT = s
OPT = 1
CFLAGS = -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE)
CFLAGS += -O$(OPT)
AVRGCC = avr-gcc $(CFLAGS)
AVRGPP = avr-g++ $(CFLAGS)
AVROBJ = avr-objcopy
AVRSIZE = avr-size
LOADER = micronucleus
# ---
all: main.hex
.c.o:
$(AVRGCC) -c $< -o $@
.cpp.o:
$(AVRGPP) -c $< -o $@
.PHONY: clean
clean:
rm -f main.hex main.elf *.o
main.elf: $(OBJ)
$(AVRGCC) -o main.elf $(OBJ)
main.hex: main.elf
rm -f main.hex
$(AVROBJ) -j .text -j .data -O ihex main.elf main.hex
$(AVRSIZE) --format=avr --mcu=$(DEVICE) main.elf
flash: all
$(LOADER) --run main.hex
main.c
#include <avr/io.h>
#include <util/delay.h>
// Digispark LED is on Pin 1 for newer versions
#define LED PB1
#define DELAY_MS 1000
int main(void)
{
DDRB |= (1 << LED); // Set pin to output
PORTB |= (1 << LED); // Set pin to high
for (;;) {
PORTB ^= (1 << LED);
_delay_ms(DELAY_MS);
}
return 0;
}
kompilieren von main.c
make
avr-gcc -Wall -Os -DF_CPU=16500000 -mmcu=attiny85 -O1 -c main.c -o main.o
avr-gcc -Wall -Os -DF_CPU=16500000 -mmcu=attiny85 -O1 -o main.elf main.o
rm -f main.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size --format=avr --mcu=attiny85 main.elf
AVR Memory Usage
----------------
Device: attiny85
Program: 84 bytes (1.0% Full)
(.text + .data + .bootloader)
Data: 0 bytes (0.0% Full)
(.data + .bss + .noinit)
Disassemblieren mit Erläuterungen
vavrdisasm main.hex
- Ganz am Anfang stehen die Reset- und Interrupt-Vektoren, welche ein relativer Sprung sein müssen
- Nach dem Reset wird an die Adresse 0 gesprungen, hier geht es dann weiter nach $1e
0: c0 0e rjmp .+28 ; 0x1e
2: c0 15 rjmp .+42 ; 0x2e
4: c0 14 rjmp .+40 ; 0x2e
6: c0 13 rjmp .+38 ; 0x2e
8: c0 12 rjmp .+36 ; 0x2e
a: c0 11 rjmp .+34 ; 0x2e
c: c0 10 rjmp .+32 ; 0x2e
e: c0 0f rjmp .+30 ; 0x2e
10: c0 0e rjmp .+28 ; 0x2e
12: c0 0d rjmp .+26 ; 0x2e
14: c0 0c rjmp .+24 ; 0x2e
16: c0 0b rjmp .+22 ; 0x2e
18: c0 0a rjmp .+20 ; 0x2e
1a: c0 09 rjmp .+18 ; 0x2e
1c: c0 08 rjmp .+16 ; 0x2e
- Es folgt der Standard-GCC-Initialisierungscode für AVR
- das SREG $3f wird gelöscht
- der Stack-Pointer wird auf das Ende des Speichers, hier $025f gesetzt
- die Routine main() ab 0x30 wird mit einem rcall aufgerufen
- Sollte sie zurückkehren, wird zur Adresse 0x50 gesprungen
1e: 24 11 eor R1, R1
20: be 1f out $3f, R1
22: e5 cf ldi R28, 0x5f
24: e0 d2 ldi R29, 0x02
26: bf de out $3e, R29
28: bf cd out $3d, R28
2a: d0 02 rcall .+4 ; 0x30
2c: c0 11 rjmp .+34 ; 0x50
2e: cf e8 rjmp .-48 ; 0x0
- die Routine main()
- das Bit 1 wird im DDR und Port B gsetzt
30: 9a b9 sbi $17, 1
32: 9a c1 sbi $18, 1
- es folgt Code zum toggeln des Bits am Port B via eor
34: e0 92 ldi R25, 0x02
36: b3 88 in R24, 0x18
38: 27 89 eor R24, R25
3a: bb 88 out $18, R24
- Die Warteschleife, die hier 3 Register verwendet, wird inline einkompiliert
3c: e9 2f ldi R18, 0x9f
3e: e5 3a ldi R19, 0x5a
40: e3 82 ldi R24, 0x32
42: 50 21 subi R18, 0x01
44: 40 30 sbci R19, 0x00
46: 40 80 sbci R24, 0x00
48: f7 e1 brne .-8 ; 0x42
- Das Programm beginnt von vorne: hier die Endlosschleife
4a: c0 00 rjmp .+0 ; 0x4c
4c: 00 00 nop
4e: cf f3 rjmp .-26 ; 0x36
- eventuelles Programmende, wenn main() keine Endlos-Schleife beinhaltet
50: 94 f8 cli
52: cf ff rjmp .-2 ; 0x52
weitere Mini-Beispiele
- hier auf Github, wurde zum Teil hier verwendet
- 1-blink und 2-button wurden von mir getestet
https://github.com/matthew-macgregor/digispark-attiny85-experiments/
Artikel erstellt am: 10 February 2025 , aktualisiert am 11 February 2025