GREETBoard ATMega32 – Interrupt gesteuerte LED

Zweiter Teil der GREETBoard ATMega32 Board Programmierung. HIER geht es zum ersten Teil.

Nachdem nun die ersten LED geblinkt und die Taster erkannt wurden, möchte ich ein paar Dinge programmieren, die mir etwas schwerer erscheinen. Zum Ziel gesetzt habe ich mir:

  1. die blinkenden LED durch einen Interrupt-gesteuerten Ablauf zu realisieren,
  2. die Taster softwareseitig zu entprellen,
  3. die TWI (bzw. I2C) Kommunikation zum Laufen zu bringen.

Ich erhoffe mir, dass ich durch diese drei Dinge etwas mehr in die Programmiersprache C eintauchen und damit auch in Zukunft die Möglichkeiten der Mikrocontroller besser ausnutzen kann.

Interrupt gesteuertes LED Blinklicht

Für diese Übung nutze ich das Mikrocontroller.net AVR-GCC-Tutorial (Link) über „Programmieren mit Interrupts“. Der Unterschied zum ersten Blinklicht besteht darin, dass der Wechsel von heller zu dunkler LED nicht durch ein Wartebefehl (delay) sondern durch einen Timer erfolgen soll. Dieser soll eine Variable hochzählen, die ab einer bestimmten Größe eine Aktion auslöst.

Die Header Dateien

Zu Beginn ist wichtig, dass die entsprechenden Header-Dateien eingebunden werden, deren Funktionen benötigt werden. Da es sich um Standard-Header handelt, muss außer dem Include Befehl nichts beachten werden.

#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

Variablen

Als nächstes benötigen wir noch zwei Variablen. Ein Zähler soll vom Timer ständig erhöht werden, bis eine definierte Schwelle überschritten ist, diese nenne ich tcount. Da sie im Hauptprogramm und in der Timer-Routine benutzt wird, muss sie „global“ verfügbar sein, sprich im gesamten Programm. Dies geschieht durch den Befehl volatile. Außerdem handelt es sich bei tcount um eine 8-Bit lange, vorzeichenlose Zahl (0-255), die mit uint8_t definiert wird. Zusätzlich wird noch ein „Schalter“ verwendet, der zwischen 1 und 0 pendelt und den Zustand der LED abbildet. Möglicherweise könnte der Ausgang direkt abgefragt werden, doch in diesem Fall mache ich das nicht.
Siehe dazu de Abschnitt im AVR-GCC-Tutorial (Link).

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

Timer konfigurieren

Das Thema Timer ist nicht in wenigen Worten erklärt, aber die grundlegende Funktion ist relativ einfach. Prinzipiell ist er nichts anderes als ein Register, das automatisch hochgezählt wird. Überschreitet es dabei sein „Maximum“, z.B. 8-Bit Zähler = 255, springt das Register wieder auf 0 und ein Interrupt wird ausgelöst. Dadurch wird sofort zu einer ISR (Interrupt Service Routine) gesprungen, diese abgearbeitet und anschließend das Hauptprogramm fortgeführt. Diese sehr kurze Beschreibung reicht bei weitem nicht, um die vielen Funktionen der Timer zu verstehen. Wer mehr hierüber erfahren möchte, kann hier (Link) weiterlesen.

Zuerst definiere ich nun, das in der Timer ISR der tcount-Zähler um eins erhöht werden soll, wenn das 8-Bit Timer-Register überläuft (TIMER0_OVF_vect). Die ISR befindet sich außerhalb des Hauptprogramms. Auf tcount komme ich später zurück.

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

Innerhalb des Hauptprogramm, aber außerhalb der Hauptschleife (while (1) {…}) wird der Timer konfiguriert. Der Befehl sei(); aktiviert die Interrupt-Funktion des Mikrocontrollers, ohne die der Timer kein Interrupt erzeugen würde.

int main(void)    // Hauptprogramm ab hier
{
...
  TCCR0 = (1<<CS02);    // Prescaler 256
  TIMSK |= (1<<TOIE0);   // Overflow Interrupt erlauben
  sei();                   // Aktiviere Interrrupts
...

Wie im oben verlinkten Tutorial, hat ein 8-Bit Timer insgesamt 256 „Schritte“ zur Verfügung (16-Bit = 65536). Die Frage ist, wie lange benötigt der Mikrocontroller, bis diese abgezählt wurden. Dafür ist der Takt des Mikrocontroller und der sogenannte Prescaler (dt. Vorteiler) verantwortlich. Hier ein Rechenbeispiel für dieses Programm

(Hz / Prescaler) / 256 = x Hz
1 / x Hz = x Sekunden

(16.000.000Hz / 256) / 256) = 244,140625 Hz
1 / 244,140625 Hz = 4,096 ms

Soll die LED alle ca 500ms umschalten, muss tcount 122 mal hochgezählt werden (122*4,096ms = 499,712ms).

Ausgang definieren

Den I/O Ports des Mikrocontroller muss mitgeteilt werden, ob sie als Ein- oder Ausgang funktionieren sollen. Für die Eingangsfunktion wird das entsprechende Register auf 0, für Ausgänge auf 1 gesetzt. Die geschiet durch den Befahl DDRx. x steht für den entsprechenden Port, in diesem Programm soll die interne LED am Port D7 benutzt werden. Das Hauptprogramm wird also erweitert um:

...
  DDRD = (1 << DDD7);   // PortD7 als Ausgang für LED1
...

Die Hauptschleife

Der Timer läuft, der Ausgang ist definiert und die Variablen stehen bereit, dann fehlt nur noch die eigentliche Funktion des Programms: Die LED soll blinken. Vor der Hauptschleife muss nur noch die „Schalter“-Variable x = 0 gesetzt werden um einen definierten Zustand zu erhalten, dann folgt auch schon der folgende Code:

int main(void)                 // Hauptprogramm ab hier
{
  x = 0;
  ...
  while (1) {                  // Endlosschleife im Hauptprogramm
    if ( tcount > 121 ) {      // 122*4,096ms Warten (499,712ms)
      if ( x == 0 ) {          // Wenn x gleich 0 ist
        PORTD &= ~( (1<<PD7)); // ...deaktiviere Port D7
        x = 1;                 // Setze X auf 1
      }                        // Zweites if beendet
      else {                   // Ansonsten
        PORTD |= (1<<PD7);     // ...aktiviere Port D7
        x = 0;                 // Setze X auf 0
      }                        // else beendet
      tcount = 0;              // Zähler wieder auf 0 setzen
    }                          // Erstes if beendet
  }                            // Ende While-Schleife
}                              // Ende Hauptprogramm

Wie das folgende, sehr kurze Video belegt, funktioniert dieses Programm sehr gut. Für die LED habe ich das GREETBoard Output Modul benutzt.

Hier das gesamte Programm zum Kopieren:

/*
 * InterruptTimer.c
 * by: Timo Gruß
 */ 

#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;
  DDRD = (1 << DDD7);        // PortD7 als Ausgang für LED1
  TCCR0 = (1<<CS02);         // Prescaler 256
  TIMSK |= (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
PORTD &= ~( (1<<PD7));       // ...deaktiviere Port D7
x = 1;                       // Setze X auf 1
      } 
      else {                 // Ansonsten
PORTD |= (1<<PD7);           // ...aktiviere Port D7
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.