Forum: Mikrocontroller und Digitale Elektronik Atmel Studio: interrupt routine liest nicht immer Variable


von Andreas W. (andy_w)


Lesenswert?

Hallo,
ich habe in einer Interrupt Routine (SysTick_Handler) die alle 1ms 
aufgerufen wird ein paar Timervariablen, die incrementiert oder 
decrementiert werden. Einige können auch angehalten werden, in dem eine 
globale Variable , die mit "volatile int8_t en_int;" definiert ist, 
abgefrgat wird und das incrementieren nur stattfindet, wenn en_int != 0 
ist. Das funktioniert auch bei meinem bisherigen Projekt. Aber nun ist 
eine neue Funktion dazugekommen, in der wird en_int auf 1 gesetzt und 
trotzdem erkennt das die Interruptroutine nicht. Die Interruptroutine 
selber läuft aber.

Mit dem Debugger kann ich das leider nicht erforschen. Kann es irgendwie 
sein, daß die Variable von anderer Stelle überschrieben wird? Das sollte 
eigentlich nicht passieren. In der neuen Funktion gebe ich den Wert von 
en_int aus, da wird er als 1 erkannt, aber offensichtlich kommt die 
Interruptroutine nicht an die Variable ran und liest stattdessen 0.

Der Code des Projektes ist zu groß (ausführbarer Code schon über 
300kByte), als daß ich den hier hochladen könnte, es wird auch keiner 
Lust haben, den durchzusehen.

RAM ist noch genug vorhanden.

Die grundsätzliche Frage ist, ob trotz volatile das Lesen einer variable 
wegoptimiert werden kann, denn in der Interruptroutine wird en_int nur 
gelesen, für sich gesehen könnte der Compiler das wegoptimieren und 
stattdessen den Defaultwert, der dann meistens Null ist, annehmen. 
Deswegen habe ich ja volatile benutzt, ebenso für alle Timercounter, die 
von der Interruptroutine verändert werden, denn die werden in anderen 
Funktionen teilweise nur gelesen.

Gruß
Andy

von Carl D. (jcw2)


Lesenswert?

> ausführbarer Code schon über 300kByte

Also kein AVR. Was dann?

von Andreas W. (andy_w)


Lesenswert?

Hallo,
ach ja, ein Ardiono Due, also SAM3X8E. Vom Ardiono nutze ich nur die 
Hardware, nicht die Arduino Entwicklungsumgebung.

Kann das Lesen einer volatile Variablen trotzdem vom Compiler 
wegoptimiert werden und was kann man dagegen noch tun?

Gruß
Andy

von Carl D. (jcw2)


Lesenswert?

Was kann man tun: eine minimal-Version bauen, die den Fehler auch zeigt. 
Oder mal mit -O0 übersetzen und prüfen, ob der Fehler noch existiert. 
Wenn ja, dann kann es der dann nicht aktive Optimizer ja kaum gewesen 
sein. NOP's per "asm volatile" vor und hinter die Verdachtsstelle und 
dann generierten Code lesen.
Es gibt vieles, was auszuprobieren wäre.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Andreas W. schrieb:
> Kann es irgendwie sein, daß die Variable von anderer Stelle
> überschrieben wird?
Klar. Sogar ganz einfach: wenn davor im Speicher ein Array/String liegt 
und ein amoklaufender Pointer/Index hinter das letzte Element dieses 
Array zugreift...

Andreas W. schrieb:
> nutze nicht die Arduino Entwicklungsumgebung.
Welche sonst?

Andreas W. schrieb:
> eine globale Variable , die mit "volatile int8_t en_int;" definiert ist
Wo und wie ist die so definiert? Weiß die "neu" dazugekommene Funktion 
auch, dass die Variable volatil ist?

von Carl D. (jcw2)


Lesenswert?

> Welche sonst?

Atmel Studio?

von Apostel13 (Gast)


Lesenswert?

