Forum: Mikrocontroller und Digitale Elektronik Timer0 CTC zu langsam mit Mega644


von Paul (Gast)


Lesenswert?

Hallo,

ich habe da ein kleines Problem mit dem 8Bit Timer0 eines Mega644.
Der Timer scheint zu langsam zu laufen.
Der Mega644 läuft mit 20MHz das stimmt 100% da liegt das Problem nicht.
Ich möchte Timer0 verwenden da das Programm nachher auf einem Tiny13 
laufen soll der nur einen 8Bit Timer hat.
Die Timerfrequenz soll bei 1MHz liegen, 1µs Interval.
Um den Timer zu testen habe ich folgendes simples Programm geschrieben:
1
#include <stdlib.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <inttypes.h>
5
#include <string.h>
6
7
volatile uint8_t onoff = 0;
8
volatile uint16_t time = 0;
9
volatile uint16_t time2 = 0;
10
11
ISR(TIMER0_COMPA_vect) // timer0 clear-timer on compare
12
{
13
  time++;
14
  if (time >= 1000)
15
  {
16
    time = 0;
17
    time2++;
18
  }
19
}
20
21
int main(void)
22
{
23
  time = 0;
24
  time2 = 0;
25
  onoff = 0;
26
27
  cli();
28
29
  DDRD |= (1 << DDD6);
30
  PORTD &= ~(1<<PD6);
31
32
  TCCR0A = (1 << WGM01); // Timer0 CTC mode
33
  TIMSK0 |= (1 << OCIE0A); // Timer0 clear-timer on compare interrupt enabled
34
  OCR0A = 19; // Time0 TOP = 1 µs
35
  TCCR0B |= (1 << CS00); // Timer0 start with prescaler 1
36
37
  sei();
38
  
39
    while (1) 
40
    {
41
    if (time2 >= 1000)
42
    {
43
      time2 = 0;
44
      if (onoff == 0)
45
      {
46
        onoff = 1;
47
        PORTD |= (1 << PD6);
48
      }
49
      else
50
      {
51
        onoff = 0;
52
        PORTD &= ~(1<<PD6);
53
      }
54
    }
55
    }
56
}

Im Timer CTC Interrupt handler wird die Variable "time" erhöht, wenn sie 
größer gleich 1000 ist, also nach 1ms, wird Variable "time2" erhöht und 
"time" wieder auf 0 gesetzt.
Ist "time2" größer gleich 1000, also nach 1s, wird eine LED ein bzw. 
ausgeschaltet und "time2" wieder auf 0 gesetzt.

Also alles recht easy, nur die LED bleibt nicht nur für 1 Sekunde wie zu 
erwarten wäre an bzw. aus sondern für ca. 3 Sekunden, eher gefühlt 2,5 
Sekunden. Ich weiss das ist jetzt keine hoch wissentschaftliche Methode 
das zu testen, aber es sollte doch funktionieren.

Wo liegt da jetzt der Fehler? Zuviel code im Timer Interrupt? - glaube 
eher nicht. Es macht auch absolut keinen Unterschied ob der vergleich 
von "time2" und das ein oder ausschalten der LED in der while Schleife 
in main steht oder im Timer Interrupt selbst.

Datenblatt schon zig mal durchforstet, wenn ichs richtig verstanden habe 
sollten die Einstellungen für den Timer richtig sein.

bei 20 MHz
prescaler 1 CS00 = 1
OCR0A = 19
CTC mode WGM01 = 1
clear-timer on compare interrupt enabled OCIE0A = 1
sollte meiner Meinung nach ein 1µs Interval sein bzw. 1MHz Frequenz.

Hat da evtl. jemand ne Idee?

von Peter D. (peda)


Lesenswert?

Paul schrieb:
> Zuviel code im Timer Interrupt? - glaube
> eher nicht.

Doch.
Mit Prolog, Epilog kommst Du kaum unter 30 Zyklen.

Etwas kürzer kriegst Du den Interrupt mit 3 8Bit-Variablen, die bis 100 
zählen, da der AVR ein 8Bitter ist.

von S. Landolt (Gast)


Lesenswert?

Ohne das nun genau nachzurechnen, rein gefühlsmäßig halte ich das für zu 
knapp. Versuchen Sie mal für OCR0A das Dreifache, ob Sie dann 3 us 
sehen.

von Peter II (Gast)


Lesenswert?

