Forum: Mikrocontroller und Digitale Elektronik Timer Overflow Counter - Wie richtig berücksichtigen?


von Veit D. (devil-elec)


Lesenswert?

Hallo,

es geht um eine Messung einer Pulslänge. Dafür nehme ich einen Timer der 
mit Prescaler 1 frei läuft. Ich lese im Interrupt jeweils den 
Zählerstand und den Overflow Count aus. Soweit ist das ja üblich. Später 
wird das miteinander verrechnet usw.

Mich treibt dafür eine Frage um wofür ich keine Lösung habe. Selbst wenn 
ich den Timercounter und Overflowcounter direkt nacheinander auslese, 
kann doch der Overflowcounter noch umspringen.

Es könnte folgendes Szenario geben. Ich lese folgendes nacheinander aus.
Timercount = 65535
Overflow = 1

Aber eigentlich war es zum Zeitpunkt den Timercount auslesens
Timercount = 65535
Overflow = 0

Weil beim auslesen von Overflow dieser mindestens einen Takt 
weitergelaufen ist steht der Timer beim auslesen vom Overflow vielleicht 
so da.
Timercount = 0
Overflow = 1

Das heißt ich hätte einen Fehler eines gesamten Overflows.
65535 oder 131070.
Wie korrigiert ihr das Problem?

von Gustl B. (gustl_b)


Lesenswert?

Wo ist denn der Vorteil von zwei Zählern? Die Bits könnte man doch auch 
zusammen als einen Zähler laufen lassen der dann erst viel später 
überläuft. Komisches Konzept.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Veit D. schrieb:
> mit Prescaler 1 frei läuft. Ich lese im Interrupt jeweils den
> Zählerstand und den Overflow Count aus. Soweit ist das ja üblich. Später
> wird das miteinander verrechnet usw.

 Nein, das ist nicht üblich.
 Interrupt wird erst angesprungen (TOV Flag gesetzt) wenn der
 Zählerstand schon auf Null ist.
 Also, in der TOV-ISR immer 65536 dazurechnen, (TOV Flag wird in der
 ISR automatisch zurückgesetzt) und weitermachen.

: Bearbeitet durch User
Beitrag #6423476 wurde vom Autor gelöscht.
von Wolfgang (Gast)


Lesenswert?

Veit D. schrieb:
> Wie korrigiert ihr das Problem?

Du kannst Overflow einmal vor und einmal nach Timercount auslesen. 
Wenn kein Überlauf stattgefunden hat, sind beide gelesenen Werte für 
Overflow gleich. Wenn sie ungleich sind, kommt es drauf an, ob 
Timercount als nahe bei 0 oder nahe bei 65535 gelesen wurde.

von Peter D. (peda)


Lesenswert?


Beitrag #6423506 wurde von einem Moderator gelöscht.
von Veit D. (devil-elec)


Lesenswert?

Hallo Peter,

Danke, ich versuche es zu verstehen ...

Beitrag #6423516 wurde von einem Moderator gelöscht.
von Theor (Gast)


Lesenswert?

@ Veit D.

> Ich lese im Interrupt ...

Das Problem scheint mir zu sein, dass der Timer weiterzählt während der 
laufende Befehl beendet und der Interrupt behandelt wird. Zwar kann mit 
einer Variante von Peters Code noch getestet werden, ob der Überlauf 
aufgetreten ist und die Behandlung des Interrupts dauer definitiv 7 
Takte lang (ATMega169P_doc8018, S. 16) aber ...

... da nicht klar ist, welchen Befehl der die CPU gerade ausgeführt hat 
können zwischen 1 und 4 Zyklen zusätzlich fehlen.

Selbst wenn Du den Befehl noch bestimmst (in dem Du die Rückkehradresse 
auf dem Stack auswertest) und die dazugehörige Zyklenzahl bestimmst, 
bleibt in vielen Fällen noch eine Unsicherheit zwischen 1 und 4 Zyklen. 
(Im Detail hängt das davon ab, von welchem Interrupt Du oben geredet 
hast).

Kannst Du nicht vielleicht den Capture Interrupt verwenden?

von Peter D. (peda)


Lesenswert?

Theor schrieb:
> Kannst Du nicht vielleicht den Capture Interrupt verwenden?

Im Capture Interrupt kann man es genau so machen. Nur muß man natürlich 
nicht nochmal atomar kapseln.

von Theor (Gast)


Lesenswert?

Peter D. schrieb:
> Theor schrieb:
>> Kannst Du nicht vielleicht den Capture Interrupt verwenden?
>
> Im Capture Interrupt kann man es genau so machen. Nur muß man natürlich
> nicht nochmal atomar kapseln.

Ich meine, dass man den Überlauf im Capture-Interrupt gar nicht 
berücksichtigen darf!

Denn entweder ist der Überlauf vor dem Capture aufgetreten, dann wird er 
in der Capture-ISR schon berücksichtigt.
Oder der Überlauf tritt nach dem Capture auf; dann ist er aber für den 
im Register festgehaltenen Timerwert nicht mehr zu berücksichtigen. Wozu 
günstigerweise die Overflow-ISR ja sowieso vorerst keine Gelegenheit 
mehr hat, weil die Capture-ISR schon läuft.

von Theor (Gast)


Lesenswert?

Meine Aufzählung war Unvollständig.

Falls der Capure simultan mit dem Überlauf auftritt, müsste der 
Überlauf auch berücksichtigt werden. Das ist durch die Prioritäten durch 
den Fall "vor" dem Capture abgedeckt, denn der Überlauf-Interrupt wird 
dann vor dem Capture abgehandelt.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