Sperrt die neu hinzugekommene Funktion Interrupts wenn sie außerhalb der 
ISR liegt und deine Variable verändert?

Warum kannst Du das nicht im Debuger testen?

von Andreas W. (andy_w)


Lesenswert?

Hallo,
mit Optimierung aus funktioniert es, allerdings ist der Prozessor dann 
(natürlich) viel zu langsam.

Mit einer Minimalversion ist das Problem nicht reproduzierbar, es tritt 
einfach nicht auf.

Es gibt auch globale Arrays, wo die tatsächlich im RAM liegen, konnte 
ich noch nicht herausfinden. Im Code sind zwischen dem ersten Array vor 
en_int und en_int selber noch Variablen, die die 
Touchscreenkalibirierung enthalten, sollte also das RAM in der gleichen 
Reihenfolge gefüllt sein, müßte der Touchscreen nicht mehr die richtigen 
Felder treffen. Das habe ich aber noch nicht beobachtet.

Der Interrupt selber wird nie gesperrt, die anderen Timer laufen ja auch 
noch, nur die in der if-Abfrage eben nicht.

Lothar M. schrieb:
> Andreas W. schrieb:
>> eine globale Variable , die mit "volatile int8_t en_int;" definiert ist
> Wo und wie ist die so definiert? Weiß die "neu" dazugekommene Funktion
> auch, dass die Variable volatil ist?

Wie teilt man das der Interruptfunktion mit? die De Mein alter 
Kernighan/Ritchie (ca. 1990) sagt zum thema volatile fast gar nichts, 
mehr als 5 Zeilen sind es nicht... Wenn es da noch eine Möglichkeit 
gibt, könnte das die Lösung sein.

Noch etwas: wenn ich den Touchscreen berühre, kommt die 
Interruptfunktion zum Zuge, aber langsamer. Wie ich herausgefunden habe, 
liegt das an den I²C-Aufrufen für den Touchscreencontroller. Sobald ich 
auf twi-Register im Prozessor zugreife, kann die Interruptroutine en_int 
lesen. die TWI-Funktion (selbst geschrieben, weil die vorhandene von 
Atmel nicht zum Laufen zu bringen war) kommt bei mir ganz ohne Interrupt 
aus und macht auch keine Zugriffe auf en_int. der I²C-Zugriff 
funktioniert dabei immer einwandfrei, auch von der neuen Funktion 
aufgerufen.

Gruß
Andy

: Bearbeitet durch User
von Apostel13 (Gast)


Lesenswert?

Also ich denke das musst Du im Debuger mit breaks anschauen und 
besonderes Augenmerk auf den assemblercode um die Bereiche legen in 
denen deine Variable auf 1 oder wieder zurück gesetzt wird und auf die 
Stelle in der ISR die den bedingten Sprung ausführt. Änderst Du deine 
Variable außerhalb der ISR an mehreren Stellen? Wie genau setzt du sie 
auf 1 und wie wieder zurück und wie genau fragst Du ab? Ändert die ISR 
auch die betreffende Variable?

von Andreas W. (andy_w)


Lesenswert?

Hallo,

Apostel13 schrieb:
> Also ich denke das musst Du im Debuger mit breaks anschauen und
> besonderes Augenmerk auf den assemblercode um die Bereiche legen in
> denen deine Variable auf 1 oder wieder zurück gesetzt wird und auf die
> Stelle in der ISR die den bedingten Sprung ausführt. Änderst Du deine
> Variable außerhalb der ISR an mehreren Stellen? Wie genau setzt du sie
> auf 1 und wie wieder zurück und wie genau fragst Du ab? Ändert die ISR
> auch die betreffende Variable?

Wie schaltet man den Assembler Code in der View an? Irgendwie kann ich 
da keinen Menüpunnt finden, denn das hatte ich auch schon vor. In der 
Entwicklungsumgebung von Keil, die ich in der Firma verwende, ist der 
Assemblercode einfach schon sichtbar, entweder irgendwo eingestellt oder 
defaultmäßig eingeschaltet. Das müßte mit Atmel Studio eigentlich auch 
möglich sein.

