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
:
Bearbeitet durch User
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
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
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.