AVR 8-Bit Timer
1
ISR(TIMER2_OVF_vect)
2
{
3
  t2_soft += 256;
4
}
5
6
u32 get_ticks(void)        // read T2 as 32 bit timer
7
{
8
  u32 val;
9
  u8 tifr;
10
11
  cli();
12
  val = t2_soft + TCNT2;
13
  tifr = TIFR;          // read interrupt flags
14
  sei();
15
  if( (tifr & 1<<TOV2) && !(val & 0x80) ) // overflow prior reading TCNT2 ?
16
    val += 256;                // then add overflow
17
18
  return val;
19
}
1
if( (tifr & 1<<TOV2) && !(b1111'1111 & b1000'0000) )  val += 256;
Wenn TCNT2 = 255 ausliest, vergeht doch schon mindestens ein Takt bis 
man TIFR ausliest. Zu dem Zeitpunkt ist TCNT2 schon bei 0 und löst einen 
OVF Interrupt aus. Jetzt speichert man in tifr das gesetzte Flag.
1
TCNT2 = 254: if( (tifr & 1<<TOV2) && !(b1111'1110 & b1000'0000) )  val += 256; >> keine Addition
2
3
TCNT2 = 255: if( (tifr & 1<<TOV2) && !(b1111'1111 & b1000'0000) )  val += 256; >> keine Addition
Bis hierher komme ich mit. Selbst wenn das OVF Flag gesetzt ist, erfolgt 
keine Addition.
Danach komme ich nicht mehr mit.

Der nächste TCNT2 Zählerstand wäre 0.
1
TCNT2 =   0: if( (tifr & 1<<TOV2) && !(b0000'0000 & b1000'0000) )  val += 256; >> Addition
Hier erfolgt theoretisch eine Addition durch die Überprüfung, wenn das 
OVF Flag gesetzt ist. Hier gehe ich allerdings davon aus, dass zu dem 
Zeitpunkt das OVF Flag nicht mehr gesetzt ist. Man liest TCNT2 mit 0 
aus, dass heißt die OVF ISR Routine wurde direkt davor oder direkt 
danach schon ausgeführt und das Flag ist gelöscht. Weil zwischen TCNT2 
und TIFR auslesen muss die ISR Routine ausgeführt sein. Wenn das nicht 
so wäre käme es zu einer doppelten Addition von 256, was falsch wäre.
Ich würde jetzt behaupten wollen, dass der Code im Grunde nichts macht 
im Sinne von er beeinflusst den Zählerstand nicht, weil es wird durch 
den Vergleich nie 256 addiert. Würde bedeuten man kann das alles 
weglassen.

Gehen wir diese Zeile nochmal durch.
val = t2_soft + TCNT2;

Wenn man hier TCNT2 mit 255 ausliest, hat OVF noch nicht ausgelöst.
t2_soft hat noch den alten Wert, wird mit 255 addiert, alles i.O.

Wenn man danach TCNT2 mit 0 ausliest, hat OVF schon ausgelöst.
t2_soft hat einen neuen Wert, wird mit 0 addiert, alles i.O.

Demzufolge liegt der Trick des korrekten Zählens darin das man in der 
OVF ISR den vollen Wert addiert und keinen Overflowcounter unterhält. 
Korrigiert mich wenn mein Gedankengang irgendwo falsch ist.

von S. Landolt (Gast)


Lesenswert?

> Würde bedeuten man kann das alles weglassen.

Und wenn andere Interrupts freigegeben sind?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

okay, mit der frei aufrufbaren Funktion get_ticks().
Das hatte ich nicht bedacht.
Wenn ich das in einer Interrupt Routine mache, bspw. Pin Interrupt, dann 
muss ich den vollen Aufwand nach meiner Überlegung nicht betreiben. 
Richtig?

von S. Landolt (Gast)


Lesenswert?

Das habe ich nicht verstanden, wie ist das gemeint?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Veit D. schrieb:
> Wenn ich das in einer Interrupt Routine mache, bspw. Pin Interrupt, dann
> muss ich den vollen Aufwand nach meiner Überlegung nicht betreiben.
> Richtig?

 Nein.
 Timer läuft immer, egal ob dein Programm sich in einer ISR befindet
 oder nicht.- gerade in einer ISR wird der Timer zwar auf 0 gesetzt,
 aber OVF-ISR kann nicht ausgeführt werden.

: Bearbeitet durch User
von S. Landolt (Gast)


Lesenswert?

Ein simples Beispiel für den beliebten ATmega328P in Assembler, Timer0 
soll XH,XL mit 16-bit zählen, der Timer2 "grätscht dazwischen":
1
...
2
.org  OVF2addr
3
  reti
4
.org  OVF0addr
5
  in    tmp2,SREG
6
  inc    XH
7
  out    SREG,tmp2
8
  reti
9
...
10
  ldi    XH,0
11
  ldi    XL,0
12
  ldi    tmp0,(1<<CS20)
13
  sts    TCCR2B,tmp0
14
  ldi    tmp0,(1<<TOIE2)
15
  sts    TIMSK2,tmp0
16
  ldi    tmp0,(1<<CS00)
17
  out    TCCR0B,tmp0
18
  ldi    tmp0,(1<<TOIE0)
19
  sts    TIMSK0,tmp0
20
  sei
21
  ldi    tmp0,253
22
  sts    TCNT2,tmp0
23
  ldi    tmp0,255
24
  out    TCNT0,tmp0
25
  nop
26
  in    XL,TCNT0
27
==> hier steht nun XH,L auf 0000
Bei dem nop laufen beide Zähler über, der Timer2-Interrupt wird zuerst 
bedient, da höhere Priorität. Nach der Rückkehr aus einer ISR wird bei 
AVR8 aber grundsätzlich der nächste Befehl im Hauptprogramm ausgeführt, 
bevor wieder in eine ISR gesprungen werden kann. Dieser nächste Befehl 
ist hier das 'in XL,TCNT0', die Timer0-ISR wird erst danach 
angesprungen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich dachte zwar ich hätte mir das heute genug x mal durch den Kopf gehen 
lassen, scheinbar noch nicht genug. Ich denke morgen nochmal darüber 
nach.
Danke erstmal.

Die Kurzantwort wäre gewesen, wenn ich das in einer ISR mache bspw. Pin 
Interrupt, dann muss ich
a) keinen Interrupt sperren
b) kann der OVF Interrupt nicht auslösen und damit das Ergebnis auch 
nicht verfälschen.

