Forum: Mikrocontroller und Digitale Elektronik Input Capture und TimerOverflow


von Ingo L. (corrtexx)


Lesenswert?

Hallo Leute,
ich habe ein Kapazitätsmessgerät (mit AVR) gebaut und bin derzeit gerade 
etwas unschlüssig. Es wird die Zeit von 0-Tau mit dem ICP gemessen. 
Funktioniert auch alles super. Ich möchte alle Eventualitäten absichern. 
Eine besondere Eventualität ist folgende:

Timer1 wird im CTC-Modus mit einem Zählumfang von 25000 betrieben. 
Gleichzeitig werden auch die Overflows gezählt. Soweit alles gut. 
Angenommen der Fall tritt auf, dass der Zähler Gerade übergelaufen ist 
und es gibt den Capture. Derzeit sieht mein Code wie folgt aus:
1
static uint16_t Overflows;
2
static uint16_t OldOverflows = 0;
3
static uint16_t OldTicks = 0;
4
5
/* Timeroverflow */
6
ISR ( TIMEBASE_2_5ms )
7
{
8
  static uint16_t Debounce = 0;
9
  
10
  Overflows++;
11
  
12
  if ( Flags.MuxEnable ){
13
    Mux();
14
  }
15
  
16
  if ( Flags.Timeout < TIMEOUTMAX && Flags.Timeout ) Flags.Timeout++;
17
  if ( Debounce ) Debounce--;
18
  
19
  if ( START_PRESSED && !Debounce && !Flags.Timeout){
20
    Debounce = DEBOUNCETIME;
21
    Flags.StartMeasurement = SET;
22
  }
23
  
24
  if  ( OFFSET_PRESSED && !Debounce && !Flags.Timeout ){
25
    Debounce = DEBOUNCETIME;
26
    Flags.OffsetCal = SET;
27
  }
28
}
29
  
30
/* Input Capture */
31
ISR ( CAPTURE )
32
{
33
   uint16_t NewTicks = ICR1;
34
   
35
  /* Special Case => Overflow + 1 */
36
  if ( NewTicks == 0 ){
37
    Overflows++;
38
    TIFR1 |= (1<<TOV1);    
39
  }
40
41
  uint16_t DiffOverflows = Overflows - OldOverflows;
42
  OldOverflows = Overflows;
43
  
44
  uint16_t DiffTicks = NewTicks - OldTicks;
45
  OldTicks = NewTicks;  
46
  
47
  Capacity = (int32_t)DiffTicks + ((int32_t)DiffOverflows * 25000);
48
  if ( !Flags.OffsetCal )Capacity -= Offset;
49
  if ( Capacity > CAPACITYMAX ) Capacity = CAPACITYMAX;
50
  else if ( Capacity < 0 ) Capacity = 0;
51
  Flags.MeasureDone = SET;
52
}
Jetzt stellt sich mir die Frage, wenn der Capture bei einem Zählwert von 
1 kommt, wird dann der Overflowinterrupt trotzdem vorher aufgerufen? Es 
liegt ja nur ein einziger Takt dazwischen. Und die Latenzzeit beträgt 
lt. DB 4 Takte. Leider gibt das DB keine weiteren Hinweise dazu.
Leider lässt sich dieser Zustand nicht durch Ausprobieren testen...

: Bearbeitet durch User
von spess53 (Gast)


Lesenswert?

Hi

>Jetzt stellt sich mir die Frage, wenn der Capture bei einem Zählwert von
>1 kommt, wird dann der Overflowinterrupt trotzdem vorher aufgerufen? Es
>liegt ja nur ein einziger Takt dazwischen.

Bei CTC gibt es keinen Overflowinterrupt.

MfG spess

von Ingo L. (corrtexx)


Lesenswert?

spess53 schrieb:
> Bei CTC gibt es keinen Overflowinterrupt.
Ok, das ist dann der Compare-Match Interrupt. Haste Recht. Aber du als 
Assembleraner kannst mir doch auch sicher weiterhelfen bei meiner 
Ursprungsfrage oder?

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Ingo L. schrieb:

> Jetzt stellt sich mir die Frage, wenn der Capture bei einem Zählwert von
> 1 kommt, wird dann der Overflowinterrupt trotzdem vorher aufgerufen?

Du hast es noch nicht erkannt: Aber dieser Fall ist irgendwie sogar noch 
die kleinere Hälfte des Problems...

