Atmega hängt sich auf

Gast #6906879
Lesenswert?

Hi Leute,

ich habe leider nicht die Möglichkeit JTag zu verwenden. Habe nur einen 
einfachen ISP Programmer (mkII)

Habe mein Programm um Source Code für die tm1637 7 Segment Anzeige 
erweitert.
Funktioniert alles auch soweit. Zusätzlich hatte ich auch schon ein 
Modul geschrieben, mit dem ich Buttons einlese. Dieses Button Modul habe 
ich x Fach in Verwendung und bisher nie Probleme gehabt und auch nichts 
geändert.

Drückt man nun einen solchen Button, hängt sich mein Programm auf.
Ich kann den Fehlerfall also auslösen.

Ich kann die Stelle die das Problem verursacht dennoch nicht 
lokalisieren. Ich kann an verschiedensten Stellen Code auskommentieren, 
der an sich nichts direkt mit den Funktionen an sich zu tun hat und das 
Problem ist weg. Deshalb gehe ich davon aus, es hat irgendwas mit dem 
Speichermapping oder Linking zu tun. Also abhängig davon wie das 
Programm zusammengebaut wird und welche Adressen verwendet werden tritt 
das Problem auf oder nicht.

Speicher ist auch nicht voll:
Program:   16164 bytes (49.3% Full)
(.text + .data + .bootloader)

Data:        738 bytes (36.0% Full)
(.data + .bss + .noinit)

Ich habe einen Scheduler, der durch eine ISR synchronisiert wird. Ich 
konnte soweit bisher herausfinden, dass wenn sich das Programm 
"aufgehängt" hat diese ISR immer noch aufgerufen wird.

Es wird kein Reset durchgeführt und alle nicht benötigten anderen ISRs 
fange ich auch ab. Die nicht benötigten ISRs werden nicht angesprungen.

Ich benutze keinen Watchdog.

Was seltsam ist, ist dass im MAP file mein neues Modul als einziges 
unter .progmem.gcc_sw_table zu finden ist.

 *(.progmem.gcc*)
 .progmem.gcc_sw_table
                0x00000054       0x2c obj/tm1637.o

und dann nochmal unter  .text

für was ist der Bereich .progmem.gcc_sw_table und was ist der Auslöser, 
dass etwas in diesem Bereich landet?

Habt ihr irgendwelche Tipps, wie ich weiter Debuggen kann um das Problem 
einzugrenzen?
Moderator (Firma: Titel) Persönliche Seite #6906912
Lesenswert?

Thomas Frosch schrieb:
> dass wenn sich das Programm "aufgehängt" hat
Das Programm bzw. der µC "hängt" sich nicht auf, sondern vermutlich 
wartet er verbittert mit seiner gesamten Rechenleistung in einer 
Mikroschleife auf irgendwas, das nie wieder kommen wird.

> Drückt man nun einen solchen Button, hängt sich mein Programm auf.
> Ich kann den Fehlerfall also auslösen.
Sprich: der Fehler ist in deinem Programm. Das kommt mit irgendeiner 
Reaktion aus der Umwelt nicht zurecht.

> Deshalb gehe ich davon aus, es hat irgendwas mit dem
> Speichermapping oder Linking zu tun.
Ich tippe auf den üblichen amoklaufenden Pointer...

> Ich kann die Stelle die das Problem verursacht dennoch nicht
> lokalisieren.
Dann gibt doch einfach uns die Chance, indem du deinen Code hier 
anhängst.
Gast #6906917
Lesenswert?

Sorry, wollte euch nicht gleich den ganzen Source Code hochladen.

hab mal im Anhang eine der Stellen, die ich hinzugefügt habe, dann geht 
es.

links geht nicht
rechts geht

Sehe im Map file zwei Auffälligkeiten.
links Zeile 1095 ist unter __do_copy_data das Object File genannt. 
rechts nicht.

links Zeile 576 steht 0x21 für den Fall, dass es nicht geht. Im Fall das 
es geht steht dort 0x0.

Im File tm1637.c.png seht ihr was ich einkommentiert habe, damit es 
geht.

Kenn mich mit map files nicht wirklich aus.
Gast #6909708
Lesenswert?

Also mir ging es ja auch ein bisschen darum, wie man einen solchen 
Fehler systematisch findet.

Folgendes habe ich getan:

=== Schritt #1: Stackoverflow ===
Zunächst habe ich anhand dieser Seite:
https://rn-wissen.de/wiki/index.php/Speicherverbrauch_bestimmen_mit_avr-gcc
den Stack Verbrauch ermittelt. Ergebnis: Es sind noch über 1000byte 
frei. Also Stackoverflow kann ich ausschließen