Wie gesagt, ich denke morgen nochmal darüber nach.   :-)

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Veit D. schrieb:
> Die Kurzantwort wäre gewesen, wenn ich das in einer ISR mache bspw. Pin
> Interrupt, dann muss ich
> a) keinen Interrupt sperren
> b) kann der OVF Interrupt nicht auslösen und damit das Ergebnis auch
> nicht verfälschen.

 Noch einmal:
 OVF Interrupt kann zwar in einer anderen ISR nicht ausgeführt werden,
 TCNT kann aber sehr wohl auf Null gehen und dann hast du eine
 Differenz von 256.

 Wenn du aber unbedingt mit Prescaler 1 und 8bit Timer arbeiten willst,
 gibt es auch dafür eine Lösung...

von Veit D. (devil-elec)


Lesenswert?

Moin,

Ich bin beim 8Bit Timer geblieben wegen dem Code von Peter. Sonst 
wechselst die Grundlage.

von Peter D. (peda)


Lesenswert?

Theor schrieb:
> Falls der Capure simultan mit dem Überlauf auftritt, müsste der
> Überlauf auch berücksichtigt werden. Das ist durch die Prioritäten durch
> den Fall "vor" dem Capture abgedeckt, denn der Überlauf-Interrupt wird
> dann vor dem Capture abgehandelt.

Welcher AVR soll das denn sein?
Bei den ATmega8/16/32 und Nachfolger wird zuerst der Captureinterrupt 
ausgeführt. Es kann also das Overflowbit gesetzt sein oder auch nicht. 
D.h. es muß behandelt werden, wie in meinem Beispielcode.

Z.B. der Overflow erfolgt und 2 Zyklen später das Capture. Nun muß aber 
ein 4Zyklen-Befehl noch beendet werden, d.h. danach sind beide Flags 
gesetzt und Capture wird zuerst ausgeführt. Ohne Auswertung des Overflow 
fehlen dann 65536 Zyklen.

: Bearbeitet durch User
von Theor (Gast)


Angehängte Dateien:

Lesenswert?

Peter D. schrieb:
> Theor schrieb:
>> Falls der Capure simultan mit dem Überlauf auftritt, müsste der
>> Überlauf auch berücksichtigt werden. Das ist durch die Prioritäten durch
>> den Fall "vor" dem Capture abgedeckt, denn der Überlauf-Interrupt wird
>> dann vor dem Capture abgehandelt.
>
> Welcher AVR soll das denn sein?
> Bei den ATmega8/16/32 und Nachfolger wird zuerst der Captureinterrupt
> ausgeführt. [...]

Ich interpretiere das Datenblatt des ATMega169 und des ATMega8 anders 
als Du.

In beiden Datenblättern finde ich den Satz: "The lower the address the 
higher is the priority level."

In beiden Datenblättern finde ich die Tabellen, deren (teilweise) 
Screenshots ich hier anhänge.

Dokumentenversionen sind:
ATMega169: 8018P–AVR–08/10
ATMega8: 2486AA–AVR–02/2013 (habe ich eben aus dem Internet bei 
Microchip)

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Theor schrieb:
> In beiden Datenblättern finde ich den Satz: "The lower the address the
> higher is the priority level."

Also bei mir ist immer noch 6 < 9

von m.n. (Gast)


Lesenswert?

Peter D. schrieb:
> Also bei mir ist immer noch 6 < 9

Und davon abgesehen, ist die Priorität auch egal. Wenn OVF-ISR vor 
CAPT1-ISR aufgerufen würde, wäre das Flag schon gelöscht und der 
OVF-Zähler schon inkrementiert.
Etwas anders sieht es aus, wenn auf anderen µCs verschiedene 
Interruptprioritäten möglich sind, STM32xxx zum Beispiel.

von Peter D. (peda)


Lesenswert?

m.n. schrieb:
> Und davon abgesehen, ist die Priorität auch egal. Wenn OVF-ISR vor
> CAPT1-ISR aufgerufen würde, wäre das Flag schon gelöscht und der
> OVF-Zähler schon inkrementiert.

Ist absolut nicht egal!
Z.B. das Capture erfolgt 2 Zyklen vor dem Overflow. Der 4Zyklen Befehl 
wird beendet und der Overflowhandler zuerst ausgeführt, d.h. der Zähler 
für die obersten 16Bit incrementiert. Nun liest der Capturehandler das 
Register, was aber vor dem Overflow gelatcht wurde und bastelt daraus 
den 32Bit Timestamp. Wir haben damit einen Fehler von 65536 Zyklen, also 
den Supergau.

Die Atmel-Leute haben sich also was dabei gedacht, warum sie die 
Priorität genau so rum festgelegt haben.

: Bearbeitet durch User
von m.n. (Gast)


Lesenswert?

Die Problematik ist nun gerade nicht mein Thema, aber sowie man in der 
Capture-ISR nachsehen kann, ob OVF schon gesetzt ist, kann man auch in 
der OVF-ISR nachsehen, ob das CAPT-Flag gesetzt ist und entsprechend 
reagieren.
Dafür hast Du bestimmt eine Minimallösung ;-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

bevor das hier driftet. Bei mir gehts nicht um das Capture. Ich werde 
den Pin Interrupt verwenden, weil am Ende 2 Pulslängen verglichen 
werden. Bei der Architektur können wir beim bekannten Atmega bleiben. 
Ich hätte einen ATmega328PB, Mega2560 und ATmega4808/4809 zur Auswahl. 
Den 2560 und 4809 in Form eines Arduino Boards. Der Einfachheit halber 
bleiben wir beim ATmega328xx oder Verwandten, die kennt hier denke ich 
jeder. Ihr bestimmt in- und auswendig. Sonst wird das zu kompliziert. 
Jetzt denke ich nochmal über den Ablauf nach ... bis später.

von Peter D. (peda)


Lesenswert?

Veit D. schrieb:
> weil am Ende 2 Pulslängen verglichen
> werden.

Die Frage ist, wie genau die Messung sein soll.
Falls man nacheinander messen kann, kann man den Captureeingang über den 
ADMUX umschalten.

von m.n. (Gast)


Lesenswert?

Ein Beispiel für Verwendung von PCINTx findest Du hier: 
http://www.mino-elektronik.de/fmeter/fm_software.htm#bsp4
Es sind zwar insgesamt vier Kanäle, die gemessen werden, aber im Prinzip 
nicht anderes, als wenn per CAPT1-ISR ausgewertet würde.