Ich würde dir empfehlen, die Sache erstmal ganz ohne Interrupts 
durchzuspielen, einzig durch Polling der IRQ-Flags. Das ist sehr viel 
einfacher als ein vollständig interruptgesteuertes Szenario (aber immer 
noch hinreichend komplex, um Einsteiger heftig in's Schwitzen zu 
bringen).

Wenn du DAS beherrschst, dann hast du das erst das nötige Rüstzeug, um 
auch die vollständig interruptgetriebene Variante zuverlässig 
hinzubekommen. Die ist nämlich richtig Tricky.

Und es hat kaum damit zu tun, dass die AVR8-Architektur keine echten 
Interruptprioritäten kennt, sondern vor allem damit, dass die 
Timer-Hardware nicht auf den MCU-Kern wartet. DAS vor allem macht die 
Sache so kompliziert. Wenn du ein Gehirn im Kopp hast, wirst du das bei 
der Umsetzung des Polling-Szenarios selber erkennen...

von Lurchi (Gast)


Lesenswert?

Das (fast) gleichzeitige auftreten vom "Überlauf" und ICP interrupt ist 
tatsächlich etwas komplizierter. Ob man es über Interupts oder per 
Polling macht, macht dabei keinen so großen Unterschied.

Das Abfragen des ICP Werte für mögliche Sonderfälle ist schon richtig 
gedacht, man muss es aber noch mit einem ggf. noch wartenden Interrupt 
kombinieren. Durch noch auszuführende Befehle kann der Sonderfall auch 
bei mehr als nur dem Wert 0 auftreten. Wenn noch andere Interrupts aktiv 
sind oder zum atomaren Auslesen Interrupts zeitweise gesperrt werden 
kann das auch einige 10 Zyklen lang sein. Der Fall den man abfangen muss 
ist, dass der ICP Wert klein ist, aber noch ein Interrupt für den 
"Überlauf" aussteht.

von Ingo L. (corrtexx)


Lesenswert?

c-hater schrieb:
> Wenn du DAS beherrschst, dann hast du das erst das nötige Rüstzeug, um
> auch die vollständig interruptgetriebene Variante zuverlässig
> hinzubekommen. Die ist nämlich richtig Tricky.
Dann erklär doch mal deinen Trick.

c-hater schrieb:
> Wenn du ein Gehirn im Kopp hast
"Eure Arroganz blendet euch Meister Joda..."

Danke Lurchi für deine Hinweise. Ich habe das Ganze mal durch den 
Simulator geschoben. Für den Fall das Input Capture und Compare-Match 
gleichzeitig kommen (TCNT1 = 0) wird der ICP-Interrupt zu erst 
ausgeführt, es muss dann der Overflowzähler per Hand um 1 erhöht werden. 
Ich werde es so lassen...

von Peter D. (peda)


Lesenswert?


von Ingo L. (corrtexx)


Lesenswert?

Hallo Peter,

den Beitrag habe ich bereits gelesen. Er hat aber wenig mit meiner 
Problemstellung zu tun, denn in meiner Lösung wird alles über Interrupts 
gelöst, ein atomarer Zugriff auf die Daten ist nicht nötig. Weiter ist 
es eindeutig, welcher Interrupt zu erst ausgeführt wird. Nur in dem 
Fall, wenn der neue Capture-Wert sehr klein ist, muss mit einem noch 
nicht berücksichtigtem Overflow (oder Compare-Match) gerechnet werden. 
Das kann man aber durch Prüfung des entsprechenden Flags abfangen.
1
  /* Special Case => Overflow missed */
2
  if ( NewTicks <= 10 && TIFR1 & (1<<TOV1) ){
3
    Overflows++;
4
    TIFR1 |= (1<<TOV1);    
5
  }
Ich hoffe ich liege mit dieser These nicht falsch, ich lasse mich gerne 
belehren.

von Peter D. (peda)


Lesenswert?

Ingo L. schrieb:
> Er hat aber wenig mit meiner
> Problemstellung zu tun

Es ist egal, ob Du den Zeitstempel im Main oder einem anderen Interrupt 
haben willst, Du hast in beiden Fällen keine Kontrolle, was zuerst 
passiert und daher mußt Du es abtesten.
Im Interrupt fällt die Interruptsperre weg, das ist der einzige 
Unterschied.

