Guten Abend zusammen,
die triviale Abfrage eines 8bit Timers bereitet mir etwas
Kopfzerbrechen,
wäre nett wenn mal jemand mit drauf schauen könnte. Sicher gibt es auch
andere Lösungsansätze aber mir gehen bei diesem Projekt die Timer aus
und daher muss ich einen 8 bit Timer für die Systemzeit und auch relativ
kurze
Zeitmessungen einsetzen (unter anderem wird die Funktion auch aus
anderen ISR´s angesprungen für eine serielle "One Wire"-Übertragung). Da
die Systemzeit natürlich keine Abweichungen haben darf, kann ich den
Timer nicht stoppen. Der Timer läuft im CTC-Mode.
1
staticvolatileuint32_tctcCounter;//Wird in ISR "CTC Match A" inkrementiert
if((TIFR&(1<<OCF0A))!=0)//Timer CTC-Reset somewhere after disabling interrupt or before during call from other ISR
10
{
11
*tcnt0Value=TCNT0;
12
*ctcCounterValue+=1;
13
USART_Push('O');//Debug
14
USART_Push('F');//Debug
15
USART_Push('\n');//Debug
16
}
17
SREG=SREG_COPY;
18
}
Bis jetzt scheint es so zu klappen, bin mir irgendwie aber nicht ganz
sicher ob es da nicht doch ein Schlupfloch gibt, wo TCNT0 und CTC nicht
zusammenpassen...(Abgesehen davon, dass der Timer 2x überläuft)
Gruß Dominik
Dominik schrieb:> Bis jetzt scheint es so zu klappen, bin mir irgendwie aber nicht ganz> sicher ob es da nicht doch ein Schlupfloch gibt, wo TCNT0 und CTC nicht> zusammenpassen...(Abgesehen davon, dass der Timer 2x überläuft)
Einen Schlupfloch hast du mit deiner OCF0A Abfrage ausgegraben.
Wenn du dir ctcCounter als Wert links von Komma vorstellst und TCNT0
als Wert rechts von Komma, wird dir vielleicht klarer warum deine
Abfrage unnötig und falsch ist.
Hallo,
ja irgendwie gefällt mir die Lösung auch noch nicht (Bauchgefühl beim
programmieren).
Weglassen der Abfrage scheint allerdings keine Option zu sein.
Betrachtet man den einfachen Fall, dass die Funktion nicht aus einer
anderen ISR angesprungen wird:
1
cli();//Interrupts aus, ctcCounter "eingefroren"
2
*tcnt0Value=TCNT0;//TCNT0 einlesen
Wenn der Timer in dem Moment überläuft wo die interrupts ausgeschaltet
werden wird die TCNT0-Abfrage den Wert 0 liefern. Damit fehlt dann eine
komplette Periode in der Zeitrechnung.
Mit Abfrage:
Läuft der Timer erst direkt im Anschluss an die Abfrage über erhalte ich
noch den Max-Wert.
Damit hätte die Zeitrechnung eine komplette Periodendauer zu viel.
(durch inkrementieren des Counters weil zwischenzeitlich flag gesetzt
wurde)
Daher lese ich den TCNT0-Wert dann auch nochmal ein.(der dann nahe oder
gleich 0 sein wird)
Ich bin natürlich an einer besseren Lösung interessiert, da
die 32 bit-Zuweisung(insb. da volatile) dazwischen den TCNT0-Wert durch
veränderliche Laufzeiten etwas verfälscht.
Besser wäre es sicher das Flag vorher und nachher abzufragen und den
TCNT0-Wert nur einmal einzulesen.
Für bessere Vorschläge bin ich natürlich offen.
Gruß Dominik
hi,
vielleicht off toptic ...
mir erklärt sich nicht der sinn/unsinn von volatile bei den call
parametern?!
>> static void getTime0(volatile uint32_t *ctcCounterValue, volatile uint8_t
*tcnt0Value)
hast du da eine erleuchtung für mich?!
warum da pointer sinn/vorteil haben frag ich lieber erst garnicht!?
oder *ctcCounterValue+=1; sieht auch recht hilflos aus anstatt z.b.
++*ctcCounterValue;
++(*ctcCounterValue);
(*ctcCounterValue)++;
mt
Hallo,
du möchtest nur den Zählerstand abfragen und den Überlauf mitbekommen?
Der Timer läuft kontinuierlich durch? Ansonsten bitte noch einmal
genauer erklären. Welcher Controller?
Apollo M. schrieb:> hi,> vielleicht off toptic ...> mir erklärt sich nicht der sinn/unsinn von volatile bei den call> parametern?!>>>> static void getTime0(volatile uint32_t *ctcCounterValue, volatile uint8_t> *tcnt0Value)
Es handelt sich offenbar um globale Variablen zum Austausch zwischen ISR
und Hauptprogramm, deren Adressen bei Aufruf übergeben werden. Damit
muss dann der Zeiger auch einer auf volatile sein.
Dominik schrieb:> Ich bin natürlich an einer besseren Lösung interessiert, da> die 32 bit-Zuweisung(insb. da volatile) dazwischen den TCNT0-Wert durch> veränderliche Laufzeiten etwas verfälscht.
Dann mach halt da keinen volatile-Zugriff. Kopier dir den Wert am Anfang
der Funktion in eine lokale Variable und ganz am Ende der Funktion
zurück. Diese Kopie wird sowieso gemacht, da die Werte ja in Registern
verarbeitet werden müssen, aber du hast dann nur einmal den
volatile-Schreibzugriff, statt zweimal.
Hallo,
vielen Dank für die Antworten. Ja, die Funktion stellt eine Zeitabfrage
für Systemzeit und kurze Zeitmessungen dar, ich muss für beides den
gleichen Timer verwenden, da die anderen durch Frequenzzählungen und PWM
blockiert sind. Da die Systemzeit einmal gestellt wird, muss der Timer
frei durchlaufen um keine Abweichungen zu bekommen.
Laufen wird das ganze auf einem Atmega328p (mit Baudratenquarz) und
zusätzlich auf einem Attiny45, beide kommunizieren bi-direktional über
einen einzelnen Draht gegen Masse mittels eines Codes mit
unterschiedlichen Phasenlängen für 1,0, Start, Stopp, etc.
Für den Sendevorgang habe ich eine State-Maschine in einer CTC-ISR
geparkt.
Das Empfangen läuft über Pin-Change, dafür ist die diskutierte Funktion
nötig.
Die etwas selten anzutreffende volatile Pointer Variante im
Funktionskopf hat Rolf ja schon richtig erklärt.
Warum Zeiger?
Nun ja, Alternativ hätte ich auch einen Zeiger auf ein Array, ein Struct
o.ä. zurückgeben können, allerdings schien mir die Variante mit zwei
16bit Zeigern am transparentesten und effektivsten. Rückgabe eines 64bit
Wertes kommt nicht in Frage, da der CTC-Wert kein 2^n Wert ist, also mit
jeder Abfrage eine Multiplikation nötig gewesen wäre und ich den TCNT
Wert für
die Systemzeit wegwerfe (also nochmal Division), abgesehen davon, das 24
bit Overhead auf den kleinen Dingern ne Menge Müll ist ;-)
Rolf M. schrieb:> Dann mach halt da keinen volatile-Zugriff.
Ja schon mal sieht man den Wald vor lauter Bäumen nicht, das war in der
Tat ein wesentlicher Hinweis.
Mir war bei anderen Projekten aufgefallen, das ein volatile Zugriff
"teurer" sein kann als eine Multiplikation...
Die zuerst gepostet Funktion hatte bei 9,216 Mhz eine Laufzeit von
11.72µs.
Diese hier läuft nur ~5.6 µs:
Wenn du jetzt noch gute Gründe für die Sicherung des Sregs und das cli()
aufführen kannst, dann tu das. Ansonsten lass das einfach weg, das spart
dann auch noch Zeit.
Dafür dauert der Aufruf einer Funktion aus einer ISR Leder sehr lange,
da der Compiler mangels Kenntniss der aufzurufenden Funktion alle
Register sichern muß. Da solltest du dir den generierten Assemblercode
mal ansehen.
Oliver
Oliver S. schrieb:> Wenn du jetzt noch gute Gründe für die Sicherung des Sregs und das cli()> aufführen kannst, dann tu das. Ansonsten lass das einfach weg, das spart> dann auch noch Zeit.
Darüber muss ich nochmal laut nachdenken...
Also wenn ich die Funktion nicht aus einer anderen ISR aufrufe, könnte
es passieren, das der TIMER-Interrupt direkt nach
1
readTCNT0=TCNT0;
ausgeführt wird, dann ist
1
OCF0A_after=(TIFR&(1<<OCF0A));
gleich 0, weil der Interrupt das Flag abgeräumt hat. Damit wäre der
ctcCounter Wert erhöht, ich habe aber wahrscheinlich den maximalen
CTC-Compare-Wert im readTCNT0, meine Zeitmessung ist dann wieder eine
Periode zu lang, oder
habe ich da gerade einen Denkfehler?
Ein Blick in den Assemblercode kann nicht schaden, im Moment habe ich
mich mit dem Blick ans Osszi gewandt, für meine Abtastung des Protokolls
bin ich erst mal am Ziel, zudem läuft der Mega später sogar mit der
doppelten Frequenz, hat allerdings auch ein paar Interrupt-Routinen mehr
zu bearbeiten.
Peter D. schrieb:> Beitrag "AVR Timer mit 32 Bit"
Hätte mich auch gewundert wenn ich der erste mit dem Problem gewesen
wäre :-)
Habe mal kurz reingesehen, da mir anfangs eine ähnliche Lösung
vorschwebte. Allerdings hatte ich noch ein paar andere Schwierigkeiten:
der verlinkte Code profitiert von der performanten Ausnutzung der
Bitmaske 0x80, da meine Obergrenze nicht zwangsläufig 0xFF ist wären
hier zusätzliche Takte nötig, wenn ich das richtig gesehen habe war es
genauer genommen aber eigentlich auch ein 0x00000080(ul).
Bei mir sah das dann so aus: (TCNT0<(OCR0A>>1))
Im Prinzip eine Wette auf die halbe TIMER-Laufzeit, klar obige Funktion
ist auch eine Wette, aber so wie ich das sehe auf die ganze Laufzeit.
(Hoffe ich zumindest ;-))
Gruß Dominik
Dominik schrieb:> habe ich da gerade einen Denkfehler?
Nein. Das Lesen des Timers und des Überlaufbits muß atomar erfolgen.
Dominik schrieb:> der verlinkte Code profitiert von der performanten Ausnutzung der> Bitmaske 0x80
Der Wert muß nicht exakt die Mitte sein.
Der worst-case ist, ein anderer Interrupt wird ausgeführt, an dessen
Anfang der Timer überläuft. Und nach diesem Interrupt wird als erstes
das CLI der Lesefunktion ausgeführt. Der Timer ist also die ganze Zeit
weiter gelaufen. Daher wäre ein Schwellwert ~10 Takte vor dem Überlauf
vielleicht besser.
Der Test auf 0x80 ist zwar codesparend, aber 1..3 Zyklen mehr, sind auch
kein Beinbruch.
Veit D. schrieb:> das einfachste ist den Overflow Interrupt vom Timer zu nutzen.
Das ist nicht die Frage, sondern ob der Timer nach oder vor dem
Überlaufinterrupt gelesen wurde.
Peter D. schrieb:> Der Timer ist also die ganze Zeit> weiter gelaufen. Daher wäre ein Schwellwert ~10 Takte vor dem Überlauf> vielleicht besser.
Ja, dass sehe ich auch so, wobei die Abfrage der Mitte (unabhängig von
den verwendeten Stilmitteln) sicher ganz gut passt.
Peter D. schrieb:>Und nach diesem Interrupt wird als erstes>das CLI der Lesefunktion ausgeführt.
Ne, ich glaube dann wird erstmal der Compare Match ausgeführt, weil der
Überlauf ja das Flag gesetzt hat.
1
ISR(TIMER0_COMPA_vect)//32 bit Counter
2
{
3
ctcCounter++;
4
}
Das Wäre zwar in Sachen "genaue" Zeitmessung der Worst-Case, aber dafür
wäre ich in der Abfrage wieder beim Standardfall, weder vor noch nach
dem Auslesen ist das Flag gesetzt, ergo stimmt auch mein ctcCounter
ebenso wie der TCNT0-Wert.
Ich kann mich glaube allmählich selbst davon überzeugen, dass die
Variante mit 2x Flag lesen wohl am sichersten ist, nur so kann ich mit
"absoluter" Sicherheit sagen, das ich den Wert aus dem Timer-Register
dem richtigen CTC-Wert zuordne. Außer irgendeine ISR oder sonstiger Code
blockieren den Compare Match für 2 Zyklen, allerdings ist dann wohl eher
der Rest vom Programm untauglich.
Sofern ich was übersehen habe, bitte ich natürlich um Korrektur, sonst
hätte ich mich ja nicht zur Prüfung an Euch gewandt :-)
Veit D. schrieb:> das einfachste ist den Overflow Interrupt vom Timer zu nutzen. Einfache> Sache. Den aktivierst du mitTIMSK0 = (1<<TOIE0); // enable Overflow> Interrupt
Ok, bei so viel Text geht das auch mal unter, (siehe ersten Post) ich
nutze allerdings den Compare Match, der OVF wird beim CTC Mode nur in
zwei besonderen Fällen angesprungen, kann man dann ganz gut zum debuggen
einsetzen ;-)
Veit D. schrieb:> ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {> overflow_count = ovf_count_T0;> }
Ich bin mit den ATOMIC_BLOCK´s nicht ganz so vertraut, passiert da noch
etwas anderes als beim "manuellen" sichern des SREG und wieder
zurückschreiben (zumindest beim Attribut ATOMIC_RESTORESTATE) ?
Bei Forceon impliziere ich mal ist es dasselbe wie cli();... sei();
oder?
Veit D. schrieb:> warum muss er das wissen? Er möchte nur die aktuelle Systemzeit haben.
Jein, nur für die Systemzeit würde ich mich mit dem ctcCounter*OCR0A
begnügnen, da macht eine 8bit Periode den Braten nicht fett, kommt halt
drauf an wie genau die Zeit sein soll, aber selbst bei Prescaler 1024
und 18,432 Mhz wäre die Abweichung max. 1/70 Sekunde.
Mein Problem ist eher, dass getTime0() zusätzlich möglichst genau
Auflösen muss, damit ich auch kurze Zeitspannen erfassen kann
(unabhängig von der Systemzeit). Da wird dann jeweils aus zwei Abfragen
das Delta berechnet.
Gruß Dominik
Wenn diese Kurzzeitabfragen nur für kürzere Zeiträume als zwei
Timererioden gedacht sind, dann lies dafür einfach nur das Timerregister
zu Anfang und zu Ende aus. Dazu noch ein Vergleich, um den einmalig
möglichen Überlauf dazwischen zu erkennen, und fertig.
Oliver
Dominik schrieb:> Ne, ich glaube dann wird erstmal der Compare Match ausgeführt, weil der> Überlauf ja das Flag gesetzt hat.
Der AVR führt nach jedem RETI erstmal einen Befehl der Mainloop aus.
Die Wahrscheinlichkeit, daß dieser Befehl das CLI einer atomaren Sektion
ist, ist zwar gering, aber nicht Null. Daher ist es umso wichtiger,
solche kritischen Abschnitte gedanklich zu verstehen und zu überprüfen.
Peter D. schrieb:> Der AVR führt nach jedem RETI erstmal einen Befehl der Mainloop aus.> Die Wahrscheinlichkeit, daß dieser Befehl das CLI einer atomaren Sektion> ist, ist zwar gering, aber nicht Null. Daher ist es umso wichtiger,> solche kritischen Abschnitte gedanklich zu verstehen und zu überprüfen.
Wieder was dazugelernt, mich beschlich schon des Öfteren die Frage was
passiert wenn ich das SREG sichere, dann ein Interrupt das SREG
verändert und ich dann bei cli fortsetze. Dann schreibe ich ja zum
Schluss das SREG falsch. (BTW: Würde das Atomic Block Makro das
verhindern?)
Wie gehst Du da vor?
Angeregt von deinem 32-bit Timer ist noch folgende Alternative
entstanden:
static uint64_t getTime0Alt()
{
uint64_t time;
uint8_t OCF0A_before, OCF0A_after, readTCNT0;
uint8_t SREG_COPY=SREG; //Save Status Register
cli(); //Disable interrupts if not already blocked by call
from other ISR
do
{
OCF0A_before=(TIFR&(1<<OCF0A));
readTCNT0=TCNT0;
OCF0A_after=(TIFR&(1<<OCF0A));
}
while(OCF0A_before!=OCF0A_after);
if(OCF0A_before==0) //Timer Overflow before readOut
{
time=softTicks+readTCNT0;
}
else
{
time=softTicks+(OCR0A+1ull)+readTCNT0;
}
SREG=SREG_COPY; //Restore Status register
return time;
}
Läuft eine µs (9,216 MHz) länger, aber bei wesentlich größerer
Auflösung. Zudem ist mir aufgefallen, dass die vorherige Variante
deutlich mehr in der Laufzeit schwankt wenn der Timer Interrupt
dazwischen kommt, liegt wahrscheinlich an den zwei volatile-Pointern.
Oliver S. schrieb:> Wenn diese Kurzzeitabfragen nur für kürzere Zeiträume als zwei> Timererioden gedacht sind, dann lies dafür einfach nur das Timerregister> zu Anfang und zu Ende aus. Dazu noch ein Vergleich, um den einmalig> möglichen Überlauf dazwischen zu erkennen, und fertig.
Ja, wäre für die ganz kurzen Zeiträume deutlich besser, denke werde das
noch einarbeiten, zumindest für die LCD-Ansteuerung kenne ich die Zeiten
ja relativ genau. Wenn aber die Gegenstelle bei meiner aktuellen
Anforderung mal die Baudrate ändert (was ich für die Zukunft nicht
ausschließen will) klappt das leider nicht.
Danke für die regen Denkanstöße!
Gruß Dominik
Dominik schrieb:> ein Interrupt das SREG> verändert
Ein Interrupt muß alle Arbeitsregister, SREG, PC und SP so hinterlassen,
als wäre er nicht passiert. Der C-Compiler kümmert sich automatisch
darum.
Creates a block of code that is guaranteed to be executed atomically. Upon entering the block the Global Interrupt Status flag in SREG is disabled, and re-enabled upon exiting the block from any exit path.
Ein Blick in die atomic.h zeigt dort wird auch das SREG gesichert und
wieder zurückgeschrieben. Auch cli(); und sei(); werden dort verwendet:
1
static__inline__uint8_t__iSeiRetVal(void)
2
{
3
sei();
4
return1;
5
}
6
7
static__inline__uint8_t__iCliRetVal(void)
8
{
9
cli();
10
return1;
11
}
12
13
static__inline__void__iSeiParam(constuint8_t*__s)
14
{
15
sei();
16
__asm__volatile("":::"memory");
17
(void)__s;
18
}
19
20
static__inline__void__iCliParam(constuint8_t*__s)
21
{
22
cli();
23
__asm__volatile("":::"memory");
24
(void)__s;
25
}
26
27
static__inline__void__iRestore(constuint8_t*__s)
28
{
29
SREG=*__s;
30
__asm__volatile("":::"memory");
31
}
liegt der Trick in dem Assembler-Teil?
Peter D. schrieb:> Ein Interrupt muß alle Arbeitsregister, SREG, PC und SP so hinterlassen,> als wäre er nicht passiert. Der C-Compiler kümmert sich automatisch> darum.
Ohne Haarspalterei betreiben zu wollen (außerdem sollte der
Programmierer es ja dann besser wissen), und ohne es auszuprobieren, was
passiert hier:
Pseudocode:
1
sei();
2
SREG_sav=SREG;
3
...
4
ISR(egal_welche)
5
{
6
SREG&=~(0b10000000);
7
}
8
...
9
SREG=SREG_sav;
Abgesehen davon hat mich Deine Antwort einiger Sorgen beraubt.
Gruß Dominik
Dominik schrieb:> Ohne Haarspalterei betreiben zu wollen (außerdem sollte der> Programmierer es ja dann besser wissen), und ohne es auszuprobieren, was> passiert hier:>> Pseudocode:> sei();
Hier wird das Interrupt-Flag gesetzt.
> SREG_sav=SREG;
Hier wird das gesetzte Interrupt-Flag in eine Variable gesichert.
> ...> ISR(egal_welche)
Hier wird das Interrupt-Flag automatisch gelöscht.
> {> SREG&=~(0b10000000);
Hier wird das bereits gelöschte Interrupt-Flag nochmal gelöscht.
> }
Hier wird es automatisch wieder gesetzt.
> ...> SREG=SREG_sav;
Hier wird das bereits gesetze Flag durch das gesetzte Flag aus der
Variable ersetzt.
Also mit anderen Worten: Das Ergebnis ist das gleiche, wie wenn du den
ganzen Krempel weggelassen hättest.
Dominik schrieb:> ISR(egal_welche)> {> SREG&=~(0b10000000);> }
Dann schau Dir mal das Assemblerlisting der ISR an (Prolog, Epilog) und
die Beschreibung des RETI.
Peter D. schrieb:> Dann schau Dir mal das Assemblerlisting der ISR an (Prolog, Epilog) und> die Beschreibung des RETI.
So oder so scheint das Flag nach einer Interrupt Routine grundsätzlich
gesetzt zu werden, da die Entwickler annahmen, niemand würde in einer
Interrupt Routine die Interrupts global abschalten?
[ ] Bingo!
Gruß Dominik
Rolf M. schrieb:> Also mit anderen Worten: Das Ergebnis ist das gleiche, wie wenn du den> ganzen Krempel weggelassen hättest.
Sorry, die ausführliche Antwort, sagt es ja auch.
Finde es nicht ganz uninteressant, dass ich auf Grund des "kleinen"
Timer-Problems einen Einblick in die Tiefen des Interrupt-Handlings
gewinnen durfte, einiges war mir vorher nicht so ganz klar.
Danke nochmal für die guten Erläuterungen.
Gruß Dominik
Dominik schrieb:> da die Entwickler annahmen, niemand würde in einer> Interrupt Routine die Interrupts global abschalten?
Man könnte auch von der anderen Seite herangehen:
Könnte man sich eine Anwendung vorstellen, bei der es sinnvoll ist, an
einer völlig zufälligen Stelle der Mainloop die Interrupts global
abzuschalten und trotzdem die Mainloop fortzusetzen?
Ein Interrupt weiß nie, wo er die Mainloop unterbricht und die Mainloop
weiß nie, wo sie unterbrochen wurde.
Apollo M. schrieb:> static void getTime0(volatile uint32_t *ctcCounterValue, volatile uint8_t> *tcnt0Value)
Hier ist volatile in der Tat überflüssig und erzeugt nur unnötig
Overhead.
Volatile braucht man, um Daten zwischen einem Interrupt und Main
auszutauschen.
Hier sind es aber Funktionsargumente, d.h. sie werden immer im selben
Kontext verwendet.
Ich würde hier auch keine Pointer verwenden, sondern die Werte direkt
verarbeiten zu einem Returnwert oder eine Struct returnen. Damit
vermeidet man unnötigen Overhead. Werte können direkt in Registern
übergeben werden und müssen nicht erst umständlich per Pointer in den
RAM geschrieben und dort wieder abgeholt werden.
Hi Peter,
Peter D. schrieb:> Hier ist volatile in der Tat überflüssig und erzeugt nur unnötig> Overhead.> Volatile braucht man, um Daten zwischen einem Interrupt und Main> auszutauschen.> Hier sind es aber Funktionsargumente, d.h. sie werden immer im selben> Kontext verwendet.>
so hatte ich auch erst gedacht, aber ...
lese mal
https://stackoverflow.com/questions/9935190/why-is-a-point-to-volatile-pointer-like-volatile-int-p-useful
...
The reason for this is that the C compiler no longer remembers that the
variable pointed at by ptr is volatile, so it might cache the value of
*p in a register incorrectly. In fact, in C++, the above code is an
error. Instead, you should write
volatile int myVolatileInt;
volatile int* ptr = &myVolatileInt; // Much better!
Now, the compiler remembers that ptr points at a volatile int, so it
won't (or shouldn't!) try to optimize accesses through *ptr.
mit den infos sehe ich das jetzt anders, sprich wie der autor und
denke, ich habe wieder was dazu gelernt.
> Ich würde hier auch keine Pointer verwenden, sondern die Werte direkt> verarbeiten zu einem Returnwert oder eine Struct returnen. Damit> vermeidet man unnötigen Overhead. Werte können direkt in Registern> übergeben werden und müssen nicht erst umständlich per Pointer in den> RAM geschrieben und dort wieder abgeholt werden.
ja, stimme dir zu. den sinn/zwang/vorteil hier pointer als call
parameter zu verwenden sehe ich auch nicht! wie du sehe ich nur die
genannten nachteile.
wenn die funtion nur diesen einen kontext hat und die laufzeit ein thema
ist, dann neige ich dazu gar keine parameter zu übergeben. sondern hier
mit globalen variablen zu arbeiten.
mt
Apollo M. schrieb:> lese mal> https://stackoverflow.com/questions/9935190/why-is-a-point-to-volatile-pointer-like-volatile-int-p-useful
Die Frage ist aber, benutzt Du die Variable in verschiedenen Kontexten?
Z.B. das Main ruft die Funktion auf, wertet die Variablen aber nicht
aus. Und ein Interrupt soll sie auswerten. In dem Fall darf ohne
volatile das Main den Schreibzugriff wegoptimieren. Aber auch nur, wenn
die Funktion geinlined wird.
Ich hatte allerdings den Eindruck, daß die Funktion im Main-Kontext
aufgerufen und ausgewertet werden soll.
Peter D. schrieb:> Apollo M. schrieb:>> static void getTime0(volatile uint32_t *ctcCounterValue, volatile uint8_t>> *tcnt0Value)>> Hier ist volatile in der Tat überflüssig und erzeugt nur unnötig> Overhead.
Nein.
> Volatile braucht man, um Daten zwischen einem Interrupt und Main> auszutauschen.
Ist ja hier der Fall.
> Hier sind es aber Funktionsargumente, d.h. sie werden immer im selben> Kontext verwendet.
Die Funktionsargumente sind Zeiger auf Variablen, die sowohl in der ISR,
als auch außerhalb verwendet werden. -> volatile
> Ich würde hier auch keine Pointer verwenden, sondern die Werte direkt> verarbeiten zu einem Returnwert oder eine Struct returnen.
Dann müsste der Aufrufer die Werte aus der Struktur wiederum zurück
kopieren.
> Damit vermeidet man unnötigen Overhead. Werte können direkt in Registern> übergeben werden und müssen nicht erst umständlich per Pointer in den> RAM geschrieben und dort wieder abgeholt werden.
Müssen sie doch eh, da volatile.
Hallo,
in der Tat war die Variante mit den Pointern aus den zuletzt genannten
Gründen nur "volatile" möglich. Sicher gebe ich allen, die Einwände
geäußert haben, recht: es führen viele Wege nach Rom. Wie im
Eingangspost
erwähnt, war mir dieser Weg gefühlt am sinnvollsten. Nach Überprüfung
der
Laufzeiten war es auch (im Schnitt) die schnellste (der getesteten)
Varianten, mit der Einschränkung dass die Laufzeit halt stärker
schwankte wenn der Timer-Interrupt dazwischen gekommen ist.
Was mich aber noch interessieren würde ist ob der Atomic-Block
tatsächlich
sicherer ist wie das manuelle Sichern des SREG´s, aber da das eigentlich
Off-Topic ist, suche ich mal separat danach.
Gruß Dominik