von Theor (Gast)


Lesenswert?

Peter D. schrieb:
> Theor schrieb:
>> In beiden Datenblättern finde ich den Satz: "The lower the address the
>> higher is the priority level."
>
> Also bei mir ist immer noch 6 < 9

Du hast Recht, Peter.
OFV hat die niedrigere Priorität. Ich habe die Timer-Nummer nicht 
beachtet.

Mein ursprünglicher Gedanke mit dem Capture-Int vom 01.10.2020 02:18 war 
also, meine ich, richtig.

@ Veit

Der Punkt ist, dass Du mit dem Capture-Interrupt nicht die Unsicherheit 
hast, wie lang der Befehl war, der unterbrochen wurde.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich wollte zwar nicht vorgreifen, weil ich erstmal das mit dem OVF Flag, 
Peters Idee verstehen und verinnerlichen möchte, aber mach es dennoch 
einmal kurz und knapp.

Die zwei zu messenden Pulslängen können im dümmsten Fall zeitlich sehr 
dicht nacheinander erfolgen. Zeitgleich im Sinne von syncron sind die 
jedoch definitiv nicht. Ich kann aktuell nicht einschätzen wieviel Zeit 
der µC zwischen den 4 Messzeitpunkten hat. Ich möchte es erstmal so 
genau wie möglich machen. Ein Roboterarm bewegt eine Scheibe durch 2 
Lichtschranken. Wenn der sich mit vollen Speed bewegt möchte man 
eigentlich nicht mehr hinschauen. Fürs Auge ist die Bewegung zu schnell. 
Die Pulslängen sollen später miteinander verglichen werden um 
Abweichungen festzustellen. Dabei sollte es nicht zu Fehlauslösungen 
kommen auf Grund von Softwarefehlern beim Timer auslesen.  :-)   Ich bin 
jedoch wiederum davon überzeugt, dass ein nacheinander auslösen von zwei 
Pin Interrupts keine zeitlichen Probleme mit sich bringt.

Der Grund für alles ist, dass ich vielleicht die Chance habe mit meinem 
Hobbywissen auf Arbeit eine kleine Nachrüstung beizusteuern. Zur Zeit 
versuche ich herauszufinden ob ich mir das überhaupt zu traue. Ich 
unterscheide zwischen Programmierung zu Hause für mich oder etwas für 
Arbeit was dann auch wirklich funktionieren muss. Es wäre peinlich am 
Ende sagen zu müsssen ich bekomme es nicht hin. Die grundlegende Idee 
kann ich immer noch als reine Idee vorschlagen. Die Lösung käme dann 
aber nicht von mir. Ich habe jedoch den Ehrgeiz dafür.  :-)

Erstmal zurück zum Capture Int.
Angenommen ich verwende den Capture. Weil dessen Interrupt Prio höher 
ist kommt es zu weniger Problemen beim auswerten? Ist das der Grund?

von m.n. (Gast)


Lesenswert?

Veit D. schrieb:
> Die zwei zu messenden Pulslängen können im dümmsten Fall zeitlich sehr
> dicht nacheinander erfolgen. Zeitgleich im Sinne von syncron sind die
> jedoch definitiv nicht. Ich kann aktuell nicht einschätzen wieviel Zeit
> der µC zwischen den 4 Messzeitpunkten hat. Ich möchte es erstmal so
> genau wie möglich machen.
> ...
> Es wäre peinlich am
> Ende sagen zu müsssen ich bekomme es nicht hin.

Die sichere Lösung wäre, einen µC mit 2 x Capture-Einheiten zu 
verwenden. Ein Kandidat wäre ein ATmega162, ein anderer ein ATmega64, 
128, 324, ..., die mehrere 16 Bit Timer bieten.

Noch besser wäre zum Beispiel ein STM32Fxxx, wo einige Timer 4 x 
Capture-Eingänge haben. Dein Problem ist mit vertretbarem Aufwand gut 
lösbar.

von Theor (Gast)


Lesenswert?

Veit D. schrieb:
> Hallo,
> [...]
>
> Erstmal zurück zum Capture Int.
> Angenommen ich verwende den Capture. Weil dessen Interrupt Prio höher
> ist kommt es zu weniger Problemen beim auswerten? Ist das der Grund?

Nein.
Der Grund ist, dass die Genauigkeit der Zeitmessung davon beeinflusst 
wird, welcher Befehl gerade ausgeführt wurde, als der Interrupt eintrat.

von Theor (Gast)


Lesenswert?

Mist. Ich habe eben einen ellenlangen Text geschrieben, der das erklärt 
hat. Aber es gab ein Problem und der Text ist weg. Grmbl.

Nochmal in kurz:

Der Capture-Interrupt speichert den Timerwert (mit einer Unsicherheit 
von +- 0.5 Zyklen) 3 Zyklen nach dem Eintritt des Ereignisses.

Bei anderen Interrupts weiß man nicht, wann er auftratt als gerade ein 
Befehl ausgeführt wurde.
Es kann gerade am Ende des Befehl gewesen sein. Dann wäre die 
Verzögerung 0. Oder es kann am Anfang des Befehl gewesen sein. Da es 
aber Befehle mit 1, 2, 3 oder 4 Zyklen Ausführungszeit gibt,

Man weiß aber auch nicht welcher Befehl es war.
Also ergibt das einen Timerwert mit einer Unsicherheit von -4 Zyklen.

Das ist der Unterschied.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

jetzt habe ich den Vorteil/Unterschied mit Capture verstanden. Vielen 
vielen Dank. Auf Grund dessen würde ich erstmal sagen wollen, dass die 
maximal 4 Takte Differenz keine entscheidende Rolle spielen sollten. Ich 
behalte das jedoch im Hinterkopf, falls die Messungen von Haus aus 
ziemlich Zappelfrei sein sollten, dann kann ich die Messgenauigkeit mit 
Capture erhöhen. Bitte nicht als Rückzug von Capture missverstehen. 
Danke.