=== Schritt #2: Softwarewatchdog implementiert ===
In der main while wird eine Zählvariable auf 0 gesetzt.
In meiner Scheduler ISR die ja auch im Fehlerfall weiterhin aufgerufen 
wird, inkrementiere ich die Variable und prüfe sie auf einen Schwellwert 
ab. So konnte ich erkennen, ab wann sich die SW "aufhängt" und auch hier 
per SW Bitbang Uart zu diesem Zeitpunkt den zuletzt verbrauchten Stack 
ausgeben. Auch hier kein Stackoverflow.

=== Schritt #3: Rücksprungadresse aus ISR herausfinden ===
Versuch den Stackframe zu interpretieren um die Rücksprungadresse und 
damit die Fehlerhafte Stelle (Hängende Schleife) zu finden. Leider bin 
ich daran gescheitert. Hab Teile vom Stack via SW Bitbang Uart 
herausgeschrieben und im lss file geprüft wie viele push und pops 
gemacht werden um die Position der Rücksprungadresse im Stackframe zu 
finden. Jedoch habe ich keine sinnvolle Adresse gefunden. Wahrscheinlich 
habe ich die Struktur des Stackframes noch nicht richtig verstanden oder 
es muss noch ein Offset draufgerechnet werden. Hier muss ich nochmal 
ansetzen. Will das für die Zukunft verstehen. Wenn hier jemand gute 
Links zur Erklärung für AVR hat, dann gerne her damit!

=== Schritt #4: Ab da an wo ISR Fehler festgestellt hat, Software 
zurückverfolgen ===
Ich habe mir in der ISR beim Fehlerfall eine Variable gesetzt und dann 
in der Software an den verschiedensten Stellen diese abgefragt. Wenn die 
Variable gesetzt war, hab ich mir eine Marke via SW Bitbang Uart 
rausgeschrieben und konnte nach mehreren Implementierungen von Marken 
die Stelle finden.

War tatsächlich eine FOR Schleife.

Hatte eine const Variable definiert und zwar so:
const uint8_t C_U16_BLABLA

und dann in dem File in dem die Schleife verwendet wird so darauf 
zugegriffen
extern const uint16_t C_U16_BLABLA

das ganze war einer Konfigurationsanpassung geschuldet, die ich vor 
einem Jahr durchgeführt hatte und leider wie man oben sieht nicht ganz 
durchgezogen hab. uint8_t statt uint16_t verwendet. Im Namen aber noch 
brav angepasst.

Hatte also überhaupt nichts mit meiner neuen TM1637 Implementierung zu 
tun sondern einfach nur damit, dass an das higher Byte dieser Konstanten 
was anderes hingemappt wurde wenn ich dieses Modul genutzt habe. Dadurch 
lief die Schleife statt bis 4 bis über 16000 und hat dadurch noch 
weiteren Schaden angerichtet.

Jetzt noch folgende Fragen:
- Wie kann man solche Fehler schneller finden?
- Wie hättet ihr das gemacht? (ohne Debugger)
- Hat jemand eine gut erklärte Seite über Stackframe bei ISR? Sodass ich 
Stack auslesen kann und dann im lss file nach der Adresse suchen kann?

Würde mich über Antworten freuen. Auch wenn ich mir zu Weihnachten 
wahrscheinlich langsam mal einen JTAG Debugger schenken werde :-)
#6909721
Lesenswert?

Thomas Frosch schrieb:
> - Hat jemand eine gut erklärte Seite über Stackframe bei ISR? Sodass ich
> Stack auslesen kann und dann im lss file nach der Adresse suchen kann?

Welche Adresse willst du denn da im lss File wann suchen? Der Prozessor 
schreibt bei einem Interrupt die Rücksprungadresse auf den Stack. Danach 
kommt der vom Compiler erzeugte Prolog mit den zu sichernden Registern, 
und dann kommt eventuell noch ein Frame für lokale Variablen, falls 
erforderlich. Wie das genaus aussieht, verrät dir der Assemblercode der 
ISR. Das einzige, was du da vom Stack holen kannst, ist die 
Rücksprungadresse.

Thomas Frosch schrieb:
> - Wie hättet ihr das gemacht? (ohne Debugger)

Mit Debugger, in der Simulation.

Oliver
Gast #6909748
Lesenswert?

Thomas Frosch schrieb:
> const uint8_t C_U16_BLABLA

Ist das die Ungarische Notation?
Dann ist das eins der besten Bespiele dafür, wie bescheiden die doch 
ist.

Thomas Frosch schrieb:
> Jetzt noch folgende Fragen:
> - Wie kann man solche Fehler schneller finden?
> - Wie hättet ihr das gemacht? (ohne Debugger)

Eine tool chain verwenden, welcher den dummen Fehler auch bemerkt und 
meldet.
1
E:\Programme\arduino\portable\sketchbook\sketch_dec14b\sketch_dec14b.ino:4:27: warning: type of 'test' does not match original declaration [-Wlto-type-mismatch]
2
    4 | extern const unsigned int test;
3
      |                           ^
