Forum: Mikrocontroller und Digitale Elektronik Unterprogramm Aufruf ohne Rücksprung


von Philipp (Gast)


Lesenswert?

Hallo zusammen,
ich bin gerade auf der Suche nach einer Lösung eines 
programmiertechnischen Problems, und würde mich freuen wenn mir jemand 
dazu einen hilfreichen Tipp geben könnte.

Ich schreibe zur Zeit an einem Programm, das AB einem Zeitpunkt eine 
gewisse Zeit lang einen Strom regelt.
Mikrocontroller ist ein Atmega8, den ich mit AVR Stuio 6.1 in C 
programmiere.

Hier kurz die grobe Funktion meines Programmausschnitts:
Zu einem gewissen Zeitpunkt tritt ein Timer1 Compare A Interrupt (ich 
nenne es mal ISR1) auf.
In dieser ISR wird wiederum zur Funktion "Stromregeln" gesprungen.
Der Strom soll für eine gewisse Zeit geregelt werden, danach soll wieder 
in ISR1 zurückgesprungen werden.
Dazu lade ich in der Funktion Stromregeln einen Wert ins OCR1B Register, 
damit will ich nach Ablauf der Stromregelzeit in die ISR für den Timer1 
Compare A Interrupt (ich nenne es ISR2) springen und die Stromregelung 
somit beenden.

Das Problem: Nach Auftreten von ISR2 und deren Abarbeitung, wird ja 
wieder in das Programm Stromregeln gesprungen (wurde ja durch ISR2 
unterbrochen).
Ich möchte aber, dass nach Abarbeiten von ISR2 wieder ISR1 zuende 
geführt wird.

Ich muss also irgendwie erreichen, dass bei Eintritt ISR2 NICHT die 
Rücksprungadresse zum Programm Stromregeln auf dem Stack abgelegt wird. 
Dadurch wäre denn noch die Adresse zu ISR1 auf dem Stack, und nach 
Abarbeiten von ISR2 würde ISR1 zuende ausgeführt werden.

Ich weiß zwar, dass es nicht gerne gesehen wird, wenn ich nur 
Codeschnipsel poste, aber es geht hier wirklich nur um die ISR's:

1
ISR(TIMER1_COMPA_vect){        // ISR1
2
    
3
  PORTB |= (1<<PB1);  // Strom an
4
  TCNT1=0;
5
  Stromregeln();    
6
  sei();
7
}
8
9
10
ISR(TIMER1_COMPB_vect){             // ISR2
11
  TIMSK &= ~(1<<OCIE1B);  // Interrupt deaktivieren
12
        // == Hier soll wieder zu ISR1 Gesprungen werden ==
13
}
14
  
15
void Stromregeln(void){
16
  ADMUX|=(1<<MUX2);
17
  OCR1B=ICR1/128;        // ICR:6 teilen
18
  GradDauer=OCR1B*21;
19
  OCR1B=GradDauer*Ansteuerdauer;         // OCR1A beladen
20
  TIMSK |= (1<<OCIE1B); 
21
  
22
  while(ADC<=PeakStrom){                 // Warten bis Peakstrom erreicht
23
  }
24
  if(ADC>=(HoldStrom+Deadband)){         // Strom auf Holdwert regeln
25
    PORTB &= ~(1<<PB1);
26
  }
27
  else if (ADC<=(HoldStrom-Deadband)){
28
    PORTB |= (1<<PB1);
29
  }
30
}

Nochmal zur Übersicht in Kurzform:
ISR1 Interrupt -> Stromregeln() -> ISR2 Interrupt -> Weiter in ISR1

Vielen Dank schonmal für eure Antwort!

Philipp

von заббэртроль (Gast)


Lesenswert?

Ist leider Quatsch. Eine ISR muss !! kurz sein.

Mach eher :

isr () {  flag = 1; } }

