Forum: Mikrocontroller und Digitale Elektronik AVR Timer1 Capture und Overflow gleichzeitig


von Helmut H. (helmuth)


Angehängte Dateien:

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?

von Veit D. (devil-elec)


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.

von Helmut H. (helmuth)


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

von Veit D. (devil-elec)


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?

: Bearbeitet durch User
von Mi N. (msx)


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.

von Purzel H. (hacky)


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.

von Helmut H. (helmuth)


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
  }

von Helmut H. (helmuth)


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.

: Bearbeitet durch User
von Oliver S. (oliverso)


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

von Mi N. (msx)


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.

von Mi N. (msx)


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 ...

von Peter D. (peda)


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.

von Veit D. (devil-elec)


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.

: Bearbeitet durch User
von Mi N. (msx)


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.

von Helmut H. (helmuth)


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

: Bearbeitet durch User
von Veit D. (devil-elec)


Angehängte Dateien:

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.

von S. L. (sldt)


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.

von Peter D. (peda)


Lesenswert?

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'

Das wäre ja noch ungenauer, alle Interrupts würden darin ihren Jitter 
abladen. Und selbst, ob ein Befehl im Main 1..4 Zyklen braucht.

von S. L. (sldt)


Lesenswert?

> alle Interrupts
Es gibt ja nur, gegebenenfalls, die OVF1-ISR, deren Einfluss eindeutig 
im Zählwert erkennbar ist.

> 1..4 Zyklen
Das verstehe ich nicht: es sind nur nops, und damit eine Auflösung von 1 
Takt.

von Mi N. (msx)


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.

von Helmut H. (helmuth)


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.

: Bearbeitet durch User
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.