en_int wird in diversen Funtionen direkt auf 0 und kurz darauf wieder 
auf 1 gesetzt, wenn ich z.B. Timervariablen, die von der 
Interruptroutine geändert werden, auf einen Startwert setzen will. 
Einige diese Variablen setzen sich aus mehreren Teilen zusammen (es sind 
nicht nur Timer) und ich will sicher sein, daß während eines Zugriffs 
die Interruptroutine nicht dazwischenfunkt.

Gesetzt wird ganz simpel mit
1
en_int = 0;
2
... jetzt Variablen lesen oder schreiben, ohne dass Int dazwischenfunkt
3
en_int = 1;

In der Interruptroutine wird folgenrdermaßen abgefragt:
1
if(en_int != 0)
2
{
3
   ... hier werden die Variablen bearbeitet
4
}

Inzwischen habe ich auch folgendes probiert:
1
int8_t   en;
2
3
en = en_int;
4
if(en != 0)
5
{
6
   ... hier werden die Variablen bearbeitet
7
}

Auch das ändert nichts am Problem. en_int wird nur außerhalb der 
Interruptroutine verändert. Die Timervariablen sowohl in der 
Interruptroutine (wenn en_int != 0) und außerhalb in Funktionen, da nur, 
wenn vorher en_int auf 0 gesetzt wurde.

Ich habe auch schon andere Größen für en_int versucht, also außer int8_t 
auch int16_t und int32_t, alle mit gleichem Ergebnis.

en_int ist in globals.h und globals.c definiert und deklariert.
globals.h:
1
extern volatile int8_t en_int;

globals_c:
1
volatile int8_t en_int;

globals.h ist in allen anderen c-Files includiert.

Wie wird eigentlich der stack in Atmel Studio definiert? Wird das in 
irgendeiner Funktion gemacht oder irgendwo in den Projekteinstellungen? 
Nicht, daß  der Stack zu klein wird. Das kann ich mir aber kaum 
vorstellen, da andere Funktionen wohl mehr Stack belegen und da gibt es 
keine Probleme.

Wohlbemerkt, das Problem gibt es nur in der neuen Funktion, die 
dazugekommen ist, in allen anderen funktioniert es.

Irgendwie traue ich den Compiler nicht ganz, ich hatte auch schon einmal 
ein anders Problem:
1
while((c != 0) && (i < 10))
2
{
3
   ...
4
   i++;
5
}

mit dem Debugger konnte ich sehen, daß die while-Schleife lustig 
weiterlief, als i den Wert 10 erreichte, die Schleife lief mit 10, 11, 
12 weiter, bis schließlich c irgendwann, aber zu spät Null wurde.

Mit leicht geändertem Code ging es dann korrekt:
1
while((i < 10) && (c != 0))
2
{
3
   ...
4
   i++;
5
}

Eigentlich darf so etwas nicht passieren, auf einen Compiler muß man 
sich doch verlassen können.

Gruß
Andy

von Andreas W. (andy_w)


Lesenswert?

Hallo,
endlich habe ich den Fehler gefunden: ich habe ein zusätzliches Delay in 
die Dauerschleife der neuen Funktion eingefügt (in der Schleife wird 
etwas angezeigt und man kann Eingaben machen, mit einer bestimmten 
erfolgt ein Return aus der Funktion). Die Schleife hatte fast genau eine 
Dauer, die ein genaues Vielfaches von 1ms betrug. Und offensichtlich 
wurde die Interruptfunktion immer genau dann aufgerufen, wenn en_int für 
ganz kurze Zeit auf Null gesetzt war. Diese Synchronizität dauerte 
offensichtlich ziemlich lange, so daß ich kaum erlebte, daß der 
Interrupt mal einen Zeitpunkt erwischte, in dem en_int 1 war.