main () {
 ..
 loop
  if flag==1 {
   stromregel();
   flag=1;
  }

von Klaus (Gast)


Lesenswert?

Philipp schrieb:

Das geht ganz anders: Du musst ein Programmparadigma verwenden, dass in 
ISRs nur ein Flag setzt und die eigentliche Funktion in main abhandelt.

Stichworte: Interrupt, State Machine, Flag, volatile

Siehe hierzu auch die Artikel im Forum.

Daumenregeln:
1. Niemals den Programmfluss durch Stackmanipulation beeinflussen.
2. Niemals lange Funktionen im Interrupt.

von Dr. Sommer (Gast)


Lesenswert?

ISR2 kann nicht ausgeführt werden, solange ISR1 läuft. Erst danach, es 
sei denn du rufst manuell sei() auf. Das sei() am Ende von ISR1 ist 
unnötig, denn beim Rückkehren geschieht das automatisch.

Aber bist du sicher, dass du Stromregeln einfach irgendwo unterbrechen 
und komplett abwürgen möchtest?
zB OCR1B ist ein 16bit-Register, wenn der Zugriff darauf unterbrochen 
wird, hast du den Timer komisch konfiguriert. Warum setzt du das 
überhaupt 2x hintereinander?

Was möchtest du überhaupt erreichen? Vielleicht, die 
"while(ADC<=PeakStrom)"-Schleife per Timer abzubrechen wenn sie zu lange 
dauert?
Das würde ich komplett anders machen:
* Starte in der ISR1 den ADC und konfiguriere OC1B
* Überprüfe das "ADC<=PeakStrom" in jedem ADC-Interrupt
* Wenn die Bedingung im ADC-Interrupt zutrifft, führe deine 
Port-Zugriffe aus und schalte den OC1B ab
* Wenn der OC1B Interrupt kommt, schalte den ADC und OC1B ab

von Philipp (Gast)


Lesenswert?

Dass man ISR's so kurz wie möglich macht, weiß ich auch schon.
Bei meinem Programm kommt es aber sehr auf Geschwindigkeit an, d.h. die 
Funktion Stromregeln muss so schnell wie möglich ausgeführt werden, 
daher die geplanten Aktionen direkt im Interrupt.

Würde ich im Hauptprogramm bloß zyklisch in der ISR gesetzen Flags 
abfragen, würde das ersten viel zu lange dauern, zweitens könnte ich 
denn die Verzögerung bis zum Ausführen der Funktion Stromregeln 
nichtmehr vorhersagen.

Im Programm ist dementsprechend sichergestellt, dass in der Zeit, in der 
die ISR abgearbeitet wird, kein anderes Interrupt auftreten kann.

@Dr Sommer:
Du hast natürlich Recht, das sei() muss natürlich direkt in der Funktion 
Stromregeln() nach dem Beladen des OCR Registers stehen, erst dann kann 
ISR2 kommen. Damit ist dann auch sichergestellt, dass das Beladen des 
16Bit Registers nicht durch ein anderes Interrupt gestört wird.

Erreichen möchte ich, dass ein Magnetventil mit einem bestimmten 
Stromprofil für eine bestimmte Zeit angesteuert wird.
Dabei soll erst so lange Spannung draufgegeben werden, bis der Peakstrom 
erreicht wird. Für die restliche Öffnungszeit muss es nur noch mit einem 
niedrigeren Hold-Strom gesteuert werden.
Die Gesamtansteuerdauer gebe ich mit OC1B vor.


Korrigiert müsste das Programm also so aussehen:
1
ISR(TIMER1_COMPA_vect){        // ISR1
2
    
3
  PORTB |= (1<<PB1);  // Strom an
4
  TCNT1=0;
5
  Stromregeln();    
6
7
}
8
9
10
ISR(TIMER1_COMPB_vect){             // ISR2
11
  PORTB &= ~(1<<PB1);    // Strom aus
12
  TIMSK &= ~(1<<OCIE1B);  // Interrupt deaktivieren
13
        // == Hier soll wieder zu ISR1 Gesprungen werden ==
14
}
15
  
16
void Stromregeln(void){
17
  ADMUX|=(1<<MUX2);
18
  OCR1B=ICR1/128;        // ICR:6 teilen
19
  GradDauer=OCR1B*21;
20
  OCR1B=GradDauer*Ansteuerdauer;         // OCR1A beladen
21
  TIMSK |= (1<<OCIE1B); 
22
  sei();
23
  
24
  while(ADC<=PeakStrom){                 // Warten bis Peakstrom erreicht
25
  }
26
  if(ADC>=(HoldStrom+Deadband)){         // Strom auf Holdwert regeln
27
    PORTB &= ~(1<<PB1);
28
  }
29
  else if (ADC<=(HoldStrom-Deadband)){
30
    PORTB |= (1<<PB1);
31
  }
32
}

von Matthias (Gast)


Lesenswert?

Wie wär's wenn du in der ISR1 den ADC startest und das ADC<=PeakStrom 
dann in der ADC-ISR machst? In der ISR2 setzt du dann ein Flag 
stop_stromregelung, das du in der ADC-ISR abfragst.

von Philipp (Gast)


Lesenswert?

Danke für deine Antwort Matthias ,
das würde funktionieren wenn ich die Stromregelung abbrechen wollte, 
nachdem ein gewisser Strom erreicht ist.
Ich möchte aber -wie in meinem Eingangsthread erwähnt- die Stromregelung 
nach einer vorgegebenen ZEIT abbrechen. Dabei spielt es keine Rolle, 
welchen Wert der Strom zu dieser Zeit hat.

von Ohoh (Gast)


Lesenswert?

Philipp schrieb:
> Würde ich im Hauptprogramm bloß zyklisch in der ISR gesetzen Flags
> abfragen, würde das ersten viel zu lange dauern, zweitens könnte ich
> denn die Verzögerung bis zum Ausführen der Funktion Stromregeln
> nichtmehr vorhersagen.

Ach ja? Wie kommst Du auf dieses Brett? Wenn
Deine Loop in der Main sehr kurz ist, dann dauert das ziemlich genau so 
lange wie wenn Du das in der ISR aufrufst.
Was verstehst Du unter schnell?
Das was Du bisher programmiert hast ist, mit Verlaub, Mist.

von Matthias (Gast)


Lesenswert?

Philipp schrieb:
> Ich möchte aber -wie in meinem Eingangsthread erwähnt- die Stromregelung
> nach einer vorgegebenen ZEIT abbrechen.
Dafür das Flag stop_stromregelung, dass du in der ISR2 setzt und dann in 
der ADC-ISR abfragst, oder du deaktivierst einfach den ADC wenn die 
Stromregelung stoppen soll, dann kann er auch kein Interrupt mehr 
erzeugen.

von Matthias (Gast)


Lesenswert?

Im Pseudo-Code ca. so:
1
volatile uint8_t stop_stromregelung = 0;
2
3
ISR(TIMER1_COMPA_vect){        // ISR1
4
    
5
  PORTB |= (1<<PB1);  // Strom an
6
  TCNT1=0;
7
  Start_ADC();  
8
  stop_stromregelung = 0;
9
}
10
11
ISR(ADC)
12
{
13
  if(stop_stromregelung)
14
    return;
15
16
  if(ADC<=PeakStrom)               // Warten bis Peakstrom erreicht
17
  {
18
    Start_ADC(); 
19
    return;
20
  }
21
22
  if(ADC>=(HoldStrom+Deadband))         // Strom auf Holdwert regeln
23
    PORTB &= ~(1<<PB1);
24
  else if (ADC<=(HoldStrom-Deadband))
25
    PORTB |= (1<<PB1);
26
}
27
28
ISR(TIMER1_COMPB_vect){             // ISR2
29
  PORTB &= ~(1<<PB1);    // Strom aus
30
  TIMSK &= ~(1<<OCIE1B);  // Interrupt deaktivieren
31
        // == Hier soll wieder zu ISR1 Gesprungen werden ==
32
  stop_stromregelung = 1;
33
}

von Philipp (Gast)


Lesenswert?

Wenn die Loop in der Main sehr kurz ist, dann wird natürlich auch sehr 
oft das Flag aus der ISR sehr häufig abgefragt. Bei mir ist die Main 
aber ziemlich lange, dort werden z.B. noch Benutzereingaben abgefragt, 
ein UART gelesen und ein Display bedient.
Da kann man sich denken, wie lange es dauern kann, bis das Programm zur 
Abfrage von Flags kommt.
Ich strebe ein Beginn der Ansteuerung von nicht mehr als 20-30 
Taktzyklen nach Auslösen des ersten Interrupts an.

@Matthias:
Ok, jetzt verstehe ich was du meinst. Ich kann das Flag ja auch einfach 
in der Stromregeln() Funktion abfragen, und diese beenden, wenn das Flag 
gesetzt ist.  Ist zwar wieder etwas mehr Ballast, aber so werde ich es 
machen wenn keiner eine bessere Lösung hat.

von Matthias (Gast)


Lesenswert?

Philipp schrieb:
> Ich kann das Flag ja auch einfach
> in der Stromregeln() Funktion abfragen
Dann bist du aber die ganze Zeit in der ISR1 und verschwendest 
Rechenzeit währen der ADC wandelt.

von Yalu X. (yalu) (Moderator)


Lesenswert?

заббэртроль schrieb:
> Ist leider Quatsch. Eine ISR muss !! kurz sein.
>
> Mach eher :
>
> isr () {  flag = 1; } }
>
> main () {
>  ..
>  loop
>   if flag==1 {
>    stromregel();
>    flag=1;
>   }

Das ist aber ebenso Quatsch ;-)

