GREETBoard ATMega128 – Interrupt gesteuerte LED mit 8-Bit-Timer

Das neue GREETBoard ATMega128 hat seinen ersten Test bestanden und blinkt munter vor sich her. Wie beim GREETBoard ATMega32 wurden die Pausen zwischen dem Blinken durch einen Wartebefehl realisiert. Aber bereits beim ATMega32 wurde der Wartebefehl durch einen Timer und den entsprechenden Interrupt ersetzt. Thema dieses Artikels ist es daher, dies auch für das GREETBoard ATMega128 umzusetzen.

Aber warum ein Interrupt und nicht einfach einen Wartebefehl nehmen? Sicher, ein Interrupt zu programmieren ist ein wenig schwieriger, aber während einer Wartezeit von nur 500 Millisekunden könnte der ATMega128 sehr häufig die Programmroutine durchlaufen (500ms entsprechen 8000.000 Takten bei 16MHz). Er könnte auf hineinkommende Signale reagieren und Signale setzen, was während einer Pause nicht möglich ist. Bei den bisherigen Beispielen ist dies nicht kritisch, bei komplexen Programmen könnte es aber zu Schwierigkeiten führen.

In diesem Artikel werde ich das Programm von der ersten bis zur letzten Zeile erklären, so dass der Aufbau und die verschiedenen Funktionsblöcke erkennbar werden.

Die Schritt-für-Schritt Erklärung

Zu Beginn eines Programmes werden die sogenannten Header-Dateien eingebunden. Diese erweitern das Programm um zusätzliche Funktionen. So werden z.B. in der Datei io.h die IO-Register des Mikrocontroller definiert.

#include <avr/io.h>          // Header-Datei f. IO-Register
#include <avr/interrupt.h>   // Header-Datei f. Interruptfunktion
#include <stdint.h>          // Header-Datei f. standard Datentypen

Das Programm benötigt zwei Variablen, damit es einwandfrei funktioniert. tcount ist dafür zuständig die Anzahl der Timer0-Überläufe zu speichern. Die LED soll nicht bei jedem Timer0-Überlauf ein- bzw. ausgeschaltet werden (das wäre viiieeel zu schnell), sondern erst nach n Überläufen.

volatile uint8_t tcount;     // Globale Variable (volatile)
uint8_t x;                   // Lokale Variable zum Schalter des Ports

Diese ISR (Interrupt Service Routine) wird ausgelöst, wenn der Timer0 übergelaufen ist. Da es sich um einen 8-Bit Timer handelt, ist dies der Fall, wenn er den Wert 256 überschreitet. In diesem Fall wird die Variable tcount um eins erhöht.

ISR (TIMER0_OVF_vect)
{
  tcount++;                  // tcount +1 pro Timer Interrupt (ca.10ms)
}

Das Hauptprogramm startet mit einigen Einstellungen, die für die Funktion wichtig sind. So wird als erstes die Variable x auf Null gesetzt um die, zu Beginn aktivierte, LED zu deaktivieren. Die zweite Zeile ist zuständig für die Definition des Ausgangs der LED (Port C3).

In der dritten Zeile gibt es tatsächlich eine Änderung des ursprünglichen Programms des GREETBoard ATMega32. Da der Timer0 des ATMega128 einen größeren Funktionsumfang hat, besitzt er zwei Register. Der Vorteiler des Timers wird bei diesem Mikrocontroller im Register B, also TCCR0B, festgelegt.

Ach ja, hier nochmal kurz die Berechnung der Timer-Geschwindigkeit:

Timer-Frequenz = (Taktfrequenz : Vorteiler) : Timergröße   [8 Bit = 256)
Timer-Frequenz = (16.000.000Hz : 256) : 256 = 244,140625Hz

Timer-Dauer = 1 : Timer-Frequenz
Timer-Dauer = 1 : 244,140625Hz = 0,004096 Sekunden = 4,096 Millisekunden

TIMSK0 aktiviert den Interrupt beim Überlauf des Timers und die letzte Zeile aktiviert die Interrupt-Funktion.

int main(void) {             // Hauptprogramm ab hier
  x = 0;                     // LED Status = 0 (ausmachen)
  DDRC = (1 << DDC3);        // Port C3 als Ausgang für LED1
  TCCR0B = (1<<CS02);        // Prescaler 256
  TIMSK0 |= (1<<TOIE0);      // Overflow Interrupt erlauben
  sei();                     // Global Interrupts aktivieren

Ab jetzt läuft der Timer und tcount wird bei jedem Überlauf (also alle 4,096ms) um eins erhöht. In der Endlosschleife wird tcount abgefragt und die LED nach 121 Timer-Überläufen umgeschaltet.

Dafür kontrolliert das Programm ob x gleich 0 ist (LED=>aus). Da diese Variable zu Beginn auf 0 gesetzt wurde, wird die leuchtende LED nun abgeschaltet. Die Änderung von x auf 1 ist der letzte Schritt.

Nach weiteren 121 Überläufen findet erneut eine Vergleich der Variable x statt. Diesmal ist sie nicht 0, daher kommt die else{}-Bedingung zum Tragen. Hier wird der Port für die LED wieder auf low gesetzt um die LED zu aktivieren. Auch hier steht eine Änderung von x am Ende. Diese sorgt dafür, dass beim nächsten Mal die if ( x == 0 ){}-Bedingung durchlaufen wird.

while (1) {                     // Endlosschleife im Hauptprogramm 
    if ( tcount > 121 ) {       // 122*4,096ms Warten (499,712ms)
      if ( x == 0 ) {           // Wenn x gleich 0 ist
	PORTC |= (1<<PC3);      // ...deaktiviere Port C3
	x = 1;                  // Setze X auf 1
      } 
      else {                    // Ansonsten
	PORTC &= ~( (1<<PC3));  // ...aktiviere Port C3
	x = 0;                  // Setze X auf 0
      }

Jedes Mal, wenn tcount größer als 121 war, wird diese Variable wieder auf 0 gesetzt um die nächsten 121 Überläufe zu erkennen. Danach müssen alle geöffneten Klammern geschlossen werden um das Programm zu beenden.

      tcount = 0;               // Zähler wieder auf 0 setzen
    }
  }
}

Das Video

Auch von diesem Test gibt es ein kleines „Beweisvideo“. Und es beweist, dass es blinkt!

Das gesamte Programm

/*
 * InterruptTimer.c
 * by: Timo Gruß
 *
 * Dieses Programm lässt eine LED blinken. Die Taktfrequenz wird durch den 8-Bit Timer0
 * erzeugt, der bei Überlauf einen Interrupt auslöst und tcount hochzählt. Nach 121 
 * Überläufen wird die LED ein-, bzw. ausgeschaltet.
 */ 

#include <avr/io.h>              // Header-Datei f. IO-Register
#include <avr/interrupt.h>       // Header-Datei f. Interruptfunktion
#include <stdint.h>              // Header-Datei f. standard Datentypen

volatile uint8_t tcount;         // Globale Variable (volatile)
uint8_t x;                       // Lokale Variable zum Schalter des Ports

ISR (TIMER0_OVF_vect)
{
  tcount++;                      // tcount +1 pro Timer Interrupt (ca.10ms)
}

int main(void) {                 // Hauptprogramm ab hier
  x = 0;
  DDRC = (1 << DDC3);            // PortD7 als Ausgang für LED1
  TCCR0B = (1<<CS02);            // Prescaler 256
  TIMSK0 |= (1<<TOIE0);          // Overflow Interrupt erlauben 
  sei();                         // Global Interrupts aktivieren 
  while (1) {                    // Endlosschleife im Hauptprogramm 
    if ( tcount > 121 ) {        // 122*4,096ms Warten (499,712ms)
      if ( x == 0 ) {            // Wenn x gleich 0 ist
	PORTC |= (1<<PC3);       // ...deaktiviere Port C3
	x = 1;                       // Setze X auf 1
      } 
      else {                    // Ansonsten
	PORTC &= ~( (1<<PC3));  // ...aktiviere Port C3
	x = 0;                  // Setze X auf 0
      }
      tcount = 0;               // Zähler wieder auf 0 setzen
    }
  }
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.