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
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
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.
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?
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?
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
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?
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
In der Interruptroutine wird folgenrdermaßen abgefragt:
1
if(en_int!=0)
2
{
3
...hierwerdendieVariablenbearbeitet
4
}
Inzwischen habe ich auch folgendes probiert:
1
int8_ten;
2
3
en=en_int;
4
if(en!=0)
5
{
6
...hierwerdendieVariablenbearbeitet
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
externvolatileint8_ten_int;
globals_c:
1
volatileint8_ten_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
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
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.
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!
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
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.
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.
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