Forum: Mikrocontroller und Digitale Elektronik Yet another AVR-timer question (Plausibilitätsprüfung ob OVF und Interrupt sicher.)


von Dominik (Gast)


Lesenswert?

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
static volatile uint32_t ctcCounter; //Wird in ISR "CTC Match A" inkrementiert 
2
3
static void getTime0(volatile uint32_t *ctcCounterValue, volatile uint8_t *tcnt0Value)
4
{
5
  uint8_t SREG_COPY=SREG;
6
  cli();
7
  *tcnt0Value=TCNT0;
8
  *ctcCounterValue=ctcCounter;
9
  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

Beitrag #5278318 wurde von einem Moderator gelöscht.
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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.

von Dominik (Gast)


Lesenswert?

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

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

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

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


Lesenswert?

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?

von Rolf M. (rmagnus)


Lesenswert?

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.

von Dominik (Gast)


Lesenswert?

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:
1
static void getTime0(volatile uint32_t *ctcCounterValue, volatile uint8_t *tcnt0Value) //6,08 µs
2
{
3
  uint8_t SREG_COPY=SREG;
4
  uint8_t OCF0A_before, OCF0A_after, readTCNT0;
5
  cli();        //Disable interrupts if not already blocked by call from other ISR
6
  //START:
7
  do
8
  {
9
    OCF0A_before=(TIFR&(1<<OCF0A));
10
    readTCNT0=TCNT0;
11
    OCF0A_after=(TIFR&(1<<OCF0A));
12
  }
13
  while(OCF0A_before!=OCF0A_after);      //5,64µs
14
  //if(OCF0A_before!=OCF0A_after)  goto START; //5,64µs
15
16
  *tcnt0Value=readTCNT0;
17
18
  if(OCF0A_after==0)  //No Timer Overflow
19
  {
20
    *ctcCounterValue=ctcCounter;
21
  }
22
  else         //Timer Overflow before readOut
23
  {
24
    *ctcCounterValue=ctcCounter+1ul;
25
  }
26
27
  SREG=SREG_COPY;
28
}

Und mein Bauchgefühl sagt mir, so ist es nahe dem Optimum :-)
Nochmals vielen Dank für die Unterstützung!

Gruß Dominik

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?


von Dominik (Gast)


Lesenswert?

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

von Veit D. (devil-elec)


Lesenswert?

Hallo,

das einfachste ist den Overflow Interrupt vom Timer zu nutzen. Einfache 
Sache. Den aktivierst du mit
1
TIMSK0 = (1<<TOIE0);   // enable Overflow Interrupt

Dann wird das nur noch miteinander verrechnet.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/atomic.h>   
4
5
volatile uint16_t ovf_count_T0;
6
uint32_t systemtime;
7
8
void getTime0();
9
10
int main(void)
11
{
12
    
13
    while (1) 
14
    {
15
    }
16
}
17
18
// ****** Funktionen ******* //
19
20
void getTime0()
21
{
22
   uint16_t overflow_count = 0;
23
   
24
   ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {  
25
        overflow_count = ovf_count_T0; 
26
   }      
27
   
28
   systemtime = (256*overflow_count)+TCNT0;
29
   
30
}
31
32
ISR(TIMER0_OVF_vect)
33
{
34
   ovf_count_T0++;
35
}


Wenn du das Overflow Flag manuell abfragst, also ohne ISR, dann musst du 
das auch manuell löschen, wenn es einen Überlauf signalisiert hat.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

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.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

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.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

warum muss er das wissen? Er möchte nur die aktuelle Systemzeit haben.

: Bearbeitet durch User
von Dominik (Gast)


Lesenswert?

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

von Oliver S. (oliverso)


Lesenswert?

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

von Veit D. (devil-elec)


Lesenswert?

Hallo,

in
ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
}

ist alles zusammgengefasst. Interrupt sperren/freigegen und SREG 
sichern/wiederherstellen. Bequemer und vorallendingen sicherer gehts 
nicht.

http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

von Peter D. (peda)


Lesenswert?

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.

von Dominik (Gast)


Lesenswert?

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

von Veit D. (devil-elec)


Lesenswert?

Hallo,

lies mal im Link was Restore macht ...
Es geht einzig und alleine um das Globale Interrupt Flag.
Und was macht dein cli / sei ?

von Peter D. (peda)


Lesenswert?

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.

von Dominik (Gast)


Lesenswert?

Hi,
1
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
    return 1;
5
}
6
7
static __inline__ uint8_t __iCliRetVal(void)
8
{
9
    cli();
10
    return 1;
11
}
12
13
static __inline__ void __iSeiParam(const uint8_t *__s)
14
{
15
    sei();
16
    __asm__ volatile ("" ::: "memory");
17
    (void)__s;
18
}
19
20
static __inline__ void __iCliParam(const uint8_t *__s)
21
{
22
    cli();
23
    __asm__ volatile ("" ::: "memory");
24
    (void)__s;
25
}
26
27
static __inline__ void __iRestore(const  uint8_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

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Dominik schrieb:
> ISR(egal_welche)
> {
> SREG&=~(0b10000000);
> }

Dann schau Dir mal das Assemblerlisting der ISR an (Prolog, Epilog) und 
die Beschreibung des RETI.

von Dominik (Gast)


Lesenswert?

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

von Dominik (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

>>> static void getTime0(volatile uint32_t *ctcCounterValue, volatile uint8_t 
*tcnt0Value)

Hi all
DANKE für den hinweis hierzu! nachdem ich nochmals weitere referenzen 
hierzu gesucht habe, passt jetzt der sinn auch für mich.

siehe:
https://stackoverflow.com/questions/9935190/why-is-a-point-to-volatile-pointer-like-volatile-int-p-useful


mt

von Peter D. (peda)


Lesenswert?

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.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

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

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Dominik (Gast)


Lesenswert?

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

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.