Wenn man Ereignisse pollen möchte (was durchaus sinnvoll sein kann),
braucht man i.Allg. keine Interrupts. Interrupts sind ja gerade dafür
erfunden worden, die Pollerei zu vermeiden.

Die Abfrage im Hauptprogramm ohne die Verwendung von Interrupts sieht so
aus:
1
  if(TIFR & 1<<OCF1A) {  // Timer-Flag gesetzt?
2
    stromregel();
3
    TIFR |= 1<<OCF1A;    // Timer-Flag löschen
4
  }


Philipp schrieb:
>   while(ADC<=PeakStrom){                 // Warten bis Peakstrom erreicht
>   }
>   if(ADC>=(HoldStrom+Deadband)){         // Strom auf Holdwert regeln
>     PORTB &= ~(1<<PB1);
>   }
>   else if (ADC<=(HoldStrom-Deadband)){
>     PORTB |= (1<<PB1);
>   }

Müsste die if-else-if-Anweisung nicht auch in einer Schleife stehen?

In dieser Schleife könntest du das Timer-Flag abfragen und, falls es
gesetzt ist, die Schleife und damit ISR(TIMER1_COMPB_vect) verlassen. Da
der Inhalt dieser Schleife sehr kurz ist, ist die Reaktionszeit mit dem
Polling vermutlich nicht länger als der Aufruf einer separaten ISR.
Falls doch, kannst du in die Schleife auch zwei Abfragen einbauen (eine
an den Anfang und eine in die Mitte).


