Forum: Mikrocontroller und Digitale Elektronik AtMega8 Timer1: zwei interrupts antreiben


von A. F. (elagil)


Lesenswert?

Hallo,

ich benötige zwei timer interrupts. Einen mit ca. 800Hz, einen mit 1Hz.

Timer1 hat ja zwei compare register, kann ich also auch zwei interrupts 
ausführen lassen, die mit den gewünschten Frequenzen ausgelöst werden?

so sieht es derzeit aus:
Der Atmega läuft mit 16Mhz
1
  cli();
2
  /* timer 1 init */
3
  TCCR1B = (1<<WGM12) | (1<<CS12) | (1<<CS10); // prescaler 1024, ctc mode
4
  OCR1A = 15624; // 1 Hz
5
  OCR1B = 20; // ca. 800 Hz
6
  TIMSK = (1<<OCIE1A) | (1<<OCIE1B) | (1<<TOIE0);  // OCR1A compare interrupt, OCR1B ..., timer0 overflow interrupt
7
  
8
  /* timer 0 init */
9
  TCCR0 = (1<<CS01); // prescaler 8 -> ca. 8 kHz
10
  sei();

So, wie es ist, laufen beide interrupt Routinen (COMPA und COMPB) bei 
1Hz. Ich muss vermutlich CTC noch ausschalten, damit nicht mit 800Hz der 
Counter zurückgesetzt wird, schließlich erreiche ich sonst nie die 
15624+1 Takte, die ich für 1Hz brauche. Ich wundere mich aber, dass 
nicht zumindest beide mit 800Hz laufen.

Danke im Voraus
Adrian

von Ingo (Gast)


Lesenswert?

Ich würde einen 2kHz Interrupt genieren und Softwareseitig bis 2000 
sowie bis 25 zählen.
Oder den 800er Hardwareseitig und dann bis 800 zählen

von A. F. (elagil)


Lesenswert?

Ja das ginge, ich könnte ja auch noch Timer2 benutzen, ich frage mich 
nur, wozu ich dann zwei compare register habe? Man kann sie ja für PWM 
benutzen, vielleicht ja auch für normale interrupts

von Karl H. (kbuchegg)


Lesenswert?

Adrian Figueroa schrieb:
> Ja das ginge, ich könnte ja auch noch Timer2 benutzen, ich frage mich
> nur, wozu ich dann zwei compare register habe? Man kann sie ja für PWM
> benutzen, vielleicht ja auch für normale interrupts

Wenn du eine Uhr mit Sekundenzeiger hast (das ist der Timer), dann 
kannst du dir ein Signal generieren lassen, wenn der Sekundenzeiger bei 
28 ist (das ist das eine Compare-Register) und du kannst dir ein Signal 
generieren lassen, wenn der Timer bei 43 ist (das ist das andere Compare 
Register).

Aber: der zeitliche Abstand, in denen sowohl Signal 1, als auch Signal 2 
ausgelöst werden, von einer Runde des Sekundenzählers zur nächsten ist 
immer 60 Sekunden. Denn solange dauert es nun mal, bis der 
Sekundenzähler von der Stellung 28 das nächste mal in Stellung 28 ist. 
Und dasselbe gilt natürlich auch für die Stellung 43: Von einer Runde 
des Sekundenzählers zur nächsten dauert es immer 60 Sekunden.

D.h. mit den beiden Compare-Registern kannst du dir 2 Signale generieren 
lassen, die zueinander zeitlich versetzt sind. Aber die Frequenz, mit 
der die beiden Signale auftreten kannst du nur für beide gemeinsam 
festlegen. Denn diese Frequenz ist nicht davon abhängig, an welchem 
Zählerstand du den Compare-Match auslösen lässt, sondern einzig und 
alleine davon, wie weit der Zähler zählt, bis er einen Überlauf hat und 
wieder bei 0 zu zählen anfängt.

Aber: du kannst ja bei 43 immer die Schreibtischlampe in den jeweils 
anderen Zustand schalten, während du die Küchenbeleuchtung nur bei jedem 
2.ten, 3.ten, 4.ten, 5.ten mal (etc) umschaltest. Dann schaltest du die 
Küchenbeleuchtung nur mit der halben, drittel, viertel, fünftel, etc. 
Frequenz, mit der du die Schreibtischlampe schaltest.

von Vuvuzelatus (Gast)


Lesenswert?

>Aber die Frequenz, mit der die beiden Signale auftreten kannst du nur für
>beide gemeinsam festlegen.

Nicht grundsätzlich! Du gehst davon aus, dass sich die Inhalte von OCRA 
und OCRB während der Laufzeit nicht (oder höchstens "gelegentlich") 
ändern - unter dieser Bedingung ist Deine Aussage richtig. Falsch wird 
sie, wenn man "böse" ist und z. B. den Inhalt von OCRB ständig ändert, 
etwa indem man ihn in jedem OCRB-Interrupt um 20 inkrementiert. Das ist 
ja nicht verboten.

Bin mal gespannt, ob's beim TE Klick macht... ;-)

von Peter D. (peda)


Lesenswert?

1
#include <avr/interrupt.h>
2
3
#define F_CPU   16e6            // 16MHz
4
5
#define F_A     1.0             // 1Hz
6
#define F_B     800.0           // 800Hz
7
8
void t1_init()
9
{
10
  TCCR1A = 0;                   // Mode 0
11
  TCCR1B = 1<<CS12;             // F_CPU / 256
12
  TIMSK |= 1<<OCIE1A | 1<<OCIE1B;  
13
}
14
15
ISR( TIMER1_COMPA_vect )
16
{
17
  OCR1A += (uint16_t)(F_CPU / 256 / F_A + 0.5);
18
}
19
20
ISR( TIMER1_COMPB_vect )
21
{
22
  OCR1B += (uint16_t)(F_CPU / 256 / F_B + 0.5);
23
}

von A. F. (elagil)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Denn diese Frequenz ist nicht davon abhängig, an welchem
> Zählerstand du den Compare-Match auslösen lässt, sondern einzig und
> alleine davon, wie weit der Zähler zählt, bis er einen Überlauf hat und
> wieder bei 0 zu zählen anfängt.

Gut, das ist logisch, sofern der CTC-Modus ausgeschaltet ist. Angenommen 
er ist eingeschaltet, ist dann der kleinere Wert der beiden compare 
register relevant? Offenbar nicht, denn ich bekomme immer den 
Sekundentakt, oder, ich kann das nicht präzise bestimmen, einen Überlauf 
der 16Bit counter-Zahl (ist ja dem Sekundentakt sehr ähnlich).

/edit: das stimmt ja gar nicht! Der Überlauf kommt erst bei ~65000

Ich meinte das mit dem ausgeschalteteten CTC oben so, dass man den 
Counter manuell zurücksetzt, aber auch das hat ja keinen Sinn, es sei 
denn, wie schon von dir erwähnt, das längere Zeitintervall ist von 
seiner Länge ein Vielfaches des kürzeren.

Zum letzten Codeabschnitt:
Zu Beginn sind doch beide compare register genullt, werden bei jedem 
erfolgreichen Vergleich aber um die Zahl erhöht, bis zu der vom jetzigen 
Zählerstand gezählt werden müsste, damit die Periodendauer wie gewünscht 
ausfällt. Anfangs also direkt beim ersten Takt, oder beim ersten 
Überlauf.

Überläufe sind ja unkritisch, weil die Zahlen nicht vorzeichenbehaftet 
sind?
Das scheint mir eine sehr elegante Lösung zu sein, die hat es direkt in 
den Code geschafft! Danke :)

von A. F. (elagil)


Lesenswert?

Eine andere Frage:
Am Ende wird aus dem Ding eine Uhr, man kann mit einem Drehimpulsgeber 
nach rechts drehend die Minuten erhöhen, nach links die Stunden.

Ich will eine eigene Funktion schreiben, mir der ich ihn auslesen kann 
und habe das hier dabei erzeugt:

Es funktioniert leider nicht, im Folgenden der Code:
Es werden nur Drehungen im Uhrzeigersinn erkannt... Ich fürchte auch, 
der Code ist ein bisschen umständlich.
Die Pins werden mit 8kHz abgefragt.
1
// ...
2
typedef enum {clockwise, counterClockwise, undefined} rotaryDirection;
3
// ...
4
typedef struct {uint8_t second, minute, hour, weekday;} time_t;
5
// ...
6
typedef enum {falling, rising, equal} signalState;
7
// ...
8
9
signalState fallRiseSame (uint8_t currentState, uint8_t lastState) {
10
  if (currentState > lastState) {return rising;}
11
  else if (lastState < currentState) {return falling;}
12
  else {return equal;}
13
}
14
15
// gibt zurück, ob die signalflanke bei einem pin steigend, fallend, oder identisch ist
16
// ...
17
18
ISR(ENCODER_TIMER) {  // 8 kHz
19
  static uint8_t lastState[] = {0,0,0};
20
  static uint8_t signalNumber = 3;
21
  static rotaryDirection rotDir = undefined;
22
  time_t container;
23
  
24
  uint8_t currentState[] = {
25
    P1_PIN(P_1) & (1<<ENCODER_PIN1),
26
    P2_PIN(P_2) & (1<<ENCODER_PIN2),
27
    S_PIN(P_S) & (1<<ENCODER_SWITCH)
28
    };
29
  // record rotEnc pin states
30
      
31
  signalState signalChange[signalNumber];
32
  
33
  for (int i=0; i<=signalNumber-1; i++) {
34
    signalChange[i] = fallRiseSame(currentState[i], lastState[i]);
35
  }    
36
  
37
  memcpy(lastState, currentState, signalNumber);
38
  
39
  debounce(signalChange[2]);  // für das drücken des knopfes
40
  
41
  if(rotDir == undefined) {
42
    if(signalChange[0] != equal && signalChange[1] == equal) {  // turn right
43
      rotDir = clockwise;
44
    }
45
    else if (signalChange[1] != equal && signalChange[0] == equal) {  // turn left
46
      rotDir == counterClockwise;  
47
    }
48
  }
49
  else {
50
    if(mode == alarmSetting) {container = alarm;}
51
    else if(mode == timeSetting) {container = time;}
52
      
53
    if(rotDir == clockwise) {
54
      beepMode = off;
55
      timeUpdate(60,container);
56
    }  // +1 Min
57
    
58
    else if(rotDir == counterClockwise) {
59
      beepMode = on;
60
      timeUpdate(3600,container);
61
    }  // +1 H
62
    rotDir = undefined;
63
  }
64
}

