Forum: Mikrocontroller und Digitale Elektronik Atmega328p - Timer1 zählt zu langsam


von Sven G. (sgut)


Lesenswert?

Hallo zusammen,

ich habe das Gefühl, dass das Problem des "zu langsam zählenden 
Timer/Counter" schon oft genug behandelt wurde, aber leider kam ich 
bislang auf keinen Lösungsansatz.

Kurz zu meinem Anliegen:
- jede Millisekunde ein Interrupt auslösen
- Interrupt als ADC Trigger nutzen und ADC-Messung auslösen
- ADC-Interrupt (Messung beendet) nutzen, um Flag (Variable) zu setzen
- Messwert in Hauptschleife weiterverarbeiten

Da ich allerdings erstmal gucken wollte, ob das soweit von der Idee 
hinhaut, war der erste Ansatz die Frequenz von den angedachten 1 kHz auf 
5 Hz zu reduzieren und mit dem Interrupt bzw. der ISR eine LED zu 
toggeln. Damit wäre dann nämlich sichergestellt, dass Zähler und 
entsprechende ISR auch wirklich wie gewollt funktionieren. Ein Schalten 
der LED mit 5 Hz sollte man zudem noch gut wahrnehmen können.

Den Atmega328p betreibe ich mit einem externen 16 MHz Quarz. Die Fuses 
gibt mir AVRDUDE wie folgt aus:

- H: 05
- L: FF
- E: D8

Gemäß der "Rückwärtsfunktion" des FuseCalculators 
(http://www.engbedded.com/fusecalc) ist "CKDIV8" also nicht aktiv. Die 
Taktfrequenz f_CLK sollte daher auch wirklich die verfügbaren 16 MHz 
sein.

Für meine weiteren Darstellungen beziehe ich mich auf das Datenblatt des 
Atmega328p, was es hier gibt: 
http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Da die Schaltfrequenz von 5 Hz nicht direkt über den Timer Overflow 
erreicht werden kann, betreibe ich den Timer im CTC-Modus. Mit einem 
Vorteiler von 256 ergibt sich gemäß der Formel aus Kapitel 20.12.2 
"Clear Timer on Compare Match (CTC) Mode" (Seite 162) für OCR1A einen 
Wert von 6249.

Gemäß Tabelle auf Seite 172 entspricht der angedachte Anwendungsfall dem 
Mode 4, sodass ich die entsprechenden Register so geschrieben habe, wie 
auch aus dem Code deutlich wird:

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
#include <stdlib.h>
5
#include <stdint.h>
6
7
#define     F_CPU       16000000UL
8
#define     LED         PB5
9
10
11
ISR (TIMER1_COMPA_vect) {
12
    // toggle LED
13
    PORTB ^= (1<<PB5);
14
}
15
16
17
void LED_Init(void) {
18
    // define pin LED as output
19
    DDRB |= (1<<LED);
20
}
21
22
23
void TC1_Init(void) {
24
  // enable CTC mode
25
  TCCR1A = (1<<WGM12) | (0<<WGM11) | (0<<WGM10);
26
27
  // timer prescaler
28
  TCCR1B |= (1<<CS12) | (0<<CS11) | (0<<CS10);  // 256
29
30
  // set compare value (for calculation see data sheet p. 162)
31
  OCR1A = 6249;  // for 256 prescaler
32
33
  // enable CTC interrupt
34
  TIMSK1 |= (1<<OCIE1A);
35
}
36
37
38
int main(void) {
39
    TC1_Init();
40
    sei();
41
42
    while(1) {
43
      // do nothing in here
44
    }
45
    return 0;
46
}

Die LED wird geschaltet, was zeigt, dass der Interrupt prinzipiell 
ausgelöst wird. Gemäß meiner Berechnung sollte dies mit 5 Hz geschehen. 
Selbst, wenn man evtl. Toleranzen in Quarz und so berücksichtigt, dann 
sollte die LED definitiv mehrmals pro Sekunde schalten.

Leider habe ich kein Oszilloskop oder Logikanalysator, um mal den Pin 
der LED "abzuhorchen". Die LED blinkt allerdings nicht mit den 
gewünschten 5 Hz, sondern lediglich mit etwas um 1 Hz. Sie bleibt also 
etwa eine Sekunde an und geht dann für eine Sekunde aus usw.

Hat jemand einen Hinweis, woran das liegen könnte? Nachdem ich das 
Datenblatt wieder und wieder gelesen, nochmals den Code und auch die 
Fuses geprüft habe, habe ich leider keinen Ansatz mehr, dem Grund für 
mein Problem auf die Schliche zu kommen. Vielleicht habe ich aber auch 
einen typischen Anfängerfehler gemacht (bin ja auch ein Anfänger) oder 
einfach was übersehen...


Vielen Dank für eure Anmerkungen und Hilfestellungen.

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Sven G. schrieb:
> Vielen Dank für eure Anmerkungen und Hilfestellungen.

 Timer1 läuft in Normal Mode, nicht in CTC.
 Deswegen blinkt die LED mit etwa 0,95Hz (Überlauf bei 65536, ergibt
 1,048s).

 Dein Fehler ist:
1
  // enable CTC mode
2
  TCCR1A = (1<<WGM12) | (0<<WGM11) | (0<<WGM10);
 WGM12 ist in TCCR1B, nicht TCCR1A.

 Deswegen:
1
  // enable CTC mode
2
  TCCR1A = (0<<WGM11) | (0<<WGM10);
3
4
  // timer prescaler + CTC
5
  TCCR1B |= (1<<WGM12) | (1<<CS12) | (0<<CS11) | (0<<CS10);  // 256

 und es sollte gehen.


 P.S.
1
  TCCR1B |= (1<<WGM12) | (1<<CS12) | (0<<CS11) | (0<<CS10);
 ändert die CS11 und CS10 bits nicht in Register TCCR1B.
 Wenn schon, dann Wert zuweisen oder AND benutzen, mit OR kannst
 du die bits nicht auf Null setzen.

: Bearbeitet durch User
von Sven G. (sgut)


Lesenswert?

Marc V. schrieb:
>  Dein Fehler ist:
>
1
>   // enable CTC mode
2
>   TCCR1A = (1<<WGM12) | (0<<WGM11) | (0<<WGM10);
3
>
>  WGM12 ist in TCCR1B, nicht TCCR1A.
>
>  Deswegen:
>
1
>   // enable CTC mode
2
>   TCCR1A = (0<<WGM11) | (0<<WGM10);
3
> 
4
>   // timer prescaler + CTC
5
>   TCCR1B |= (1<<WGM12) | (1<<CS12) | (0<<CS11) | (0<<CS10);  // 256
6
>

Das ist in der Tat die Lösung. Nochmals vielen Dank dafür.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.