Klaus schrieb:
> Daumenregeln:
> 1. Niemals den Programmfluss durch Stackmanipulation beeinflussen.
> 2. Niemals lange Funktionen im Interrupt.

Bei Regel 1 stimme ich dir zu. Solche Tricks haben nur in der
Programmierung von Betriebssystemkernen etwas zu suchen. Was man in
manchen Fällen machen kann, ist ein Longjump, um über mehrere
Unterprogrammebenen irgendwowin zurück zu springen. Damit macht man die
Stackmanipulation auf standardisierte Weise. Aber auch das würde ich im
vorliegenden Fall nicht empfehlen.

Regel 2 gilt vor allem für Anwendersoftware, die auf Multitasking-
Plattformen läuft. Auf einfachen Mikrocontrollern, wo i.Allg. nur eine
einzelne Anwendung läuft, können lange ISR durchaus sinnvoll sein, es
muss nur das Gesamtkonzept atimmen.

von Philipp (Gast)


Lesenswert?

Vollkommend richtig! Auch die IF - ELSE Anweisung müsste in einer 
Schleife stehen, sonst würde sie ja nur einmal augeführt! Sorry, hatte 
den Programmteil noch nicht simulieren können, eben wegen meinem 
"Sprungproblem".

Das Abfragen des Timer Flags ist eine Super Idee, die ADC Konvertierung 
bringt ja auch eine gewisse Verzögerung, da macht es dann nichts mehr 
aus, noch eine weitere Abfrage zu haben.

So werde ich es dann wohl machen. Tatsächlich ist also die Stack 
Manipulation garnicht notwendig gewesen.

Vielen Dank an Alle!

Falls mir noch jemand erklären könnte, wie ich auf geringe 
Verzögerungszeiten komme, ohne den kompletten Ansteuerteil in der ISR 
durchzuführen, gerne her damit.
In meinem Programm steuere ich auch noch andere Kanäle, diese aber ohne 
Stromregelung, dabei erreiche ich durch geschicke Struktur in den ISR's 
Verzögerungszeiten um die 20 - 30 Takte. Hatte bisher noch keine Idee, 
wie ich das anders schaffen soll, also wenn das quatsch ist, nehme ich 
auch gerne andere Vorschläge an.

