Forum: Mikrocontroller und Digitale Elektronik Atmega hängt sich auf


von Thomas Frosch (Gast)


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?

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Meine Glaskugel lokalisiert den Fehler in Zeile 42.
Ohne Glaskugel würde ich auf ein vollgelaufenes Ram/Stack tippen.

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


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.

: Bearbeitet durch Moderator
von Thomas Frosch (Gast)



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.

von Stefan F. (Gast)


Lesenswert?

Thomas Frosch schrieb:
> Sorry, wollte euch nicht gleich den ganzen Source Code hochladen.

Mache das, oder du bekommst keine Hilfe. Und zwar inklusive der Linker 
Konfiguration, falls vorhanden.

von Frank G. (frank_g53)


Lesenswert?

Thomas Frosch schrieb:
> Habt ihr irgendwelche Tipps, wie ich weiter Debuggen kann um das Problem
> einzugrenzen?

In dem von dir verdächtigen Code-Abschnitt eine Ausgabe auf die 
7-Segment Anzeige schreiben.

Nach Code-Zeile .. Ausgabe "1"
Nach Code-Zeile .. Ausgabe "2"

Dann weißt du wie weit das Programm gelaufen ist.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Was soll der PNG-Mist.
Stell das vollständige ZIP rein, was ohne Error / Warnings compiliert.

: Bearbeitet durch User
von Pandur S. (jetztnicht)


Lesenswert?

Jetzt aber weg mit dem Troll. Wenn man sich schon derart zersplitterten 
Code antut sollte man etwas weiter mir Debugstrategien sein.

von Christian S. (roehrenvorheizer)


Lesenswert?

Wie viele Unterprogramme samt IRQs können denn da maximal aufgerufen 
sein?


Mfg

von Thomas Frosch (Gast)


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 :-)

von Oliver S. (oliverso)


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

: Bearbeitet durch User
von EAF (Gast)


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
      |                     ^

von Stefan F. (Gast)


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.

von Oliver S. (oliverso)


Lesenswert?

Stefan ⛄ F. schrieb:
> Probiere mal Qt Creator als IDE aus. Sie erkennt viele potentielle
> Fehler und zeigt sie schon beim Tippen an.

Den Fall nicht.

Oliver

von Stefan F. (Gast)


Angehängte Dateien:

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.

von Oliver S. (oliverso)


Lesenswert?

Stefan ⛄ F. schrieb:
> Datei-Übergreifend erkennt sie es leider nicht. Schade.

Das schafft auch gcc nur mit lto, mit der aktivierten Warnung.
Eine statische Codeanalyse sollte sowas auch finden, aber wer nutzt 
sowas privat schon.

Oliver

von EAF (Gast)


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
      |                           ^~~~

von EAF (Gast)


Angehängte Dateien:

Lesenswert?

Hier mal im konkreten Beispiel, wie sowas aussehen könnte/sollte.
Geht natürlich mit JEDER IDE so, oder so ähnlich.
Und auch ohne IDE

von Oliver S. (oliverso)


Lesenswert?

EAF schrieb:
> er ü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.

Der richtige Weg wäre: gar keine globalen Variablen benutzen.

Oliver

von EAF (Gast)


Lesenswert?

Oliver S. schrieb:
> Der richtige Weg wäre: gar keine globalen Variablen benutzen.

Wenn man denn Bäcker ist, ist das im Bereich der Möglichkeiten.

Kann es sein, dass du etwas angepisst bist, weil dir ein Arduino User 
das Zeugs unter die Nase reibt?

von Anarchist (Gast)


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.

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.