Um das hinzukriegen, muß man wohl fast soviel "Glück" haben wie für 
einen großen Gewinn im Lotto... Jetzt ist ein Delay von 10ms mit in der 
Schleife, so daß der Interrupt zumindest während des Delays zum Zuge 
kommt, das reicht.

Kein Wunder, daß man das nicht debuggen konnte und man auch nicht mit 
Breakpoints was sehen kann, das Problem taucht nur in Echtzeit auf. Der 
Compiler war diesmal offensichtlich unschuldig, das Problem mit dem 
while und der Und-verknüpften Bedingung zweier Vergleiche ist mir aber 
immer noch scheierhaft. Schließlich muß C beide Vergleiche durchführen, 
wenn die while-Schleife weiter durchlaufen soll. Nur bei einem Abbruch 
kann einer der Vergleiche nicht ausgeführt worden sein, wenn der zuerst 
gemachte schon False ist.

Gruß
Andy

von Bernd K. (prof7bit)


Lesenswert?

Andreas W. schrieb:
> while-Schleife lustig weiterlief, als i den Wert 10 erreichte, die
> Schleife lief mit 10, 11, 12 weiter

Glaub ich nicht.

In 99.9% ist es das Problem vor dem Bildschirm und nicht dahinter. in 
diesem Falle wahrscheinlich ebenfalls.

Das musst du  verinnerlichen, dann geht die Fehlersuche schneller.

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

Gibt denn der Compiler in Fehlerfall keine einzige Warnung mehr aus? 
Bevor man den beschuldigt, sollte man nämlich zweifelsfreien Ode haben, 
also OHNE Wranungen!

von Andreas W. (andy_w)


Lesenswert?

Hallol,
Warnungslevel habe ich auf "pedantic" und in den eigenen files keine 
einzige Warnung mehr gehabt. Nur in core_cm3.h werden noch einige 
Warnungen ausgegeben, das ist aber unverändert von mir.

Ich kann mir auch nicht erklären, warum das Vertauschen der beiden 
Vergleiche, die mit && verknüpft sind, in einem Fall die while-Schleife 
weiter laufen läßt. Ich habe tatsächlich nur in dieser einen Zeile die 
Reihenfolge geändert und in einem Fall läuft es richtig, im anderen Fall 
zeigt der Debugger eindeutig, wie die while-Schleife mit i >= 10 
fröhlich weiter läuft. Dazu müßten beide Vergleich durchgeführt werden 
und beide True sein, sonst Abbruch. Der Debugger hat wohl auch nichts 
vorgelogen, denn normal laufend zeigten die Programme das gleiche 
Verhalten.

So etwas wie Zuweisungen in if- oder while-Ausdrucken vermeide ich 
grundsätzlich, manchmal funktioniert das
z.B. if((i = j) != 5)
und in anderen Fällen ist es compilerabhängig, weil C selber da nichts 
festlegt. Und manchmal hat man fehlerhafterweise "=" statt "==", da gibt 
es aber normalerweise eine Warnung.

Gruß
Andy

von Thomas H. (Firma: CIA) (apostel13)


Lesenswert?

Andreas W. schrieb:
> Die Schleife hatte fast genau eine
> Dauer, die ein genaues Vielfaches von 1ms betrug. Und offensichtlich
> wurde die Interruptfunktion immer genau dann aufgerufen, wenn en_int für
> ganz kurze Zeit auf Null gesetzt war. Diese Synchronizität dauerte
> offensichtlich ziemlich lange, so daß ich kaum erlebte, daß der
> Interrupt mal einen Zeitpunkt erwischte, in dem en_int 1 war.

Deshalb unterbindet man ISR's in der Regel  während andere Funktionen 
Variablen oder Register manipulieren die in einer ISR ebenfalls 
verwendet werden. Stichwort Atomarer Zugriff.

