AVR Timer1 Capture und Overflow gleichzeitig

OP #7592131
Lesenswert?

Wollte den harmonischen Thread nicht kapern, daher hier neu:

Mi N. schrieb:

Alternativ folgende Überlegung: CAPT und OVL passieren gleichzeitig. Auf Grund der AVR Hardware hat TIMER1_CAPT höhere Priorität als TIMER1_OVL. Folglich wird TIMER1_CAP bedient und ICP = 0 gelesen. T1OVF_Ctr ist noch auf 0 und das Gesamtergebnis ist auch 0, müßte aber 65536 sein.

Habe das mal mit einem Arduino Pro Mini 16 MHz nachgestellt. Dazu den OC1B Ausgang (PB2 pin 10) mit dem Capture Eingang (PB0 pin 8) verbinden (D4). Aufruf der ISRs setzt pin 12 (Overflow, D2) und pin 13 (Capture, D3), als letztes in der ISR werden diese wieder 0 gesetzt. OC1A (PB1 pin 9) wird auf D0 dargestellt.

Fast PWM 8 bit (wgm 5) wird mit folgenden Parametern ausgeführt:

1
_a OCR1A 252       match at 252
2
_b OCR1B 4         match at  4
3
_c cs    2         clock 2 eg /8
4
_d cmoA  3         Set OC1A/OC1B on Compare Match, clear at BOTTOM
5
_e cmoB  3
6
_f ocie  33        Interrupts ICE1 und TOIE1 
7
_m ica   0         no noise canceling
8
_w wgm   5         Fast PWM 8 bit

D.h. T1 zählt von 0 bis 255, beim Übergang auf 0 wird Overflow und über die fallende Flanke von OC1B das Input Capture "gleichzeitig" getriggert. Was ich nicht verstehe, warum manchmal die Overflow-ISR vor und manchmal nach der Capture-ISR ausgeführt wird?

Angehängte Dateien:
#7592158
Lesenswert?

Hallo,

wie sehen dazu die Messwerte aus? Pendeln die um den Sollwert oder springen diese manchmal völlig daneben? Könnte am Compare Match Interrupt Timer 1 liegen. Nimm einmal für die Takterzeugung einen anderen freien Timer, Takterzeugung rein in Hardware ohne ISRs, und befeuer damit Timer 1.

OP #7592163
Lesenswert?

Veit D. schrieb:

wie sehen dazu die Messwerte aus?

Wie zu erwarten (da im Programm noch nicht gefixt) stimmen manche nicht: Hi ist Overflow-Counter, Lo ist ICR1

1
 P 16  auto 1  avg      65536  uspt 0.062550 clo 0.500400   30.493
2
 #   Hi     Lo        long        wert      abw  
3
 0    1      0       65536           0       +0 
4
 1    2      0      131072       65536       +0  
5
 2    3      0      196608       65536       +0  
6
 3    3      0      196608           0   -65536  
7
 4    5      0      327680      131072   +65536  
8
 5    6      0      393216       65536       +0  
9
 6    7      0      458752       65536       +0  
10
 7    8      0      524288       65536       +0  
11
 8    9      0      589824       65536       +0  
12
 9   10      0      655360       65536       +0  
13
10   10      0      655360           0   -65536  
14
11   12      0      786432      131072   +65536  
15
12   13      0      851968       65536       +0  
16
13   14      0      917504       65536       +0  
17
14   15      0      983040       65536       +0  
18
15   16      0     1048576       65536       +0
#7592193
Lesenswert?

Hallo,

deine eigentliche Frage ist demnach nur die, warum der OVF Interrupt manchmal vor dem Capt Interrupt kommt?

Noch eine Anmerkung. Dir muss bewusst sein das dein Programm durch die vielen Eigenkreationen der Bitnamen kaum bis nicht lesbar ist. Um herauszufinden wie die Timer wirklich konfiguriert sind ist viel Aufwand notwendig. Warum verwendest du nicht das _BV() Makro?

#7592312
Lesenswert?

Helmut H. schrieb:

D.h. T1 zählt von 0 bis 255, beim Übergang auf 0 wird Overflow und über die fallende Flanke von OC1B das Input Capture "gleichzeitig" getriggert. Was ich nicht verstehe, warum manchmal die Overflow-ISR vor und manchmal nach der Capture-ISR ausgeführt wird?

Es gibt kein "gleichzeitig". Der Ausgang wird verzögert gesetzt und der capture-Eingang verzögert erkannt. Im Datenblatt kann man sich die D-FFs ansehen. So kann es passieren, daß die OVF-ISR schon angesprungen wird, während 'capture' noch nicht erkannt ist.

Wenn Du Dein 'to do' eingefügt hast

1
  //if ( (TIFR1 & 1 << TOV1)) {  //TODO if pending timer verflow
2
  //  ovcnt++;
3
  //}

wird richtig gezählt. Warum das "// ovcnt++;" ganz schlecht ist hatte ich ja beschrieben: Beitrag "Re: AVR T1 Capture Problem - Meinung erbeten" Anmerkung: Beim Wechsel auf einen anderen µC kann einem das noch deutlicher auf die Füsse fallen.

#7592323
Lesenswert?

Fall A ) Jeder Interrupt is unabhaengig vom Anderen und macht was er tun muss, Eine (fast-)gleichzeitigkeit wird nachher korrigiert.

Fall B) Jeder Interrupt is unabhaengig vom Anderen und macht was er tun muss, Eine (fast-)gleichzeitigkeit wird im 2.Interrupt korrigiert.

Fall C) Die beiden Interrupts schauen, jeweils ob der Andere grad kam, und verarbeiten den auch grad.

Fall C, Dabei wird meines Erachtenz zuviel Zeit verplempert. Ich wuerd nach Fall A arbeiten.

OP #7592331
Lesenswert?

Veit D. schrieb:

deine eigentliche Frage ist demnach nur die, warum der OVF Interrupt manchmal vor dem Capt Interrupt kommt?

Ja.

Noch eine Anmerkung. Dir muss bewusst sein das dein Programm durch die vielen Eigenkreationen der Bitnamen kaum bis nicht lesbar ist. Um herauszufinden wie die Timer wirklich konfiguriert sind ist viel Aufwand notwendig.

Ja. Habe zunächst mal showRegs() angepasst:

1
TCCR1A: 0xF1  TCCR1B: 0x0A  TIMSK1: 0x21
2
_a OCR1A    252
3
_b OCR1B    4
4
_c CS       2
5
_d COM1A    3
6
_e COM1B    3
7
_f TIMSK1   33
8
_m ICNC1/ICES1  0
9
_w WGM      5

Warum verwendest du nicht das _BV() Makro?

jetzt z.B.

1
const byte povf = 12;      // pb4
2
const byte dovfS = _BV(4); // to set/reset pin fast

Mi N. schrieb:

Warum das "// ovcnt++;" ganz schlecht ist hatte ich ja beschrieben:

Beim Timer Overflow flag wird jetzt ovcnt+1 gespeichert, ovcnt wird ja in der nachfolgenden Overflow ISR erhöht.

1
  if ( (TIFR1 & 1 << TOV1)) {  // if pending timer verflow
2
    ripu[ripuP].za16[1] = ovcnt + 1;
3
  } else {
4
    ripu[ripuP].za16[1] = ovcnt;
5
  }
OP #7592345
Lesenswert?

Mi N. schrieb:

Es gibt kein "gleichzeitig". Der Ausgang wird verzögert gesetzt und der capture-Eingang verzögert erkannt. Im Datenblatt kann man sich die D-FFs ansehen. So kann es passieren, daß die OVF-ISR schon angesprungen wird, während 'capture' noch nicht erkannt ist.

Was ich halt nicht verstehe, warum hier (OC1B Ausgang mit dem Capture Eingang des gleichen Timers verbunden) manchmal zuerst die Capture-ISR und manchmal zuerst die Overflow-ISR aufgerufen wird. Hatte das nur gemacht um die von Dir vorgeschlagene Behandlung der "Gleichzeitigkeit" zu testen, es kommt jetzt ja in beiden Fällen das richtige Ergebnis. NB: wenn ich den ICNC1: Input Capture Noise Canceler einschalte, wird immer zuerst der Timer-Overflow aufgerufen, ist ja klar weil die Bearbeitung des Captures um 4 Takte verzögert wird.