Paul schrieb:
> time++;
>   if (time >= 1000)
>   {
>     time = 0;

hier wird wohl wieder mal das volatile Problem zuschlagen, es vergrößert 
den code merklich!

time muss gar nicht volatile sein.

von Ingo Less (Gast)


Lesenswert?

1MHz sind zu straff, das wären nur 20 Takte zwischen den ISRs

von Paul (Gast)


Lesenswert?

Vielen Dank für die Hilfe!

Es lag wirklich an der zu hohen Frequenz, habs auf 100KHz runter gesetzt 
und nun läufts einwandfrei. (Prescaler 8 und OCR0A 24)

Btw. ohne "volatile" wird zu viel code "wegoptimiert" und das Programm 
tut einfach garnix mehr, habs ausprobiert.

von Peter II (Gast)


Lesenswert?

Paul schrieb:
> Btw. ohne "volatile" wird zu viel code "wegoptimiert" und das Programm
> tut einfach garnix mehr, habs ausprobiert.

dann kann bei time nicht sein, das wird ja nur in der ISR verwendet.

(oder es gibt noch code, den du uns nicht gezeigt hast)

von Paul (Gast)


Lesenswert?

Ok, korrigiere, nur "time" als nicht volatile geht!

von Carl D. (jcw2)


Lesenswert?

Nur lokal benutzte Daten, wie time, sollte man nicht global definieren. 
Der Grund ist hier sicher daß der Inhalt zwischen 2 Aufrufen der ISR 
überleben soll. Und dazu gibt es "static"-Variablen. Diese werden von 
der Laufzeitumgebung mit 0 initialisiert. Falls andere Werte gebraucht 
werden, dann kann das bei der Deklaration passieren und wird genau ein 
mal getan. Es braucht dann auch kein "volatile", denn wer außer der ISR 
selbst sollte den Wert ändern, Bugs ausgenommen. Und das sorgt für 
kompakteren Code.
1
ISR(TIMER0_COMPA_vect) // timer0 clear-timer on compare
2
{
3
  static uint16_t time;
4
5
  time++;
6
  if (time >= 1000)
7
  {
8
    time = 0;
9
    time2++;
10
  }
11
}

: Bearbeitet durch User
von neuer PIC Freund (Gast)


Lesenswert?

Wenn ich simulavr richtig deute, wird der Timer-Interrupt noch während 
der ISR neu getriggert (siehe 0x00f2), inmitted des Epilog. Nicht gut 
für code in der main.
1
0x0040:                                JMP 98                                
2
 0x0040:                                CPU-waitstate
3
 0x0040:                                CPU-waitstate
4
 0x0098:                                PUSH R1 SP=0x10fa 0x0 
5
 0x0098:                                CPU-waitstate
6
 0x009a:                                PUSH R0 SP=0x10f9 0x0 
7
 0x009a:                                CPU-waitstate
8
 0x009c:                                IN R0, 0x3f 
9
 0x009e:                                PUSH R0 SP=0x10f8 0x35 
10
 0x009e:                                CPU-waitstate
11
 0x00a0:                                EOR R1, R1 SREG=[--H---ZC] 
12
 0x00a2:                                PUSH R18 SP=0x10f7 0x1 
13
 0x00a2:                                CPU-waitstate
14
 0x00a4:                                PUSH R24 SP=0x10f6 0x0 
15
 0x00a4:                                CPU-waitstate
16
 0x00a6:                                PUSH R25 SP=0x10f5 0xfc 
17
 0x00a6:                                CPU-waitstate
18
 0x00a8:                                LDS R24, 0x103 
19
 0x00a8:                                CPU-waitstate
20
 0x00ac:                                 interrupt on index 16 is pending
21
LDS R25, 0x104 
22
 0x00ac:                                CPU-waitstate
23
 0x00b0:                                ADIW R24, 1 SREG=[--H-----] 
24
 0x00b0:                                CPU-waitstate
25
 0x00b2:                                CPI R24, 0xe8 SREG=[-------C] 
26
 0x00b4:                                LDI R18, 0x03 
27
 0x00b6:                                CPC R25, R18 SREG=[--HS-N-C] 
28
 0x00b8:                                BRCS ->0x002a                                
29
 0x00b8:                                CPU-waitstate
30
 0x00e4:                                STS 0x104, R25 
31
 0x00e4:                                CPU-waitstate
32
 0x00e8:                                STS 0x103, R24 
33
 0x00e8:                                CPU-waitstate
34
 0x00ec:                                POP R25 SP=0x10f6 0xfc 
35
 0x00ec:                                CPU-waitstate
36
 0x00ee:                                POP R24 SP=0x10f7 0x0 
37
 0x00ee:                                CPU-waitstate
38
 0x00f0:                                POP R18 SP=0x10f8 0x1 
39
 0x00f0:                                CPU-waitstate
40
 0x00f2:                                POP R0 SP=0x10f9 0x35 
41
 0x00f2:                                 interrupt on index 16 is pending
42
CPU-waitstate
43
 0x00f4:                                OUT 0x3f, R0 
44
 0x00f6:                                POP R0 SP=0x10fa 0x0 
45
 0x00f6:                                CPU-waitstate
46
 0x00f8:                                POP R1 SP=0x10fb 0x0 
47
 0x00f8:                                CPU-waitstate
48
 0x00fa:                                RETI SP=0x10fc 0x0 SP=0x10fd 0x97  IrqSystem: IrqHandler Finished Vec: 16
49
50
 0x00fa:                                CPU-waitstate
51
 0x00fa:                                CPU-waitstate
52
 0x00fa:                                CPU-waitstate

von Paul (Gast)


Lesenswert?

neuer PIC Freund schrieb im Beitrag #4399125:
> Wenn ich simulavr richtig deute, wird der Timer-Interrupt noch während
> der ISR neu getriggert (siehe 0x00f2), inmitted des Epilog. Nicht gut
> für code in der main.

Ja, ich habe das einfach nicht bedacht das da ja noch ein bischen mehr 
dazu gehört eine ISR aufzurufen etc. da war die Zeit für die Abarbeitung 
einfach zu gering.
Irgendwo muss man ja einfach mal an die Grenzen bei den "kleinen 
Dingern" stoßen und wie sich gezeigt hat war die Frequenz von 1MHz für 
die eigentlich Aufgabe (CPPM Signal von einem OrangeRX R415X Empfänger 
auslesen und 2 x PWM für Motorsteuerung erzeugen) eh overkill.
Das hätte der ATTiny13 den ich nachher eigentlich dafür einsetzen will 
nie hinbekommen mit seinem ~9.6 MHz internem Takt.

Carl D. schrieb:
> Der Grund ist hier sicher daß der Inhalt zwischen 2 Aufrufen der ISR
> überleben soll. Und dazu gibt es "static"-Variablen.

Danke Carl, das es ja auch noch static gibt vergesse ich leider manchmal 
:)
wobei sich mir aber "die Nackenhaare aufstellen" wenn ich eine statische 
Variablendeklaration in einer Methode sehe, was natürlich absolut 
legitim ist, aber für mich eher ein etwas ungewohnter Stil.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

