Ein Timer ist im Wesentlichen ein spezielles Register im Mikrocontroller, dessen Wert hardwaregesteuert fortlaufend um 1 erhöht (oder verringert) wird. Anstatt also im Programm Befehle vorzusehen, die regelmäßig ausgeführt werden und ein Register inkrementieren, übernimmt dies der Mikrocontroller selbstständig.
Ein Timer zählt von 0 bis zu einem bestimmten Wert (Compare Match genannt) oder bis er überläuft. Der Überlauf findet bei 8-Bit Timern beim Wert 255 statt (nicht bei 256!, da er beim nächsten Zyklus wieder bei 0 beginnt) und löst beim Erreichen eines dieser Ereignisse einen Interrupt aus oder toggelt einen Pin. Analog verhält es sich bei 16-Bit Timern, die ebenfalls bis zum Overflow bei $2^{16}-1$ oder bis zum Compare Match-Wert zählen.
Das Verhalten eines Timers wird über spezielle Timer-Kontroll-Register konfiguriert. Diese haben Bezeichnungen wie OCRxA, TIMSKx, oder TCCRx-Register. Über diese Register wird beispielsweise festgelegt, in welchem Modus der Zähler betrieben wird, oder bei welchem Wert ein Interrupt ausgelöst wird und weitere Eigenschaften durch setzen bestimmter Bits. Darum werden diese Register im Folgenden Teil des Beitrags ausführlich erläutert werden.
Arduino Boards basieren größtenteils auf einem Atmel AVR ATmega328 oder ATmega168 Microchip, der 3 Timer (Timer0, Timer1 und Timer2) zur Verfügung stellt. Die Timer0 und Timer2 sind 8-Bit-Timer und der Timer1 ist ein 16-Bit-Timer.
Die Arduino Mega Serie basiert dagegen auf dem Atmel AVR ATmega 1280/2560. Diese Chips haben alle 6 Timer. Die ersten 3 Timer (Timer 0, Timer1 und Timer2) sind somit identisch mit dem ATmega168 / 328. Die übrigen Timer3, Timer4 und Timer5 sind alle 16 bit-Timer. In der Programmierung verhalten sich aber alle identisch.
Bevor wir aber die verschiedenen Timer und deren Kontroll-Register im Detail besprechen vorab eine kurze Zusammenfassung welche Schritte bei der Arduino Timer Programmierung gemacht werden müssen.
Der Arduino besitzt drei Timer (Timer0, Timer1 und Timer2). Die Timer0 und Timer2 sind 8-Bit-Timer und der Timer1 ist ein 16-Bit-Timer. Je nach Anwendung kann der passende Timer ausgewählt werde:
Die Arduino Mega Serie hat sechs Timer. Die ersten 3 Timer (Timer 0, Timer1 und Timer2) sind identisch mit dem ATmega328. Timer3, Timer4 und Timer5 sind alle 16-Bit-Timer.
Nun muss der Arbeitsmodus des Timers festgelegt werden:
Der Prescaler bestimmt, wie stark der Systemtakt geteilt wird. Dazu gibt es im zum Timer zugehörigen Kontrollregister bestimmte Bits (CSx0,CSx1,CSx2) die entsprechend gesetzt werden müssen.
Prescale Faktor | CSx0 | CSx1 | CSx2 |
---|---|---|---|
1 | 1 | 0 | 0 |
8 | 0 | 1 | 0 |
64 | 1 | 1 | 0 |
256 | 0 | 1 | 1 |
1024 | 1 | 1 | 1 |
Mögliche Werte liegen bei 8, 32 (nur bei 16-Bit), 64, 256 oder 1024. Seine Aufgabe ist es, den Systemtakt um den angegebenen Faktor zu teilen. Steht der Prescaler beispielsweise auf 1024, so wird nur bei jedem 1024-ten Impuls vom Systemtakt das Timerregister um 1 erhöht. Entsprechend weniger häufig kommen dann natürlich die Overflows. Die allgemeine Formel für die Overflow-Zeit bis ein Zähler einen Interrupt auslöst lautet:
Overflow-Zeit = (Prescaler × Maximalwert) / CPU-Taktfrequenz
Wobei der Maximalwert bei 8-Bit Timern bei $2^{8}-1 = 255$ liegt und bei 16-Bit Timern entsprechend bei $2^{16}-1 = 65535$. Warum 255 bzw. 65355, da sobald der Timer von 255 auf 256 oder 65535 auf 65536 zählen will, erfolgt der Rücksprung zurück auf 0.
Das setzen der Bits erfolgt in C/C++ über die üblichen Operatoren. Der nachfolgende Code setzt den Prescaler von Timer2 beispielsweise auf 1024 (also alle Bits auf 1) und für Timer1 auf 8.
TCCR2B = (1<<CS22) + (1<<CS21) + (1<<CS20); // prescaler = 1024
TCCR1B |= (1 << CS11); // Prescaler 8 für Timer1
Für CTC oder PWM muß man einen Vergleichswert setzen: Beispielsweise OCR1A = 15624. Was bedeutet, dass der Kanal A des Timer1 zählt bis 15623 zählt und beim nächsten Takt dann einen Interrupt auslöst und wieder bei 0 startet. Da in der Arduino Programmiersprache die Register schon mit richtigem Namen enthalten sind, ist das Setzen kein Problem:
OCR1A = 15624; // Output Compare Register A wird gesetzt
Aktiviere den gewünschten Interrupt. Das wird erreicht in dem das entsprechende Bit im Interrupt-Maskenregister (z.B. TIMSK1) gesetzt wird:
TIMSK1 |= (1 << OCIE1A); // Setze Compare Match A Interrupt für Timer1
Wenn nun ein Ereignis auftritt (z.B. Timer1 erreicht den Vergleichswert der im OCR1A Register definiert wurde), dann wird bei Erreichen dieses Zählwerts das zugehörige Interrupt-Flag gesetzt (in unserem Beispiel wäre das dann das OCF1A Flag, welches sich im TIFR1-Register befindet), somit wird der CPU signalisiert, das ein Interrupt vorliegt. Die CPU unterbricht daraufhin sofort den Programmlauf.
Nun wird die Sprung-Adresse aus der Interrupt-Vektor-Tabelle geholt, die für dieses Ereignis reserviert ist. Für TIMER1_COMPA_vect ist die Adresse z. B. 0x002A. Dort beginnt die ISR, die im Programm mit ISR(TIMER1_COMPA_vect) definiert wurde.
Ein Interrupt-Vektor ist die Adresse im Speicher, an der die ISR für einen bestimmten Interrupt gespeichert ist. Wenn ein Interrupt ausgelöst wird, springt der Mikrocontroller zu dieser Adresse, um den beschriebenen Code in der passenden ISR auszuführen.
Definiere die Funktion, die bei einem Interrupt ausgeführt werden soll. Diese Funktion sollte möglichst kompakt gehalten werden. Im Programmcode sieht das dann beispielsweise so aus:
ISR(TIMER2_COMPA_vect) {
// Dieser Code wird ausgeführt, wenn Timer 2 OCR2A erreicht
// z. B. LED toggeln oder eine Variable setzen
PORTD ^= (1<<PD7); // toggle PD7
}
Durch das Setzen des Timer Control Register TCCR wird der Betriebsmodus festgelegt und der Timer gestartet
Um Interrupts grundsätzlich verwenden zu können, muss das das I-Bit im Statusregister (SREG) des Microprozessors gesetzt werden. Nur dann kann auf Interrupt-Signale reagiert werden. Dies erfolgt beim Arduino über die Funktion interrupts();. Mit der Funktion noInterrupts(); wird dagegen verhindert, dass auf Interrupt-Signale reagiert wird.
Das folgende Beispiel zeigt wie das "Kochrezept" in der Praxis angewendet wird, um beispielsweise einen 1 Hz Zähler zu entwickeln. Die dabei verwendeten Kontrollregister werden im Anschluss ausführlich erklärt. Daher für den Moment einfach als erforderlich hinnehmen.
// Timer1 so konfigurieren, dass er jede Sekunde einen Interrupt auslöst
void setup() {
DDRD |= (1<<PD7);
noInterrupts(); // Deaktiviert alle Interrupts, damit die Timer-Konfiguration nicht gestört wird.
TCCR1A = 0; // Timer1 Konfiguration zurücksetzen
TCCR1B = 0; // damit stören keine alten Einstellungen
OCR1A = 15624; // Vergleichswert für den Timer
// bei 16 MHz / Prescale 1024 = 15625
// wegen Start bei Null ergibt das 15624
TCCR1B |= ( 1<< WGM12); // CTC-Modus = Clear Time on Compare
// bedeutet Timer wird beim erreichen des OCR!A Wertes auf 0 gesetzt
TCCR1B |= ( 1<< CS12) | (1<<CS10); // Prescaler wird auf 1024 gesetzt
TIMSK1 |= (1<< OCIE1A); // Compare Match A Interrupt aktivieren
interrupts(); // Interrupts aktivieren
}
ISR(TIMER1_COMPA_vect) {
PORTD ^= (1<<PD7); // toggle PD7
}
Das Programm erzeugt einen Interrupt pro Sekunde. D.h. bei jedem Interrupt wird der Pin7 getoggelt. Was bedeutet das Signal ist 1 Sekunde High und 1 Sekunde auf Low. Das ergibt ein T von 2 Sekunden und damit eine Frequenz auf dem Oszi von 0,5 Hz. Was die Test-Messung bestätigt.
Wie oben im Beitrag schon beschrieben, sind die Timer-Kontrollregister das Herzstück der Timer-Konfiguration auf dem Arduino. Sie bestimmen, wie der Timer arbeitet, welchen Modus er verwendet, wie schnell er zählt, und was passieren soll, wenn bestimmte Ereignisse eintreten. Hier eine Übersicht der wichtigsten Timer-Kontrollregister:
Die wesentlichen Einstellungen für alle Timer werden in den Timer Control Registern vorgenommen. Über diese drei Register wird festgelegt, wie der Timer arbeitet, welchen Modus er verwendet, wie schnell er zählt und was passieren soll, wenn ein bestimmtes Ereignis eingetreten ist.
Das Register setzt sich aus zwei bzw. bei 16-Bit Timern aus drei 8-Bit Registern zusammen. Diese Register werden mit TCCRxA,TCCRxB undTCCRxC bezeichnet und haben folgenden Aufbau:
--------bis hier von oben stimmig-------
Es gibt verschiedene Interrupt-Arten und somit auch Interrupt-Vektoren. Im folgenden werden diese im Detail vorgestellt:
Jeder Interrupt besitzt einen spezifischen Interrupt-Vector. Er dient dazu, den Prozessor bei einem Interrupt direkt zur richtigen Interrupt Service Routine (ISR) zu leiten – also zu dem Code, der auf das Ereignis reagieren soll.
Timer | Vektoren & Adressen | Beschreibung |
---|---|---|
Timer 0 |
Compare A: TIMER0_COMPA_vect
Adresse: 0x002A Compare B: TIMER0_COMPB_vect
Adresse: 0x0028 Overflow: TIMER0_OVF_vect
Adresse: 0x0026 |
8-bit Timer für einfache Zeitsteuerung, z. B. LED-Blinken |
Timer 1 |
Input Capture: TIMER1_CAPT_vect
Adresse: 0x0016 Compare A: TIMER1_COMPA_vect
Adresse: 0x0018 Compare B: TIMER1_COMPB_vect
Adresse: 0x001A Overflow: TIMER1_OVF_vect
Adresse: 0x001C |
16-bit Timer mit Input Capture – ideal für präzise Messungen |
Timer 2 |
Compare A: TIMER2_COMPA_vect
Adresse: 0x0022 Compare B: TIMER2_COMPB_vect
Adresse: 0x0020 Overflow: TIMER2_OVF_vect
Adresse: 0x001E |
8-bit Timer mit unabhängigem Takt – gut für Audio oder PWM |
Die Adresse der Interrupt-Vektoren gibt an, wo im Speicher die Interrupt-Service-Routine (ISR) liegt. Sie stellt sozusagen das Sprungziel dar. Jeder Interrupttyp (z. B. Compare Match A, Overflow) hat eine eigene Adresse im Speicher. Diese Adresse ist in der Interrupt-Vektor-Tabelle (IVT) hinterlegt.
Um beispielsweise den Timer0 zu programmieren, orientiert man sich am einfachsten erstmal an der Schritt-für-Schritt-Einleitung (siehe oben). Diese schlägt vor wie folgt vorzugehen:
Um den Time0 zu aktivieren und zu konfigurieren, verwenden wir das TCCR0-Register zusammen mit dem Timer-Counter Daten Register TCNT0-Register und dem TIMSK-Register, das definiert, bei welchen Ereignissen welche Art Interrupts ausgelöst werden.
Für die gewählte Frequenz, mit 16 MHz des Arduino und einem 8-Bit Zähler (256 Schritte bis Overflow) berechnet sich der Prescale-Wert wie folgt:
\begin{equation} \begin{aligned} P &= \frac{16 \cdot 10^6 \; Hz} {256 \cdot 100 \; Hz} \\[10pt] P &= 625 \Longrightarrow 1024 \\ \end{aligned} \end{equation}Der nächstgelegene Wert wäre somit 1024, was bedeutet, dass die Bits für die Prescaler Einstellung im Kontrol-Register entsprechend gesetzt werden müssen. Dies ist in der nachfolgenden Grafik veranschaulicht.
Nun haben wir aber noch ein Problem zu besprechen. Mit dem gewählten Prescaler-Wert von 1024 erhalten wir eine Frequenz $f = \frac{16 \; MHz}{256 * 1024} \approx 61 \; Hz$, was natürlich nicht der gesuchten 100 Hz Frequenz entspricht.
Nachfolgend der entsprechende Beispielcode:
volatile uint8_t overflowCount = 0;
void setup() {
// Pin 7 als Ausgang setzen
DDRD |= (1<< PD7); // Direktes Setzen von Pin 7 (PD7)
// Timer0 konfigurieren
TCCR0A = 0; // Normal Mode
TCCR0B = (1<< CS02) | (1<< CS00); // Prescaler 1024
TIMSK0 = (1<<TOIE0); // Overflow Interrupt aktivieren
sei(); // Globale Interrupts aktivieren
}
ISR(TIMER0_OVF_vect) {
overflowCount++;
if (overflowCount >= 2) { // Toggle alle 2 Überläufe → ca. 61 Hz
PIND |= (1<< PD7); // Toggle Pin 7 durch Schreiben ins PIN-Register
overflowCount = 0;
}
}
void loop() {
// Nichts nötig – alles läuft über Interrupts
}
Es gibt auch eine genauere Version mit dem 16-Bit Timer1:
void setup() {
// Pin 7 als Ausgang setzen
DDRD |= (1<<PD7); // Direktes Setzen von Pin 7 (PD7)
// Timer1 konfigurieren
TCCR1A = 0; // Kein PWM, CTC-Modus
TCCR1B = (1<< WGM12) | (1 << CS12); // CTC-Modus, Prescaler 256
OCR1A = 624; // Compare-Wert für 100 Hz
TIMSK1 = (1<< OCIE1A); // Compare Match A Interrupt aktivieren
sei(); // Globale Interrupts aktivieren
}
ISR(TIMER1_COMPA_vect) {
PIND |= (1<<); // Toggle Pin 7
}
void loop() {
// Nichts nötig – alles läuft über Interrupts
}
Als Beispiel soll der Timer2 so konfiguriert werden, dass ein Impuls einer bestimmten Frequenz eine LED Leuten soll. Der Timer2 soll dabei im Normal-Modus betrieben werden mit einem Prescaler von 1024. Dazu müssen die Parameter in den zughörigen Kontroll-Register entsprechend gesetzt werden:
Als erstes wird das Kontrollregister TCCR2A des Timer2 auf 0 gesetzt, damit ist der Pin OC2Ainaktiv und der Timer im Normalmodus aktiviert.Da die WGM2x Bits standardmässig auf 0 gesetzt sind, braucht man hier keine zusätzlichen Aktivitäten durchführen, um den Normal-Modus zu konfigurieren.
Im nächsten Schritt soll der Prescale Faktor auf 1024 gesetzt werden. Dazu müssen die entsprechenden Bits CS1-CS2 im Register TCCR2B auf High gesetzt werden.
Als nächstes wird im TIMSK das Bit OIE2 für den Timer Overflow Interrupt gesetzt. Dadurch wird jedesmal, wenn das TCNT2-Register überläuft (Zählerwert > 255), ein Interrupt ausgelöst.
Nun muss noch die Festlegung des Ausgangs-Pins für die LED definiert werden. Hierzu nutzen wir die Port-Manipulations-Anweisungen, um Pin 7 als Ausgang zu definieren.
Im weitern Schritt wird noch die Interrupt-Service Routine definiert, die den Timer2 Overflow Interrupt Vektor TIMER2_OVF_vect abfängt.
Das komplette Arduino-Programm, ist trotz der komplexen Vorüberlegungen alles in allem sehr kompakt:
void setup(){
TCCR2A = 0x00; // Set register TCCR2A from Timer2 to LOW;
TCCR2B = (1<<CS22) + (1<<CS21) + (1<<CS20); // prescaler = 1024
TIMSK2 = (1<<TOIE2); // if TCNT2 > 255, wird ein Interrupt ausgelöst
DDRD |= (1<<PD7); // Portmanipulation: replaces pinMode(7, OUTPUT);
}
void loop() {
}
ISR(TIMER2_OVF_vect){
PORTD ^= (1<<PD7); // toggle PD7
}
Wir starten mit dem Fundament: einer kleinen Timer-Datenbank für den ATmega328P, die wir später erweitern können. Wir nutzen JSON zum definieren der Datenstruktur, weil es leicht lesbar, editierbar und super mit Python kombinierbar ist. Hier ein erster Entwurf wie sowas aussehen kann:
{
"ATmega328P": {
"Timer0": {
"type": "8-bit",
"prescalers": [1, 8, 64, 256, 1024],
"registers": {
"TCCR0A": {
"WGM00": "Waveform Generation Mode Bit 0",
"WGM01": "Waveform Generation Mode Bit 1"
},
"TCCR0B": {
"CS00": "Clock Select Bit 0",
"CS01": "Clock Select Bit 1",
"CS02": "Clock Select Bit 2",
"WGM02": "Waveform Generation Mode Bit 2"
},
"OCR0A": "Output Compare Register A",
"TIMSK0": {
"OCIE0A": "Output Compare Match Interrupt Enable"
}
}
},
"Timer1": {
"type": "16-bit",
"prescalers": [1, 8, 64, 256, 1024],
"registers": {
"TCCR1A": {
"WGM10": "Waveform Generation Mode Bit 0",
"WGM11": "Waveform Generation Mode Bit 1"
},
"TCCR1B": {
"WGM12": "Waveform Generation Mode Bit 2",
"WGM13": "Waveform Generation Mode Bit 3",
"CS10": "Clock Select Bit 0",
"CS11": "Clock Select Bit 1",
"CS12": "Clock Select Bit 2"
},
"OCR1A": "Output Compare Register A",
"TIMSK1": {
"OCIE1A": "Output Compare Match Interrupt Enable"
}
}
}
}
}
Zum Testen kann diese Datei nun mit folgendem Programm eingelesen und überprüft werden:
import json
# Pfad zur Datei
json_path = "xxxxx pfad zu TimerDataModel.json"
# JSON laden
with open(json_path, "r") as f:
data = json.load(f)
# Mikrocontroller auswählen
mcu = "ATmega328P"
timers = data[mcu]
# Ausgabe
print(f"Timer-Konfigurationen für {mcu}:")
for timer_name, timer_info in timers.items():
print(f"\n🔧 {timer_name} ({timer_info['type']})")
print(f"Prescaler-Optionen: {timer_info['prescalers']}")
print("Register:")
for reg, bits in timer_info["registers"].items():
print(f" {reg}:")
if isinstance(bits, dict):
for bit, desc in bits.items():
print(f" - {bit}: {desc}")
else:
print(f" - {bits}")
Timer-Konfigurationen für ATmega328P:
🔧 Timer0 (8-bit)
Prescaler-Optionen: [1, 8, 64, 256, 1024]
Register:
TCCR0A:
- WGM00: Waveform Generation Mode Bit 0
- WGM01: Waveform Generation Mode Bit 1
TCCR0B:
- CS00: Clock Select Bit 0
- CS01: Clock Select Bit 1
- CS02: Clock Select Bit 2
- WGM02: Waveform Generation Mode Bit 2
OCR0A:
- Output Compare Register A
TIMSK0:
- OCIE0A: Output Compare Match Interrupt Enable
🔧 Timer1 (16-bit)
Prescaler-Optionen: [1, 8, 64, 256, 1024]
Register:
TCCR1A:
- WGM10: Waveform Generation Mode Bit 0
- WGM11: Waveform Generation Mode Bit 1
TCCR1B:
- WGM12: Waveform Generation Mode Bit 2
- WGM13: Waveform Generation Mode Bit 3
- CS10: Clock Select Bit 0
- CS11: Clock Select Bit 1
- CS12: Clock Select Bit 2
OCR1A:
- Output Compare Register A
TIMSK1:
- OCIE1A: Output Compare Match Interrupt Enable
Nun Erweitern wir das Programm mit der Festlegung konkreter Frequenzen und ermitteln die passenden Prescaler Werte, sowie OCR Werte und dem Vorschlag welcher Timer am geeignetsten ist, um die gewünschte Frequenz präzise darzustellen:
import json
# 🔧 Parameter
json_path = "/Users/phof/Desktop/TimerDataModel.json"
mcu = "ATmega328P"
desired_freq = 1000 # Ziel-Frequenz in Hz
cpu_freq = 16_000_000 # Arduino-Taktfrequenz in Hz
selected_timer = "Timer1" # Timer auswählen: "Timer0" oder "Timer1"
# 📥 JSON laden
with open(json_path, "r") as f:
data = json.load(f)
# 🧠 Timer-Daten extrahieren
timer_info = data[mcu][selected_timer]
timer_bits = timer_info["type"]
prescalers = timer_info["prescalers"]
# 🧮 Berechnung Prescaler & OCR
def calculate_timer_settings(desired_freq, cpu_freq, timer_bits, prescalers):
max_count = 255 if timer_bits == "8-bit" else 65535
results = []
for prescaler in prescalers:
ocr = int(cpu_freq / (prescaler * desired_freq) - 1)
if 0 < ocr <= max_count:
achieved = cpu_freq / (prescaler * (ocr + 1))
results.append({
"prescaler": prescaler,
"ocr": ocr,
"achieved_freq": round(achieved, 2),
"error": round(achieved - desired_freq, 2)
})
return results
# 📊 Ergebnisse anzeigen
settings = calculate_timer_settings(desired_freq, cpu_freq, timer_bits, prescalers)
print(f"\n⚙️ Timer-Konfiguration für {selected_timer} ({timer_bits}) bei {desired_freq} Hz Ziel-Frequenz:")
for s in settings:
print(f"- Prescaler: {s['prescaler']}, OCR: {s['ocr']}, erreicht:
{s['achieved_freq']} Hz, Fehler: {s['error']} Hz")
#-----Ausgabe------
⚙️ Timer-Konfiguration für Timer1 (16-bit) bei 1000 Hz Ziel-Frequenz:
- Prescaler: 1, OCR: 15999, erreicht: 1000.0 Hz, Fehler: 0.0 Hz
- Prescaler: 8, OCR: 1999, erreicht: 1000.0 Hz, Fehler: 0.0 Hz
- Prescaler: 64, OCR: 249, erreicht: 1000.0 Hz, Fehler: 0.0 Hz
- Prescaler: 256, OCR: 61, erreicht: 1008.06 Hz, Fehler: 8.06 Hz
- Prescaler: 1024, OCR: 14, erreicht: 1041.67 Hz, Fehler: 41.67 Hz
Nun können wir im nächsten Schritt das Programm erweitern, um die genauen Register Konfigrationen zu ermitteln:
import json
# 🔧 Parameter
json_path = "/Users/phof/Desktop/TimerDataModel.json"
mcu = "ATmega328P"
desired_freq = 1000
cpu_freq = 16_000_000
selected_timer = "Timer1"
# 📥 JSON laden
with open(json_path, "r") as f:
data = json.load(f)
# 🧠 Timer-Daten extrahieren
timer_info = data[mcu][selected_timer]
timer_bits = timer_info["type"]
prescalers = timer_info["prescalers"]
registers = timer_info["registers"]
# 🧮 Berechnung Prescaler & OCR
def calculate_timer_settings(desired_freq, cpu_freq, timer_bits, prescalers):
max_count = 255 if timer_bits == "8-bit" else 65535
results = []
for prescaler in prescalers:
ocr = int(cpu_freq / (prescaler * desired_freq) - 1)
if 0 < ocr <= max_count:
achieved = cpu_freq / (prescaler * (ocr + 1))
results.append({
"prescaler": prescaler,
"ocr": ocr,
"achieved_freq": round(achieved, 2),
"error": round(achieved - desired_freq, 2)
})
return results
# 📊 Ergebnisse anzeigen
settings = calculate_timer_settings(desired_freq, cpu_freq, timer_bits, prescalers)
print(f"\n⚙️ Timer-Konfiguration für {selected_timer} ({timer_bits})
bei {desired_freq} Hz Ziel-Frequenz:")
for s in settings:
print(f"\n🔧 Prescaler: {s['prescaler']}, OCR: {s['ocr']},
erreicht: {s['achieved_freq']} Hz, Fehler: {s['error']} Hz")
print("📘 Register-Empfehlung:")
print(f"- Setze `TCCR1B` → WGM12 = 1 (CTC-Modus aktivieren)")
print(f"- Setze `TCCR1B` → CSx entsprechend Prescaler {s['prescaler']}")
print(f"- Setze `OCR1A` = {s['ocr']} → Zählgrenze")
print(f"- Optional: `TIMSK1` → OCIE1A = 1 → Compare-Match Interrupt aktivieren")
print("🧠 Erklärung:")
print("- WGM12 aktiviert den CTC-Modus → Timer zählt bis OCR1A und startet neu")
print(f"- CS10/CS11/CS12 steuern den Taktvorteiler → Prescaler {s['prescaler']}")
print("- OCR1A bestimmt die Frequenzgrenze")
print("- OCIE1A erlaubt Interrupts bei Compare-Match (wenn gewünscht)")
#-----Ausgabe------
⚙️ Timer-Konfiguration für Timer1 (16-bit) bei 1000 Hz Ziel-Frequenz:
🔧 Prescaler: 1, OCR: 15999, erreicht: 1000.0 Hz, Fehler: 0.0 Hz
📘 Register-Empfehlung:
- Setze `TCCR1B` → WGM12 = 1 (CTC-Modus aktivieren)
- Setze `TCCR1B` → CSx entsprechend Prescaler 1
- Setze `OCR1A` = 15999 → Zählgrenze
- Optional: `TIMSK1` → OCIE1A = 1 → Compare-Match Interrupt aktivieren
🧠 Erklärung:
- WGM12 aktiviert den CTC-Modus → Timer zählt bis OCR1A und startet neu
- CS10/CS11/CS12 steuern den Taktvorteiler → Prescaler 1
- OCR1A bestimmt die Frequenzgrenze
- OCIE1A erlaubt Interrupts bei Compare-Match (wenn gewünscht)
🔧 Prescaler: 8, OCR: 1999, erreicht: 1000.0 Hz, Fehler: 0.0 Hz
📘 Register-Empfehlung:
- Setze `TCCR1B` → WGM12 = 1 (CTC-Modus aktivieren)
- Setze `TCCR1B` → CSx entsprechend Prescaler 8
- Setze `OCR1A` = 1999 → Zählgrenze
- Optional: `TIMSK1` → OCIE1A = 1 → Compare-Match Interrupt aktivieren
🧠 Erklärung:
- WGM12 aktiviert den CTC-Modus → Timer zählt bis OCR1A und startet neu
- CS10/CS11/CS12 steuern den Taktvorteiler → Prescaler 8
- OCR1A bestimmt die Frequenzgrenze
- OCIE1A erlaubt Interrupts bei Compare-Match (wenn gewünscht)
🔧 Prescaler: 64, OCR: 249, erreicht: 1000.0 Hz, Fehler: 0.0 Hz
📘 Register-Empfehlung:
- Setze `TCCR1B` → WGM12 = 1 (CTC-Modus aktivieren)
- Setze `TCCR1B` → CSx entsprechend Prescaler 64
- Setze `OCR1A` = 249 → Zählgrenze
- Optional: `TIMSK1` → OCIE1A = 1 → Compare-Match Interrupt aktivieren
🧠 Erklärung:
- WGM12 aktiviert den CTC-Modus → Timer zählt bis OCR1A und startet neu
- CS10/CS11/CS12 steuern den Taktvorteiler → Prescaler 64
- OCR1A bestimmt die Frequenzgrenze
- OCIE1A erlaubt Interrupts bei Compare-Match (wenn gewünscht)
🔧 Prescaler: 256, OCR: 61, erreicht: 1008.06 Hz, Fehler: 8.06 Hz
📘 Register-Empfehlung:
- Setze `TCCR1B` → WGM12 = 1 (CTC-Modus aktivieren)
- Setze `TCCR1B` → CSx entsprechend Prescaler 256
- Setze `OCR1A` = 61 → Zählgrenze
- Optional: `TIMSK1` → OCIE1A = 1 → Compare-Match Interrupt aktivieren
🧠 Erklärung:
- WGM12 aktiviert den CTC-Modus → Timer zählt bis OCR1A und startet neu
- CS10/CS11/CS12 steuern den Taktvorteiler → Prescaler 256
- OCR1A bestimmt die Frequenzgrenze
- OCIE1A erlaubt Interrupts bei Compare-Match (wenn gewünscht)
🔧 Prescaler: 1024, OCR: 14, erreicht: 1041.67 Hz, Fehler: 41.67 Hz
📘 Register-Empfehlung:
- Setze `TCCR1B` → WGM12 = 1 (CTC-Modus aktivieren)
- Setze `TCCR1B` → CSx entsprechend Prescaler 1024
- Setze `OCR1A` = 14 → Zählgrenze
- Optional: `TIMSK1` → OCIE1A = 1 → Compare-Match Interrupt aktivieren
🧠 Erklärung:
- WGM12 aktiviert den CTC-Modus → Timer zählt bis OCR1A und startet neu
- CS10/CS11/CS12 steuern den Taktvorteiler → Prescaler 1024
- OCR1A bestimmt die Frequenzgrenze
- OCIE1A erlaubt Interrupts bei Compare-Match (wenn gewünscht)
Bitte beachten:
Das Programm ist noch in der Entwicklung...und ist daher in keinster Weise auf Stabilität und Fehlerfreiheit geprüft. Es soll nur einen Impuls geben, wie der Komplexität der Timer-Programmierung begegnet werden kann. Ob hilfreich oder sinnvoll möge jeder selber entscheiden.
Es ist praktisch unmöglich sämtliche Variationen die es in der Timer-Programmierung gibt umfassend darzustellen. Es ist wie bei vielen Dingen nur das eigene Machen und die dabei gewonnene Erfahrung vervollständigt das Wissen. Heute bietet das Internet eine Vielzahl von Quellen, die unbedingt genutzt werden sollen:
Hier vorab eineÜbersicht aller Interrupt-Vektoren des ATmega328P Mikrocontrollers. Diese werden im folgenden noch näher beschrieben und erläutert, wie und wann sie verwendet werden können.
Vektorname | Adresse | Beschreibung |
---|---|---|
RESET | 0x0000 | Reset-Vektor, Startpunkt nach Power-On oder Reset |
INT0_vect | 0x0002 | Externer Interrupt Request 0 |
INT1_vect | 0x0004 | Externer Interrupt Request 1 |
PCINT0_vect | 0x0006 | Pin Change Interrupt Request 0 |
PCINT1_vect | 0x0008 | Pin Change Interrupt Request 1 |
PCINT2_vect | 0x000A | Pin Change Interrupt Request 2 |
WDT_vect | 0x000C | Watchdog Timer Interrupt |
TIMER2_COMPA_vect | 0x000E | Timer/Counter2 Compare Match A |
TIMER2_COMPB_vect | 0x0010 | Timer/Counter2 Compare Match B |
TIMER2_OVF_vect | 0x0012 | Timer/Counter2 Overflow |
TIMER1_CAPT_vect | 0x0014 | Timer/Counter1 Capture Event |
TIMER1_COMPA_vect | 0x0016 | Timer/Counter1 Compare Match A |
TIMER1_COMPB_vect | 0x0018 | Timer/Counter1 Compare Match B |
TIMER1_OVF_vect | 0x001A | Timer/Counter1 Overflow |
TIMER0_COMPA_vect | 0x001C | Timer/Counter0 Compare Match A |
TIMER0_COMPB_vect | 0x001E | Timer/Counter0 Compare Match B |
TIMER0_OVF_vect | 0x0020 | Timer/Counter0 Overflow |
SPI_STC_vect | 0x0022 | SPI Serial Transfer Complete |
USART_RX_vect | 0x0024 | USART Receive Complete |
USART_UDRE_vect | 0x0026 | USART Data Register Empty |
USART_TX_vect | 0x0028 | USART Transmit Complete |
ADC_vect | 0x002A | ADC Conversion Complete |
EE_READY_vect | 0x002C | EEPROM Ready |
ANALOG_COMP_vect | 0x002E | Analog Comparator |
TWI_vect | 0x0030 | 2-Wire Serial Interface (I²C) |
SPM_READY_vect | 0x0032 | Store Program Memory Ready |