Jetzt muss ich nochmal zurück zur Basis für mein Verständnis. Das lässt 
mir keine Ruhe.
https://www.mikrocontroller.net/attachment/highlight/16302

AVR 8-Bit Timer
1
u32 t2_soft;
2
3
ISR(TIMER2_OVF_vect)
4
{
5
  t2_soft += 256;
6
}
7
8
u32 get_ticks(void)        // read T2 as 32 bit timer
9
{
10
  u32 val;
11
  u8 tifr;
12
13
  cli();
14
  val = t2_soft + TCNT2;
15
  tifr = TIFR;          // read interrupt flags
16
  sei();
17
  if( (tifr & 1<<TOV2) && !(val & 0x80) ) // overflow prior reading TCNT2 ?
18
    val += 256;                // then add overflow
19
20
  return val;
21
}

Meine Gedanken kreisen um folgende Zustände die laut meiner Meinung nach 
zufällig auftreten können.
1
TCNT2 = 254: if( ('x' & 1<<TOV2) && !(b1111'1110 & b1000'0000) )  val += 256; >> keine Addition
2
3
TCNT2 = 255: if( (   'false'   ) && !(b1111'1111 & b1000'0000) )  val += 256; >> keine Addition
4
5
TCNT2 = 255: if( (   'true'    ) && !(b1111'1111 & b1000'0000) )  val += 256; >> keine Addition
6
7
TCNT2 =   0: if( (   'true'    ) && !(b0000'0000 & b1000'0000) )  val += 256; >> Addition erfolgt

Die letzte Annahme macht mir Sorgen. Ich lege mal meine Gedankengänge 
offen. Man liest den Zählerstand mit 0 ein. Das OVF Flag ist schon 
gesetzt. Die OVF ISR kam aber aus irgendwelchen Gründen noch nicht zum 
Zug. Bis hierher so möglich? Wenn ja mach ich weiter. Jetzt addiert man 
selbst 256 dazu. Soweit noch okay. Danach wird die OVF ISR ausgeführt 
und addiert auch nochmal 256 dazu. Dann hätte man laut meiner Meinung 
nach für die nächste Zählerstandabfrage einmal zu viel 256 addiert.

get_ticks macht aus dem Zählerstand 0 richtigerweise 256. Dann addiert 
die OVF ISR paar Takte später 256 dazu. Damit hat man ein paar Takte 
später mindestens 512. Wenn man dann get_ticks nochmal aufruft, 
meinetwegen mit Timer-Zählerstand 100 ohne erneuten Überlauf, müßte der 
richtige Zählerstand eigentlich 256 + 100 = 356 sein. Man hätte laut 
meiner Überlegung jedoch falsche 512 + 100 = 612.

Hab ich einen Denkfehler oder nicht? Wenn ja an welcher Stelle?
Übrigens, wenn ich das sagen darf, gibts wirklich einen kleinen Fehler. 
Die globale Variable t2_soft muss volatile sein.

von Theor (Gast)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> jetzt habe ich den Vorteil/Unterschied mit Capture verstanden. Vielen
> vielen Dank.
Bitte. Gerne.

> Bitte nicht als Rückzug von Capture missverstehen.
> Danke.

Du machst das so, wie Du das für richtig hältst und wie es für Dich 
passt. Ich gebe nur Infos.

> Jetzt muss ich nochmal zurück zur Basis für mein Verständnis. Das lässt
> mir keine Ruhe.
> [...]
> Man liest den Zählerstand mit 0 ein. Das OVF Flag ist schon
> gesetzt. Die OVF ISR kam aber aus irgendwelchen Gründen noch nicht zum
> Zug. Bis hierher so möglich?

Seufz. Wieviel Tassen Kaffee schulde ich Peter eigentlich inzwischen?
Aber ja. Soweit möglich.

> Wenn ja mach ich weiter. Jetzt addiert man
> selbst 256 dazu. Soweit noch okay. Danach wird die OVF ISR ausgeführt
> und addiert auch nochmal 256 dazu. Dann hätte man laut meiner Meinung
> nach für die nächste Zählerstandabfrage einmal zu viel 256 addiert.
> [...]
> Hab ich einen Denkfehler oder nicht? Wenn ja an welcher Stelle?
> [...]

Ja.
Du hast übersehen (wenigstens geht's nicht nur mir so, hi hi ha ha hu 
hu), dass in dem einen Fall die Variable "val" um 256 erhöht wird und 
dem anderen Fall die Variable "t2_soft". Es wird also nicht zweimal 
die selbe Variable erhöht.

von Theor (Gast)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> [...]
> Übrigens, wenn ich das sagen darf, gibts wirklich einen kleinen Fehler.
> Die globale Variable t2_soft muss volatile sein.

Das macht in dem Fall nichts. Denn die Variable wird nur an einer Stelle 
und nur in einem "Thread" - einer Folge von Anweisungen -, verändert.

Volatile ist nötig dann, falls eine Variable in verschiedenen Folgen von 
Anweisungen geschrieben wird. Also z.B. im Hauptprogramm und in einem 
Interrupt.

von Oliver S. (oliverso)


Lesenswert?

Wird die Lesefunktion jetzt aus einer ISR aufgerufen, oder nicht?

Oliver

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ Theor:
Oh man, Code lesen müßte man können. :-)  Zu tief in einen Gedanken und 
man sieht die Unterschiede nicht mehr. Sind verschiedene Variablen die 
sich automatisch korrigieren. Puhh. Die Welt ist wieder in Ordnung. 
Alles macht wieder Sinn. Vielen Dank für den entscheidenden Hinweis.

Das mit dem volatile nehme ich mal so hin. Ich selbst mach jede Variable 
volatile die außerhalb des normalen Programmablaufes verändert werden 
könnte. Dann kann nichts schief gehen.  :-)   Oder ist das wieder ein 
Fall von Handoptimierung für genau diesen Code seitens Peter?