Ingo L. schrieb:
> Timer1 wird im CTC-Modus mit einem Zählumfang von 25000 betrieben.

Das macht das ICP allerdings schwer, da Du damit unkontrollierte Sprünge 
im Zählbereich hast.
Besser ist daher, den Timer linear durchlaufen zu lassen und den 
Comparewert im Interrupthandler neu zu setzen:
1
  OCR1 += 25000;

von Ingo L. (corrtexx)


Lesenswert?

Peter D. schrieb:
> Du hast in beiden Fällen keine Kontrolle, was zuerst
> passiert und daher mußt Du es abtesten.
Doch, die Interruptpriorität des Capture ist höher als die der 
Compare-Match (für zeitgleiches Auftreten beider).

Peter D. schrieb:
> Das macht das ICP allerdings schwer, da Du damit unkontrollierte Sprünge
> im Zählbereich hast.
Kannst du mir das näher erklären? Ich verstehe das Problem leider nicht 
was du beschreibst. So wird der Timer initialisiert:
1
/* Capture Timer1 */
2
  OCR1A = 24999;
3
  TCCR1B |= (1<<WGM12) | (1<<CS10);    // Prescaler = 1 => 400Hz Compare-Match
4
  TIMSK1 |= (1<<ICIE1) | (1<<OCIE1A);    // Enable Input Capture and CTC Interrupt

von Peter D. (peda)


Lesenswert?

Ingo L. schrieb:
> Doch, die Interruptpriorität des Capture ist höher als die der
> Compare-Match (für zeitgleiches Auftreten beider).

Und wenn das Compare nun einen Zyklus früher erfolgt?
Dann hängt die Aufrufreihenfolge davon ab, ob zufällig ein Befehl zuende 
ist oder noch weitere Zyklen braucht.


Bei einem Zählbereich bis 24999 muß man Differenzen noch modulo 25000 
korrigieren.
Ich zähle daher lieber binär, dann spart man sich die Division /25000.

von Ingo L. (corrtexx)


Lesenswert?

Peter D. schrieb:
> Und wenn das Compare nun einen Zyklus früher erfolgt?
> Dann hängt die Aufrufreihenfolge davon ab, ob zufällig ein Befehl zuende
> ist oder noch weitere Zyklen braucht.
Das sollte damit abgefangen werden, oder? Wird der Compare-Match zuerst 
aufgerufen ist kein Eingreifen mehr erforderlich. Wird der Capture 
zuerst aufgerufen muss nachgebessert werden.
1
  /* Special Case => Overflow missed */
2
  if ( NewTicks <= 10 && TIFR1 & (1<<TOV1) ){
3
    Overflows++;
4
    TIFR1 |= (1<<TOV1);    
5
  }


Peter D. schrieb:
> Bei einem Zählbereich bis 24999 muß man Differenzen noch modulo 25000
> korrigieren.
Du meinst das hier ist falsch?
1
Capacity = (int32_t)DiffTicks + ((int32_t)DiffOverflows * 25000);
ist meine Lösung so deiner Meinung nach Schussfest?

von Lurchi (Gast)


Lesenswert?

Ob man die Überläufe gleich in Schritten von 25000 hoch zählt, oder 
später mit 25000 multipliziert, kommt auf das selbe heraus.

Beim Test auf einen kleinen ICP-wert sollte man etwas großzügiger sein. 
Wenn kein anderer Interrrupt oder so dazwischen kommt sollte es mit 10 
als Grenze ausreichen, aber man muss die Grenze nicht so knapp setzen. 
Ein gesetztes Flag für den "Überlauf" kommt nur vor wenn die ICP werte 
entweder klein ist (meist kleiner 10) oder sehr groß ( hier > 24000). 
Man darf da also mit der Grenze großzügiger (z.B. 10000) sein und so 
auch andere Interrupts oder Phasen mit gesperrtem Interrupt zulassen. Wo 
man die Grenze genau hinlegt ist relativ egal, da ist ein großer Bereich 
der nicht vorkommen sollte.

Beim Zurücksetzen des Interuptflags sollte man nur das eine zurücksetzen 
und nicht gleich alle. Also eher
 TIFR1 = (1<<TOV1);
statt
 TIFR1 |= (1<<TOV1);

Man verliert so aber auch ein Durchgang für die Tastenentprellung. Da 
wäre es ggf. günstiger das Flag nicht zu löschen und den extra Überlauf 
nur temporär zu merken.