vielleicht hat jemand lust drüberzuschauen ;)

danke im voraus!

von Vuvuzelatus (Gast)


Lesenswert?

>  if (currentState > lastState) {return rising;}
>  else if (lastState < currentState) {return falling;}

In der ersten Zeile c > l und in der zweiten l < c?

von A. F. (elagil)


Lesenswert?

Vuvuzelatus schrieb:
> In der ersten Zeile c > l und in der zweiten l < c?

Oh ja das war falsch, danke!

Es läuft jetzt, ich hatte einen Denkfehler. Ich nehme mal an, es geht 
auch hübscher (platzsparender), als mit Arrays, so ist es aber recht 
übersichtlich.
1
ISR(ENCODER_TIMER) {  // 8 kHz
2
  static uint8_t lastState[] = {0,0,0};
3
  static const uint8_t signalNumber = 3;
4
  time_t container;
5
  
6
  uint8_t currentState[] = {
7
    P1_PIN(P_1) & (1<<ENCODER_PIN1),
8
    P2_PIN(P_2) & (1<<ENCODER_PIN2),
9
    S_PIN(P_S) & (1<<ENCODER_SWITCH)
10
    };
11
      
12
  signalState signalChange[signalNumber];
13
  
14
  for (int i=0; i<=signalNumber-1; i++) {
15
    signalChange[i] = fallRiseSame(currentState[i], lastState[i]);
16
  }    
17
  
18
  memcpy(lastState, currentState, signalNumber);
19
  
20
  debounce(signalChange[2]);
21
  
22
    if((signalChange[0] == falling && currentState[1] == 1) || (signalChange[0] == rising && currentState[1] == 0)) {  // turn right
23
      
24
    }
25
    else if ((signalChange[1] == falling && currentState[0] == 1) || (signalChange[1] == rising && currentState[0] == 0)) {  // turn left  
26
      
27
    }
28
}

von A. F. (elagil)


Lesenswert?

Ich habe noch mal ausprobiert, wie es mit dem eleganteren Weg von Peter 
Dannegger aus dem wiki funktioniert, mit folgendem code kann ich die 
Richtung irgendwie nicht erkennen...

Ich habe mir eine Wahrheitstabelle geschrieben:

PIN  21 |BIN
  00 |00  :Pin1 off/Pin2 off -> 0
  10 |01  :Pin1 off/Pin2 on -> 1
  11 |10  :Pin1 on/Pin2 on -> 2
  01 |11  :Pin1 on/Pin2 off -> 3

Das Setzen der "3", sobald Pin1 angeschaltet ist und dem Umschalten des 
unteren bits, sobald der andere Pin an ist, habe ich so nachvollzogen.

Die Differenz der Zustände ist bei einem um 1 kleineren currentState als 
lastState negativ, also eine -1 im Zweierkomplement (entspricht 11111111 
bin). Das niedrigste Bit ist immer 1, sofern die Zahlen sich um eins 
unterscheiden, das zweitniedrigste ist dann eins, wenn currentState 
kleiner war als lastState. Habe ich das richtig verstanden?

damit ist
1
int8_t direction = (diff & 2) - 1;
entweder 1 (diff & 2 = 2) oder -1 (diff & 2 = 0).

im unteren code togglet "beep();" einen pieper. wenn ich einen 
rastschritt drehe, schaltet sich der pieper an und wieder aus, 
unabhängig von der Richtung!
1
ISR(ENCODER_TIMER) {  // 8 kHz
2
  static uint8_t lastState = 0;
3
  time_t *container;
4
5
  uint8_t currentState;
6
   if(P1_PIN(P_1) & (1<<ENCODER_PIN1)) {
7
    currentState = 3; // binary for gray 1
8
   }    
9
   if(P2_PIN(P_2) & (1<<ENCODER_PIN2)) {
10
     currentState ^= 1;
11
   }
12
   int8_t diff = currentState - lastState;   
13
  // S_PIN(P_S) & (1<<ENCODER_SWITCH) switch
14
  
15
  // debounce(signalChange[2]);
16
  
17
  if(clock == alarmSetting) {container = &alarm;}
18
  else if(clock == timeSetting) {container = &time;}
19
  
20
  if(diff & 1) {  // change by 1 step
21
    lastState = currentState;
22
    int8_t direction = (diff & 2) - 1;
23
    if(direction < 0) {  // turn left
24
      beep();
25
      inc(1800,container);  // two incs per click  
26
    }
27
    else if(direction > 0) {  // turn right  
28
      beep();
29
      inc(30,container);
30
    }
31
  }    
32
}

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.