RoboterNetz.de Foren-Übersicht               
 RN-Wissen Home  -  Community Home  -  Alle Artikel -  Mitglieder -  Moderatoren  -  Bilderliste  -  Letzte Änderungen
 Kategorien  -  Beliebte Seiten  -  Sackgassenartikel  -  Artikel ohne Kategorie  -  Neue Artikel  -  Anmelden

Timer/Counter (Avr)

aus RN-Wissen, der freien Wissensdatenbank

Die Mikrocontroller der AVR-Familie besitzen je nach Typ eine unterschiedliche Anzahl an programmierbaren Timern. Bei den aktuellen ATmegas sind das mindestens ein 8-Bit Timer und ein 16-Bit Timer. Die Timer werden immer Timerx benannt, wobei x fĂŒr die Timernummer steht (also 0, 1, 2, usw.). Die Konfigurationsmöglichkeiten sind von Timer zu Timer unterschiedlich.

Hinweis: Die folgenden Code-Beispiele sind in C programmiert und wurden fĂŒr einen ATmega32 entwickelt. Sie lassen sich also ohne große Änderungen auch auf anderen Mikrocontrollern der AVR-Familie einsetzen. Allerdings hat Atmel bei den neueren ”Cs (etwa Mega88, Mega324 und fast alle der aktuellen Tiny) die Namen fĂŒr die Register vielfach geĂ€ndert.

Inhaltsverzeichnis

Allgemeine Funktionsweise

Timer funktionieren nach dem allgemeinen Prinzip, dass sie eine Ganzzahl (im weiteren als ZÀhler bezeichnet) je nach Betriebsmodus auf- oder abwÀrtszÀhlen, d.h. inkrementieren bzw. dekrementieren.

Angenommen, der Timer arbeitet im einfachsten Betriebsmodus, dem Normalen Modus. Die ZÀhlrichtung des Timers ist aufsteigend gerichtet. Je nach Auflösung, also 8-Bit oder 16-Bit, folgt auf den maximalen ZÀhlerstand wieder die Null. Wenn z.B. bei einem 8-Bit Timer der Wert 255 inkrementiert wird folgt die Null (siehe Grafik).

Bild:AbstrakterZaehlvorgang.png

Der Prescaler

Der Prescaler (eng. = Vorteiler) kann der Takt fĂŒr den Timer herunter geteilt werden. Oft hat man Faktoren von 1, 8, 64 ,256 oder 1024 zur Auswahl. Über das selbe Register kann der Timer auch ganz angehalten werden oder ein externer Takt ausgewĂ€hlt werden. Ein externer Takt darf dabei höchstens halb so hoch wie der Prozezessortakt sein. Hier eine Grafik die den Prescaler veranschaulicht:

Bild:Prescaler.png

Das obere Diagramm zeigt den Betrieb ohne Prescaler, das untere mit Prescaler (:2). Die gestrichelte Linie zeigt, wann der Timer weiterzÀhlt.

Im Teil Die Betriebsmodi wird weiter auf die praktische Verwendung des Prescalers eingegangen.

Die Betriebsmodi

Die AVR-Timer können in unterschiedlichen Betriebsmodi betrieben werden. Diese sind:

  • Normaler Modus
  • CTC Modus
  • PWM

Normaler Modus (Normal Mode)

Der einfachste Betriebsmodus ist der normale Modus. Er funktioniert wie im Abschnitt "Allgemeine Funktionsweise" beschrieben. Die ZĂ€hlrichtung des Timers ist immer aufsteigend, bis zum Überlauf - da fĂ€ngt der ZĂ€hler wieder bei 0 an. Der Überlauf kann einen Interrupt (Timer-Overflow) auslösen. Im einfachsten Fall kann dieser Modus im folgendem Diagramm dargestellt werden:

Bild:NormalerModus_1.png

Der ZĂ€hler des Timers (im Diagramm oben, die aufsteigende und dann wieder zurĂŒckgesetzte Linie) ist in dem Register TCNTx gespeichert, wobei x fĂŒr eine Zahl steht. Soll z.B. auf den Timer0 (siehe Datenblatt des jeweiligen Controllers) des Controllers zugegriffen werden, so ist an TCNT eine 0 anzuhĂ€ngen, also TCNT0. Wie lange es braucht, bis der ZĂ€hler einen Overflow auslöst, ist von der Taktfrequenz des Controllers, dem eingestellten Prescaler-Wert und von der Timerauflösung abhĂ€ngig. Nun wĂ€re es ja sehr unpraktisch, wenn wir den ZĂ€hler nicht anpassen könnten. Denn sonst mĂŒssten wir unsere Software, die den Timer benutzt, evtl. anpassen und viel rechnen, um z.B. fĂŒr 1000 ms zu schlafen. Deswegen kann auf den ZĂ€hler zugegriffen werden und ihn vorladen, bevor dieser wieder vom eigentlichen Timer hochgezĂ€hlt wird. Dies veranschaulicht folgendes Diagramm:

Bild:NormalerModus_1_Vorladen.png

Dadurch kann eingestellt werden, wie lange es dauert, bis ein Overflow auftritt. Um zu berechnen, welchen Wert wir vorladen mĂŒssen, kann auch ein Java-Applet genutzt werden, siehe unter Weblinks Java Applet.

NatĂŒrlich kann das auch "von Hand" berechnet werden. Die Berechnung des Preloader- sowie Prescalerwerts bei Verwendung der Overflow-Interrupts, eines Prescalers von 64 (nicht alle Prescaler können verwendet werden) und eines Quarzes mit der Frequenz von 8 MHz sieht folgendermaßen aus (gesuchte Frequenz betrĂ€gt 1000 Hz unter der Verwendung des Timer0 eines ATmega32):

  1. Prescale = Frequenz * 1000000[Hz] = 8000000
  2. Wir definieren den maximalen ZĂ€hlerwert. Dieser ist bei einem 8-Bit Timer 256, bei einem 16-Bit Timer 65536. In unserem Fall ist der maximale ZĂ€hlerwert 256, weil Timer0 verwendet wird.
  3. Nun wird die Variable Prescale (s.o.) durch den verwendeten Prescaler (64) geteilt (8000000Hz / 64 = 125000).
  4. Als nÀchstes wird der im dritten Punkt errechnete Wert durch die gesuchte Frequenz geteilt = 125000 / 1000Hz = 125.
  5. Nun wird mathematisch ĂŒberprĂŒft, ob der errechnete Wert aus dem vierten Punkt kleiner als der maximale ZĂ€hlerwert ist. Trifft dies zu, so wird der errechneten Wert vom maximalen ZĂ€hlerwert subtrahiert( = 256 - 125 = 131).

Damit haben wir den Wert errechnet, der bei jedem Interrupt, den der Timer0 auslöst, in TCNTx (in diesem Fall TCNT0) nachgeladen werden muss, damit die Interrupts in dem gewĂŒnschten Zeitabstand von einer Millisekunde ausgelöst werden.

Zwischen dem Timer Overflow und dem tatsĂ€chlichen Aufrufen der ISR mit dem Nachladen des Timers ergibt sich eine kleine Verzögerung, die nicht einmal immer gleich ist. Bei einem genĂŒgend großen Prescaler (z.B. 64) kommt durch die Verzögerung kein zusĂ€tzlicher Timerschritt zustande, und auch die Methode mit dem Nachladen liefert exacte Ergebnisse. Bei kleinen Prescalern kommt es durch die Verzögerung zu lĂ€ngeren und nicht immer gleichen ZeitabstĂ€nden. Wenn möglich wird fĂŒr die Erzeugung einer konstanten Interruptrate deshalb besser der CTC Moduls benutzt.