@ Oliver:
Für diese soeben erfolgte Betrachtung nicht. Wollte nur mit Peters Code 
Peters Rechentrick verstehen. In meinem späteren Vorhaben verwende ich 
nach aktuellen Überlegungen 2 Pin Interrupts.

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Veit D. schrieb:
> Für diese soeben erfolgte Betrachtung nicht. Wollte nur mit Peters Code
> Peters Rechentrick verstehen. In meinem späteren Vorhaben verwende ich
> nach aktuellen Überlegungen 2 Pin Interrupts.

 Also wird TCNT2 (später) doch in der ISR gelesen.
 Um wirkliche Kontrolle und exakte Resultate zu haben, wäre es besser,
 die ISRs in Assembler anstatt in C zu schreiben.
 Beispiel:
 Capture-Interrupt feuert bei TCNT2 von 254 und Befehlslänge von 2 Cy.
 Bis dein Programm in der ISR landet, vergehen noch mal 9 Takte. Je
 nachdem, was du in der Capture-ISR machst, vergehen noch 9-15 Takte
 bis du TCNT2 auslesen kannst (in C).
 Das ist ein Overhead von etwa 18-24 Cyclen.
 In Assembler hast du die 9 Cyclen + 3 PUSH(6Cy) + IN reg, SREG(1Cy)
 + LDS reg, TCNT2(2Cy) = 18 Cyclen bis zum auslesen.

 Also ist jeder Zählerstand > 18 (in ASM) schon aktualisiert, du gibst
 als val (t2_soft+TCNT2-18) und brauchst dich nicht weiter darum zu
 kümmern.

 Bei Zählerstand <= 18 wird (t2_soft+256+TCNT2-18) als val
 zurückgegeben. Dass t2_soft gleich nach Capture-ISR in der OVF-ISR um
 256 erhöht wird, interessiert dich nicht weiter - das wird erst beim
 nächsten Auslesen benötigt.

 Auf diese Weise hast du einen Fehler von max. 1Cy.

 P.S.
 Da die 18Cy konstant sind, brauchst du diese auch nicht unbedingt
 abzuziehen.

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


Lesenswert?

Hallo,

scheitert daran das ich kein Assembler kann. :-)
Ich setze das erstmal in C++ um und schau was die Schwankungsbreite vom 
Zähler macht. Frequenzgenerator kann ich mir mit anderen µC 
programmieren und paar Messmittel habe ich auch zu Hause. Mal schauen 
was bei raus kommt ...
Danke.

von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> scheitert daran das ich kein Assembler kann. :-)

Braucht’s dafür auch nicht. Auf die paar Zyklen mehr oder weniger kommt 
es nicht an.

Und Peters „Rechentrick“ ist gar keiner. Der prüft schlicht und einfach 
mit Test auf das gesetzte MSB, ob TCNT größer oder kleiner TCNT Max/2 
ist. Wenn nein, und das OVF-Flag gesetzt ist, dann gabs einen nicht 
gezählten Oberflow.

Oliver

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


Lesenswert?

Hallo,

lasse uns bitte nicht über das Wort streiten. Ob nun als Rechentrick 
oder Auswertetrick betitelt, ich meine, dass sollte egal. sein. 
Hauptsache ich habe es verstanden.  :-)

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Veit D. schrieb:
> scheitert daran das ich kein Assembler kann. :-)

 Ist ja auch nicht nötig.
 Eine einfache Prüfung von TOV2 reicht auch, vorausgesetzt deine
 beiden PinChange-ISR dauern nicht so lange, dass die beiden ständig
 nacheinander feuern.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

letzteres wird herausstellen.  :-)   Ich ziehe mich erstmal freundlich 
zurück.

von m.n. (Gast)


Lesenswert?

Marc V. schrieb:
> Das ist ein Overhead von etwa 18-24 Cyclen.
>  In Assembler hast du die 9 Cyclen + 3 PUSH(6Cy) + IN reg, SREG(1Cy)
>  + LDS reg, TCNT2(2Cy) = 18 Cyclen bis zum auslesen.

Diese Berechnungen taugen nur dann etwas, wenn nur Interrupts für die 
Impulsmessung freigegeben sind. Sobald zum Beispiel noch ein anderer 
Timer, die USART, usw. per Interrupt bedient werden, blockiert deren ISR 
die genaue Zeitmessung der Impulse. Die Verzögerungen können dann auch 
in den Bereich von >= 10 µs kommen.
Aber vielleicht ist das ja auch völlig ausreichend.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

m.n. schrieb:
> Diese Berechnungen taugen nur dann etwas, wenn nur Interrupts für die
> Impulsmessung freigegeben sind. Sobald zum Beispiel noch ein anderer
> Timer, die USART, usw. per Interrupt bedient werden, blockiert deren ISR
> die genaue Zeitmessung der Impulse.

 Nein, Timer2 kommt gleich nach INT0/1 und PCINT.
 Und eine Impulsmessung bei der alle Interrupts freigegeben sind, hätte
 auch wenig Sinn.

 Was ich aber nicht verstehe - wenn es so genau sein soll, warum wird
 dann nicht Timer1 mit seinen 16bit benutzt?
 Es sind über 4ms bis zum Überlauf, bei Timer2 sind es nur 16us.
 Bei Timer1 könnte man bedenkenlos TCNT1 Werte aufzeichnen - wenn
 der aktuelle Wert kleiner als der vorherige ist, addiert man ganz
 einfach 65536 dazu...

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


Lesenswert?

Moin,

keine Sorge, ich nehme dafür einen 16 Bit Timer. Wie schon mehrfach 
geschrieben war der 8Bit Timer aus Peters Codebsp. Was für das 
Verständnis keine Rolle gespielt hat.

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe es in Form gegossen, erhalte jedoch große Messwertschwankungen. 
Unterschiede bis zu 6375 Ticks was ca. 0,4ms bzw. 0,08% entspricht. Eine 
Testfrequenz von 1Hz erzeugt ein Arduino Mega2560 mittels Timerausgang. 
Der wird von einem Resonator getaktet. Davon wird die positive 
Signallänge vermessen.

Bei 1Hz hat die Pin ISR nur aller 500ms etwas zu tun. Der Timercount 
wird immer am Anfang an der gleichen Codestelle ermittelt. Danach 
erfolgt "nur" eine Auswertung wem das Ergebnis zugewiesen wird. Die 
Serielle wird nur am Ende einer Messung aktiv. Um das auszuschließen 
habe ich eine Zwischenspeicherung im Array eingebaut. Hat nichts 
gebracht. Dann habe ich den Timer TCB komplett stillgelegt, hat auch 
nichts gebracht. Dann habe ich eine generelle Messfreigabe eingebaut, 
erst wenn die Serielle fertig ist. Hilft auch nichts. Es gibt auch 
mittendrin größere Abweichungen.