time2 muss atomar zugegriffen werden.

von Horst M. (horst)


Lesenswert?

Nur, um's nochmal zu dokumentieren:
Es geht prinzipiell mit dem ATmega644 bei 20 MHz.


Zählen wir die Taktzyklen.
1
 .org 0
2
 rjmp start
3
4
;minimum interrupt response time 4 cycles
5
 .org TIMER0_COMPA_vect
6
 in r13,SREG             ;1
7
 subi r22,1              ;1      decrement time variable
8
 sbci r23,0              ;1
9
 brne timer_x            ;2/1
10
 movw r23:r22,r15:r14    ;1      reload time variable
11
 adiw r25:r24,1          ;2      increment time2 variable
12
timer_x: out SREG,r13    ;1
13
 reti                    ;4
14
15
start:
16
;initialize TIMER0 and port here
17
 ldi r22,LOW(1000)       ;initialize variable "time"
18
 ldi r23,HIGH(1000)
19
 movw r15:r14,r23:r22    ;save reload value for variable "time"
20
 clr r24                 ;clear variable "time2"
21
 clr r25
22
 sei
23
;do main stuff

Wenn time2 nicht angefasst wird, braucht ein Interruptdurchlauf 14+x 
Taktzyklen, anderenfalls 16+x (x ist abhängig vom im Moment des 
Interrupts gerade ausgeführten Befehl und kann m.E. zwischen 0 und 2 
liegen).
Es bleibt also auch bei 1 MHz Interruptfrequenz noch ein bißchen Zeit 
für das Hauptprogramm übrig.
Logischerweise dürfen r13-r15 und r22-r23 nicht mehr anderweitig 
verwendet werden und auch r24-r25 ist programmweit ausschließlich für 
time2 reserviert, aber ein bißchen Schwund ist halt immer...

Das Beispiel ist natürlich grenzwertig, ein Interrupt alle 10us macht 
das Leben deutlich leichter und dann klappt's auch locker mit dem 
ATtiny13.

von S. Landolt (Gast)


Lesenswert?

Wenn allerdings ein Interruptvektor nach TIMER0_COMPA_vect benötigt 
wird, kommen u.U. noch zwei Takte für rjmp hinzu.

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.