Zusammenfassend ein Beispielprogramm:

/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz mit 7,3728 MHz
betrieben wird. Im Abstand von etwa 0,1 ms erzeugt der Timer einen Interrupt, also eine
Frequenz von 10000 Hz. Der Timer wird auf einen Prescaler von 64 und
einem Preloader von 244 konfiguriert.*/

volatile uint8_t countTimer2;	// Speichert den aktuellen ZĂ€hlerwert

// ISR zum auffangen der Interrupts:
SIGNAL(SIG_SIG_OVERFLOW2)       // alter Form, fĂŒr neuere GCC Versionen:   ISR(TIMER2_OVF_vect)
{
	TCNT2 = 244;		// Nachladen
	countTimer2++;
}

// Initialisierung:
TCCR2 = (1<<CS22);		// Prescaler von 64 und damit Timer starten
TCNT2  = 244;			// Vorladen
TIMSK |= (1<<TOIE2);		// Interrupts aktivieren 
sei();

// Funktionen zum benutzen der Timer:
/** Diese Funktion nicht aufrufen. Wird von sleep_millisec aufgerufen.
Bei t=10 schlÀft die Funktion 1 ms. */
inline void sleep (uint8_t t)
{
	// countTimer2 wird in der ISR oben inkrementiert
        countTimer2 = 0;         // 1 Byte Typ, daher kein cli()... sei() nötig
	while (countTimer2 < t);
}

/** SchlÀft x-Millisekunden. */
inline void sleep_millisec(uint16_t msec)
{
	uint16_t i;
	for(i=0; i<msec; i++) {
		sleep(10);
	}
}

Dieses Beispiel zeigt nicht unbedingt eine vorbildliche Nutzung des Timers. Eine ISR einfach nur zum schnellen HochzÀhlen der Zeit verbraucht recht viel Rechenzeit und sollte sonst eher vermieden werden. Das HochzÀhlen der "Zeit" ist eigentlich genau das, was der Timer in Hardware macht - nur halt nicht immer in geraden Zeitschritte, wie 0,1 ms, sondern halt in den Schritten, die der Prescaler vorgibt (z.B. 256 Takte). Die Umrechnung kann man aber gut auch bei der Wartezeit vorher, oder bei einer gemessenen Zeit nachher machen.

Input Capture

Die 16 Bit Timer haben eine "Input Capture" Funktion. Dieser Hardwareteil dient zur genauen Zeitmessung. Die typische Anwendung ist die Messung von kurze Zeiten, wie z.B. die Zeit fĂŒr eine Motorumdrehung. Außer in einigen PWM Betriebsarten, wo das ICP Register als TOP-wert fĂŒr den Timer benutzt wird, ist die ICP-Funktion immer aktiv. Wenn am ICP Pin die ĂŒber das Bit "ICESx" eingestellte Flanke auftritt, wird der aktuelle ZĂ€hlerstand in das ICP Register kopiert. Außerdem kann ein Interrupt ausgelöst werden. Der Interrupt wird, wie die anderen Timer Interrupts, in den Registern TIMSK und TIFR an- oder abgestellt. Man kann zwar die ICP-Funktion selber nicht ohne weiteres abschalten, aber natĂŒrlich den dazugehörigen Interrupt. Als eine spezielle Funktion ("Noise Cancler") gibt es die Möglichkeit sehr kurze Pulse (unter 4 Zyklen) zu unterdrĂŒcken. In der Regel kann man diese Funktion angestellt lassen, denn so schnell kann man die Daten ohnehin nicht verarbeiten.

Solange die 16 Bit des Timers ausreichen ist die Benutzung ganz einfach: Der Timer wird mit dem gewĂŒnschten Vorteiler im normalen Modus gestartet. Im ICP-Interrupt wird die Differenz aus zwei aufeinanderfolgenden Zeiten (Werte in ICP-Register) berechnet. Dazu wird jeweils die vorherige Zeit im RAM zwischengespeichert. Wenn man bei der Rechnung (vorzeichenlose 16 Bit Zahlen) eventuelle ÜberlĂ€ufe ignoriert, bekommt man die richtige Zeitdifferenz, auch wenn der Timer wĂ€hrend der Messzeit einen Überlauf hatte. Das funktioniert so einfach, denn wenn noch weitere (höherwertige) Bytes vorhanden wĂ€ren, damit es keinen Überlauf gibt, wĂŒrde man genau so die unteren Bits berechnen.

Etwas komplizierter wird es, wenn die 16 Bit Auflösung nicht mehr ausreicht. Dann kann der Timer-Überlauf benutzt werden, um auch lĂ€ngere Zeiten mit voller Auflösung zu messen. Die wesentliche Schwierigkeit ist es, den Fall zu berĂŒcksichtigen, dass ein Überlauf Interrupt und der ICP Interrupt fast gleichzeitig ausgelöst werden. Es kann passieren, dass der ICP-Interrupt aufgerufen wird, obwohl eigentlich erst der Overflow Interrupt dran gewesen wĂ€re. Dieser seltene Fall lĂ€sst sich daran erkennen, dass das Overflow-Interrupt Flag gesetzt ist und der Wert im ICP Register klein ist (high Byte < 128, meistens 0).

Beispielpropgramm (fĂŒr GCC): (Bisher nur im Simulator getestet)

// Beispielprogramm fĂŒr Zeitmessung mit ICP-Funktion
// Erweiterung des Timers auf 32 Bit durch Software
// Es wird die Periodendauer am ICP-Eingang gemessen und als ASCII via UART ausgegeben
// Code fĂŒr Mega48 / Mega88 / Mega 168 / ...
// mit leichten Anpassungen auch fĂŒr Tiny2313, Mega16, Mega32,...

#include <stdlib.h>          // fĂŒr utoa
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>       // UnterstĂŒtzung fĂŒr sleep mode

#define F_CPU  1000000UL     // Definition der Frequenz, ist ggf. im makefile
#define BAUD        19200UL
#define UBRR_BAUD   ((F_CPU/(16UL*BAUD))-1)

typedef union {  // union erlaubt einen effektiven, separaten Zugriff auf Teile der Variable
        unsigned long i32;
        struct {uint8_t i8l;        // low
                uint8_t i8m;        // mid
		unsigned int high;  // high, soft timer                
               };
              } convert32to8;

volatile unsigned long timestamp;    // volatile wegen Zugriff im Interrupt
volatile unsigned int softtimer;
volatile unsigned long zeitdifferenz;
unsigned long zeit;
char puffer[12];           // Puffer fĂŒr Ausgabe als Ascii

ISR(TIMER1_OVF_vect)   	   // Timer1 Überlauf
{ 
  ++softtimer; 		   // zĂ€hlen der ÜberlĂ€ufe
} 

ISR(TIMER1_CAPT_vect)      //  Flanke an ICP pin
{ 
  convert32to8 cap;        // Variablendeklaration
   
  cap.i8l = ICR1L;         // low Byte zuerst, high Byte wird gepuffert
  cap.i8m = ICR1H;  
  // overflow verpasst, wenn ICR1H klein und wartender Overflow Interrupt
  if ((cap.i8m < 128) && (TIFR1 & (1<<TOV1)))
   {   // wartenden timer overflow Interrupt vorziehen
     ++softtimer;         
     TIFR1 = (1<<TOV1);    // timer overflow int. löschen, da schon hier ausgefĂŒhrt
   }
  cap.high = softtimer;    // obere 16 Bit aus Software ZĂ€hler
  zeitdifferenz = cap.i32 - timestamp;
  timestamp = cap.i32;     // Zeit merken
}

