Hallo Habr! Ich prĂ€sentiere Ihnen die Ăbersetzung des Artikels "Timer Interrupts" von E.
Vorwort
Mit dem Arduino-Board können Sie eine Vielzahl von Problemen schnell und minimal lösen. Wenn jedoch beliebige Zeitintervalle erforderlich sind (periodisches Abfragen von Sensoren, hochprĂ€zise PWM-Signale, Impulse von langer Dauer), sind Standardbibliotheksverzögerungsfunktionen nicht zweckmĂ€Ăig. FĂŒr die Dauer ihrer Aktion wird die Skizze angehalten und kann nicht mehr verwaltet werden.
In einer Ă€hnlichen Situation ist es besser, die eingebauten AVR-Timer zu verwenden. Wie Sie dies tun und sich nicht in der technischen Wildnis von DatenblĂ€ttern verlieren, heiĂt es in einem erfolgreichen Artikel , auf dessen Ăbersetzung Sie aufmerksam gemacht werden.

Dieser Artikel beschreibt AVR- und Arduino-Timer und deren Verwendung in Arduino-Projekten und Benutzerschaltungen.
Was ist ein Timer?
Wie im Alltag von Mikrocontrollern kann ein Timer in dem Moment, in dem Sie ihn einstellen, in der Zukunft signalisieren. In diesem Moment wird der Mikrocontroller unterbrochen, was ihn daran erinnert, etwas zu tun, um beispielsweise einen bestimmten Code auszufĂŒhren.
Timer arbeiten wie externe Interrupts unabhĂ€ngig vom Hauptprogramm. Anstatt den Millis () - Verzögerungsaufruf zu wiederholen oder zu wiederholen, können Sie einen Timer zuweisen, der seine Aufgabe erledigt, wĂ€hrend Ihr Code andere Aufgaben ausfĂŒhrt .
Angenommen, es gibt ein GerĂ€t, das etwas tun muss, z. B. alle 5 Sekunden eine LED blinken lassen. Wenn Sie keine Timer verwenden, sondern regulĂ€ren Code schreiben, mĂŒssen Sie in dem Moment, in dem die LED gezĂŒndet wird, eine Variable einstellen und stĂ€ndig prĂŒfen, ob der Moment ihres Schaltens gekommen ist. Bei einem Timer-Interrupt mĂŒssen Sie nur den Interrupt konfigurieren und dann den Timer starten. Die LED blinkt genau pĂŒnktlich, unabhĂ€ngig von den Aktionen des Hauptprogramms.
Wie funktioniert der Timer?
Es wirkt durch Inkrementieren einer Variablen, die als ZĂ€hlregister bezeichnet wird . Das ZĂ€hlregister kann abhĂ€ngig von seiner GröĂe bis zu einem bestimmten Wert zĂ€hlen. Der Timer erhöht seinen ZĂ€hler immer wieder, bis er seinen Maximalwert erreicht. Zu diesem Zeitpunkt lĂ€uft der ZĂ€hler ĂŒber und wird auf Null zurĂŒckgesetzt. Ein Timer setzt normalerweise ein Flag-Bit, um Sie darĂŒber zu informieren, dass ein Ăberlauf aufgetreten ist.
Sie können dieses Flag manuell ĂŒberprĂŒfen oder einen Timer umschalten - verursachen Sie automatisch einen Interrupt, wenn das Flag gesetzt ist. Wie bei jedem anderen Interrupt können Sie eine Interrupt Service Routine ( ISR ) zuweisen, um den angegebenen Code auszufĂŒhren, wenn der Timer ĂŒberlĂ€uft. Der ISR selbst löscht das Ăberlaufflag, daher ist die Verwendung von Interrupts aufgrund seiner Einfachheit und Geschwindigkeit normalerweise die beste Wahl.
Um die ZĂ€hlerwerte in genauen Zeitintervallen zu erhöhen, muss der Timer an die Taktquelle angeschlossen werden. Die Taktquelle erzeugt ein sich stĂ€ndig wiederholendes Signal. Jedes Mal, wenn der Timer dieses Signal erkennt, erhöht er den ZĂ€hlerwert um eins. Da der Timer auf einer Taktquelle lĂ€uft, ist die kleinste messbare Zeiteinheit die Zyklusperiode. Wenn Sie ein 1-MHz-Taktsignal anschlieĂen, betrĂ€gt die Timer-Auflösung (oder Timer-Periode):
T = 1 / f (f ist die Taktfrequenz)
T = 1/1 MHz = 1/10 ^ 6 Hz
T = (1 †10 ^ -6) s
Somit betrĂ€gt die Auflösung des Timers eine Millionstel Sekunde. Obwohl Sie eine externe Taktquelle fĂŒr Timer verwenden können, wird in den meisten FĂ€llen die interne Quelle des Chips selbst verwendet.
Timer-Typen
In Standard-Arduino-Boards auf einem 8-Bit-AVR-Chip gibt es mehrere Timer gleichzeitig. Die Atmega168- und Atmega328-Chips verfĂŒgen ĂŒber drei Timer0-, Timer1- und Timer2-Timer. Sie haben auch einen Watchdog-Timer, der zum Schutz vor Fehlern oder als Software-Reset-Mechanismus verwendet werden kann. Hier sind einige Funktionen jedes Timers.
Timer0:
Timer0 ist ein 8-Bit-Timer, was bedeutet, dass sein ZĂ€hlregister Zahlen bis zu 255 (d. H. Ein vorzeichenloses Byte) speichern kann. Timer0 wird von temporĂ€ren Arduino-Standardfunktionen wie delay () und millis () verwendet. Verwechseln Sie es daher am besten nicht, wenn Sie sich um die Konsequenzen kĂŒmmern.
Timer1:
Timer1 ist ein 16-Bit-Timer mit einem maximalen ZĂ€hlwert von 65535 (vorzeichenlose Ganzzahl). Dieser Timer verwendet die Arduino Servo-Bibliothek. Denken Sie daran, wenn Sie ihn in Ihren Projekten verwenden.
Timer2:
Timer2 ist 8 Bit und ist Timer0 sehr Àhnlich. Es wird in der Funktion Arduino Tone () verwendet.
Timer3, Timer4, Timer5:
Die Chips ATmega1280 und ATmega2560 (in Arduino Mega-Varianten installiert) verfĂŒgen ĂŒber drei zusĂ€tzliche Timer. Alle von ihnen sind 16 Bit und funktionieren Ă€hnlich wie Timer1.
Konfiguration registrieren
Um diese Timer verwenden zu können, verfĂŒgt der AVR ĂŒber Einstellungsregister. Timer enthalten viele solcher Register. Zwei davon - Timer / ZĂ€hler-Steuerregister - enthalten Einstellvariablen und werden als TCCRxA und TCCRxB bezeichnet, wobei x die Nummer des Timers ist (TCCR1A und TCCR1B usw.). Jedes Register enthĂ€lt 8 Bits und jedes Bit speichert eine Konfigurationsvariable. Hier sind die Details aus dem Atmega328-Datenblatt:
Die wichtigsten sind die letzten drei Bits in TCCR1B: CS12, CS11 und CS10. Sie bestimmen die Taktfrequenz des Timers. Wenn Sie sie in verschiedenen Kombinationen auswÀhlen, können Sie den Timer so einstellen, dass er mit unterschiedlichen Geschwindigkeiten arbeitet. Hier ist eine Datenblatttabelle, die die Wirkung der Auswahlbits beschreibt:
StandardmĂ€Ăig sind alle diese Bits auf Null gesetzt.
Angenommen, Sie möchten, dass Timer1 mit einer Taktfrequenz mit einem Sample pro Periode lĂ€uft. Wenn es ĂŒberlĂ€uft, möchten Sie die Interrupt-Routine aufrufen, die die an Bein 13 angeschlossene LED in den Ein- oder Ausschaltzustand schaltet. In diesem Beispiel werden wir den Arduino-Code schreiben, aber wir werden die Prozeduren und Funktionen der avr-libc-Bibliothek verwenden, wenn dies die Dinge nicht zu kompliziert macht. UnterstĂŒtzer von reinem AVR können den Code nach Belieben anpassen.
Initialisieren Sie zunÀchst den Timer:
Das TIMSK1-Register ist ein Timer / ZĂ€hler1-Interrupt-Maskenregister. Es steuert den Interrupt, den der Timer verursachen kann. Durch Setzen des TOIE1-Bits wird der Timer angewiesen, zu unterbrechen, wenn der Timer ĂŒberlĂ€uft. Dazu spĂ€ter mehr.
Wenn Sie das CS10-Bit setzen, beginnt der Timer zu zĂ€hlen und sobald ein Ăberlauf-Interrupt auftritt, wird der ISR (TIMER1_OVF_vect) aufgerufen. Dies geschieht immer dann, wenn der Timer ĂŒberlĂ€uft.
Als nÀchstes definieren wir die ISR-Interrupt-Funktion:
ISR(TIMER1_OVF_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Jetzt können wir den loop () -Zyklus definieren und die LED schalten, unabhÀngig davon, was im Hauptprogramm passiert. Um den Timer auszuschalten, setzen Sie jederzeit TCCR1B = 0.
Wie oft blinkt die LED?
Timer1 ist auf Ăberlauf-Interrupt eingestellt. Nehmen wir an, Sie verwenden einen Atmega328 mit einer Taktfrequenz von 16 MHz. Da der Timer 16-Bit ist, kann er bis zum Maximalwert (2 ^ 16 - 1) oder 65535 zĂ€hlen. Bei 16 MHz lĂ€uft der Zyklus 1 / (16 â 10 ^ 6) Sekunden oder 6,25e-8 s. Dies bedeutet, dass 65535 Proben in (65535 â 6,25e-8 s) auftreten und der ISR nach ungefĂ€hr 0,0041 s aufgerufen wird. Und so immer wieder alle viertausendstel Sekunden. Es ist zu schnell, um ein Flackern zu sehen.
Wenn wir ein sehr schnelles PWM-Signal mit einer Abdeckung von 50% an die LED anlegen, erscheint das Leuchten kontinuierlich, aber weniger hell als gewöhnlich. Ein solches Experiment zeigt die erstaunliche Leistung von Mikrocontrollern - selbst ein kostengĂŒnstiger 8-Bit-Chip kann Informationen viel schneller verarbeiten, als wir erkennen können.
Timer-Teiler und CTC-Modus
Um die Periode zu steuern, können Sie einen Teiler verwenden, mit dem Sie das Taktsignal in verschiedene Zweiergrade teilen und die Timer-Periode verlÀngern können. Sie möchten beispielsweise, dass die LED im Abstand von einer Sekunde blinkt. Das TCCR1B-Register enthÀlt drei CS-Bits, die die am besten geeignete Auflösung einstellen. Wenn Sie die Bits CS10 und CS12 setzen mit:
TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12);
dann wird die Frequenz der Taktquelle durch 1024 geteilt. Dies ergibt eine Timerauflösung von 1 / (16 â 10 ^ 6/1024) oder 6,4e-5 s. Jetzt lĂ€uft der Timer alle (65535 â 6.4e-5s) oder fĂŒr 4.194s ĂŒber. Es ist zu lang
Es gibt jedoch einen anderen AVR-Timer-Modus. Dies wird als Coincident Timer Reset oder CTC bezeichnet. Anstatt bis zum Ăberlauf zu zĂ€hlen, vergleicht der Timer seinen ZĂ€hler mit der zuvor im Register gespeicherten Variablen. Wenn die Anzahl mit dieser Variablen ĂŒbereinstimmt, kann der Timer entweder ein Flag setzen oder einen Interrupt verursachen, genau wie im Fall eines Ăberlaufs.
Um den CTC-Modus verwenden zu können, mĂŒssen Sie wissen, wie viele Zyklen Sie benötigen, um ein Intervall von einer Sekunde zu erhalten. Angenommen, das TeilungsverhĂ€ltnis betrĂ€gt immer noch 1024.
Die Berechnung wird wie folgt sein:
(target time) = (timer resolution) * (# timer counts + 1) (# timer counts + 1) = (target time) / (timer resolution) (# timer counts + 1) = (1 s) / (6.4e-5 s) (# timer counts + 1) = 15625 (# timer counts) = 15625 - 1 = 15624
Sie mĂŒssen der Anzahl der Samples eine zusĂ€tzliche Einheit hinzufĂŒgen, da sich der ZĂ€hler im CTC-Modus auf Null zurĂŒcksetzt, wenn er mit dem eingestellten Wert ĂŒbereinstimmt. Das ZurĂŒcksetzen dauert eine Taktperiode, die bei den Berechnungen berĂŒcksichtigt werden muss. In vielen FĂ€llen ist ein Fehler in einer Periode nicht sehr signifikant, aber bei hochprĂ€zisen Aufgaben kann er kritisch sein.
Die Funktion setup () sieht folgendermaĂen aus:
void setup() { pinMode(LEDPIN, OUTPUT);
Sie mĂŒssen auch den Ăberlauf-Interrupt durch einen zufĂ€lligen Interrupt ersetzen:
ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); }
Jetzt schaltet sich die LED genau eine Sekunde lang ein und aus. Und Sie können alles in einer loop () - Schleife tun. Bis Sie die Timer-Einstellungen Àndern, hat das Programm nichts mit Interrupts zu tun. Sie haben keine EinschrÀnkungen bei der Verwendung eines Timers mit verschiedenen Modi und Einstellungen des Teilers.
Hier ist ein vollstĂ€ndiges Startbeispiel, das Sie als Grundlage fĂŒr Ihre eigenen Projekte verwenden können:
Denken Sie daran, dass Sie die integrierten ISR-Funktionen verwenden können, um die Timerfunktionen zu erweitern. Beispielsweise mĂŒssen Sie den Sensor alle 10 Sekunden abfragen. Es gibt jedoch keine Timer-Einstellungen, die eine so lange ZĂ€hlung ohne Ăberlauf ermöglichen. Sie können jedoch ISR verwenden, um die ZĂ€hlvariable einmal pro Sekunde zu erhöhen und dann den Sensor abzufragen, wenn die Variable 10 erreicht. Im STS-Modus aus dem vorherigen Beispiel könnte der Interrupt folgendermaĂen aussehen:
ISR(TIMER1_COMPA_vect) { seconds++; if(seconds == 10) { seconds = 0; readSensor(); } }
Da die Variable innerhalb des ISR geĂ€ndert wird, muss sie als flĂŒchtig deklariert werden. Wenn Sie Variablen zu Beginn des Programms beschreiben, mĂŒssen Sie daher Folgendes schreiben:
volatile byte seconds;
Nachwort des Ăbersetzers
Dieser Artikel hat mir einmal viel Zeit bei der Entwicklung eines Prototyp-Messgenerators gespart. Ich hoffe, dass es fĂŒr andere Leser nĂŒtzlich sein wird.