Jens Altmann | |||
Bedienung von JSIM-51 |
JSIM51 ist ein leistungsfähiges Werkzeug zur Softwareentwicklung für 8051-Controller und Derivate. Das Programm simuliert den Prozessorkern und ausgewählte Hardwarefunktionalitäten. Es ist entstanden, da kommerziell verfügbare Simulatoren alle in einer Preisklasse rangieren, die für den Privatanwender jenseits von Gut und Böse rangieren. Natürlich wird, wie bei allen Freewareprodukten, keine Garantie für die Funktion übernommen. Allerdings werde ich versuchen, Fehler, die mir mitgeteilt werden, so schnell wie mir möglich zu beseitigen und eine neue Version bereitzustellen. Im Folgenden das Wesentliche zur Bedienung des Programms.
Inhaltsverzeichnis |
Details zum Laden von SDC51/ASxxxLink - Projekten |
Downloads |
jsim.zip | Deutsche Version V4.05 für Win32 vom 03. 01. 2000 | 286 kB | ||
jsim_e.zip | Englische Version V4.05 für Win32 vom 08. 01. 2000 | 285 kB | ||
8051.zip | DLL zur Simulation eines Standard-8051, V1.017 - 17. 09. 1999 | 122 kB | ||
80320.zip | DLL zur Simulation eines 80C320 -Dallas, V1.019 - 03.10.2000 | 112 kB | ||
jsimdoc.zip | Aktuelle Dokumentation auf Deutsch und Englisch, HTML | 143 kB | ||
jsim.pdf | Original-Dokumentation auf Deutsch, PDF | 81 kB | ||
omf51.exe | OMF-51 Spezifikation auf Englisch, MS-Word | 43 kB | ||
Leider habe ich keine Zeit mehr, um das Projekt weiter zu pflegen. Wer also Änderungen haben möchte, muß sie sich selbst einbauen. Hier sind die aktuellen Quellen dazu. Ich würde erwarten, daß sinnvolle Verbesserungen auch allen anderen zugänglich gemacht werden! | ||||
coresrc30.zip | Quellen für die Core Applikation | 374 kB | ||
8051src22.zip | Quellen für die 8051-DLL | 53 kB | ||
80320src14.zip | Quellen für die 80320-DLL | 60 kB |
Hier nur ein kleines Beispielprojekt zum ausprobieren: | testprj.zip | (3 kB) | |
Liste der beseitigten Fehler | |||
Here is the same description in English ! |
Allgemeines |
Das Programm simuliert den Prozessorkern und einige Hardwarefunktionen des 8052.
Es ist damit möglich, Softwareteile unabhängig von der Zielplattform vorzutesten.
Die Geschwindigkeit des Simulators erreicht auf einem Pentium 200 ungefähr die
eines mit 10 MHz getakteten 8051. Das sollte einigermaßen akteptabel sein.
Die Bedienphilosophie und Oberfläche des Simulators wurde, soweit als möglich und
sinnvoll, analog der von MS-VisualC++ 4.2 aufgebaut.
Es ist erforderlich, daß die Datei comctl32.dll,
die es ab dem IE 3.0 gibt, installiert ist.
Aufgrund der Komplexität des Programms sind Fehler keinesfalls auszuschließen,
ja geradezu zu erwarten. Im Zuge der Fehlerbeseitigung und der Weiterentwicklung
sind durchaus Änderungen in der hier dargestellten Bedien- und Funktionsweise möglich.
Um dem Benutzer die Bedienung zu erleichtern, besitzen die meisten Elemente der
Oberfläche Tooltips. Alle Fenster können im Main-Window angedockt werden,
wodurch die Oberfläche deutlich an Übersichtlichkeit gewinnt.
Da es mich schon immer geärgert hat, wenn ein Programm mehrere DLLs benötigt
und in der Registry rumschmiert, habe ich mich bemüht, dieses zu vermeiden.
Bis zur Version 2.09 war das auch so. Allerdings hat es sich gezeigt, daß viele
den Wunsch haben, unterschiedliche Derivate eines 8051 zu simulieren, oder die
gleiche Oberfläche auch für die Simulation anderer Controller zu nutzen.
Also habe ich die Applikation geteilt. Es besteht jetzt aus einer EXE (jsim.exe)
und mindestens einer DLL, die die prozessorspezifischen Dinge enthält.
Ansonsten hat sich nichts geändert.
Es gibt keine Einträge in der Registry, das Programm wird
man genauso einfach wieder los, indem man jsim.exe, jsim.ini und die Prozessor-DLLs
im gleichen Verzeichnis löscht.
Die Einstellungen eines Projektes werden bei dem Projekt mit der Dateierweiterung
".wsp" abgelegt.
Das Workspacefile ab Version 3.0 ist nicht mehr kompatibel mit den älteren Versionen. Ebenso könnten Workspacefiles, die mit der englischen Version erzeugt wurden, bei der deutschen Version evtl. nicht mehr funktionieren und umgekehrt. Also löschen und neu anlegen.
Wer mehr über die interne Struktur des Programms wissen will,
findet mehr unter der Seite:
Hintergründe zum Programm und Implementierungshinweise
Auswahl des zu simulierenden Prozessors |
Der zu simulierende Prozessor muß ab Version 3.0 über eine DLL geladen werden.
Das macht man über Datei-Prozessor->Auswahl.
In dem sich öffnenden Dialog kann entweder gleich der Prozessor ausgewählt oder
gesucht und geladen werden. Die vorhandenen DLLs werden in "jsim.ini"
eingetragen und stehen dann in der Liste zur Verfügung.
(Beim ersten Start muß also gesucht werden!)
Der gewählte Prozessor wird im Workspacefile vermerkt, muß also nur einmal
über die Liste selektiert werden.
Wenn zum Zeitpunkt des Ladens eines Workspacefiles schon ein Prozessor
ausgewählt ist,
wird dieser anstelle des im Workspacefile eingetragenen
genommen.
Laden von Files |
JSIM kann folgende Datei-Formate laden:
OMF51-Ext wird nur durch den Keil-C51-Compiler unterstützt.
Dieses Format sollte auf jeden Fall bevorzugt werden, da es erhebliche Vorteile
beim Debuggen bietet! (Unterscheidung zwischen Groß/Kleinschreibung von Symbolen,
Typdefinitionen für komplexe Datentypen.) Ich habe den Hochsprachendebugger mit
Quelltexten getestet, die mit dem Keil-C51 Compiler V3.40 übersetzt wurden.
Mittlerweile gibt es schon die Version 5.1 und ich bin mir nicht sicher, ob die
Debuginformationen tatsächlich noch genauso dargestellt werden. Der Simulator
unterstützt auch keine unterschiedlichen Speicherbänke wie die neueren Compiler
und Linker.
Das einfache OMF-51-Format kennt nur Großschreibung. Da keine Typbeschreibung existiert, ist von einem Symbol lediglich seine Adresse bekannt, nicht aber seine Größe. Beispielsweise Strukturen lassen sich deshalb im Watchfenster nicht darstellen.
Ein Absolutfile wird mit Datei-Öffnen->Auswahl geladen. Die C-Sourcefiles müssen sich im gleichen Verzeichnis wie das Absolutfile befinden. Seit Version 2.04 können Dateien auch mit Drag-Drop oder über die Kommandozeile geladen werden. Wenn nicht, muß in die Workspacedatei für das Projekt unter "SourcePath=..." per Hand der dann für alle Quellen gültige Pfad eingetragen werden. Das zu ladende File wird sequentiell eingelesen. Für jedes enthaltene Modul (Sourcefile) wird ein Eintrag im Browserfenster mit dem Namen des Moduls angelegt. Ist ein Sourcefile jünger als das Absolutfile, wird eine entsprechende Warnung ausgegeben. Wenn aufgrund solch eines Fehlers die Zuordnung zwischen Quellfile und Zeileninformation nicht richtig hergestellt werden kann, stürzt im ungünstigsten Fall das Programm in der MIX-Darstellung ab. Prozeduren, die innerhalb eines Moduls definiert sind, werden als Untereinträge unter dem Modul dargestellt. Libraryfunktionen und durch den Compiler generierter Code werden in einem eigenen Fenster mit dem Namen "LIB" angezeigt.
Laden von HEX-Files |
Für manche Projekte ist es notwendig, auch ein Hex-File zu laden.
Ein Hexfile enhält allerdings keine Symbolinformationen. Das bedeutet,
beim Reassemblieren werden auch Konstanten in Programmcode übersetzt,
was natürlich Unsinn ist.
Trotzdem kann man u.U. auch ohne Symbolinformationen debuggen.
Ein Hexfile kann auch vor (!) dem Absolutfile geladen werden.
Das darauffolgende Laden eines Absolutfiles löscht den Speicher nicht.
Ein Hexfile wird geladen mit: Datei-Öffnen->Auswahl.
JSIM51 merkt sich, wenn ein Hexfile geladen wurde und versucht beim nächsten
Laden des gleichen Projekts wieder das Hexfile vor dem Absolutfile zu laden.
Wird das Hexfile nicht mehr gefunden, wird es wieder aus dem Projekt ausgetragen.
(Das kann natürlich auch von Hand erledigt werden, indem man den Schlüssel
"hexfile=" in der Workspacedatei löscht.)
Details zum Laden von SDC51/ASxxxLink - Projekten |
Abspeichern der eingestellten Umgebung |
Alle eingestellten Fenster, Watches, Breakpoints usw. können unter Datei-Speichern als->Auswahl als ASCII-Datei abgespeichert werden. Die Datei erhält voreingestellt den gleichen Namen wie das geladene Absolutfile nur mit der Dateierweiterung ".wsp". Der Name kann aber auch frei gewählt werden. Dieses File enthält alle Kommandos um über die aktuelle Oberfläche wieder aufzubauen. Das File könnte auch von Hand editiert werden.
Größte Vorsicht ist allerdings geboten, da viele
Angaben zur Fensterposition, ID und Dockzustand sehr kryptisch und auch
noch voneinander abhängig sind. Sollte das Programm nach dem Editieren
beim Laden abstürzen, so empfiehlt es sich, zuallererst die Workspacedatei
zu löschen oder umzubenennen.
Das Programm läuft dann mit seinen Defaulteinstellungen hoch.
Einstellungen zu Watchausdrücken oder Breakpoints lassen sich dann
problemlos und ohne Risiko aus der korrumpierten WSP-Datei übernehmen.
Beim Laden eines Absolutfiles (ohne .wsp) wird zunächst nach der
entsprechenden Workspace-Datei gesucht und wenn gefunden,
die Oberfläche wieder hergestellt. Weiter ist zu beachten,
daß Breakpoints durch Code-Änderungen ungültig werden können.
Browser-Fenster und Anzeige des geladenen Codes |
Als Startfenster wird das Modul, das die Marke "main" enthält, gesucht und dargestellt. Gibt es diese Marke nicht, wird der Code ab der Resetadresse (0x0000) angezeigt. In der Regel (d.h. C-Programme) wird das das "LIB"-Fenster sein und Assemblercode enthalten. Man kann nun durch Doppelklick auf ein Modul oder eine Prozedur im Browserfenster sofort zum gewünschten Code schalten. Jedes Modul besitzt ein eigenes Fenster, das je nach Bedarf geladen oder in den Vordergrund geschaltet wird. Ist der Programmteil in "C" geschrieben und der Quelltext ist im eingestellten Pfad bzw. im Verzeichnis des Absolutfiles verfügbar, erfolgt die Darstellung als C-Quelltext. Alternativ können über die Toolbar-Buttons auch die Modi Mixed(MIX) und Assembler(ASM) gewählt werden. Enthält der Code keine HLL-Informationen oder ist der Quelltext nicht verfügbar, sind die Buttons "MIX" und "HLL" grau und können nicht betätigt werden. Codebereiche, die nicht über das Absolutfile geladen wurden, können auch auf Assemblerlevel betrachtet werden. Die Eingabe der Adresse erfolgt dann über die Kommandozeile ("d.p. adresse"). Ab dieser Adresse wird der Inhalt des Codespeichers reassembliert und im Fenster "UnKnown" angezeigt. Zur Unterscheidung von geladenem Code ist das Fenster farblich hinterlegt. Es kann nur vorwärts gescrollt werden. Wird das Fenster geschlossen und erneut geöffnet, wird jedesmal neu reassembliert. (Für den Fall, jemand baut selbstmodifizierenden Code.) Die Anzeige kann alternativ auch über die Kommandozeile durch Angabe der Adresse oder eines Labels umgeschaltet werden.
Der Zustand der angezeigten Module (Größe, Position, Sichtmode) wird NICHT im Workspacefile gespeichert.
Beispiele:
d.p.0x380 | - | zeigt den Code ab Adresse 0x380 |
d.p.main | - | zeigt den Code ab dem Label "main" |
Beginnt an der Adresse kein Opcode, wird je nach Displaymodus entweder die nächste folgende HLL-Zeile oder der nächste Assemblerbefehl "angefahren".
Den Code abarbeiten ... |
Die Bedienung kann sowohl durch die Tastatur als auch über Toolbar-Buttons erfolgen. Die aktuelle Position des Programcounters wird immer durch einen gelben Pfeil markiert. Der Cursor wird durch einen grauen Pfeil dargestellt. Die Wirkung der Befehle ist abhängig vom aktuell eingestellten Darstellungsmodus.
Ab Version V2.00 ist im Codefenster auch ein Kontextmenü über die rechte Maustaste verfügbar, was oft benötigte Befehle enthält.
Step-In (F11): | |||
Assembler- und Mixmode | - | Es wird genau ein Assemblerbefehl abgearbeitet. Ist der Befehl ein "call", wird in die Prozedur gesprungen. | |
HLL | - | Es wird eine HLL-Zeile abgearbeitet. Das bedeutet, es werden solange Assemblerbefehle ausgeführt, bis der Programcounter wieder auf eine Adresse zeigt, der eine HLL-Zeile zugeordnet ist. Das kann die nächst folgende HLL-Zeile sein, oder aber auch eine Zeile in einer anderen Prozedur/Modul. | |
Step-Over (F10): | |||
Assembler- und Mixmode | - | Ist der Assemblerbefehl KEIN "call", wirkt Step-Over wie Step-In, der Befehl wird ausgeführt und der PC zeigt danach auf die Adresse des nächsten Assemblerbefehls. Wenn der Befehl ein "call" ist, wird auf die, dem "call" folgende Adresse ein temporärer Breakpoint gesetzt und das Programm im freien Lauf gestartet. Bei Erreichen des Breakpoints wird angehalten und der Breakpoint gelöscht. Dadurch können innerhalb des "calls" weitere Unterprogramme verschachtelt sein, bei denen nicht angehalten wird. Wichtig zu beachten ist, daß das Programm nicht mehr anhält, wenn der auf den "call" folgende Befehl nie erreicht wird. Das passiert zum Beispiel dann, wenn innerhalb des Unterprogramms der Stack verändert wird, so daß der Returnbefehl zu einer anderen Adresse führt. | |
HLL | - | Enthält die HLL-Zeile keinen Prozeduraufruf, wirkt Step-Over wie Step-In auf HLL-Level. Wird innerhalb der Zeile eine Prozedur aufgerufen, so werden solange Assembleranweisungen ausgeführt, bis der Programcounter wieder auf eine HLL-Zeile in der aktuellen Prozedur zeigt. Da diese Abbruchbedingung nach jedem Assemblerbefehl geprüft werden muß, ist ein solcher Step-Over recht langsam. Der Grund, nicht wie beim Assemblermode die nächste HLL-Zeile zu suchen und dort einen temporären Breakpoint zu setzen, liegt z.B. an der goto-Anweisung deren Ziel nicht bekannt ist, aber in aller Regel nicht die nächstfolgende HLL-Zeile sein wird. | |
Run-To-Cursor (F8): | |||
Assembler-, Mix- und HLL-Mode | - | Auf die Adresse des Befehls, der in der Zeile steht, auf die der graue Cursorpfeil zeigt, wird ein temporärer Breakpoint gesetzt und das Programm im freien Lauf gestartet. Es werden solange Assembleranweisungen abgeabeitet, bis der nächste Breakpoint erreicht ist. (Das kann auch durchaus ein anderer permanenter Breakpoint sein.) Wird der temporäre Breakpoint erreicht, wird das Programm angehalten und der Breakpoint gelöscht. | |
Run (F5): | |||
Assembler-, Mix- und HLL-Mode | - | Das Programm wird ohne Abbruchbedingung gestartet. Es werden solange Assembleranweisungen ausgeführt, bis entweder ein Breakpoint erreicht wird, oder der Benutzer die Programmausführung selbst mit Stop abbricht. | |
Stop (F6): | |||
Assembler-, Mix- und HLL-Mode | - | Die Programmausführung wird sofort abgebrochen. Es wird zum aktuellen Stand des Programcounters gescrollt. |
Breakpoints |
Neben den intern verwalteten temorären Breakpoints hat der Nutzer die
Möglichkeit, an beliebigen Stellen im Programm Breakpoints zu setzen.
Breakpoints werden durch einen roten Punkt dargestellt. Sie können direkt
von der Oberfläche mit F9 gesetzt und gelöscht werden.
Das ist in allen Modi möglich. Im Mixed-Mode werden Breakpoints auf
Assembleranweisungen gesetzt. Die HLL-Anweisungen dienen nur zur
Kommentierung des Codes.
(SHIFT-F9 setzt/toggelt einen Breakpoint deaktiviert)
Alternativ können Breakpoints auch über die Kommandozeile eingegeben werden.
Die Angabe der Adresse kann aber nur als Label erfolgen. Der Grund besteht
darin, daß ein Breakpoint durch das Einfügen eines verbotenen OP-Codes an
der entsprechenden Stelle realisiert wird. Könnte man einen Breakpoint durch
Angabe einer beliebigen Adresse setzen, würde möglicherweise nur ein Datenbyte
und nicht ein OP-Code geändert. Die Folge wäre fatal: Der Breakpoint würde
nie erreicht, aber das Programm macht Unvorhersehbares!
Ab Version 1.10 werden die Breakpoints durch ein zusätzliches Byte für jeden Speicherplatz im Codespeicher verwaltet, und nicht mehr über illegal Opcode realisiert. Am oben dargestellten Verhalten ändert sich dadurch aber nichts.
Ab Version 2.00 können Breakpoints auch auf Speicherzugriffe, wahlweise auf Lese/Schreib oder beides, gesetzt werden. Das kann man auf verschiedene Weise tun:
- | Im geöffneten Speicherfenster (nur in der Byte- oder ASCII-Ansicht möglich !!) | |||
1. | Mit Klick auf linke Maustaste die zu überwachende Speicherzelle markieren. | |||
2.1 | Mit rechter Maustaste das Kontextmenü öffnen und den Typ der Überwachung wählen. (Read/Write) | |||
2.2 | .. oder auch mit CTRL-R (Lese-Breakpoint) oder CTRL-W (Schreib-Breakpoint) setzen. | |||
3. | Ein gesetzter Breakpoint wird orange markiert. (Programmbreakpoints sind im Speicherfenster nicht sichtbar.) | |||
- | Über die Kommandozeile mit folgender Syntax: | |||
b.s Adresse(hex) Zugriffstyp Speicherbereich |
Beispiele:
b.s 0xF014 0x200 c | setzt auf die Adresse 0xF14 im CODE-Speicher einen Lesezugriffsbreakpoint | |
b.s 0x14 0x400 d | setzt auf die Adresse 0x14 im DATA-Speicher einen Schreibzugriffsbreakpoint | |
b.s 0x95A 0x600 x | setzt auf die Adresse 0x95A im XDATA-Speicher einen Schreib+Lesezugriffsbreakpoint |
Wichtig zu beachten!
Das Setzen auch nur eines Zugriffsbreakpoints oder Veränderung der
Default-Speicherbelegung bewirkt, daß die Simulation über eine andere,
wesentlich langsamere Funktion läuft. Es muß ja jetzt bei jedem Zugriff
überprüft werden, ob ein Breakpoint erreicht wurde.
Das verlangsamt die Simulation um ungefähr 70-80% !!!
Die Anzahl der Zugriffsbreakpoints wirkt sich dann allerdings nicht mehr aus.
Also entweder gar keinen, oder wenn schon, dann nur solange wie notwendig.
Programmbreakpoints können deaktiviert werden. Das ist entweder über die Oberfläche für alle Breakpoints gleichzeitig möglich, oder aber mit Bearbeiten-Breakpoint->Auswahl-Toggle, oder auf der Cursorposition mit CTRL-F9. Breakpoints werden im Workspacefile mit ihrem aktuellen Zustand gespeichert.
Beispiel:
b.s.main | setzt einen Breakpoint auf die Adresse "main" |
Register-Fenster |
Das Registerwindow wird entweder über das Menü Ansicht-Register bzw. den Toolbar-Button aufgerufen, oder über die Kommandozeile mit "w.r". Alle wesentlichen Prozessorregister werden hier dargestellt und können auch direkt modifiziert werden. Dazu wird der neue Wert eingegeben und mit "ENTER" bestätigt. Wird während des Programmlaufs der Inhalt eines Registers geändert, stellt es sich beim nächsten Halt rot dar. Prinzipiell erfolgt ein Refresh nach jedem Programmhalt, nach Step und wenn ein Wert in einem anderen Watch- oder Speicher-Window verändert wurde.
Wird der PC im Register-Fenster geändert, erfolgt keine unmittelbare Umschaltung des Code-Fensters, sondern erst beim nächsten Step!
Das Ausgaberegister des Registers P2 kann eigentlich nicht rückgelesen werden. Der Simulator setzt aber Ein- und Ausgabe in P2 gleich. Normalerweise dürfte das nicht stören, da bei der Benutzung externen Speichers das Port nicht für Eingaben zur Verfügung steht.
Speicher-Fenster |
Ein Memoryfenster wird über das Menü "Ansicht-Speicher"
erzeugt. Es können maximal 10 Fenster angezeigt werden. Das sollte reichen.
Die Alternative ist die Kommandozeile mit dem Kommando
"w.m.viewmode.adresse /speicherbereich".
Die Adresse kann dezimal oder hexadezimal eingegeben werden.
Auch ein Label oder ein qualifizierter C-Ausdruck können verwendet werden.
Das bedeutet dann aber auch, daß sich der Bereich, auf den das Fenster zeigt,
ständig ändern kann!
Wichtig: mindestens 1 Leerzeichen vor "/" !
Der Viewmodus wird mit folgenden Werten spezifiziert:
a ASCII b Byte s short (16 Bit) l long (32 Bit)
Der Speicherbereich kann mit folgenden Werten spezifiziert werden:
x XDATA c (oder keine Angabe) CODE d DATA i IDATA p PDATA
(Wird P2 geändert, ändert sich auch der
Bereich, auf den das Speicherfenster zeigt.)
Beispiele:
0x1F00 /x - Xdata ab 0x1F00 20 /d - Data ab 0x14 testarray - Speicher ab dem Symbol "testarray" *xptr+2 /c - Code ab Inhalt des Zeigers xptr.
(Solch ein Ausdruck ist nur verwendbar, wenn OMF51-Ext geladen
wurde. Ansonsten kennt man ja den Typ, auf den xptr zeigt, nicht.)MAIN - Codespeicher ab Symbol MAIN
Klick auf die rechte Maustaste öffnet ein Kontextmenü, mit dem die Darstellungsarten
Byte Short Long ASCII
ausgewählt werden können.
In allen Darstellungen können die Werte direkt geändert werden.
Dazu auf den Eintrag klicken und den neuen Wert eintragen.
Der Wert wird als Hexadezimalzahl interpretiert.
Im ASCII-Modus wird direkt der ASCII-Code der Taste eingetragen.
Bestätigt wird mit "ENTER". Ist die Eingabe gültig,
wird der Wert übernommen und in den Speicher geschrieben.
Seit dem letzten Windowupdate geänderte Speicherinhalte werden
rot dargestellt. (Also auch beim Step durch ein Programm,
das gerade angezeigte Speicherbereiche modifiziert.)
Copy und Paste im Speicherfenster |
Es ist auch möglich, ganze Speicherbereiche mittels Copy-Paste zu modifizieren. Um einen String einzutragen, muß der Sichtmodus auf ASCII gestellt werden. Die Zelle, ab der der String einzutragen ist, wird mit der Maus selektiert und der String wie üblich mit Ctrl-V, SHIFT-Insert oder über das Kontextmenü eingefügt. Die abschließende 0 wird mit kopiert! Will man binäre Daten in den Speicher füllen, muß der Sichtmodus auf BYTE gestellt werden. Der String muß dann in der Form "01 dd E5 77 00 FF" als Text geschrieben und dann genauso wie ein ASCII-String in den Speicher kopiert werden.
Watch-Fenster |
JSIM51 bietet leistungsfähige Möglichkeiten, schnell und umfassend nicht nur die Werte beliebiger Variablen anzuzeigen, sondern auch komplexere C-Ausdrücke zu evaluieren. Es gibt verschiedene Methoden, sich die Werte von Variablen und Ausdrücken anzeigen zu lassen:
Tooltip |
Einfach mit dem Mauszeiger im Code-Window über einem Variablennamen stehen bleiben oder mit Doppelklick markieren. Nach kurzer Zeit wird der aktuelle Wert der Variablen angezeigt. Wenn es sich um einen eingebauten C-Datentyp handelt, wird der Wert angezeigt. Ist es ein Pointer, der Speicherbereich auf den der Zeiger zeigt und sein Wert. Ist es ein komplexer Datentyp, beispielsweise eine Struktur, wird die Anfangsadresse und der Speicherbereich in geschweiften Klammern angezeigt. Kann der Wert des Labels, auf dem der Mauszeiger steht, nicht angezeigt werden, erscheint der Tooltip nicht.
Diese Form der Anzeige sucht nach einer lokalen Variablen entsprechenden Namens, d.h. der Variablenname wird automatisch in der Form Modul:Prozedur:Variablenname expandiert. Dazu wird die Stellung des Cursors im Programm ausgewertet. Das bedeutet, gibt es eine globale und lokale Variable gleichen Namens, wird immer die lokale Variable angezeigt. Steht der Cursor außerhalb des Codes (z.B. auf einer Deklaration), wird die erste Variable mit diesem Namen in dem Modul gesucht und angezeigt. Das ist, wenn vorhanden, die globale Variable oder aber eine ganz andere lokale Variable, was aber aus dem Kontext nicht ohne weiteres erkennbar ist.
Tooltip auf markierten Text |
Ist der Ausdruck komplexerer Art (z.B. testval[ x ]), kann man den gewünschten Ausdruck mit der Maus markieren und anschließend den Mauszeiger darauf stellen. Die Anzeige erfolgt wie oben, nur mit dem Unterschied, daß keine Erweiterung der Variablennamen vorgenommen wird.
Temporärer Watchdialog |
Die Darstellung mittels Tooltip erlaubt nur die Anzeige einfacher Datentypen. Will man eine Struktur oder den Inhalt eines Zeigers sehen, öffnet man, ausgehend vom selektierten Ausdruck, einen temporären Watchdialog mit SHIFT-F9. Die Struktur wird in Form eines Strukturbaumes angezeigt. Elemente, die ein vorangestelltes "+" aufweisen, können durch Klick auf das Symbol weiterverfolgt werden. Einfache Datentypen und Zeiger können direkt editert werden. Dazu wird der neue Wert eingegeben und mit "ENTER" bestätigt. Wünscht man die Übernahme in das permanente Watchwindow, klickt man auf "Add to Watchwindow". Der selektierte Ausdruck wird dann direkt übernommen. Das Fenster wird mit Klick auf "OK" geschlossen.
Permanentes Watchwindow |
Die Anzeige erfolgt über das Menü mit "Ansicht-Watch" über Toolbarbutton oder alternativ mittels Kommandozeile: "w.w". Ein neuer Watchausdruck wird entweder über den temporären Watchdialog eingetragen, oder aber direkt in die unterste freie Zeile geschrieben. Leerzeichen sind erlaubt. Ist der Ausdruck gültig, wird er angezeigt, im anderen Fall eine Fehlerausschrift erzeugt. (Ein ungültiger Ausdruck muß nicht zwangsläufig immer ungültig bleiben, z.B Generic Pointer bei Keil-C51.) Ein Ausdruck kann auch editiert werden. Bestätigt wird immer mit "ENTER". Entfernt wird ein Ausdruck durch Löschen des Ausdrucks und "ENTER". Ist unter dem zu löschenden oder geänderten Ausdruck ein Baum aufgeklappt, wird dieser natürlich mit gelöscht. Um gleichnamige lokale und globale Symbole voneinander zu unterscheiden, können Modulname und Prozedurname angegeben werden.
Beispiele für Watchausdrücke:
x | - | die Variable x. Gibt es mehrere Variablen x, dann ist es entweder die global definierte, oder die erste gefundene, lokale Variable x (also unsicher). | |
_UP1:x | - | die im Unterprogramm UP1 definierte, lokale Variable x | |
sisa[4].perfdat->BERcnt[x-1] | - | ein komplexer Ausdruck, der auch mehrere Variablen beinhalten kann | |
{0x0200,x} | - | der Inhalt der Speicherzelle 0x200 im XDATA-Bereich als Byte. (Die Adresse kann nur hexadezimal angegeben werden.) Diese Form ist zwar kein C-Ausdruck, kann aber nicht verwechselt werden und erlaubt so die Anzeige des Inhaltes direkter Speicherzellen. |
Die Darstellung der berechneten Ausdrücke erfolgt analog zum temporären Watchdialog.
Beispiele:
{XD: 0x3450} | - | die Adresse eines komplexen Datentyps (nicht editierbar, aber in der Regel aufzuklappen) | |
{-> D: 0x55} | - | Zeiger auf die Adresse 0x55 im Datenbereich (editierbar und aufzuklappen) | |
0x42 "Beta" | - | Ein Character (signed char). Um die Lesbarkeit eines Strings zu vereinfachen, wird der ab dieser Adresse folgende C-String bis zur nßchsten 0 mit angezeigt. (Nur das erste Zeichen ist editierbar.) | |
0x61 | - | Ein Unsigned Char. (editierbar) | |
0x1234 | - | short (editierbar) |
Über das Kontextmenü, das mit der rechten Maustaste geöffnet wird, läßt sich die Darstellung zwischen dezimal, hexadezimal und binär umstellen. Die Binärdarstellung ist nur für die Typen CHAR und UCHAR möglich. Die aktuelle Einstellung wird im Workspace mit abgespeichert. Ein Update des Fensters erfolgt bei jedem Code-Step und bei Änderungen im Register- oder einem Speicherfenster.
OMF-51 kennt keine PDATA-Variablen. Das bedeutet, eine in PDATA deklarierte Variable erscheint im Watchfenster als in XDATA deklariertes Symbol. Das stimmt nur, solange P2 nicht geändert wird. Um das Problem beim Debuggen zu beheben, kann über das Kontextmenü (linke Spalte, nicht markierte Variable !!) der Speicherbereich zwischen XDATA und PDATA umgeschaltet werden. Ebenso möglich über die Kommandozeile mit: "c.p Symbolname" ändert zu PDATA oder "c.x Symbolname" zu XDATA. Das Watchfenster folgt dann dem Register P2. Diese Einstellung wird nicht im Workspace gespeichert.
Datentypen ohne "OBJECTEXTEND" |
Ist ein Absolutfile nicht mit der Option "OBJECTEXTEND" übersetzt worden, sind keine Typinformationen für Symbole vorhanden. Alle oben beschriebenen komfortablen Möglichkeiten der Betrachtung von komplexen Datentypen stehen damit nicht zur Verfügung. Um das Debuggen dennoch zu erleichtern, kann einem Symbol ein einfacher Datentyp zugewiesen werden. Dazu trägt man zunächst das Symbol in das Watchfenster ein, stellt den Mauszeiger auf das Feld, in dem das Symbol steht und öffnet mit der rechten Maustaste einen Dialog, der es ermöglicht, dem Symbol einen Typ zuzuweisen. Verfügbar sind:
signed char unsigned char signed short unsigned short signed long unsigned long float Pointer auf vorangestellte Datentypen in jeden Speicherbereich
(Nicht unterstützt werden die generischen Keil-C51-Zeiger. Wer die benutzt, kann auch mit OBJEXT übersetzen!) Der Typ kann auch während der Debugsession geändert werden, wird aber NICHT im Workspacefile gespeichert. Diese Möglichkeit der Datentypmanipulation steht nur zur Verfügung, wenn kein erweitertes Objektformat vorhanden ist, d.h. nur wenn alle Module ohne "OBJECTEXTEND" übersetzt wurden!!
Locals |
Eine sehr bequeme Einrichtung wie bei VisualC++. Das Fenster zeigt immer alle lokalen Variablen einer Prozedur an. Das bedeutet, die angezeigten Variablen ändern sich jedesmal, wenn ein Prozedurwechsel aufgrund von Call oder Return erfolgt. Die Anzeige erfolgt über das Menü mit "Ansicht-Locals", Toolbarbutton oder alternativ mittels Kommandozeile: "w.l". In das Fenster können keine weiteren Einträge aufgenommen werden. Ansonsten unterscheiden sich Bedienung und Verhalten nicht von der des permanenten Watchwindow.
Breakpoint-Fenster |
Das Breakpoint-Fenster wird über das Menü "Bearbeiten-Breakpoints" geöffnet. Es zeigt alle aktuell gesetzten Breakpoints an. Ein Breakpoint kann hier gelöscht, deaktiviert oder aktiviert werden. Deaktivierte Breakpoints werden im Code-Window durch einen roten Kreis dargestellt, aktive durch einen roten Punkt. Zugriffsbreakpoints (ab Version 2.00) werden mit einem orangen Quadrat gekennzeichnet. Sie können nur gelöscht und nicht deaktiviert werden.
Speicherkonfigurations-Fenster |
Das Fenster wird über das Menü "Bearbeiten-Speicher Konfiguration" geöffnet. In diesem Fenster kann der Speicher, den das Programm scheinbar zur Verfügung hat, konfiguriert werden. Beliebige Aufteilungen sind möglich, auch die Segmentierung eines Bereiches in mehrere Stücke. Damit ist es möglich, Zugriffe in Bereiche zu erkennen, die in der späteren Applikation nicht verfügbar sind. Die Änderung der Default-Speicherkonfiguration hat allerdings auch Konsequenzen für die Simulationsgeschwindigkeit, die sich dann deutlich verringert.
Analysator-Fenster |
Das Analysatorfenster erlaubt verschiedene Einstellungen zur Laufzeitmessung und Konfiguration des Tracelogs. Mit "Prozessortakt" wird die Taktfrequenz der Zielplattform eingestellt. Die Zeiten im Fenster "reale Programmlaufzeit" sind davon abhängig. Eine Verringerung der Taktfrequenz bewirkt sofort eine Erhöhung der Laufzeit. Die Angabe der Simulationslaufzeit ist mehr informativer Art. Sie wird von den Traceoptionen und dem Rechner, auf dem der Simulator läuft, abhängen. "Autostart" bewirkt, daß bei jedem Step, Step-Over, Run usw. die Zähler neu gestartet werden. Damit ist es möglich, für einzelne C-Anweisungen Programmteile - bis hin zu einzelnen Assemblerbefehlen - die Zeiten zu messen. Ist "Autostart" nicht gesetzt, werden die Zeiten akkumuliert, auch wenn in einzelnen Schritten simuliert wird. Die Zähler können explizit mit "Reset" gelöscht werden. Die Berechnung der Simulationszeit erfolgt nur im Autostartmodus. Wenn man Informationen über minimale, mittlere und maximale Laufzeit eines Programmteils benötigt (etwa bei Schleifen, die über veschiedene Pfade durchlaufen werden), ist es möglich, im Codefenster einen Meßpunkt zu setzen und diesen im Analysator-Fenster freizugeben (Messpunkt EIN). Immer wenn dieser Meßpunkt erreicht wird, wird der Wert des aktuellen Zykluszählers abgespeichert. Der Zähler wird beim Durchlaufen eines jeden, auch deaktivierten (!) Breakpoints auf Null gesetzt. Eine Messung wird also folgendermaßen durchgeführt:
Nach Erreichen des Ausgangsbreakpoints werden die Anzahl der Durchläufe, kürzeste, mittlere und längste Ausführungszeit für das Programmstück angezeigt.
Es gibt zwei verschiedene Arten, Trace zu schreiben:
Die Traceaufzeichnung wird mit "Enable Trace" gestartet. Der Trace wird in die Datei "temptrace.log" geschrieben. Diese Datei speichert den ASM-Trace binär, den Variablen-Trace als ASCII ab, ist also nur bedingt mit einem Editor lesbar. Um den aufgezeichneten Trace anzusehen, benutzt man besser den Trace-Viewer. Dieser wird mit "Ansicht-Trace", einem Toolbar-Button, oder alternativ über die Kommandozeile mit "w.t" geöffnet. Die Länge des Trace ist fest auf 1000 Einträge begrenzt. Danach wird das File wieder von vorn beginnend geschrieben. Das Tracefenster ist nur verfügbar, wenn "Enable Trace" ausgeschaltet ist.
Stimulationsfile |
Neu ab Version 3.01 ist die Möglichkeit, einen sogenannten Stimulationspunkt zu setzen. Das bedeutet, während des Programmlaufs wird bei jedem Passieren des Stimulationspunktes eine Zeile aus einem Skriptfile gelesen und über die Kommandozeile eingegeben. Das Skriptfile ist ein Textfile, das alle zulässigen Kommandozeilenstrings enthalten kann. Mehrere Anweisungen in einer Zeile werden durch Kommata getrennt. Der Sinn dieses Stimulationsfiles ist zum Beispiel, Eingaben über Ports zu simulieren. Ein Beispiel könnte so aussehen:
$m.d 0x20 0x05 , s.d 0x22 0xAA | setzt den Datenspeicher an Adresse 0x20 auf 5 und auf Adresse 0x22 auf 0xAA | |
$m.x 0x1000 0xAB | setzt die XDATA-Zelle 0x1000 auf 0xAB | |
w.m.l 0x9F0 /x | öffnet ein Speicherfenster | |
b.s 0x3400 | setzt einen Breakpoint auf die Adresse 0x3400 |
Bei Erreichen des Fileendes wird wieder von vorn begonnen. Ein Stimulationspunkt kann selbst auch über die Kommandozeile gesetzt werden bzw. vom Toolbar aus. Der Stimulationspunkt, wie auch der Pfad des Stimulationsfiles, werden im Workspace gespeichert.
Interrupt-Fenster |
Der Simulator kann nicht vollständig die periphere Hardware des Prozessors simulieren. Das bedeutet zum Beispiel, daß zwar die Timer seit der DLL-Version 1.011 unterstützt werden, die serielle Schnittstelle aber nur mehr funktional vorhanden ist. (Problem Baudrate ..) Dennoch sollen Aktionen, die durch die Peripherie über Interrupts in Gang gesetzt werden, auch testbar sein. Dazu dient das Interruptfenster. Es erlaubt, die 5 Standardinterrupts des 8051 zu konfigurieren und auszulösen. Für den seriellen Interrupt wird noch zwischen Receive- und Transmit-Interrupt unterschieden. Um einen Interrupt zu erzeugen, muß der freigegeben sein und auch das globale Interruptfreigabeflag muß gesetzt sein. Es ist auch möglich, mehrere Interrupts gleichzeitig auszulösen. Der Simulator sollte die Reihenfolge über die Priorität richtig auflösen. Ein anstehender, aber noch nicht bearbeiteter Interrupt ist daran erkennbar, daß der Interruptbutton unten bleibt. Der Simulator setzt den Interrupt bei Annahme selbständig zurück. (Ausnahme: die Flags RI und TI des seriellen Interrupts, die durch das Programm zu löschen sind.) Das Fenster wird nur aktualisiert, wenn das Programm anhält. Das Ändern der Interrupteinstellungen im Programm wird also erst sichtbar nach dem nächsten Halt!
Stack-Fenster |
Das Stackfenster zeigt die Tiefe der Unterprogrammaufrufe. Mit jedem Call oder auch Interruptannahme wird oben ein Eintrag für die aktuelle Prozedur und ihre Aufrufadresse angefügt. Durch Doppelklick auf einen Listeneintrag kann man die Sicht zu dem jeweiligen Aufrufpunkt schalten. Die Sache wird natürlich problematisch, wenn in irgendeinem Programmteil auf den Stack geschrieben, der Stackzeiger selbst verändert, oder Inhalte gezielt verändert werden. Das Stackfenster versucht zwar, solche einzelnen Stackaktionen mitzuverfolgen, aber es gehört nicht viel dazu, es durcheinanderzubringen. Wer es benutzt, muß wissen, worauf er sich einläßt, wenn er PUSH und POP benutzt. Wenn der Stackpointer überläuft, wird der Programmlauf unterbrochen und eine Warnung ausgegeben. Sollte das tatsächlich beabsichtigt sein, kann natürlich fortgesetzt werden.
Terminal-Fenster |
Da sehr oft Programme zur Kommunikation über die serielle Schnittstelle zu entwickeln sind, habe ich hier eine begrenzte Ausnahme gemacht, was die Hardwaresimulation des Controllers betrifft. Wohl aber nur qualitativ. Auf Zeitbedingungen wurde kein Wert gelegt. Auch die Baudrate hat keine Bedeutung. Die korrekte Initialisierung des Timers wird nicht überprüft. Das Terminalfenster wird über das Menü mit "Ansicht-Terminal" oder mittels Kommandoeingabe "w.c" geöffnet. Das Terminalfenster simuliert einen Receive-Interrupt, wenn ein Zeichen eingegeben wird. Umgekehrt simuliert es einen Transmit-Interrupt, wenn das Register S0BUF geändert wird. Es wird nur eine Übertragung mit 8 Bit Breite simuliert. Das Starten des Terminal-Fensters verlangsamt den Simulationslauf etwas, da es einen eigenen Thread benutzt. Es sollte also nur geöffnet werden, wenn es wirklich benötigt wird. Die Einstellung "lokales Echo" stellt jedes eingegebene Zeichen direkt auf dem Fenster dar. Ist die Option ausgeschaltet, werden eingegebene Zeichen direkt in S0BUF geschrieben und wenn freigegeben (ES=1 in IE0 und REN=1 in S0CON), der Interrupt ausgelöst (RI=1 in S0CON). Will man auch binäre Zeichen darstellen, ist die Option "Binär String" einzuschalten. Der empfangene String hat dann das Format 00 02 09 41 FF ... usw. Auch die Eingabe muß in dieser Form erfolgen. Alle Zeichen, ausgenommen Backspace, die keine hexadezimalen Ziffern darstellen, werden ignoriert.
Es ist nicht möglich, Strings über Copy-Paste an das Terminalfenster zu schicken. Wie hätte man das auch behandeln sollen? Das Terminalfenster müßte ja dann darauf warten, daß der Interrupt für ein Zeichen bearbeitet wurde. Das allerdings entspräche nicht den realen Verhältnissen und man käme leicht in Gefahr, mit dem Simulator etwas "Gut" zu testen.
Details zum Laden von SDC51/ASxxxLink - Projekten |
Im Gegensatz zu anderen Compilern/Linkern erzeugen SDC51/ASxxxLink nicht nur eine einzelne Absolut-Objektdatei, sondern die zum Debuggen notwendigen Informationen sind in mehreren Textdateien verteilt. Die Symboltypinformationen befinden sich in den .cdb - Dateien (je eine für ein C-Quellfile). Die Symboladressen findet man im Mapfile. Der eigentliche Code steht im .ihx-File (ein normales Intel-Hexfile). Um also ein SDC51-Projekt zu debuggen, benötigt man folgende Dateien im gleichen Verzeichnis:
Um das Projekt zu laden, öffnet
man das .ihx-File.
JSIM weiß dann, daß ein SDC51-Projekt geladen werden soll und sucht
selbständig zunächst nach dem Mapfile (.map)
mit dem gleichen Namen wie das Hexfile und dann nach den
CDB-Dateien und C-Quelldateien.
Die Quellen müssen mit der Option --debug
compiliert werden!
Jede Quelldatei muß einzeln übersetzt und anschließend alle gemeinsam
gelinkt werden!
Hier ein Beispiel für ein Batchfile:
..\bin\sdcc -c --debug mod1.c
..\bin\asx8051 -olj aprog.a51
..\bin\sdcc -c --debug main.c
..\bin\sdcc -Wl-mjkC:\sdcc\sdcc51lib main.rel aprog.rel mod1.rel
Es gibt einige Einschränkungen beim Debuggen von SDC51-Projekten:
JFE eignet sich gut, um solche Projekte zu editieren, übersetzen und korrigieren. Dazu muß man lediglich ein eigenes Tool definieren, das die Batchdatei aufruft, den Compilerfilter auf "MS C/C++" setzen und die Ausgabe zu JFE umleiten. Man kann dann direkt per Doppelklick vom Ausgabefenster zu den Fehlern springen. |
Der Autor: Jens Altmann Dresden, Deutschland |
||
Jens.Altmann@t-online.de |
Letzte Änderung: W.W. Heinz, 10. April 2022