void uart_init(void)       // USART initialisieren (Mega48 etc.)
{
// Baudrate einstellen (Normaler Modus)
// kann bei Ă€lteren AVR Typen etwas anders sein (kein UBRR0H, dafĂŒr prescaler)
  UBRR0H = (uint8_t) (UBRR_BAUD>>8);             // bei Mega32  anders !
  UBRR0L = (uint8_t) (UBRR_BAUD & 0x0ff);        // bei Mega32 UBRRL
  UCSR0B = (1<<TXEN0);   // Aktivieren des Senders, bei Mega32 UCSRB
  UCSR0C = (1<<UCSZ01)|(1<<UCSZ00)|(1<<USBS0);   // bei Mega32 UCSRC
// Einstellen des Datenformats: 8 Datenbits, 2 Stopbit:
}

void putser(char c)  // sende ein Byte via UART
{
  while ( !( UCSR0A & (1<<UDRE0)) ) ; // Warten bis der Sendepuffer frei ist
  UDR0 = c;
}

void main(void)
{
 unsigned char i;
// Datenrichtungen:
 DDRB  = 0;  			  // Alles EingÀnge, PB0 ist ICP
 PORTB = 0xFF - (1<<PB0);         // Pullups an EingĂ€ngen außer ICP
 DDRC  = 0;     		  // EingÀnge
 PORTC = 0xFF;  		  // Pullups an EingÀngen 
 DDRD  = (1<<PD1);                // EingĂ€nge, außer PD1 = Tx (UART)
 PORTD = 0xFF- (1<<PD1);          // Pullups an alle EingĂ€ngen (außer TX)
// Timer1 initialisieren:
 TCCR1A = 0;                      // normal mode, keine PWM AusgÀnge
 TCCR1B = (1<< ICNC1) + (1<<CS10)    // start Timer mit Systemtakt
          + (1 << ICES1);            // steigende Flanke auswÀhlen
 TIMSK1 = (1<<TOIE1) + (1<<ICIE1);   // overflow und Input-capture aktivieren, Mega32: TIMSK
 TIFR1 = (1<<TOIE1) + (1<<ICIE1);    // Schon aktive Interrupts löschen, Mega32: TIFR
// UART initialisieren:
 uart_init();                

 zeitdifferenz = 0;
 softtimer = 0;         // wird fĂŒr Zeitdifferenzmessung nicht mal gebraucht,
                        // denn Differenz geht auch ĂŒber Überlauf bei Softtimer
 set_sleep_mode (SLEEP_MODE_IDLE);  // idle Mode: timer lÀuft weiter, int zum aufwachen

 sei();                 // Interrupts erlauben: Messung startet
 while (1)
 {
  sleep_enable ();      // Sleep Befehl freigeben 
  sei();
  sleep_cpu();          // wartet auf irgendeinen Interrupt, z.B. ICP, timer_ovr,...
  sleep_disable();      // Sleep Befehl sperren
  cli();		// Interrupt sperren wegen Zugriff auf volatile Variable
  zeit = zeitdifferenz;
  zeitdifferenz = 0;   	// als Markierung fĂŒr ungĂŒltigen Wert
  sei();
  if (zeit > 0)
  { 
    ultoa(zeit,puffer,10);  // nach ASCII umwandeln
    i = 0;
    while (puffer[i])
     {
      putser(puffer[i++]);  // Ausgabe
     }
    putser(13); putser(10); // Zeilenumbruch senden
  }
 }  // Ende von While-schleife
}

CTC Modus (Clear Timer on Compare Match mode)