#7592359
Lesenswert?

Helmut H. schrieb:

Was ich halt nicht verstehe, warum hier (OC1B Ausgang mit dem Capture Eingang des gleichen Timers verbunden) manchmal zuerst die Capture-ISR und manchmal zuerst die Overflow-ISR aufgerufen wird.

Weil halt aus irgendwelchen Gründen mal beide, und mal nur ein ISR-Flag gesetzt ist, wenn die ISR aufgerufen wird.

Wenn da im Test des Programm mal die Interrupts gesperrt und wieder freigegeben werden, was bei dem ganzen Arduino-Funktionen, die du da ja aufrufst, vielleicht der Fall ist, dann sind halt die Flags mal beide gesetzt, und mal nicht.

Oliver

#7592374
Lesenswert?

Helmut H. schrieb:

Was ich halt nicht verstehe, warum hier (OC1B Ausgang mit dem Capture Eingang des gleichen Timers verbunden) manchmal zuerst die Capture-ISR und manchmal zuerst die Overflow-ISR aufgerufen wird.

Das wird gerade auf der Kippe sein. Wann auf Interrupts reagiert wird, hängt ja auch von dem gerade bearbeitetem Befehl ab. Vermutung: Man könnte jetzt spekulieren, daß ein Befehl mit einem Zyklus der OVF-ISR den Vorrang gibt. Bei >= zwei Zyklen für den aktuellen Befehl und beiden anstehenden Interrupts, hat die CAPT-ISR die höhere Priorität.

Setze mal OCR1B auf 0x0001 oder 0x0002, dann wird vermutlich immer die OVF-ISR gewinnen. Den OC1B-Ausgang mit 1 - 10 nF zu belasten, könnnte dafür auch schon reichen. Wichtig ist letztendlich, daß es keine Fehler bei der Erkennung gibt. Und das wird ja erreicht.

#7592381
Lesenswert?

Oliver S. schrieb:

Wenn da im Test des Programm mal die Interrupts gesperrt und wieder freigegeben werden, was bei dem ganzen Arduino-Funktionen, die du da ja aufrufst, vielleicht der Fall ist, dann sind halt die Flags mal beide gesetzt, und mal nicht.

Das ist auch noch ein wichtiger Punkt: Bei den Arduino-Programmen ist immer noch (zumindest mir) unbekannte Hintergrundaktivität vorhanden. Das läßt sich vermeiden, indem man setup() und loop() wegläßt und die Initialisierungen wie gewohnt durchführt.

Beim Versuch, meine Programme auf "Arduino-Niveau" zu bringen, bin ich zum Beispiel daran gescheitert, daß TIMER0 schon anderwärtig verwendet wird. Das liebe ich ja sowas von ...

#7592390
Lesenswert?

Helmut H. schrieb:

Was ich nicht verstehe, warum manchmal die Overflow-ISR vor und manchmal nach der Capture-ISR ausgeführt wird?

Der Overflow kann nur dann zuerst kommen, wenn das Capture noch nicht erfolgt ist. Da kann schon ein Zyklus später ausreichen (muß aber nicht). Der kritische Fall ist immer nur, wenn das Capture kurz nach dem Overflow erfolgt, dann wurde noch nicht der Overflow gezählt, d.h. das TOV1 ist noch gesetzt. Das muß man dann, wie im Thread von Gerhard gezeigt, behandeln.

Ob die Statemaschine in der Capture-ISR keine Seiteneffekte hat, kann ich nicht einschätzen. Ich lasse die ISR nur die Erweiterung auf 32 Bit machen und bilde dann in der Mainloop die Differenz von 2 Capture-Werten. Für sehr kurze Intervalle läßt man noch einen Capture-Counter hochzählen, durch den man dann teilen kann (Messung über n Perioden). Das erhöht die Auflösung.

