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