Viele Timer haben "Output-Compare" Register: OCRx oder OCRAx,OCRBx. Wenn der ZÀhler den darin eingestellten Wert erreicht hat, kann ein Interrupts ausgelöst werden. Der CTC Modus ist eine Erweiterung des "Output-Compare"-Funktion. Der CTC Modus eignet sich besonders, um einen mit konstanter Frequenz wiederkehrenden Interrupt zu erzeugen. Wie im normalen Modus zÀhlt der Timer hoch. Wenn der Wert im OCRx Register erreicht wird, wird zusÀtzlich zum möglichen Interrupt der ZÀhler wieder auf 0 gesetzt. Es kann also die maximalen ZÀhlergrenze selber definiert werden. Dieses Diagramm veranschaulicht den CTC Modus.

Bild:NormalerModus_CompareMatch.png

Beispielprogramm:

/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz 
   mit 7,3728 MHz betrieben wird. Im Abstand von 0,01 ms erzeugt der Timer 
   einen Interrupt, also eine Frequenz von 100000 Hz (oder 100 kHz). 
   Der Timer wird auf einen Prescaler von 1 und einem OCR2-Wert von 73 konfiguriert. */

volatile uint8_t countTimer2;	// Speichert den aktuellen ZĂ€hlerwert

// ISR zum auffangen der Interrupts:
SIGNAL(SIG_OUTPUT_COMPARE2)
{
  countTimer2++;
}

// Initialisierung:
TCCR2 = (1<<CS20) | (1<<WGM21);	// Prescaler von 1 | CTC-Modus (siehe unten fĂŒr Beschreibung)
OCR2  = 73;			// Vergleichswert
TIMSK |= (1<<OCIE2);		// Interrupts aktivieren und damit Timer starten
sei();

// Funktionen zum benutzen der Timer:
/** Diese Funktion nicht aufrufen. Wird von sleep_millisec aufgerufen.
Bei t=100 schlÀft die Funktion 1 ms. */
inline void sleep(uint8_t t)
{
	// countTimer2 wird in der ISR oben inkrementiert
	countTimer2 = 0;
	while (countTimer2 < t);
}

/** SchlÀft x-Millisekunden. */
inline void sleep_millisec(uint16_t msec)
{
	uint16_t i;
	for(i=0; i<msec; i++) {
		sleep(100);
	}
}

PWM

Eine hĂ€ufige Aufgabe fĂŒr Mikrocontroller ist die Erzeugung von PWM-Signalen, zum Beispiel fĂŒr Motorsteuerungen. Daher sind in den meisten AVRs PWM-Einheiten als Hardware vorhanden. Sie sind direkt mit den Timern verbunden und nutzen diese als Taktquelle. Die Hardware-PWM-Einheiten haben den Vorteil, sehr wenig Rechenzeit in Anspruch zu nehmen. Es muss nur die PWM aktiviert werden und bei Änderungen den gewĂŒnschten Wert in ein Register schreiben. Der Rest lĂ€uft automatisch und unabhĂ€ngig vom restlichen Programm, ohne den AVR stĂ€ndig zu beschĂ€ftigen wie bei einer PWM-Lösung in Software. Allerdings stehen meist nur zwei bis drei solcher PWM-KanĂ€le zur VerfĂŒgung, die außerdem an bestimmte Pins gebunden sind. FĂŒr die meisten Roboter mit zwei Antriebsmotoren reicht dies aber fĂŒr gewöhnlich aus.

nutzbare Pins am AVR

Die Hardware-PWM-Funktion steht nur an bestimmten Pins zur VerfĂŒgung. In der PinbelegungsĂŒbersicht im Datenblatt ist erkenntbar, dass als Sonderfunktion in Klammern "OC..." angegeben ist. Beim Mega32 sind dies zb. OC0 an PB3, OC1A an PD5, OC1B an PD4 und OC2 an PD7. Der Mega32 hat also insgesamt vier Hardware-PWM-KanĂ€le. Die Zahl hinter dem "OC" gibt an, zu welchem der Timer dieser PWM-Kanal gehört. Wenn noch ein Buchstabe dahinter kommt, dann gehören mehrere PWMs zu diesem Timer. Beim Mega32 sind also OC1A und OC1B demselben Timer, nĂ€mlich Timer1, zugeordnet.