von Bestromer (Gast)


Lesenswert?

hmmm, also ich hatte mal ein ähnliches Problem und hatte damals einfach 
in der ISR die Sprungadresse des abzuarbeitenden Programmes auf den 
Stack gelegt, wodurch diese dann nach Beendigung der ISR angesprungen 
wurde,statt z.B. die Main.
Am Ende des abzuarbeitenden Programmes ist man dann mit einem normalen 
Return in das ursprünglich unterprochene Programm (z.B. Main) 
zurückgekehrt.
Man hat quasi seine ISR ausgelagert und dazwischen geschoben.
Man muss in der ISR nur einen Mechanismus einbauen der sicher stellt das 
diese Stackmanipulation nur stattfindet wenn die "ausgelagerte ISR" 
verlassen wurde, sonst gibts böse Überraschungen :)
Auf diese Weise hatte ich mir eine automatische Priorisierung und eine 
Art Programmswitch mit einer Switchtabelle geschaffen.....war sehr 
lustig, hatte schon einen minimalen Hauch eines Multitasking :D

von Philipp (Gast)


Lesenswert?

Auf den Stack kommt aber doch schon bei Einsprung in die ISR die 
Rücksprungadresse zu dem unterbrochenen Programm, musst du diese nicht 
auch löschen? Sonst würdest du je bei jedem ISR Aufruf 2 Adressen auf 
den Stack legen, aber nur eine für den Rücksprung verwenden, und 
irgendwann läuft dein Stack über?

von Bestromer (Gast)


Lesenswert?

Philipp schrieb:
> Auf den Stack kommt aber doch schon bei Einsprung in die ISR die
> Rücksprungadresse zu dem unterbrochenen Programm.....
...das stimmt,diese wird ja dann auch von dem "dazwischen geschobenen" 
Programm für den Rücksprung in das unterbrochene Programm genutzt.

Philipp schrieb:
> irgendwann läuft dein Stack über?
...nicht wirklich,die ISR springt zu Deinem "Zwischenprogramm" und 
dieses dann zum unterbrochenen Programm....macht zwei Rücksprünge :)
Man muss eben nur verhindern,das die ISR erneut den Stack manipuliert 
während das Zwischenprogramm noch nicht beendet ist....
Wer mit dem Stack spielt, muss halt aufpassen!

von Klaus (Gast)


Lesenswert?

Yalu X. schrieb:
> заббэртроль schrieb:
>> Ist leider Quatsch. Eine ISR muss !! kurz sein.
>>
>> Mach eher :
>>
>> isr () {  flag = 1; } }
>>
>> main () {
>>  ..
>>  loop
>>   if flag==1 {
>>    stromregel();
>>    flag=1;
>>   }
>
> Das ist aber ebenso Quatsch ;-)
>
> Wenn man Ereignisse pollen möchte (was durchaus sinnvoll sein kann),
> braucht man i.Allg. keine Interrupts. Interrupts sind ja gerade dafür
> erfunden worden, die Pollerei zu vermeiden.

Das ist haarscharf an der der eigentlichen Problematik vorbei gedacht, 
wenn Du mich fragst. Wenn es nur darum ginge, bräuchte man niemals 
Interrupts. Die sind aus meiner Sicht nicht da um komfortables pollen zu 
ermöglichen sondern um asynchrone Ereignisse zu erfassen und das 
nötigenfalls zeitnah.

> Klaus schrieb:
>> Daumenregeln:
>> 1. Niemals den Programmfluss durch Stackmanipulation beeinflussen.
>> 2. Niemals lange Funktionen im Interrupt.
>
> Regel 2 gilt vor allem für Anwendersoftware, die auf Multitasking-
> Plattformen läuft. Auf einfachen Mikrocontrollern, wo i.Allg. nur eine
> einzelne Anwendung läuft, können lange ISR durchaus sinnvoll sein, es
> muss nur das Gesamtkonzept atimmen.

Das Gesamtkonzept kennen wir nicht. Wir wissen auch nicht wie schnell 
sich der Strom durch Last verändert. Wir wissen nicht warum der TO 
dieses Konzept gewählt hat. Klar sind diese Daumenregeln alle relativ 
(deswegen heissen sie ja "Daumenregeln") aber wer so anfängt, sollte sie 
entweder kennen und klar erklären können, warum er davon abweicht oder 
sie lernen und erstmal damit arbeiten.

