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 einfch 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 andere Code zur Ausführung!
Inhaltsverzeichnis
Von der C-Quelle zum hex-File
Das Beispiel besteht ganz bewusst aus zwei getrennten Quelldateien (Modulen) um zu zeigen, wie man Code auf mehrere Dateien aufteilen kann um die Übersichtlichkeit bei grösseren Projekten zu wahren.
Ausserdem wird das Übersetzen über Kommandozeilen-Eingaben erlegigt und nicht über Werkzeuge wie make, dessen inkorrekte Anwendung eine häufige Fehlerquelle ist.
Compilieren
Linken
Umwandeln nach hex
Listfile erstellen
Die Größe ermitteln
Quellcode
blinky.c
#include <avr/io.h> #include <avr/interrupt.h> #include "timer1.h" /* PortB.1 blinkt jede Sekunde */ /* also mit einer Frequenz von 1/2 Hz */ #define DDR_LED DDRB #define PORT_LED PORTB #define PAD_LED 1 static void ioinit(); static void job_timer1(); /* Zählt in jedem aufgetretenem IRQ eins hoch */ static volatile uint16_t irq_count = 0; /* Diese Funktion ist ein 'Callback' */ /* Sie wird an timer1_init() übergeben */ /* und von dort aus aufgerufen, und zwar */ /* INTERRUPTS_PER_SECOND mal pro Sekunde. */ /* Sie wird also auf IRQ-Ebene ausgeführt */ void job_timer1() { uint16_t count; /* irq_count um 1 erhöhen und */ /* gegebenenfalls die LED blinken */ count = 1+irq_count; if (count >= INTERRUPTS_PER_SECOND) { count = 0; PORT_LED ^= (1 << PAD_LED); } irq_count = count; } void ioinit() { /* Port als Ausgang */ DDR_LED |= (1 << PAD_LED); /* Initialisiert Timer1, um jede Sekunde */ /* INTERRUPTS_PER_SECOND mal die Funktion job_timer1 */ /* aufzurufen */ timer1_init (job_timer1); } int main (void) { /* Peripherie initialisieren */ ioinit(); /* Interrupts aktivieren */ sei(); /* Nach main landen wir in exit(), */ /* das nur aus einer Endlosschleife besteht (avr-gcc) */ /* Der Interrupt lässt die LED weiterhin */ /* im Sekundentakt blinken */ return 0; }
timer1.h
#ifndef _TIMER1_H_ #define _TIMER1_H_ #include <inttypes.h> #ifndef INTERRUPTS_PER_SECOND #define INTERRUPTS_PER_SECOND 1000 #endif /* INTERRUPTS_PER_SECOND */ extern void timer1_init (void (*) (void)); #endif /* _TIMER1_H_ */
timer1.c
#include <avr/io.h> #include <avr/signal.h> #include "timer1.h" #ifndef F_CPU #define F_CPU 1000000 #endif /* F_CPU */ /* Test von F_CPU und INTERRUPTS_PER_SECOND */ /* auf Gültigkeitsbereich */ #if (F_CPU / INTERRUPTS_PER_SECOND -1 < 0) \ || (F_CPU / INTERRUPTS_PER_SECOND -1 >= 0x10000) #error Werte für F_CPU bzw. INTERRUPTS_PER_SECOND ungeeignet #error evtl. muss der Prescaler verwendet werden #endif /* Callback-Funktion */ static void (*timer1a_job) (void); void timer1_init (void (*job) (void)) { timer1a_job = job; #if defined (__AVR_AT90S2313__) /* AVR Classic: */ /* Timer1 läuft mit vollem Takt */ /* CTC: Clear Timer on CompareMatch */ /* Timer1 ist Zähler */ TCCR1A = 0; TCCR1B = _BV (CS10) | _BV (CTC1); #elif defined (__AVR_ARCH__) && ((__AVR_ARCH__==4) || (__AVR_ARCH__==5)) /* AVR Mega: */ /* Mode #4 für Timer1 (ATMega8 Manual S. 97) */ /* und voller MCU Takt (Prescale=1) */ TCCR1A = 0; TCCR1B = _BV (WGM12) | _BV (CS10); #else #error Dont know how to setup timer1 #endif /* OutputCompare1A Register setzen */ OCR1A = (uint16_t) ((uint32_t) F_CPU / INTERRUPTS_PER_SECOND -1); /* evtl. gesetztes OC1A-Flag zurücksetzen */ TIFR = (1 << OCF1A); /* OutputCompare1A Interrupt aktivieren */ TIMSK |= (1 << OCIE1A); } /* Die Interrupt Service Routine ruft lediglich */ /* timer1a_job auf (callback) */ SIGNAL(SIG_OUTPUT_COMPARE_1A) { timer1a_job(); }