Zu beachten ist, dass die fĂŒr die PWM benutzten Pins zuvor explizit als Ausgang konfiguriert werden mĂŒssen! Ansonsten gelangt das PWM-Signal nicht nach draußen!

Funktionsprinzip

Das "OC" in den Pinbezeichnungen steht fĂŒr "Output Compare Unit", also frei ĂŒbersetzt Ausgangs-Vergleicher-Einheit. Dies beschreibt die Funktionsweise der PWM-KanĂ€le: der ZĂ€hlerstand des Timers wird fortlaufend mit einen einstellbaren Referenzwert verglichen, und wenn beide Werte ĂŒbereinstimmen, kann ein Ausgangspin des AVRs automatisch geschaltet werden (und ein Interrupt ausgelöst werden, was allerdings fĂŒr die PWM-Funktion nicht relevant ist). Dies entspricht dem Verfahren im Beispiel zur Software-PWM. Es lĂ€uft nun allerdings vollautomatisch im Hintergrund, sodass der Controller nicht damit belastet wird.

Die verschiedenen PWM-Modi

Es gibt -je nach AVR und Timer- etliche Betriebsarten, in denen die PWM-Einheit betrieben werden kann. Sie unterscheiden sich vor allem darin, wie schnell und mit welchen Nebeneffekten sich Änderungen des Sollwertes auf das Ausgangssignal auswirken. FĂŒr den Anfang sind diese Unterschiede erst einmal nebensĂ€chlich, und fĂŒr eine einfache Motorsteuerung meist auch irrelevant. Daher wird hier zunĂ€chst der "Fast PWM Mode" ("Schneller PWM Modus", weil hier die grĂ¶ĂŸte Ausgangsfrequenz möglich ist) beschreiben, welcher der einfachste von allen ist.

Hierbei zĂ€hlt der Timer immer von Null an aufwĂ€rts, bis er den Maximalwert (teilweise einstellbar) erreicht hat. Dann lĂ€uft er ĂŒber und fĂ€ngt von vorne an. Wie schnell dies geschieht, wird, wie im normalen Modus, ĂŒber den Prescaler eingestellt. Der gewĂŒnschte PWM-Ausgangswert wird im "OCRn"-Register abgelegt. Er darf zwischen Null und dem Maximalwert des Timers liegen. Er wird nun mit dem Timer-Wert verglichen. Was dann passiert, regeln die "COM..."-Bits. Sie bestimmen, wie der Ausgang geschaltet wird. Die ĂŒbliche Konfiguration ist, dass bei Erreichen des Sollwertes die AusgĂ€nge auf high geschaltet werden, und beim Überlauf auf low. Damit ergibt sich ein nichtinvertiertes PWM-Signal. Schließt man (ĂŒber einem passenden Motortreiber!) einen Motor an, dreht er sich bei einem Sollwert von 0 gar nicht und beim Maximalwert mit voller Geschwindigkeit.

Beispielcode fĂŒr den Timer1 des Mega16/32 (und vieler anderer AVRs):

#include <avr/io.h>
#include <util/delay.h> // Warteschleife fĂŒr die Demo. FĂŒr die eigentliche PWM nicht benötigt!  

/*  PWM-Beispiel fĂŒr Mega16/32 (beiden haben den gleichen Timer)
	Benutzt wird Timer1 im Fast PWM Mode, 8 Bit Auflösung
	Die PWM-Signale liegen auf PD5/OC1A und PD4/OC1B
*/	

// 1. Den Prescaler einstellen, der die Frequenz festlegt
	TCCR1B |= (1<<CS12); //Prescaler 256
	
// 2. Den Timer in den Fast PWM Mode, 8 Bit schalten
//    ACHTUNG: Die WGM-Bits sind auf beide Konfigurationsregister verteilt!
	TCCR1A |= (1<<WGM10);
	TCCR1B |= (1<<WGM12);
	