Nach allem was ich gesehen habe, wäre das beste aus meiner Sicht ohnehin 
das Ganze im ADC-Interrupt abzuhandeln. Die Zeit sperrt dann den 
ADC-Interrupt bzw. setzt den Zustand auf "Idle" oder sowas.

Das wäre kurz und direkt - im wesentlichen ein Switch-Case-Konstrukt für 
eine kleine State-Machine mit einigen if-Anweisungen für die 
Schwellwerte. Eigentlich ein Zwei-Punkt-Regler. Und vom Konzept her auch 
nicht das was ich ursprünglich riet. So strikt sehe ich das also 
durchaus auch nicht.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Klaus schrieb:
> Yalu X. schrieb:
>> ...
>> Wenn man Ereignisse pollen möchte (was durchaus sinnvoll sein kann),
>> braucht man i.Allg. keine Interrupts. Interrupts sind ja gerade dafür
>> erfunden worden, die Pollerei zu vermeiden.
>
> Das ist haarscharf an der der eigentlichen Problematik vorbei gedacht,
> wenn Du mich fragst. Wenn es nur darum ginge, bräuchte man niemals
> Interrupts. Die sind aus meiner Sicht nicht da um komfortables pollen zu
> ermöglichen sondern um asynchrone Ereignisse zu erfassen und das
> nötigenfalls zeitnah.

Damit sind wir doch exakt der gleichen Meinung, oder nicht?

Es gibt Anwendungsfälle für Interrupts, aber auch welche für Polling,
das steht außer Frage.

Ich habe mich oben nur dagegen ausgesprochen, das Polling mit Hilfe von
Interrupts zu realisieren, nämlich in der Form, dass die ISR nichts
weiter tut, als eine globale Variable von 0 auf 1 zu setzen, und diese
dann im Hauptprogramm zyklisch abgefragt wird. Denn das bringt gegenüber
der direkten Abfrage des entsprechenden Interruptflags nur Nachteile.

von Philipp (Gast)


Lesenswert?

Ich habe bewusst nur diesen Ausschnitt aus dem Code gezeigt, weil es das 
war, wo ich nichtmehr weiter wusste.

Ob jetzt eine lange ISR gut oder schlecht ist, oder wie gut die 
Stromregelung im Detail funktioniert, darum ging es ja hier nicht.

Ich hätte natürlich auch das ganze Programm posten können, aber kann mir 
kaum Vorstellen, dass jeder hier Lust hat, sich in 300 Zeilen Code 
hereinzudenken. Deswegen hab ich wie gesagt nur den interessierenden 
Ausschnitt hier reingestellt.

Den ADC Interrupt zu benutzen hatte ich mir übrigens auch schon 
überlegt. Macht aber keinen Unterschied, ich habe in der 
Stromregeln-Phase sowieso keine weiteren Aufgaben zu erledigen, daher 
macht es auch nichts wenn die meiste Zeit der gleiche ADC Wert gelesen 
wird, weil dieser sich ja wegen der Wandlungszeit aus CPU-Sicht selten 
ändert.

Wen die Begründung für meine langen ISR interessiert:
Das Programm soll ein 60+2 Geberrad (58 Zähne, 2 Fehlende Zähne) 
auswerten, und 2 Magnetventile AB einem Winkel BIS ZU einem Winkel 
ansteuern. Diese Winkel sollen vom Benutzer einstellbar sein.
Zudem soll der Winkel auf ein Grad genau wählbar sein, d.h. das 
Interpolieren (Zahnabstand ist 6 Grad) braucht auch nochmal einige 
Takte.

Die Ventile müssen also so schnell wie möglich nach Auftreten des 
Interrupts angesteuer werden, je mehr Takte bis dahin vergehen, desto 
größer wird nämlich  -gerade bei hohen Drehzahlen- auch der 
Winkelfehler.
Ich kann deswegen nicht erst warten, bis in der Main mal nach dem UART 
auslesen und verarbeiten, sagen wir mal 100 Takte nach dem Interrupt, 
das Flag, das in der ISR gesetzt wurde, erkannt wurde. Das könnte dann 
schon einen Fehler von einem ganzen Grad ausmachen.

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.