Ich sehe nirgends eine atomare Kapselung der Zugriffe auf Interruptvariablen in der Mainloop. Du mußt zuerst mal alle interessierenden Variablen als atomaren Snapshot puffern, ehe Du sie einzeln verarbeiten kannst.

#7592443
Lesenswert?

Mi N. schrieb:

Beim Versuch, meine Programme auf "Arduino-Niveau" zu bringen, bin ich zum Beispiel daran gescheitert, daß TIMER0 schon anderwärtig verwendet wird. Das liebe ich ja sowas von ...

Weil der für micros(), millis() und delay() verwendet wird. Braucht man das nicht oder stört einem das legt man Timer0 still. Beim Verzicht auf setup() und loop() fällt auch alles andere weg. Serial bspw. Wer bei Arduino in die Tiefe eintaucht muss wissen was er tut. Dafür ist Arduino so nicht vorgesehen. Muss man auch dazu sagen. Man kann aber alles machen wenn man die Defaultconfig kennt und abschaltet. Woran bist du denn gescheitert?

Übrigens ist das timer1Init von Helmut schon gefährlich.

1
void timer1Init() {
2
  TIMSK1 = 0; // no timer ints during init
3
  TCNT1 = 0;
4
  ovcnt = 0;
5
  TCCR1A = (tim1cmoA << 6) | (tim1cmoB << 4) | (tim1wgm & 3);
6
  TCCR1B = (tim1ica << 6) | tim1cs | ((tim1wgm << 1) & 0x18);
7
  TIMSK1 = tim1ocie;     // Timer interrupts
8
}

Ohne gestoppten Timer wird konfiguriert und läuft auch ohne fertige Konfig los bzw. weiter. Das heißt der Timer läuft für einen Moment unkontrolliert.

#7592469
Lesenswert?

Veit D. schrieb:

Weil der für micros(), millis() und delay() verwendet wird. Braucht man das nicht oder stört einem das legt man Timer0 still.

Wenn ich mich richtig erinnere, sind die TIMER0_xxx-Vektoren schon belegt und kollidieren mit den eigenen.

Man kann aber alles machen wenn man die Defaultconfig kennt und abschaltet. Woran bist du denn gescheitert?

Man muß sie erst einmal finden. Da geht es schneller, die eigenen Routinen hinzuzuklicken. Bei einer 'richtigen' IDE klicke ich auf 'Goto Definition' und die Sache wird angezeigt. Das soll aber hier nicht das Thema werden.

OP #7592521
Lesenswert?

Veit D. schrieb:

Übrigens ist das timer1Init von Helmut schon gefährlich. Ohne gestoppten Timer wird konfiguriert und läuft auch ohne fertige Konfig los bzw. weiter. Das heißt der Timer läuft für einen Moment unkontrolliert.

Ist es so besser?

1
void timer1Init() {
2
  TCCR1B = 0;  // stop timer by setting CS to 0
3
  TIMSK1 = 0;  // no timer ints during init
4
  TCNT1 = 0;
5
  ovcnt = 0;
6
  TCCR1A = (tim1COM1A << 6) | (tim1COM1B << 4) | (tim1WGM & 3);
7
  TIMSK1 = tim1TIMSK1;   
8
  TCCR1B = (tim1IC << 6) | tim1CS | ((tim1WGM << 1) & 0x18);
9
}

NB: Da alle tim1... Parameter zur Laufzeit eingegeben werden können, kann ich die übliche Schreibweise z.B.

1
 TCCR1B = (1<<ICNC1) | (1<<CS10);

nicht verwenden

#7592570
Lesenswert?

Hallo,

Michael, dass muss ich nun schon richtig stellen. Es kollidiert erstmal so gesehen nichts. Man muss nur, eben wegen der Defaultkonfig des Arduino Frameworks, am Besten alle Register nullen. Also in deinem Fall alle vom Timer 0. So wie es auch in "bare metal" wäre. Danach den Timer 0 nach seinen Wünschen konfigurieren. Die ISRs kann man jederzeit selbst verwenden. An den Vectoren muss man nicht rumfummeln. Um die Konfig vom Arduino Framework zu sehen muss man nur sich den Inhalt der Register ausgeben lassen. Das geht schneller als sich durch die Dateien zu wühlen. Wenn man die Timerregister sowieso resetet, muss man auch das nicht machen. Wäre nur zur eigenen Information was default ist.

