Ich sehe gerade den Wald vor lauter Bäume nicht mehr.
Warum ist es ein riesen Unterschied ob ich
volatile char *pointer;
meine_ISR()
{
bla bla bla;
}
oder aber
meine_ISR()
{
static char *pointer;
bla bla bla;
}
schreibe???
Im zweiten Fall stürzt der µC nach einer Weile ab.
Oder ist das eigentlich egal und der Fehler muss doch woanders liegen?
im oberen beispiel ist dein pointer global und kann überall im programm
benutzt werden im unteren legst du ihn lokal an und kann im restlichen
programm nicht genutzt werden.
Ich möchte ihn auch nur lokal nutzen.
Nur wie gesagt, der µC stürzt nach ein paar Durchläufen ab.
Möglicherweise ist das untere Beispiel ja irgendwie Resourcenfressend
oder sowas???
Das obere Beispiel läuft perfekt.
gast schrieb:
> Ich möchte ihn auch nur lokal nutzen.> Nur wie gesagt, der µC stürzt nach ein paar Durchläufen ab.> Möglicherweise ist das untere Beispiel ja irgendwie Resourcenfressend> oder sowas???
Nein, ist es nicht.
> Das obere Beispiel läuft perfekt.
Mit Testen kann man immer nur das Vorhandensein von Fehlern
heruasfinden, nicht das Fehlen.
Soll heissen: Weil es im Moment so aussieht, als ob alles funktioniert,
heisst das noch lange nicht, dass keine Fehler enthalten sind.
Deine 2-te Version hat ein etwas anderes Memory-Layout. Das kann schon
ausreichen, dass sich in einem Fall ein (anderer) Fehler zeigt, im
anderen Fall aber nicht zeigt.
Danke, sehr gut geschrieben.
Mist, ich hab es ja geahnt :-(
Ok, dann hole ich mal weiter aus.
LPC2138, WinArm, wir befinden uns in der SPI1_ISR;
Dort drin ist eine Statemachine, die den Empfang regelt. Startbyte,
Prüfsumme etc...
Recht komplex, daher hier nur mal gekürzt/schematisch dargestellt.
In case 0 wird die Adresse vom daten-struct übernommen (wenn Startbyte
erkannt wurde).
Im nächsten Durchlauf dann werden die Empfangsbytes in mein Struct
geschrieben.
Wenn ich die Zeile "*rx_buffer++ = rx_byte;" auskommentiere stürzt der
µC auch nicht ab.
Er schreibt aber auch scheinbar an die richtige Adresse, denn die paar
Durchläufe die funktionieren, sind die Daten später korrekt im
daten-struct enthalten.
Ich habe auch schon
rx_buffer_end = (U8*)(&daten + sizeof(daten_t));
gegen
rx_buffer_end = (U8*)(&daten + 2); getauscht, dass er auf keinen Fall zu
weit schreibt.
Stürzt trotzdem irgendwann ab.
Spurious-Interrupt kann ich auch ausschließen, der ist definiert und
wird nicht angesprungen.
Wenn ich hingegen anstelle von
static U8 *rx_buffer, *rx_buffer_end;
innerhalb der ISR,
volatile U8 *rx_buffer, *rx_buffer_end;
außerhalb der ISR deklariere, dann rennt alles super…
Da ich damit aber nicht weiß, ob der Fehler damit wirklich behoben ist,
habe ich dabei ein ungutes Gefühl :-(
gast schrieb:
> Recht komplex, daher hier nur mal gekürzt/schematisch dargestellt.
Schon mal schlecht.
Da dein Fehler, so es einen gibt, überall stecken kann, reicht eine
schamtische Darstellung nicht.
> case 0:> if(rx_byte) == 0x55;> {> rx_state = 1;> rx_buffer = (U8*)&daten;> rx_buffer_end = (U8*)(&daten + sizeof(daten_t));
Wie ist 'daten' definiert?
Das daten_t da hinten ist seltsam. Normal findet man an sochen Stellen
rx_buffer_end = (U8*)(daten + sizeof(daten));
und daten ist irgend ein Array.
Könnte bei dir aber auch ein struct sein. Dann aber eher so
rx_buffer_end = (U8*)(&daten + sizeof(*daten));
> case 1:> if(rx_buffer < rx_buffer_end)
Der Fall wird hoffentlich nie auftreten bzw. erst dann, wenn das
Datenpaket wirklich vollständig ist.
> {> *rx_buffer++ = rx_byte; // hier vermute ich den Fehler
Der einzige Fehler, der hier möglich ist, ist der, dass rx_buffer
irgendwo in den Speicher zeigt. Der Fall ist aber so gut wie
ausgeschlossen, wenn rc_buffer im restlichen, nicht gezeigten, Code
nicht verändert wird oder über einen anderen Out-Of-Bounds Zugriff
irrtümlich verändert wird.
Denk immer drann: Was du siehst sind nur die Symptome. Dein eigentliches
Problem kann auch ganz woanders sitzen.
Was mir auffällt...
1.
*rx_buffer und *rx_buffer_end sind beim Programmstart nicht
initialisiert.
Du geht offenbar davon aus dass das erste empfangene Zeichen ein 0x55
ist.
Das ist sehr optimistisch.
2. Davon ausgehen dass 'daten' ein Array eines beliebigen Typs ist
(typ daten[array_length]).
2a. Offenbar muss dort stehen
rx_buffer_end = ((U8*)daten) + sizeof(daten);
denn
2b. Da wir den Typ von daten nicht kennen, muss vor dem Berechnen der
Endaddresse der Datentyp auf U8* gecastet werden.
Für den Typ U8 und U16 von daten ergibt (U8*)(&daten + sizeof(daten));
jeweils etwas anderes, denn
daten + sizeof(daten) == &daten[sizeof(daten)]
Werner B. schrieb:
> *rx_buffer und *rx_buffer_end sind beim Programmstart nicht> initialisiert.
Doch, sind sie, mit 0.
> Du geht offenbar davon aus dass das erste empfangene Zeichen ein 0x55> ist.> Das ist sehr optimistisch.
Warum? Wenn das erste Zeichen nicht 0x55 ist, schadet es doch nicht.
Er wartet doch einfach nur auf das nächste 0x55.
Werner B. schrieb:
> *rx_buffer und *rx_buffer_end sind beim Programmstart nicht> initialisiert.
Alle uninitialisierten static Daten landen doch im BSS. Und das muss der
Startcode ausnullen.
Danke für eure Beteiligung an der Problemlösung.
'daten' ist ein Struct.
Global definiert.
In meiner main.h steht:
1
typedefstruct
2
{
3
U32dummy1;
4
U32dummy2;
5
U32dummy3;
6
U32dummy4;
7
U8test[256];
8
}daten_t;
9
10
externdaten_tdaten;
und in der main.c noch mal
1
daten_tdaten;
Ich komme jetzt ins Schleudern,
1
rx_buffer_end=(U8*)(&daten+sizeof(daten_t));
oder
1
rx_buffer_end=(U8*)(&daten)+sizeof(daten_t);
was ist nun richtiger? :-)
Des Weiteren ist mir noch folgendes aufgefallen: Wenn ich innerhalb
einer beliebigen ISR eine Variable als volatile deklariere, dann ist der
Hang des µC zum Absturz viel größer.
Aber nur wenn sie wirklich innerhalb als volatile deklariert und
verwendet wird. Global deklarieren und in der ISR verwenden ist kein
Problem.
Ich vermute ja, dass das einfach wieder mit verändertem Speicherlayout
zu tun hat, aber ich frag zur Sicherheit trotzdem mal.
Man darf doch in einer ISR eine Variable als volatile deklarieren? Oder
ist das eine goldene „no go Regel“ die ich nicht kenne? :-)
Ich arbeite mich gerade in das Thema debuggen mit JTAG ein. Wie könnte
man da am besten vorgehen um solche fiesen Fehler zu finden?
>> was ist nun richtiger? :-)
Da haben wir alle bisher geschlafen.
1
rx_buffer_end=(U8*)(&daten)+sizeof(daten_t);
ist korrekt.
sizeof liefert die Länge in Bytes. Pointer Arithmetik ist aber so
definiert, dass der Compiler bei einer Addition immer automatisch mit
der Datentyplänge des Datentyps auf den der Pointer zeigt, implizit
multipliziert.
Du hättest daher auch
1
rx_buffer_end=(U8*)(&daten+1);
schreiben können, wäre aufs gleiche rausgelaufen (und ist IMHO sogar die
bessere Lösung).
-> Dein bisheriger End-Pointer zeigt irgendwo in den Speicher, nur nicht
auf das Ende der Struktur. (Das ist mit ein Grund warum ich solche
"Empfange bis irgendwelche sizeof Bytes" Programmstrukturen nicht mag.
Der andere Grund ist der, dass ich mich damit einem eventuellen Padding
des Compilers ausliefere, bzw. darauf achten muss. Ganz schlimm wirds
dann, wenn Sender und Empfänger unterschiedliche Vorstellungen vom
Padding haben und keiner die alles entscheidende Compilerswitches
findet, wie man das Padding für diese eine Struktur abdrehen kann)
Sorry, so genau hatte ich mir den Code dann doch nicht angeguckt.
Nee, wenn das kein Array ist, geht das natürlich nicht so (``subscripted
value is neither array nor pointer''), dann geht nur deine Variante.
Jörg Wunsch schrieb:
> Sorry, so genau hatte ich mir den Code dann doch nicht angeguckt.> Nee, wenn das kein Array ist, geht das natürlich nicht so (``subscripted> value is neither array nor pointer''),
Ja, da hab ich zu kurz gedacht. Klaus hat aufgepasst. Ich hab mich nur
auf die Index-Poiner_Addition Äquivalenz konzentriert.
1
rx_buffer_end=(U8*)&(&daten)[1];
schmack. Das hat doch was :-)
Das ist ein klassischer Arbeitsplatzsicherer :-)
Gleiche Liga, wie
gast schrieb:
> Des Weiteren ist mir noch folgendes aufgefallen: Wenn ich INNERHALB> einer beliebigen ISR eine Variable als volatile deklariere, dann ist der> Hang des µC zum Absturz viel größer.> Aber nur wenn sie wirklich innerhalb als volatile deklariert und> verwendet wird.> Global deklarieren und in der ISR verwenden ist kein> Problem.> Ich vermute ja, dass das einfach wieder mit verändertem Speicherlayout> zu tun hat, aber ich frag zur Sicherheit trotzdem mal.
Es klingt zumindest stark danach.
> Man darf doch in einer ISR eine Variable als volatile deklarieren? Oder> ist das eine goldene „no go Regel“ die ich nicht kenne? :-)
Nein.
Du kannst alles volatile machen, wenn du willst. Du hebelst nur so gut
wie sämtliche Optimierungsbemühungen des Compilers damit aus. :-)
>
1
> Ich arbeite mich gerade in das Thema debuggen mit JTAG ein. Wie könnte
2
> man da am besten vorgehen um solche fiesen Fehler zu finden? Stack
3
> überwachen etc?
4
>
Das hängt schwer vom Debugger ab.
Solche Fehler sind wahnsinnig schwer zu finden, weil jede kleine
Veränderung des Programms den Fehler an eine andere Stelle verschieben
kann.
Ausgangspunkt ist immer: das Problem reproduzierbar machen. Das heist du
brauchst eine exakte Abfolge von Input-Schritten, die du in das Programm
einspeist und die zuverlässig das Problem zum Vorschein bringen. Solange
du das nicht hast, ist alles andere ein Stochern im Nebel.
Angenommen, du hast das.
Dann crasht dein Pgm irgendwo. Jetzt heist es rausfinden warum es das
tut. Wahrscheinlich weil ein Pointer oder irgendeine sonstige Variable
urplötzlich auf einem Wert steht, den sie nicht haben sollte. Und das
fiese daran: Du setzt den Wert gar nicht. Laut deinem Programm passiert
eigentlich ganz was anderes, und nachdem eine Sequenz abgelaufen ist,
hat eine Variable wie von Geisterhand ihren Wert gewechselt (natürlich
gibts dafür eine Erklärung, aber die versteht man erst dann, wenn man
die tatsächliche Problemstelle identifiziert hat. Das ist der fiese Teil
daran)
Jetzt musst du diese Variable im Speicher suchen und wenn dein Debugger
dir das erlaubt einen Watch-Point auf die Speicherzelle setzen. Wichtig:
Nicht auf die Variable! Du musst direkt im Speicher das Byte überwachen.
Und dann geht das Spielchen wieder von vorne los. Irgendwann wirst du
eine Codezeile identifizieren, die mit deiner Variablen nicht das
geringste zu tun hat und trotzdem ändert sie genau den Bytewert im
Speicher. Meistens ist die betreffende Codezeile dann irgendein Pointer,
der genau auf diese Speicherzelle zeigt und eigentlich woanders
hinzeigen sollte. Dann heist es herausfinden warum dem so ist. Und so
hantelt man sich vom Symptom zur Ursache durch.
Und wenn man dann die Ursache kennt, ist alles plötzlich sonnenklar.
Wie sowas zb im Code aussehen könnte
int main()
{
int i = 0;
int k[4];
int m = 0;
k[4] = 8;
}
Nach der Zuweisung an k[4] stehen die Chancen nicht schlecht, dass
entweder i oder m nicht mehr den Wert 0 hat. Wenn nachfolgender Code
aber zb darauf abfrägt und bei nicht 0 einen Motor startet, der bei 0
nicht gestartet hätte werden dürfte, dann hast du ein mächtiges Problem
Veränderst du aber das Speicherlayout ein klein wenig
int main()
{
int i = 0;
int j = 0;
int k[4];
int l = 0;
int m = 0;
k[4] = 8;
}
dann behalten m (oder i) ihren Wert, die Abfrage auf 0 stimmt wieder und
alles geht gut. Das Problem ist nicht, dass mit m (oder i) irgendetwas
nicht stimmt (das ist nur das Symptom), sondern dass der Zugriff k[4]
ausserhalb des Arrays ist (= die Ursache). Du siehst aber nur, dass sich
der Motor dreht obwohl er das nicht sollte und suchst dir die Augen
wund, wo du denn etwas seltsames mit m (oder i) anstellst.
Hi,
ich würde den kram mit dem rx_buffer_end ganz weglassen und stattdessen
eine einfaches int mit der länge benutzen und runterzählen.
also in case 0: remain=sizeof(daten)
in case 1: if(remain-->0) ...
falls du unbedingt den endezeiger so lassen willst würde ich zumindest
das < durch ein != ersetzen um ein evtl. warp-around zu vermeiden (gab
es zumindest auf dem z80)
Peter
Peter schrieb:
> Hi,>> ich würde den kram mit dem rx_buffer_end ganz weglassen und stattdessen> eine einfaches int mit der länge benutzen und runterzählen.
Yep.
Die Länge würde ich mir allerdings vom Sender geben lassen (wenn ich
Einfluss auf das Protokoll habe) und nicht blindlings annehmen, dass das
schon passen wird. Sowas hat sich in der Vergangeheit bei Erweiterungen
immer wieder als Boomerang erwiesen.
case 0:
if(rx_byte) == 0x55;
...
Ist das ein Tippfehler oder ich verstehe der Syntax nicht... Ich glaube
dass die "if abfrage" so nicht funktionieren wuerde? Wenn "rx_byte" = 1
klappt, wenn 0 dann nicht. Das sollte aber gegen 0x55 verglichen werden,
oder?
hydravliska schrieb:
> case 0:> if(rx_byte) == 0x55;> ...>> Ist das ein Tippfehler
Das ist ein Tippfehler. Das wär so nie durch den Compiler gegangen.
Sowas kommt vor, wenn nicht Originalcode gepostet wird, sondern der Code
fürs Forum noch mal umformatiert wird.
Und ich hoffe auch, dass der ; da am Zeilenende nur ein Tippfehler ist.
Den nämlich würde der Compiler akzeptieren.
struct daten_t {
char x[8] ;
} ;
struct daten_t daten ;
rx_buffer_end = (U8*)(&daten + sizeof(daten_t));
= 64 mehr als rx_buffer
ohne fehler, weil
daten = eine struct
&daten = Pointer auf diese struct
So wie dieses einfachere Beispiel zeigt:
int *p ;
int *n ;
n = p+1 ;
durch das +1 wird dann sizeof(int) hinzuzählt zum Pointer.
Richtig wären die folgenden Variationen:
rx_buffer_end = ((U8*)(&daten)) + sizeof(daten_t);
rx_buffer_end = ((U8*)(&daten)) + 8;
rx_buffer_end = (U8*)(&daten + 1);
rx_buffer_end = (U8*)(&(&daten)[1]);
Hallo,
vielen Dank für die zahlreichen Tipps. Mein Problem scheint jedoch viel
tiefer zu liegen.
Ich habe mein Projekt auf ein kleines simples Beispiel für ein Olimex
Board (LPC-P2148) runterbrechen können und komme aus dem Staunen nicht
mehr raus.
Das angehängte WinArm Projekt soll eigentlich folgendes machen:
- Timer 1 wird initialisiert
- nach einer kurzen Wartezeit wird in eine Endlosschleife gesprungen in
der "main_update_flag" abgefragt wird
- ist "main_update_flag" = "true" dann toggle "PAD1" und setze
"main_update_flag" auf "0"
- im Timerinterrupt wird "timer_counter" um "1" erhöht und "PAD2"
getoggled
- wenn "timer_counter" glatt durch 20 teilbar ist, dann wird
"main_update_flag" auf "1" gesetzt
PAD1 entspricht auf einem Olimex 2148 Board LED1 und PAD2 = LED2
Eigentlich keine große Sache sollte man meinen. Mein Miniprojekt benimmt
sich da aber etwas störrisch.
Es gibt viele kleine Dinge die man tun kann, damit das Programm
scheinbar läuft. Doch diese erklären nicht wirklich den Fehler.
Beschränken wir uns auf folgendes Phänomen.
Lasse ich "timer_counter" auf teilbar durch 20 checken also:
1
if(!(timer_counter%20))
stolpert das Programm nur so rum. LED1 blinkt total unregelmäßig.
Lasse ich "timer_counter" auf teilbar durch 32 checken also:
1
if(!(timer_counter%32))
läuft das Programm scheinbar.
GCC 4.1.1 mit Optimierung s, ohne Optmierung gibts keinen Fehler.
Findet einer den Fehler? Was ist da los?
Gast schrieb:
> Lasse ich "timer_counter" auf teilbar durch 20 checken also:>>
1
if(!(timer_counter%20))
>> stolpert das Programm nur so rum. LED1 blinkt total unregelmäßig.>> Lasse ich "timer_counter" auf teilbar durch 32 checken also:>>
1
if(!(timer_counter%32))
>> läuft das Programm scheinbar.
Hab' mir das Programm angeguckt, nur so als Idee: der Modulus mit
32 wird durch den Compiler durch eine Bitmaskierung ersetzt, während
der durch 20 die vergleichsweise langsame Divisionsroutine aufrufen
muss.
Ja, das scheint in der Tat damit etwas zu tun zu haben. Denn mit 16
geht's dann auch wieder. Aber Schein trügt denke ich.
Das bisschen Rechnerei sollte doch den guten guten nicht außer Tritt
bringen.
Er hat doch jede Menge Zeit. Der nächste Interrupt kommt doch erst nach
50ms.
Ich hatte in meinen inzwischen nicht mehr zählbaren Test's auch
Versionen, in denen sich der Prozessor in der Art weghängt, dass ISR gar
nicht mehr angesprungen wird.
Wenn ich timer_counter nicht mehr volatile hab und die ISR wie folgt
umbaue:
Dann kommt er auch mit 32 aus dem Tritt. LED1 blinkt SEHR unregelmäßig.
Wie Karl heinz Buchegger vermute ich, der Fehler liegt an ganz anderer
Stelle und äußert sich durch Codeverschiebung nur jedes Mal anders.
Nur ist in diesem Beispiel ja kaum noch Platz für Fehler.
Keine Pointer keine Arrays, nichts!
Jörg Wunsch schrieb:
> Hab' mir das Programm angeguckt, nur so als Idee: der Modulus mit> 32 wird durch den Compiler durch eine Bitmaskierung ersetzt, während> der durch 20 die vergleichsweise langsame Divisionsroutine aufrufen> muss.
Bei höheren Optimierungen wird "% 20" auf dem ARM7 übrigens sogar vom
GCC in eine sehr effiziente Inline Sequenz übersetzt. Zugegeben, immer
noch langsamer als das Maskieren von Bits.
Wie wäre es mit einem Zähler der überhaupt nur bis 20/32/etc. zählt. Das
ist in jedem Fall schnell und unabhängig vom tatsächlichen Divisor.
Gruß
Marcus
http://www.doulos.com/arm/
Marcus Harnisch schrieb:
> Wie wäre es mit einem Zähler der überhaupt nur bis 20/32/etc. zählt. Das> ist in jedem Fall schnell und unabhängig vom tatsächlichen Divisor.
Dann hätte ich ja nur die Symptome beseitigt, nicht aber den Fehler, der
mir dann später sicher wieder auf den Fuß fällt.
Gast schrieb:
> Dann hätte ich ja nur die Symptome beseitigt, nicht aber den Fehler, der> mir dann später sicher wieder auf den Fuß fällt.
Wenn Du dir da so sicher bist... Diese Änderung ist in jedem Fall
sinnvoll und würde Deine Theorie bestätigen, dass der Fehler woanders
liegt.
Lass doch einfach mal die LED aus tcl_isr heraus blinken. Dort wo Du
main_update_flag auf 1 setzt. Wenn das genauso flackert, ist es mit
Sicherheit keine race condition bei der Kommunikation über die Variable.
Dann kann es eigentlich nur noch die Division/Modulo sein.
Mach Dir mal das Vergnügen und sieh Dir die Routine __aeabi_uidivmod
(und von dieser aufgerufene Funktionen) in libgcc.a an.
Interessanterweise löst GCC (CodeSourcery, 4.3.3) bei -O0(!) die
Division in eine sehr effiziente Routine auf, in der Art wie ich es
weiter oben beschrieben hatte. Bei -Os hingegen wird die langsame
Hilfsfunktion __aeabi_uidivmod bemüht.
Gruß
Marcus
http://www.doulos.com/arm/
Sicher bin ich mir inzwischen über gar nichts mehr :-)
Aaaaaber, wenn ich z.B. die Zeile:
test++;
aus der ISR entferne. Dann togglen die LEDs wie gewünscht.
Die Variable test hat jedoch im Codeverlauf rein gar nichts mit den LEDs
zu tun. Sie ist nämlich eigentlich ungenutzt.
Daher meine Annahme, das der Fehler nur durch Codeverschiebung auftritt
oder eben nicht zu sehen ist.
Tja, und an der Stelle bleibt fast nur noch die Vermutung, dass der GCC
4.1.1 hier selber die Fehlerursache darstellt oder?
Mit GCC 4.3.3 geht es übrigens, habe ich inzwischen getestet.
Theoretisch denkbar wäre natürlich immer noch, dass der GCC 4.3.3 durch
andere Optimierung eine race condition (die ich allerdings nicht
erkennen kann) verdeckt. Es empfiehlt sich, einen Blick auf den
erzeugten Code zu werfen, und diesen nachzuvollziehen.
Einstweilen hier kann ich mich nur selbst wiederholen:
GCC & ARM => CodeSourcery
Während der Zeit in der ich aktiv an diesem Forum teilgenommen habe,
haben wir schon einige Merkwürdigkeiten auf GCC zurückführen können. In
keinem Fall konnte das mit dem CodeSourcery GCC reproduziert werden.
Ich habe im Übrigen keine Anteile an dieser Firma und verwende GCC
ohnehin selten im Zusammenhang mit ARM.
Gruß
Marcus
http://www.doulos.com/arm/
Danke für den Tipp! Werde ich sicher mal mit der Evaluation Version
durchspielen.
Was ich eben noch getestet habe, wenn ich nicht die Ports P0:10 und
P0:11 sondern die Ports P1:16 und P1:17 verwende und dann mit dem Oszi
drauf schaue, dann geht's auch...
So macht das doch keinen Spaß... /&%(/&"%§ :-)
Jemand einen Tipp was das nun am Ende sein kann?
Gast schrieb:
> Was ich eben noch getestet habe, wenn ich nicht die Ports P0:10 und> P0:11 sondern die Ports P1:16 und P1:17 verwende und dann mit dem Oszi> drauf schaue, dann geht's auch...
Hab mich geirrt, auch mit P1 geht es nicht wirklich. Hatte nur die
Definition von PAD2_FLASH etwas umgestellt und da ist wohl wieder nur
Code verschoben worden.
Derzeit wundere ich mich wie das eigentliche Projekt in dem mir das
aufgefallen ist, und welches doch um einiges grösser war, bislang so
störungsfrei lief...
Nun ist jedenfalls das Vertrauen erst mal weg.