// 3. Compare Output mode einstellen: Pin geht auf high bei Compare match, auf low bei Überlauf. 
//    Ergibt nichtinvertierte PWM.	
	TCCR1A |= (1<<COM1A1) | (1<<COM1B1) ;	
	
// In diesen Registern wird der gwĂŒnschte PWM-Wert abgelegt. Erlaubter Bereich: 0 bis 255.
	OCR1A =	0;
	OCR1B = 0;	

// 4. Die Pins als AusgÀnge konfigurieren. Erst jetzt liegt das PWM-Signal an den Pins an!	
	DDRD |= (1<<PD4) | (1<< PD5);

/*Nun ist der PWM-Modus aktiv! Der Ausgangswert kann nun ĂŒber die Register OCR1A und OCR1B
  vorgegeben werden. Man könnte ihnen per define noch einen Zweitnamen verpassen, zb */
#define MotorLinks OCR1A
#define MotorRechts OCR1B
//Und nun kann man per 
MotorLinks = 127;
MotorRechts = 127;
//seinen Roboter mit halber Kraft vorwÀrts fahren lassen.

/*PWM-Demo: Die PWM-Werte werden erst bis zum Maximalwert erhöht und dann wieder verringert. 
Ein angeschlossener Motor wird beschleunigen und dann wieder abbremsen. */
	uint8_t wert;
	while(1)
	{
		for (wert=0; wert<255; wert++)
			{
				OCR1A =	wert;
				OCR1B = wert;
				_delay_ms(10);
			}
	
		for (wert=255; wert>0; wert--)
			{
				OCR1A =	wert;
				OCR1B = wert;
				_delay_ms(10);
			}	
	}

Es sind also vier Einstellungen zu treffen:

  1. Takt anlegen per Prescaler
  2. PWM-Modus wÀhlen
  3. Ausgangs-Aktion festlegen
  4. Pins als AusgÀnge schalten

Diese Schritte mĂŒssen bei jedem AVR-PWM-Kanal ausgefĂŒhrt werden. Die genauen Registernamen und Werte können sich jedoch je nach Timer-AusfĂŒhrung etwas unterscheiden.

RegisterĂŒbersicht

Hinweis: Diese Registertabellen wurden fĂŒr den aktuellen Atmel Controller Mega16 und Mega32 erstellt. Wenn Sie ein anderes Modell verwenden kann es sein, dass ein oder mehrere Register nicht existieren, oder sie eine andere Bezeichnung haben.

TIMSK
Mit diesem Register, der von allen Timern verwendet wird, lĂ€sst sich die InterruptausfĂŒhrung und Art des jeweiligen Timers bestimmen.

Bit: 7 6 5 4 3 2 1 0
Name: OCIE2 TOIE2 TICIE1 OCIE1A OCIE1B TOIE1 OCIE0 TOIE0
  • OCIE2 (Timer/Counter2 Output Compare Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter2 Compare Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TOIE2 (Timer/Counter2 Overflow Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter2 Overflow Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TICIE1 (Timer/Counter1, Input Capture Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Input Capture Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • OCIE1A (Timer/Counter1 Output Compare A Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Output Compare A Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • OCIE1B (Timer/Counter1 Output Compare B Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Output Compare B Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TOIE1 (Timer/Counter1 Overflow Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Overflow Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • OCIE0 (Timer/Counter0 Output Compare Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, so wird der Timer/Counter0 Compare Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TOIE0 (Timer/Counter0 Overflow Interrupt Enable)
    Wenn dieses Bit gesetzt wird, so wird der Timer/Counter0 Overflow Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).


Siehe auch

Weblinks

'Persönliche Werkzeuge

USB-RS232 Modul
Controller einfach
mit USB nachrĂŒsten
robotikhardware.de


Lichtprofi.de
LED Shop
www.lichtprofi.de