Ist der Messfehler normal? Ich hoffe nicht. Wenn nein, Ideen was den 
Count verfälschen könnte? Verwendet wird ein Arduino Nano Every Board 
mit Atmega4809.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

hab noch einen ATmega328PB rausgeholt, einen 16MHz Quarz rangebammelt 
und lasse Timer 1 einen 1Hz Takt erzeugen. Steckbrettaufbau. Über 100 
Werte gesehen beträgt die max. Abweichung nur noch 2522 Ticks (0,15ms).

von Veit D. (devil-elec)


Lesenswert?

Hallo,

vielleicht buddel ich auf der falschen Baustelle.
Also. Jeder Quarz bzw. RC hat doch eine Fehlertoleranz.
Quarze meistens +/-30ppm und der Atmega4809 interne RC +/- 1.5% (25°C).
1.5% von 16MHz wären 240kHz.
Was ist damit genau gemeint?
Ist das der erlaubte Jitter vom Takt oder ist das die erlaubte 
Taktabweichung vom Sollwert welche ohne spezifierte Jitterangabe 
"stabil" taktet?
Anders gefragt, wenn bspw. der interne RC mit 16.2 statt 16.000 MHz 
taktet, bleibt der dann weitgehend stabil auf 16.2MHz oder schwankt 
dieser zwischen 15.8 und 16.2 MHz ständig hin und her? Konstante 
Temperatur und Spannung vorrausgesetzt.

von S. Landolt (Gast)


Lesenswert?

> vielleicht buddel ich auf der falschen Baustelle
(Arm am Beutel, krank am Herzen
Schleppt' ich meine langen Tage ...)

Da der ATmega4809 ja sicher mit dem internen RC-Oszillator läuft, ist 
ein Jitter von 0.15/500 = 300 ppm normal.


(... Und so zog ich Kreis' um Kreise,
Stellte wunderbare Flammen,
Kraut und Knochenwerk zusammen:
Die Beschwörung war vollbracht.)

von m.n. (Gast)


Lesenswert?

Veit D. schrieb:
> Anders gefragt, wenn bspw. der interne RC mit 16.2 statt 16.000 MHz
> taktet, bleibt der dann weitgehend stabil auf 16.2MHz

So ist es. Wie er dann typisch mit der Versorgungsspannung und der 
Temperatur driftet steht im Datenblatt.

Veit D. schrieb:
> Über 100
> Werte gesehen beträgt die max. Abweichung nur noch 2522 Ticks

Das würde ich anders formulieren:
"... beträgt die max. Abweichung doch noch 2522 Ticks ..."
Mit einem Quarzoszillator betrieben muß die Genauigkeit deutlich besser 
sein.

Du hoppst mit Deinen Gechichten zur sehr durch die Gegend.
Wie Deine "messwerte.txt" enstanden ist, ist unklar. Zunächst wird über 
einen ATmega328 geredet, dann ein ATmega4809 verwendet und zum Schluß 
ein Arduino-Code für den ATmega2560 gezeigt.
Weiter oben hatte ich Dir einen funktionierenden Code gezeigt. Der 
scheint jedoch nicht angekommen zu sein.

von m.n. (Gast)


Lesenswert?

S. Landolt schrieb:
> (... Und so zog ich Kreis' um Kreise,
> Stellte wunderbare Flammen,
> Kraut und Knochenwerk zusammen:
> Die Beschwörung war vollbracht.)

Und? Haben die Keckse geschmeckt?
;-)

von S. Landolt (Gast)


Lesenswert?

> Und? Haben die Keckse geschmeckt?
Sieht man doch!

von S. Landolt (Gast)


Lesenswert?

> ... und der Atmega4809 interne RC ...

Ich habe eben mal einen ATmega4809 mit seinem internen RC-Oszillator 
gegen 10 MHz quarzstabil gemessen, über ca. 40 Werte, und erhalte als 
Minimum 0x9898B6, als Maximum 0x98AE0C, also 546 ppm.


(Trinke Mut des reinen Lebens!
Dann verstehst du die Belehrung,
Kommst mit ängstlicher Beschwörung
Nicht zurück an diesen Ort.
Grabe hier nicht mehr vergebens!)

von Veit D. (devil-elec)


Lesenswert?

m.n. schrieb:

> Wie Deine "messwerte.txt" enstanden ist, ist unklar.
Der erste Wert ist die gemessene Pulsdauer in Timerticks, dahinter 
umgerechnet in ms.

> Du hoppst mit Deinen Gechichten zur sehr durch die Gegend. ...
> Zunächst wird über einen ATmega328 geredet, dann ein ATmega4809 verwendet
> und zum Schluß ein Arduino-Code für den ATmega2560 gezeigt.

Das hier ist kompletter Unsinn. Ohne weiteren Kommentar.

Ansonsten, ich überlege weiter ...

von Theor (Gast)


Lesenswert?

@ Veit

Vermutlich bist Du darauf auch schon gekommen, aber da seit gut 7 
Stunden nichts kommt, sage ich es mal sicherheitshalber:

Betreibe am besten beide Schaltungen, den Mess-uC und den Generator-uC 
mit einem Quarz oder Resonator.

von Peter D. (peda)


Lesenswert?

Theor schrieb:
> Betreibe am besten beide Schaltungen, den Mess-uC und den Generator-uC
> mit einem Quarz oder Resonator.

Der ATmega2560 hat genügend Timer, um beides zu machen. Damit sind schon 
mal Taktfehler eleminiert.
Die Frequenzen und Meßzeiten wählt man dann leicht unterschiedlich, um 
worst case Bedingungen zu durchlaufen, d.h. keine gemeinsamen Teiler.
Die Testsignale werden von den PWM-Einheiten in HW erzeugt, also ohne 
Interrupts.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich denke laut S.Landolt ist die Baustelle Takterzeugung die falsche 
Baustelle?
Deswegen bin ich am Code durchgehen und habe vielleicht meine eigene Pin 
Lib in Verdacht, allerdings ohne ersichtlichen Grund, und überlege die 
rauszunehmen und alles pure zu programmieren. Was den Eingangspin und 
dessen Interruptauswertung betrifft.