von Thomas H. (Firma: CIA) (apostel13)



Lesenswert?

Andreas W. schrieb:
> Wie schaltet man den Assembler Code in der View an? Irgendwie kann ich
> da keinen Menüpunnt finden, denn das hatte ich auch schon vor. In der
> Entwicklungsumgebung von Keil, die ich in der Firma verwende, ist der
> Assemblercode einfach schon sichtbar, entweder irgendwo eingestellt oder
> defaultmäßig eingeschaltet. Das müßte mit Atmel Studio eigentlich auch
> möglich sein.

Im Studio: während Du im Debugmodus bist Im Menü Debug -> Windows auf 
den Eintrag Assembly klicken und schon hast Du die Assembleransicht. 
Dann auf das Disassembly Fenster klicken, dann laufen die Einzelschritte 
dort drinnen und auf Maschinenbefehlsebene ab. Wenn Du im Simulator auch 
die ISR Simulieren willst unter Optionen -> Tools -> "Mask interrupts 
while stepping" auf False setzen.

: Bearbeitet durch User
von Andreas W. (andy_w)


Lesenswert?

Hallo,
die Interruptroutine selbst kann ich nicht anhalten, da die auch eine 
Timer für die Uhrzeit enthält, die Uhr würde nachgehen, wenn die öfters 
kurz angehalten wird. Die anderen Funktionen, die en_int benutzen, 
setzen en_:int nur selten und für kurze Zeit auf Null, so daß das 
funktioniert.

Inzwischen konnte ich das Anhalten der Interruptroutine in der neuen 
Funktion ganz entfernen, wenn es kritisch wird, die 64 Bit Timervariable 
für die Uhrzeit zu ändern (d.h. wenn der niederwertige 32-Bit Teil > 
0xffffff00 ist), wird eben solange gewartet (ca. 1/4 Sekunde), das kommt 
nur etwa alle 1-2 Monate einmal vor. Diese Timervariable wird nun in der 
Interruptroutine immer incrementiert, egal, welchen Wert en_int hat.

Die neue Funktion ist für die Uhrzeit zuständig, vor allem zum 
Einstellen von Uhrzeit und Datum. Ich  brauche eine Variable, die 
ständig schnell genug incrementiert wird und sich praktisch nie 
wiederholt, um die Funkübertragung dieser Fernbedienung kryptografisch 
abzusichern, so daß fremde Sender nicht die Kontrolle über den 
Verstärker übernehmen können. Die Variable ist nötig, damit keine 
Replayattacken möglich sind. Außerdem hat man dann auch eine 
Fehlerabsicherung, so daß keine gestörten und verfälschten Kommandos 
angenommen werden. Da eine AES-Verschlüsselung kaum aufwendiger als eine 
gute Prüfsumme ist, habe ich gleich die kryptografische Lösung genommen, 
der Arbeitsaufwand ist nahezu gleich und man ist dann sogar gegen aktive 
Angriffe gesichert, auch wenn solche relativ unwahrscheinlich sind... 
Der Empfänger im Verstärker nimmt nur Kommandos an, in denen der 
Timerwert größer als vom letzten akzeptierten Kommando ist. Er hat 
ebenfalls eine Echtzeituhr, die dann evtl. auch so synchronisiert wird 
und im Ram von der Echtzeituhr wird der letzte akzeptierte Timerwert 
gespeichert.

Da die Funkkommandos manchmal auch deutlich schneller als alle Sekunden 
kommen können, reicht die Echtzeituhr alleine nicht aus. Die Echtzeituhr 
(DS1307) setzt den 64 Bit Timer nach jedem Einschalten der 
Fernbedienung.

Die Disassembleranzeige in Atmel Studio habe ich inzwischen auch 
gefunden, nachdem ich auf die Idee gekommen bin, daß die nur bei aktivem 
Debugger einschaltbar ist.

Gruß
Andy

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.