von Ingo L. (corrtexx)


Lesenswert?

Danke Lurchi für deine Anmerkungen. Einen Durchgang zu verlieren macht 
keinen Unterschied, die Entprellung dauert 1000ms (bewusst so lang), da 
ist +2,5ms verschmerzbar.

Grade noch dank dir einen Fehler gefunden, es muss natürlich das 
Compare-Match-Flag gelöscht werden:
1
  /* Special Case => Overflow missed */
2
  if ( NewTicks <= 10 && TIFR1 & (1<<OCF1A) ){
3
    Overflows++;
4
    TIFR1 |= (1<<OCF1A);    
5
  }

von Peter D. (peda)


Lesenswert?

Ingo L. schrieb:
> /* Special Case => Overflow missed */
>   if ( NewTicks <= 10 && TIFR1 & (1<<OCF1A) ){
>     Overflows++;
>     TIFR1 |= (1<<OCF1A);
>   }

Das entspricht ja meiner Lösung, nur setze ich die Schwelle nicht bei 10 
sondern auf den halben Zählbereich. Damit können noch anderen Interrupts 
zuschlagen, die mehr als 10 Zyklen brauchen.
Und die Reihenfolge der beiden Tests ist bei mir vertauscht. Da das Flag 
nur selten gesetzt sein wird, ergibt das weniger Zeitverbrauch.
Ich setze das Flag auch nicht zurück, so daß der Interrupt nicht 
verloren geht. Man will ja nur das Ergebnis korrigieren.

Ob das mit den 25000 so hinhaut, habe ich nicht geprüft. Ich lasse die 
CPU immer alles in ihrem Lieblingsformat (Binär) machen und rechne erst 
für die Ausgabe für den Menschen um.

von Lurchi (Gast)


Lesenswert?

Der Befehl
 TIFR1 |= (1<<OCF1A);
löscht alle Interrupt flags, nicht nur den einen. Das dort eingetragene 
Flag hat nur einen minimalen Einfluss (falls genau zu der Zeit das Flag 
gesetzt wird). Wegen der Speziellen Logic im TIFR1 Register ist der 
passende Befehl
 TIFR1 = (1<<OCF1A);

von Ingo Less (Gast)


Lesenswert?

Jo danke Lurchi

von c-hater (Gast)


Lesenswert?

Ingo L. schrieb:

> Dann erklär doch mal deinen Trick.

Kein besonderer persönlicher Trick. Einfach nur angewandtes Wissen. Ich 
wollte dir nur aufzeigen, wie du am schmerzärmsten ebenfalls zu diesem 
Wissen kommen kannst.

> c-hater schrieb:
>> Wenn du ein Gehirn im Kopp hast
> "Eure Arroganz blendet euch Meister Joda..."

Von ganz unten betrachtet sieht alles irgendwie nach Arroganz aus...

> Danke Lurchi für deine Hinweise. Ich habe das Ganze mal durch den
> Simulator geschoben. Für den Fall das Input Capture und Compare-Match
> gleichzeitig kommen (TCNT1 = 0) wird der ICP-Interrupt zu erst
> ausgeführt

Wenn die Interruptauslösung wirklich gleichzeitig passiert, ist die 
Reihenfolge der Interruptabarbeitung klar, dazu braucht man keinen 
Simulator bemühen, sondern einfach nur das Datenblatt zu lesen. Wenn du 
nichtmal das weißt...

Viel spannender sind aber die Fälle, wenn der niedriger priorisierte 
Interrupt kurz vor dem höher priorisierten ausgelöst wird. Dann kann es 
nämlich passieren, dass seine ISR zuerst ausgeführt wird. Es kann aber 
genausogut das Gegenteil passieren. Du musst wirklich noch viel 
lernen...

Und dazu kommt dann halt noch, dass die Interruptabarbeitung beider 
ISRs im Prinzip beliebig lange hinter der Interruptauslösung 
hinterherhinken kann. Da schlägt dann gnadenlos das Problem der nicht 
wartenden Hardware zu, auf dass ich eingangs bereits hinwies...

von Ingo Less (Gast)


Lesenswert?

Dann lass uns doch teilhaben an deinem Wissen. Peter und Lurchi sind 
sicher auch gespannt wie deine Lösung aussieht.

von Ingo L. (corrtexx)


Lesenswert?

Dachte ich mir... nur heiße Luft.

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.