Alles auf dem Mega2560 zu testen ist eine gute Idee, werde alles 
rüberziehen.
Ich bitte um Geduld. Danke.

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Gute Nacht,

habe den Code auf den Arduino Mega2560 portiert. Läuft.
Prescalereinstellung sollte keine Rolle spielen, weil das alles 
gemeinsame Vielfache sind.
Habe erstmal mit 1Hz, 3Hz und 9Hz probiert. Maximale Messabweichung sind 
8 Timerticks.
Im Mittel weniger. Das sieht schon einmal gut aus.
Wenn ich morgen dazu komme teste ich das mit fremden Takt, also zwei 
verschiedene µC.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Zwischenstand ist:
Taktgeber ATmega328PB mit 16MHz Quarz.
Messung mit Arduino Mega2560.
Soweit stabil bis max. 7 Timerticks, bis auf einen Ausreißer mit 27 
Timerticks von 500 Messwerten.

Jetzt bau ich gerade den Code für den Arduino Nano Every (ATmega4809) um 
und steck dabei gerade fest. Der misst derzeit völligen Mist.

von Peter D. (peda)


Lesenswert?

Veit D. schrieb:
> bis auf einen Ausreißer mit 27
> Timerticks von 500 Messwerten.

Dann wurde wohl grad ein Interrupthandler abgearbeitet bei der Flanke.

Veit D. schrieb:
> Der misst derzeit völligen Mist.

Sicher, daß der Quarz als Takt ausgewählt wurde.
Der RC-Oszillator reicht nur für 2-3 Digits.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

um die Taktquellenauswahl kümmert sich die Arduino IDE. Das überlasse 
ich ihr. Da sind standardmäßig 16MHz eingestellt. Die Taktquelle ist der 
interne RC-Oszillator. Daran kann ich auch nichts ändern. Wenn man einen 
externen Quarz ranhängen möchte, benötigt die megaAVR0 Serie einen Quarz 
mit einem Taktausgangspin. Ich komme gerade nicht auf die genaue 
Bezeichnung. Meine vorhandenen HC49 Typen sind dafür die Falschen.

Das derzeitige Messproblem betrifft nicht nur eine Abweichung von paar 
Ticks, nein, der misst permanent völlig falsche 80.000 Ticks als 
Abweichung. Ich bin  dabei meine PinLib rauszunehmen. Ich erreiche nicht 
einmal die Anfangswerte mit Abweichungen von "nur" 6375 Ticks. Da muss 
ein Fehler im Code sein.  :-)  Ich melde mich wieder.

von S. Landolt (Gast)


Lesenswert?

> Ich komme gerade nicht auf die genaue Bezeichnung.
Nanu? "Quarzoszillator" heißt so ein Teil, schlicht&einfach.

von S. Landolt (Gast)


Lesenswert?

PS:
Der Atmega4809 hat nur einen eingebauten Quarzoszillator für einen 32 
KiHz-Quarz, zur Verwendung als RTC; wobei dieser allerdings auch für den 
Haupttakt verwendet werden kann. Ansonsten aber muss, wenn der interne 
RC-Oszillator zu ungenau ist, ein externer Takt angeschlossen werden.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Quarz vs. Quarzoszillator.    :-)  Gut.

> Ich habe eben mal einen ATmega4809 mit seinem internen RC-Oszillator
> gegen 10 MHz quarzstabil gemessen, über ca. 40 Werte, und erhalte als
> Minimum 0x9898B6, als Maximum 0x98AE0C, also 546 ppm.
Diese Abweichung ist derzeit nicht mein Problem.

Wenn ich nicht weiterkomme zeige ich den Code.

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe jetzt aus 285 Messwerten eine maximale Differenz von 3182 
Timerticks. Frequenz ist 1Hz und Duty-Cycle ist 1/3. 1/3 deswegen, damit 
ich prüfen kann ob ich immer die positive Signallänge messe.
Die durchschnittliche Pulslänge beträgt 5.361.638 Ticks.
Mal 3 ergibt die Periodendauer 16.084.914 Ticks, dass mal 62,5ns = 
1.005.307.125ns = 0,99472Hz
3182 Ticks von 16.084.914 Ticks = 3182 / 16,084914 = 197ppm
Alles in ppm umgerechnet wären laut meiner Logik 197ppm.
Würde bedeuten der interne RC-Oszillator schwankt permanent mit 200ppm. 
Soweit korrekt?
Oder sieht jemand Fehler im Code, in der Auswertelogik?
Wenn nicht, liegst allein am internen RC-Oszillator?

von S. Landolt (Gast)


Lesenswert?

Das verstehe ich, wieder einmal, nicht: 3182/5361638 entspricht doch 593 
ppm. Was übrigens auch eher dem entspricht, was hier mein ATmega4809 mit 
seinem internen RC-Oszillator zeigt.

von S. Landolt (Gast)


Lesenswert?

PS:
Ich meine, sonst könnte man ja einfach den Tastgrad auf z.B. 1/100 
absenken, und, hast du nicht gesehen, wäre der ATmega4809 auf 6 ppm 
stabil.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ja stimmt, ich habe die falschen Werte ins Verhältnis gesetzt.
Danke.

Edit:
Vielleicht fragt sich jemand, warum ich nur noch eine Abweichung von ca. 
3200 zu anfänglichen ca. 6400 Ticks messe. Das Einzigste was ich beim 
Umbau in meiner Lib entdeckt habe, war eine falsch hinterlegte Bitmaske 
zum reseten für Interruptpins. Dadurch waren im PINnCTRL Register alle 
"sens on ... edges" aktiviert statt auf 0.

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


Lesenswert?

Guten Morgen,

ich wollte mich nochmal bei allen Beteiligten bedanken für die 
Hilfestellungen und Korrekturen.

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.