4
E:\Programme\arduino\portable\sketchbook\sketch_dec14b\test.c:2:21: note: type 'const unsigned char' should match type 'const unsigned int'
5
    2 | const unsigned char test = 34;
6
      |                     ^
Gast #6909761
Lesenswert?

Thomas Frosch schrieb:
> Wie kann man solche Fehler schneller finden?

Probiere mal Qt Creator als IDE aus. Sie erkennt viele potentielle 
Fehler und zeigt sie schon beim Tippen an.

Ich habe auch keine Ahnung wie man den Stack untersucht. Der Debugger 
nimmt mir diese Arbeit ab. Wenn ich den Debugger nicht verwenden kann 
(passiert bei AVR oft) gebe ich den Inhalt von Variablen und 
Auführungs-Markern seriell aus und schreibe sie mit dem PC in eine 
Datei. Wenn der serielle Port belegt ist, benutze ich einen I/O Pin mit 
Soft-UART (nur Ausgabe ist einfach). Wie du gesehen hast kann es sogar 
hilfreich sein, die Zählvariablen von Schleife auszugeben.

Leider verändert sich dadurch das Timing der Anwendung erheblich, 
deswegen kann man auch das nicht immer machen. Der letzte Notnagel ist, 
Zustände auf I/O Pins zu signalisieren. Wenn der hängt kann man dann an 
den I/O Pins ablesen, wo er hängt. Früher als ich noch Mikroprozessoren 
verwendete, konnte man die Adresse des Programmzählers direkt an den 
entsprechenden Pins ablesen.

Ich benutze gerne Git (oder Subversion) um Änderungen schrittweise zu 
protokollieren. Wenn dann später ein Fehler auftritt, kann ich das 
Programm schrittweise wieder zurück bauen, bis der Fehler verschwindet. 
Dann wiederhole ich meine Änderungen (anhand des Protokolls) in kleinen 
Schritten und achte ganz genau darauf, ab wann der Fehler wieder 
auftritt. Das gibt mir meistens einen Hinweis, woran es liegen könnte.
Gast #6909773
Lesenswert?

Screenshot von Qt Creator

Oliver S. schrieb:
> Den Fall nicht.

Datei-Übergreifend erkennt sie es leider nicht. Schade.

Abgesehen davon meldet die IDE mehr Fehler, als viele andere (Netbeans, 
Eclipse, Visual Studio Code, AVR Studio, Atmel Studio). Zumindest war 
das vor 3 Jahren noch so. Vielleicht wurden die anderen IDE inzwischen 
auch verbessert.

Auf jeden Fall kann es nicht schaden, fast alle Warnungen des Compilers 
mit dem Parameter -Wall zu aktivieren und das Programm zu zu bereinigen, 
dass er keine Warnungen ausgibt - auch wenn da ab und zu mal ein 
"Fehler" angemeckert wird, der keiner ist. Ich verstehe nicht, warum die 
das in der Arduino IDE nicht zur Standard-Vorgabe gemacht haben.

Auf der Arbeit programmiere ich in Java, da nutzen zur zusätzlich zum 
Compiler das Programm Spotbugs (entsprechend Lint bei C). Dort haben wir 
die Regel, dass unsere Programme ohne Warnungen gebaut werden müssen. 
Wir haben die Möglichkeit, einzelne Checks an gewünschten Code-Stellen 
zu deaktivieren, aber dann auch die Pflicht zu kommentieren, warum wir 
uns da 100% sicher sind. In 1.500.000 Zeilen Quelltext kommt das 
vielleicht 20 mal vor. Nicht öfter.
Angehängte Dateien:
Gast #6909790
Lesenswert?

Stefan ⛄ F. schrieb:
> Datei-Übergreifend erkennt sie es leider nicht. Schade.
Nur wenn man sich blöd anstellt.

Wer eingetretene Pfade verlassen will, muss halt mit solchen Problemen 
rechnen.

Der übliche Weg wäre:
In irgendeiner Datei, die Variable definieren
In einer *.h Datei die Variable deklarieren.

Diese *.h Datei in alle Dateien einbinden, wo die Variable auftaucht.
1
test.c:2:21: error: conflicting types for 'test'; have 'unsigned char'
2
    2 | const unsigned char test = 34;
3
      |                     ^~~~
4
In file included from E:\Programme\arduino\portable\sketchbook\sketch_dec14b\test.c:1:
5
E:\Programme\arduino\portable\sketchbook\sketch_dec14b\test.h:2:27: note: previous declaration of 'test' with type 'unsigned int'
6
    2 | extern const unsigned int test;
7
      |                           ^~~~
Gast #6909827
Lesenswert?

Deshalb ist es auch in der heutigen Zeit gut, einen gut zugänglichen 
Reset-Knopf aufs Board zu setzen. Damit man nicht jedes mal die 
Spannungsversorgung abschalten muss, nur weil der Prozessor sich 
aufgehängt hat.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren