(→Baustelle) |
|||
Zeile 18: | Zeile 18: | ||
Eine LED an Port B1 wird einmal pro Sekunde an bzw. ausgeschaltet. Die LED blinkt also mit einer Frequenz von 1/2 Hz. | Eine LED an Port B1 wird einmal pro Sekunde an bzw. ausgeschaltet. Die LED blinkt also mit einer Frequenz von 1/2 Hz. | ||
− | |||
− | |||
=Von der C-Quelle zum hex-File= | =Von der C-Quelle zum hex-File= | ||
− | Das | + | Das Übersetzen über Kommandozeilen-Eingaben erledigt und ''nicht'' |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
über Werkzeuge wie [[make]], | über Werkzeuge wie [[make]], | ||
das die Zusammenhänge eher verschleiert als erhellt, | das die Zusammenhänge eher verschleiert als erhellt, | ||
und dessen inkorrekte Anwendung eine häufige Fehlerquelle ist. | und dessen inkorrekte Anwendung eine häufige Fehlerquelle ist. | ||
− | Nach Speichern der Quelldatein in ein eigenes Verzeichnis enthält dieses die | + | Nach Speichern der Quelldatein in ein eigenes Verzeichnis enthält dieses die Datei |
− | blinky.c | + | blinky.c |
==Compilieren== | ==Compilieren== | ||
− | Zunächst | + | Zunächst wird die C-Dateie übersetzt. |
Der Übersetzungsvorgang wird gesteuert durch Kommandozeilen-Parameter (siehe [[avr-gcc]]). | Der Übersetzungsvorgang wird gesteuert durch Kommandozeilen-Parameter (siehe [[avr-gcc]]). | ||
Die Option | Die Option | ||
Zeile 48: | Zeile 40: | ||
;<tt>-Os</tt>: optimiert auf Codegröße. | ;<tt>-Os</tt>: optimiert auf Codegröße. | ||
> avr-gcc blinky.c -c -o blinky.o -Os -g -mmcu=atmega8 | > avr-gcc blinky.c -c -o blinky.o -Os -g -mmcu=atmega8 | ||
− | + | Danach ist eine neue Dateien entstanden (*.o) | |
− | Danach | + | blinky.c blinky.o |
− | blinky.c blinky | + | |
In der Quelle wird der Wert für das OCR1A-Register aus der Taktfrequenz des | In der Quelle wird der Wert für das OCR1A-Register aus der Taktfrequenz des | ||
Zeile 57: | Zeile 48: | ||
Standardmässig sind [[AVR|AVRs]] mit dem internen RC-Oszillator mit ca. 1 MHz getaktet. | Standardmässig sind [[AVR|AVRs]] mit dem internen RC-Oszillator mit ca. 1 MHz getaktet. | ||
− | Daher wird <tt>F_CPU</tt> in <tt> | + | Daher wird <tt>F_CPU</tt> in <tt>blinky.c</tt> zu 1000000 definiert: |
− | + | ||
#define F_CPU 1000000 | #define F_CPU 1000000 | ||
− | |||
{{FarbigerRahmen| | {{FarbigerRahmen| | ||
Dieser Wert hat rein informativen Charakter und bewirkt ''nicht'', daß der Controller mit einer anderen Frequenz läuft! Das geht über einen anderen Quarz/Oszillator oder andere Fuse-Einstellungen. | Dieser Wert hat rein informativen Charakter und bewirkt ''nicht'', daß der Controller mit einer anderen Frequenz läuft! Das geht über einen anderen Quarz/Oszillator oder andere Fuse-Einstellungen. | ||
Zeile 66: | Zeile 55: | ||
Hat man den AVR mit einem anderen Takt laufen, dann gibt man diesen einfach in der | Hat man den AVR mit einem anderen Takt laufen, dann gibt man diesen einfach in der | ||
− | Kommandozeile an, z.B. für 8 MHz | + | Kommandozeile an, z.B. für 8 MHz. Dies setzt den Define für <tt>F_CPU</tt> auf <tt>8000000</tt> |
> avr-gcc ... -DF_CPU=8000000 | > avr-gcc ... -DF_CPU=8000000 | ||
− | |||
− | |||
− | |||
==Linken== | ==Linken== | ||
− | Die | + | Die erzeugten Objek-Datei wird nun zusammengebunden, um die Ausgabedatei (*.elf) zu erhalten: |
− | > avr-gcc blinky | + | > avr-gcc blinky.o -o blinky.elf -mmcu=atmega8 |
erzeugt die ausführbare Datei (*.elf), die noch zusätzliche Informationen wie | erzeugt die ausführbare Datei (*.elf), die noch zusätzliche Informationen wie | ||
Debug-Infos etc. beinhaltet mit dem Namen <tt>blinky.elf</tt>: | Debug-Infos etc. beinhaltet mit dem Namen <tt>blinky.elf</tt>: | ||
− | blinky.c blinky.elf blinky | + | blinky.c blinky.elf blinky.o |
==Umwandeln nach hex== | ==Umwandeln nach hex== | ||
Zeile 87: | Zeile 73: | ||
Damit enthält die hex-Datei die Sections <tt>.text</tt> (Programm) und <tt>.data</tt> (Daten). | Damit enthält die hex-Datei die Sections <tt>.text</tt> (Programm) und <tt>.data</tt> (Daten). | ||
Das Verzeichnis beinhaltet jetzt | Das Verzeichnis beinhaltet jetzt | ||
− | blinky.c blinky.elf blinky.hex blinky | + | blinky.c blinky.elf blinky.hex blinky.o |
===EEPROM=== | ===EEPROM=== | ||
Zeile 106: | Zeile 92: | ||
Hier ein Ausschnitt aus dem Listfile: | Hier ein Ausschnitt aus dem Listfile: | ||
<pre> | <pre> | ||
− | + | 00000072 <wait_10ms>: | |
− | + | ||
− | + | // ////////////////////////////////////////////////////////////////////// | |
− | + | // Wartet etwa t*10 ms. | |
− | + | // timer_10ms wird alle 10ms in der Timer1-ISR erniedrigt. | |
− | + | // Weil es bis zum nächsten IRQ nicht länger als 10ms dauert, | |
− | + | // wartet diese Funktion zwischen (t-1)*10 ms und t*10 ms. | |
− | + | void wait_10ms (const uint8_t t) | |
− | + | { | |
− | + | timer_10ms = t; | |
− | + | 72: 80 93 61 00 sts 0x0061, r24 | |
− | + | while (timer_10ms); | |
− | + | 76: 80 91 61 00 lds r24, 0x0061 | |
− | + | 7a: 88 23 and r24, r24 | |
− | + | 7c: e1 f7 brne .-8 ; 0x76 <wait_10ms+0x4> | |
− | + | 7e: 08 95 ret | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | 76: | + | |
− | + | ||
− | 7a: | + | |
− | + | ||
− | + | ||
− | + | ||
− | 7c: | + | |
− | + | ||
− | + | ||
</pre> | </pre> | ||
Links stehen die Adressen, dann die Maschinencodes der Befehle, | Links stehen die Adressen, dann die Maschinencodes der Befehle, | ||
Zeile 149: | Zeile 120: | ||
landen. Erstellt wird das Mapfile während des Linkens, indem <tt>avr-gcc</tt> | landen. Erstellt wird das Mapfile während des Linkens, indem <tt>avr-gcc</tt> | ||
ein Option an den Linker weiterreicht, die diesem zum Erstellen eines solchen Files veranlasst: | ein Option an den Linker weiterreicht, die diesem zum Erstellen eines solchen Files veranlasst: | ||
− | > avr-gcc blinky | + | > avr-gcc blinky.o -o blinky.elf ... -Wl,-Map,blinky.map |
Dadurch entsteht das Mapfile <tt>blinky.map</tt>. | Dadurch entsteht das Mapfile <tt>blinky.map</tt>. | ||
Zeile 155: | Zeile 126: | ||
Die Größe der erhaltenen Objekte/Files können mit <tt>avr-size</tt> ausgegeben werden. | Die Größe der erhaltenen Objekte/Files können mit <tt>avr-size</tt> ausgegeben werden. | ||
− | > avr-size -x blinky | + | > avr-size -x blinky.o |
druckt aus: | druckt aus: | ||
text data bss dec hex filename | text data bss dec hex filename | ||
− | + | 0x7c 0x0 0x2 126 7e blinky.o | |
− | + | ||
Das sind die Größen der einzelnen [[avr-gcc#Sections|Sections]]. | Das sind die Größen der einzelnen [[avr-gcc#Sections|Sections]]. | ||
Für das Flash relevant ist die Größe von | Für das Flash relevant ist die Größe von | ||
Zeile 166: | Zeile 136: | ||
+ <tt>.bss</tt> (null-initialisierte Daten). | + <tt>.bss</tt> (null-initialisierte Daten). | ||
− | Im Flash werden also | + | Im Flash werden also 126+0=126 Bytes belegt, und im SRAM 0+2=2 Bytes. |
Die Gesamtgrösse ergibt sich jedoch erst aus dem elf-File, | Die Gesamtgrösse ergibt sich jedoch erst aus dem elf-File, | ||
Zeile 174: | Zeile 144: | ||
<pre> | <pre> | ||
blinky.elf : | blinky.elf : | ||
− | section | + | section size addr |
− | .text | + | .text 0xd8 0x0 |
− | .data | + | .data 0x0 0x800060 |
− | .bss | + | .bss 0x2 0x800060 |
− | .noinit | + | .noinit 0x0 0x800062 |
− | .eeprom | + | .eeprom 0x0 0x810000 |
− | .stab | + | .stab 0x36c 0x0 |
− | .stabstr | + | .stabstr 0x84 0x0 |
− | + | .debug_aranges 0x14 0x0 | |
− | </pre> | + | .debug_pubnames 0x48 0x0 |
− | Im Flash werden somit | + | .debug_info 0xfc 0x0 |
+ | .debug_abbrev 0xa2 0x0 | ||
+ | .debug_line 0x101 0x0 | ||
+ | .debug_str 0xa6 0x0 | ||
+ | Total 0x86b</pre> | ||
+ | Im Flash werden somit 0xda+0=218 Bytes belegt, also 92 Bytes mehr als das Object benötigt; davon entfallen z.B. schon 38 Bytes auf die Vektortabelle des [[ATmega8]], | ||
die 2*19 Bytes groß ist. | die 2*19 Bytes groß ist. | ||
Zeile 191: | Zeile 166: | ||
was nach Größe sortiert ausdruckt: | was nach Größe sortiert ausdruckt: | ||
<pre> | <pre> | ||
− | 00800060 | + | 00800060 00000001 b interrupt_num_10ms.0 |
− | + | 00800061 00000001 b timer_10ms | |
− | + | 00000072 0000000e T wait_10ms | |
− | + | 0000005c 00000016 T timer1_init | |
− | + | 000000bc 0000001c T main | |
− | + | 00000080 0000003c T __vector_6 | |
</pre> | </pre> | ||
− | = | + | =Quellcode= |
+ | Der Quellcode ist für die Version 3.x von [[avr-gcc]]. In der 4er-Version gab es tiefgreifende interne Änderungen im Compiler; er ist noch instabil und kann momentan noch nicht für den Produktiv-Einsatz empfohlen werden (Stand 02/2005). | ||
#include <avr/io.h> | #include <avr/io.h> | ||
#include <avr/interrupt.h> | #include <avr/interrupt.h> | ||
Zeile 354: | Zeile 330: | ||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
=Spin-off= | =Spin-off= | ||
Zeile 629: | Zeile 342: | ||
* Bedingte Codeübersetzung/Controllerunterscheidung mit <tt>#ifdef</tt> (in <tt>timer1_init</tt>) | * Bedingte Codeübersetzung/Controllerunterscheidung mit <tt>#ifdef</tt> (in <tt>timer1_init</tt>) | ||
* Abchecken der Güligkeit von Defines durch den Präprozessor | * Abchecken der Güligkeit von Defines durch den Präprozessor | ||
− | |||
− | |||
=Siehe auch= | =Siehe auch= |
Version vom 4. Oktober 2006, 17:44 Uhr
Das erste C-Programm, das man zu sehen bekommt, ist für die meisten das "Hallo Welt". Bei "Hallo Welt" geht es weniger um die Funktionalität an sich, sondern darum zu lernen, wie man überhaupt ein Programm übersetzt und einen Compiler verwendet.
Was für den PC das "Hallo Welt", ist für einen kleinen Microcontroller der "Hallo Blinky", der einfach nur eine Leuchtdiode (LED) blinken lässt.
Im Vergleich zu "Hallo Welt" sieht der Blinky viel komplizierter aus, aber eigentlich ist er einfacher, dann es werden keine umfangreichen Funktionen oder Black-Boxen benutzt wie etwa printf(). Ausser dem eingegebenen Quellcode kommt also kein anderer Code zur Ausführung!
Bitte beachte auch die Hinweise zu Inkompatibilitäten von avr-gcc!
Inhaltsverzeichnis
Beschreibung
Eine LED an Port B1 wird einmal pro Sekunde an bzw. ausgeschaltet. Die LED blinkt also mit einer Frequenz von 1/2 Hz.
Von der C-Quelle zum hex-File
Das Übersetzen über Kommandozeilen-Eingaben erledigt und nicht über Werkzeuge wie make, das die Zusammenhänge eher verschleiert als erhellt, und dessen inkorrekte Anwendung eine häufige Fehlerquelle ist.
Nach Speichern der Quelldatein in ein eigenes Verzeichnis enthält dieses die Datei
blinky.c
Compilieren
Zunächst wird die C-Dateie übersetzt. Der Übersetzungsvorgang wird gesteuert durch Kommandozeilen-Parameter (siehe avr-gcc). Die Option
- -c
- legt fest, daß nur compiliert wird (und nicht gelinkt),
- -o name
- gibt den Name der Ausgabedatei an. Ohne diese Option heisst die Ausgabedatei immer a.out
- -mmcu=atmega8
- legt den Controllertyp fest, in dem Beispiel den ATmega8
- -g
- erzeugt Debug-Infos und
- -Os
- optimiert auf Codegröße.
> avr-gcc blinky.c -c -o blinky.o -Os -g -mmcu=atmega8
Danach ist eine neue Dateien entstanden (*.o)
blinky.c blinky.o
In der Quelle wird der Wert für das OCR1A-Register aus der Taktfrequenz des Controllers (F_CPU) und der Anzahl an Interrupts, die pro Sekunde ausgelöst werden sollen (INTERRUPTS_PER_SECOND), berechnet.
Standardmässig sind AVRs mit dem internen RC-Oszillator mit ca. 1 MHz getaktet. Daher wird F_CPU in blinky.c zu 1000000 definiert:
#define F_CPU 1000000
Dieser Wert hat rein informativen Charakter und bewirkt nicht, daß der Controller mit einer anderen Frequenz läuft! Das geht über einen anderen Quarz/Oszillator oder andere Fuse-Einstellungen.
Hat man den AVR mit einem anderen Takt laufen, dann gibt man diesen einfach in der Kommandozeile an, z.B. für 8 MHz. Dies setzt den Define für F_CPU auf 8000000
> avr-gcc ... -DF_CPU=8000000
Linken
Die erzeugten Objek-Datei wird nun zusammengebunden, um die Ausgabedatei (*.elf) zu erhalten:
> avr-gcc blinky.o -o blinky.elf -mmcu=atmega8
erzeugt die ausführbare Datei (*.elf), die noch zusätzliche Informationen wie Debug-Infos etc. beinhaltet mit dem Namen blinky.elf:
blinky.c blinky.elf blinky.o
Umwandeln nach hex
Code und Daten
Viele Progger wollen die zu ladende Datei im Intel-hex-Format (*.hex bzw. *.ihex). Dazu gibt man an
> avr-objcopy -j .text -j .data -O ihex blinky.elf blinky.hex
Damit enthält die hex-Datei die Sections .text (Programm) und .data (Daten). Das Verzeichnis beinhaltet jetzt
blinky.c blinky.elf blinky.hex blinky.o
EEPROM
Ein hex-File, das den Inhalt des EEPROMs wiederspiegelt, erhält man mit
> avr-objcopy -j .eeprom --change-section-lma .eeprom=0 -O ihex blinky.elf blinky_eeprom.hex
Für das Beispiel ist das EEPROM-File blinky_eeprom.hex leer, da wir keine Daten ins EEPROM gelegt haben.
Listfile erstellen
Falls man ein Listfile haben möchte, dann geht das mit
> avr-objdump -h -S -j .text -j .data blinky.elf > blinky.lst
Das Listfile ist eine Textdatei, die alle Assembler-Befehle auflistet, wie sie letztendlich auf den Controller geladen werden.
avr-objdump gibt seine Ausgabe auf das Terminal aus. Diese Ausgabe wird mit '>' in die Datei blinky.lst umgeleitet. Hier ein Ausschnitt aus dem Listfile:
00000072 <wait_10ms>: // ////////////////////////////////////////////////////////////////////// // Wartet etwa t*10 ms. // timer_10ms wird alle 10ms in der Timer1-ISR erniedrigt. // Weil es bis zum nächsten IRQ nicht länger als 10ms dauert, // wartet diese Funktion zwischen (t-1)*10 ms und t*10 ms. void wait_10ms (const uint8_t t) { timer_10ms = t; 72: 80 93 61 00 sts 0x0061, r24 while (timer_10ms); 76: 80 91 61 00 lds r24, 0x0061 7a: 88 23 and r24, r24 7c: e1 f7 brne .-8 ; 0x76 <wait_10ms+0x4> 7e: 08 95 ret
Links stehen die Adressen, dann die Maschinencodes der Befehle, danach die Maschinencodes in Assembler-Darstellung. Ganz rechts nach dem Kommentarzeichen ';' stehen die Dezimalcodes von Konstanten (z.B. 24 für die 0x18 an Adresse 74) oder die Zieladressen von Sprüngen, wie bei dem brcs-Befehl an Adresse 6e, der gegebenenfalls 12 Bytes überspringt und dann an Adresse 7c landet.
Mapfile erstellen
Ein Mapfile gibt Auskunft darüber, an welcher Adresse Code und Objekte landen. Erstellt wird das Mapfile während des Linkens, indem avr-gcc ein Option an den Linker weiterreicht, die diesem zum Erstellen eines solchen Files veranlasst:
> avr-gcc blinky.o -o blinky.elf ... -Wl,-Map,blinky.map
Dadurch entsteht das Mapfile blinky.map.
Die Größe ermitteln
Die Größe der erhaltenen Objekte/Files können mit avr-size ausgegeben werden.
> avr-size -x blinky.o
druckt aus:
text data bss dec hex filename 0x7c 0x0 0x2 126 7e blinky.o
Das sind die Größen der einzelnen Sections. Für das Flash relevant ist die Größe von .text (Code) + .data (initialisierte Daten), für das SRAM relevent ist .data (initialisierte Daten) + .bss (null-initialisierte Daten).
Im Flash werden also 126+0=126 Bytes belegt, und im SRAM 0+2=2 Bytes.
Die Gesamtgrösse ergibt sich jedoch erst aus dem elf-File, denn auch die Vektortabelle und der Startup-Code belegen Platz:
> avr-size -x -A blinky.elf
druckt aus:
blinky.elf : section size addr .text 0xd8 0x0 .data 0x0 0x800060 .bss 0x2 0x800060 .noinit 0x0 0x800062 .eeprom 0x0 0x810000 .stab 0x36c 0x0 .stabstr 0x84 0x0 .debug_aranges 0x14 0x0 .debug_pubnames 0x48 0x0 .debug_info 0xfc 0x0 .debug_abbrev 0xa2 0x0 .debug_line 0x101 0x0 .debug_str 0xa6 0x0 Total 0x86b
Im Flash werden somit 0xda+0=218 Bytes belegt, also 92 Bytes mehr als das Object benötigt; davon entfallen z.B. schon 38 Bytes auf die Vektortabelle des ATmega8, die 2*19 Bytes groß ist.
Die Größen einzelner Funktionen lassen sich anzeigen mit
> avr-nm --size-sort -S blinky.elf
was nach Größe sortiert ausdruckt:
00800060 00000001 b interrupt_num_10ms.0 00800061 00000001 b timer_10ms 00000072 0000000e T wait_10ms 0000005c 00000016 T timer1_init 000000bc 0000001c T main 00000080 0000003c T __vector_6
Quellcode
Der Quellcode ist für die Version 3.x von avr-gcc. In der 4er-Version gab es tiefgreifende interne Änderungen im Compiler; er ist noch instabil und kann momentan noch nicht für den Produktiv-Einsatz empfohlen werden (Stand 02/2005).
#include <avr/io.h> #include <avr/interrupt.h> // Geblinkt wird PortB.1 (push-pull) // Eine LED in Reihe mit einem Vorwiderstand zwischen // PortB.1 und GND anschliessen. #define PAD_LED 1 #define PORT_LED PORTB #define DDR_LED DDRB // Der MCU-Takt. Wird gebraucht, um Timer1 mit den richtigen // Werten zu initialisieren. Voreinstellung ist 1MHz. // (Werkseinstellung für AVRs mit internem Oszillator). // ! Der Wert von F_CPU hat rein informativen Character für // ! die korrekte Codeerzeugung im Programm! // ! Um die Taktrate zu ändern müssen die Fuses des Controllers // ! und/oder Quarz/Resonator/RC-Glied/Oszillator // ! angepasst werden! #define F_CPU 1000000 // So viele IRQs werden jede Sekunde ausgelöst. // Für optimale Genauigkeit muss // IRQS_PER_SECOND ein Teiler von F_CPU sein // und IRQS_PER_SECOND ein Vielfaches von 100. // Ausserdem muss gelten F_CPU / IRQS_PER_SECOND <= 65536 #define IRQS_PER_SECOND 2000 /* 500 µs */ // Anzahl IRQs pro 10 Millisekunden #define IRQS_PER_10MS (IRQS_PER_SECOND / 100) // Gültigkeitsprüfung. // Bei ungeeigneten Werten gibt es einen Compilerfehler #if (F_CPU/IRQS_PER_SECOND > 65536) || (IRQS_PER_10MS < 1) || (IRQS_PER_10MS > 255) # error Diese Werte fuer F_CPU und IRQS_PER_SECOND # error sind ausserhalb des gueltigen Bereichs! #endif // Compiler-Warnung falls die Genauigkeit nicht optimal ist. // Wenn das nervt für deine Werte, einfach löschen :-) #if (F_CPU % IRQS_PER_SECOND != 0) || (IRQS_PER_SECOND % 100 != 0) # warning Das Programm arbeitet nicht mit optimaler Genauigkeit. #endif // Prototypen void wait_10ms (const uint8_t); void timer1_init(); // Zähler-Variable. Wird in der ISR erniedrigt und in wait_10ms benutzt. static volatile uint8_t timer_10ms; // ////////////////////////////////////////////////////////////////////// // Implementierungen der Funktionen // ////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////// // Timer1 so initialisieren, daß er IRQS_PER_SECOND // IRQs pro Sekunde erzeugt. void timer1_init() { // Timer1: keine PWM TCCR1A = 0; // Timer1 ist Zähler: Clear Timer on Compare Match (CTC, Mode #4) // Timer1 läuft mit vollem MCU-Takt: Prescale = 1 #if defined (__AVR_AT90S2313__) || defined (__AVR_AT90S8515__) || defined (__AVR_AT90S8535__) TCCR1B = (1 << CTC1) | (1 << CS10); #elif (__AVR_ARCH__==4) || (__AVR_ARCH__==5) || defined (__AVR_ATtiny2313__) TCCR1B = (1 << WGM12) | (1 << CS10); #else #error Keine Ahnung, wie Timer1 fuer diesen Controller zu initialisieren ist! #endif // OutputCompare für gewünschte Timer1 Frequenz // TCNT1 zählt immer 0...OCR1A, 0...OCR1A, ... // Beim überlauf OCR1A -> OCR1A+1 wird TCNT1=0 gesetzt und im nächsten // MCU-Takt eine IRQ erzeugt. OCR1A = (unsigned short) ((unsigned long) F_CPU / IRQS_PER_SECOND-1); // OutputCompareA-Interrupt für Timer1 aktivieren #if defined (__AVR_ATmega169__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) TIMSK1 |= (1 << OCIE1A); #else TIMSK |= (1 << OCIE1A); #endif } // ////////////////////////////////////////////////////////////////////// // Wartet etwa t*10 ms. // timer_10ms wird alle 10ms in der Timer1-ISR erniedrigt. // Weil es bis zum nächsten IRQ nicht länger als 10ms dauert, // wartet diese Funktion zwischen (t-1)*10 ms und t*10 ms. void wait_10ms (const uint8_t t) { timer_10ms = t; while (timer_10ms); } // ////////////////////////////////////////////////////////////////////// // Die Interrupt Service Routine (ISR). // In interrupt_num_10ms werden die IRQs gezählt. // Sind IRQS_PER_SECOND Interrups geschehen, // dann sind 10 ms vergangen. SIGNAL (SIG_OUTPUT_COMPARE1A) { static uint8_t interrupt_num_10ms; // interrupt_num_10ms erhöhen und mit Maximalwert vergleichen if (++interrupt_num_10ms == IRQS_PER_10MS) { // 10 Milli-Sekunden sind vorbei // interrupt_num_10ms zurücksetzen interrupt_num_10ms = 0; // Alle 10ms wird timer_10ms erniedrigt, falls es nicht schon 0 ist. // Wird verwendet in wait_10ms if (timer_10ms != 0) timer_10ms--; } } // ////////////////////////////////////////////////////////////////////// // Das Hauptprogramm: Startpunkt int main() { // LED-Port auf OUT DDR_LED |= (1 << PAD_LED); // Timer1 initialisieren timer1_init(); // Interrupts aktivieren sei(); // Endlosschleife // Die LED ist jeweils 1 Sekunde an und 1 Sekund aus, // blinkt also mit einer Frequenz von 0.5 Hz while (1) { // LED an PORT_LED |= (1 << PAD_LED); // 1 Sekunde warten wait_10ms (100); // LED aus PORT_LED &= ~(1 << PAD_LED); // 1 Sekunde warten wait_10ms (100); } // main braucht keinen return-Wert, weil wir nie hier hin kommen }
Spin-off
Eine LED blinken zu lassen könnte auch wesentlich einfacher implementert werden, also z.B. ohne Funktionsaufrufe oder Interrupt-Programmierung.
Neben der eigentlichen Aufgabe "LED blinken lassen" kann man an dem Code aber noch andere Dinge lernen:
- Programmierung eines Interrupts
- Initialisierung von Timer1 als Zähler mit "Clear Timer on Compare Match"
- Bedingte Codeübersetzung/Controllerunterscheidung mit #ifdef (in timer1_init)
- Abchecken der Güligkeit von Defines durch den Präprozessor