Das Arduino Framework bereitet alle Timer vor für micros(), millis(), delay() und analogWrite(). Im Fall von analogWrite() heißt das aber nicht das alle schon aktiv sind, nur vorbereitet. Deswegen bei Arduino erste Grundregel bei eigener Konfig der Hardware immer erst alle Register reseten. Bei Timer 0 hat man dann natürlich kein micros(), millis(), delay() mehr.

Helmut, dass sieht schon besser aus. Ich habe begonnen einzelne Funktionen zum konfigurieren zu schreiben. Siehe Anhang. Einmal großer Aufwand danach nur noch anwenden. Die Nummern der Timer Modi entsprechen dem Manual, dass ist wichtig, für sich selbst und für andere. Statt der Nummern kann man noch sprechende Variablennamen anlegen welche dem Timermode klar erkennen lassen.

Angehängte Dateien:
#7592629
Lesenswert?

Nimm einmal für die Takterzeugung einen anderen freien Timer, Takterzeugung rein in Hardware ohne ISRs.

Dann doch gleich direkt per Setzen von PB0: Timer1 mit einem Wert knapp unterhalb des Uberlaufs starten, eine veränderliche Anzahl von 'nop's warten, dann 'sbi PORTB,0' - ja, ich halte bei dieser Thematik Assembler für das geeignete Werkzeug, aber in C geht das wohl auch.

#7592742
Lesenswert?

Veit D. schrieb:

Michael, dass muss ich nun schon richtig stellen. Es kollidiert erstmal so gesehen nichts.

Wenn Du Erfahrung damit hast, wird es sicherlich kein großes Problem sein. Wegen besserer Alternativen habe ich bei wiederholten Anläufen relativ schnell das Handtuch geworfen. Die Arduino-IDE habe ich immer dann verwendet, wenn der schon weitgehend getestete Sourcecode (AVR Studio oder IAR) vom Anwender mit einfachen Mitteln modifizierbar sein sollte.

[OT] Meine letzten Versuche diesbezüglich habe ich mit Quellcode für ein RP2040 Pico-Board gemacht. Bei Arduino ist wohl das Pico-SDK unterlegt. Viele, viele Bäume und kein Wald zu sehen. Die übliche 'RP2040.h' Datei ist überall angeeckt und die verwunschenen Pfade vom Pico-SDK wollte ich nicht gehen. Zudem brauchen schon kleinste Programme eine kleine Ewigkeit, bis sie übersetzt sind. Das macht einfach keinen Spaß; erst recht nicht mit fehlendem Degugging. Schade. [/OT]

S. L. schrieb:

Dann doch gleich direkt per Setzen von PB0: Timer1 mit einem Wert knapp unterhalb des Uberlaufs starten, eine veränderliche Anzahl von 'nop's warten, dann 'sbi PORTB,0' - ja, ich halte bei dieser Thematik Assembler für das geeignete Werkzeug, aber in C geht das wohl auch.

Was soll das denn jetzt noch bringen? Das Problem ist in C vollständig und fehlerfrei handhabbar. Auf Assembler umzusteigen bringt - gerade auch für Anfänger - eher Verwirrung und vermittelt den Eindruck, es wäre kompliziert.

OP #7592813
Lesenswert?

Mi N. schrieb:

Das wird gerade auf der Kippe sein. Wann auf Interrupts reagiert wird, hängt ja auch von dem gerade bearbeitetem Befehl ab.

Das ist für mich die Erklärung des Verhaltens, weil zwischen Ereignis und Bearbeitung zufällig 1 bis 4 Takte liegen. Da hier der Overflow (O) das Capture-Ereignis (C) auslöst, wird es immer (1 oder 2 Takte?) später erkannt. Wenn der aktuelle Befehl nach O aber vor C fertig ist, wird die Overflow-ISR zuerst ausgeführt. Wenn nach C, wird die Capture-ISR zuerst ausgeführt. Vielen Dank an alle für die freundliche Beratung.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren