ctr

DeMon48_128k

intel 8049 / MCS-48 Hardware-Debugger & Monitor

Dokumentation - Kapitel 4


4.0.0 Die Monitor-Software : "God Mode" für den 8049-Entwickler

Die DeMon48 Monitor-Software für MCS-48 funktioniert stark vereinfacht folgendermaßen :

Monitor Aufruf
  1. Retten der internen Register (PC, A, T, PSW etc)
  2. P1, P2, F1 sowie Zustand der T0-, T1- und INT-Eingänge retten
  3. Internes RAM retten

Der 8049-µC ist jetzt gestoppt, im Windows-GUI können durch den Anwender Inhalte von PC, A, PSW, interne Registern, internes/externes RAM, Ports etc. geändert werden. Wird durch den Anwender das Ausführen des nächsten, durch den "Program Counter" adressierten Befehls durch Klick auf die entsprechende Schaltfläche ausgelöst, werden die folgenden Schritte ausgeführt :

Monitor-Ausgang
  1. Upload von geänderten Speicherbereichen im externen RAM
  2. Upload von geänderten Speicherbereichen im internen RAM
  3. Upload von geänderten 8049-Registern
  4. Wiederherstellen der 8049-Register
  5. Wiederherstellen des Programmzählers (PC) im 8049
  6. Ausführen des nächsten Befehls (adressiert durch Programmzähler)
  7. Sprung zu : Monitor-Aufruf

(Der Umgang mit Monitor-Aufruf und Monitor-Ausgang wird durch die Firmware möglichst intelligent gehandhabt : Wird bspw. die Ausführung eines Einzelschritts angefordert ohne dass der Monitor aktiv ist, wird zunächst nur der Monitor-Aufruf ausgeführt - erst bei einer erneuten Einzelschritt-Anforderung wird der Einzelschritt dann ausgeführt.)

Durch Zusammenspiel zwischen externem RAM, Debugger-Firmware, und kleinen MCS-48-Programmen, die dynamisch durch den Debug-Mikrocontroller geladen und unter dessen Kontrolle vom 8049 ausgeführt werden, wurde es möglich, eine Monitorsoftware zu entwickeln, die tatsächlich keine On-Chip-Ressource des 8049 blockiert oder nach Verlassen der Monitorsoftware in einem geänderten Zustand zurücklässt.

Der Transport der Inhalte der internen Register nach Aussen kann nur mit "MOVX @Ri,A"-Befehlen durchgeführt werden, aber beim Aufruf des Monitors sind die Inhalte der Indexregister (Ri) R0 in Registerbank 0, R1 in Registerbank 0, R0 in Registerbank 1, R1 in Registerbank 1, des PSW ("Program Status Word") mit dem BS ("Bank Select")-Bit, des PC ("Program Counter") sowie von A ("Accumulator") unbekannt und dürfen vor ihrer Rettung auch nicht verändert werden.

Während der Monitor aktiv ist und Monitor-Routinen durch den 8049 ausgeführt werden, hat der 8049, wenn überhaupt, nur über die unteren 8 Adressleitungen A0 bis A7 Kontrolle. A8 wird über die MMU mit dem "PSEN"-Signal verbunden, so dass maximal 256 Byte als Programmspeicher und 256 Byte als Datenspeicher angesprochen werden können. Die Umschaltung zwischen Programm- und Datenspeicherbereich erfolgt über die Adressleitung A8. Die übrigen Adress-Signale werden vom Debug-µC bereitgestellt.

4.1.0 Monitor-Aufruf

Beim Aufruf der Monitor-Software wird zunächst geprüft, ob sich der µC bereits im "HALT"-Zustand der Single-Step-Hardware befindet und, sollte dies nicht der Fall sein, die Single-Step-Hardware aktiviert und gewartet, bis der 8049 µC alle zum gerade ausgeführten Befehl gehörenden Befehlszyklen abgearbeitet hat.

4.1.1 PC (Program-Counter), PBR, DBR retten

Nun wird der durch den 8049 µC an BUS und dem unteren Nibble (den niederwertigen 4-Bit) von Port2 (P2) ausgebene Stand des Programmzählers (PC) gelesen und gespeichert, der der Adresse des nächsten auszuführenden Befehls im Anwenderprogramm entspricht. Im gleichen Schritt werden die Inhalte des Program Bank Registers (PBR) und Data Bank Registers (DBR) gelesen und gespeichert.

4.1.2 Monitor-Start

  1. Lesen des aktuellen Program-Counter Inhalts (von P2.0...P2.3 und BUS)
  2. Laden und Ausführen (ohne Prüfung auf Hardware-Interrupt) :
    $   04 JMP 009
    $+1 09
    ($ = untere 8 Bit des aktuellen Inhalts des PC (Program-Counter))
  3. Laden :
    09 00/65 NOP / STOP TCNT
    0A 04    JMP 009
    0B 09

    Wenn im GUI die Monitor-Option "STOP TCNT" (Stop Timer / Counter) ausgewählt ist, wird durch die Firmware des Debug-µC an Adresse 09 der entsprechende Opcode (0x65) eingetragen, andernfalls der für "NOP" (0x00).
  4. Adressen 0x09 bis 0x0A ausführen und vor jedem Befehlsschritt den Adressbus auf 0x007 (Interrupt-Vektor für TCINT = Timer-/Counter-Interrupt) oder 0x003 (Interrupt-Vektor für *INT = externer Interrupteingang) prüfen.

4.1.3 Hardware-Interrupts abfangen

Die Firmware des Debuggers fängt Hardware-Interrupts, also solche, die durch einen Timer-/Counter-Überlauf oder durch L-Pegel am *INT-Eingang des 8049 ausgelöst wurden, vor Ausführung jedes auf die erste "JMP 009"-Anweisung (siehe "4.1.2 Monitor-Start") folgenden Befehls des durch den 8049 auszuführenden Teils der Monitor-Software ab, manipuliert den Stack des 8049-Mikrocontrollers (weil der jeweilige Hardware-Interrupt während der Ausführung der Monitor-Software ausgelöst wurde, wurde natürlich auch eine Adresse in der Monitor-Software auf dem Stack abgelegt, die durch die Monitor-Software nach Rettung des PSW/des Stackpointers (s.u.) durch die Adresse des 8049-Anwender-Programms ersetzt wird, zu der nach Beenden der Interrupt-Service-Routine im 8049-Anwender-Programm durch den "RETR"-Befehl zurück gesprungen wird) und die Rücksprungadresse nach Beenden der Monitor-Software in das 8049-Anwender-Programm (also zum Timer-/Counter-Interrupt-Vektor 0x007 oder 0x003 im Fall eines durch L-Pegel am *INT-Eingang ausgelösten Interrupts). Der 8049-Mikrocontroller verhält sich so, als wäre ein entsprechender Interrupt ohne Monitor-Software aufgetreten. Auch beim gleichzeitigen Auslösen eines Timer-/Counter-Interrupts und eines Hardware-Interrupts durch L-Pegel am *INT-Eingang wird zunächst der höher priorisierte, also der durch L-Pegel am *INT-Eingang ausgelöste, Hardware-Interrupt bedient und nach Beenden der zugehörigen ISR per "RETR"-Befehl zum Interrupt-Vektor des noch anstehenden Timer-/Counter-Interrupts gesprungen.

Das Abfangen eines Hardware-Interrupts durch die Monitor-Software wurde realisiert, indem vor der Ausführung eines Befehls der Adressbus auf 0x003 (Interruptvektor für durch L-Pegel am *INT-Eingang ausgelöste Interrupts) und 0x007 (Interruptvektor für Timer-/Counter-Interrupts) geprüft wird.

Wird von den unteren 12 Bit des Adressbus 0x007 (Interruptvektor für Timer-/Counter-Interrupts) oder 0x003 (Interruptvektor für durch L-Pegel am *INT-Eingang ausgelöste Interrupts) als Adresse des nächsten auszuführenden Befehls gelesen, dann wird ab Adresse 0x007 bzw. 0x003 ein "JMP 009" Befehl geladen und ausgeführt, um den PC wieder definiert auf die Start-Adresse des jeweiligen Monitor-Programms zu setzen und eine bequeme Wiederholung desselben zu ermöglichen, schließlich ist nicht bekannt, wie weit das jeweilige Monitor-Programm vor der Interrupt-Anforderung ausgeführt werden konnte und es würde bei den kompakten Monitor-Routinen einen unverhältnismäßigen Mehraufwand bedeuten, darauf zu optimieren :

003 (007) 04 JMP 009
004 (008) 09

(Adressen in Klammern gelten für den Timer-/Counter-Interrupt)

Ausserdem wird ein Flag-Bit gesetzt, um nach erfolger Rettung des PSW (s.u.) den Stack so zu manipulieren, dass beim Beenden der Interrupt-Service-Routine im Anwenderprogramm durch den "RETR"-Befehl an die auf den letzten Befehl des Anwenderprogramms vor dem Wirksamwerden eines Hardware-Interrupts (der ja an einer Adresse der Monitor-Software wirksam und dort abgefangen wurde) folgenden Adresse gesprungen wird.

Beispiel :

  1. Ein den Timer-/Counter-Interrupt auslösendes Ereignis geschieht gleichzeitig mit der Ausführung des Befehls an Adresse 0x030 des Anwender-Programms, Adresse des nächsten Befehls im Anwender-Programm ist 0x031, die Stack-Pointer-Bits S0, S1, S2 im PSW haben den Wert 0x0, zeigen also auf Adressen 0x08 und 0x09 im internen RAM des 8049.
    Der Timer-/Counter-Interrupt wurde noch nicht wirksam, das heisst von der Interruptlogik im 8049 wurde u.a. noch kein Unterprogrammaufruf zum Timer-/Counter-Interruptvektor (0x007) generiert.
  2. Die Monitor-Software wird aufgerufen, als Rücksprungadresse aus der Monitor-Software zurück ins Anwenderprogramm wird 0x031 gespeichert.
    [SP = 0x00; Stack@SP-1 = ?; ReturnAddress = 0x031]
  3. Die ersten Befehle der Monitor-Software werden mit jeweils vorheriger Prüfung des Adress-Busses auf die Adresse des Timer-/Counter-Interruptvektors (0x007) ausgeführt.
  4. Es wird an Adresse 0x00A in der Monitor-Software eine Verzweigung zum Timer-/Counter-Interruptvektor (0x007) festgestellt. Bei der Erzeugung des Timer-/Counter-Interrupts wurde durch die Interruptlogik des 8049 im Stack-Registerpaar 0x08, 0x09 die Rücksprungadresse 0x00A gespeichert und der Stackpointer (Bits S0, S1, S2 im PSW) inkrementiert, so dass er jetzt den Wert 0x01 hat.
    [SP = 0x01; Stack@SP-1 = 0x00A; ReturnAddress = 0x031]
  5. Zur Korrektur wird als Rücksprungadresse aus der Monitor-Software der Timer-/Counter-Interruptvektor (0x007) und auf dem Stack im durch (Stackpointer - 1) adressierten Registerpaar 0x08, 0x09 die oben gespeicherte Monitor-Rücksprungadresse (0x031=Adresse des nächsten Befehls im Anwenderprogramm) eingetragen. Es geht im Grunde darum, den Zustand herzustellen, den ein Unterprogrammaufruf im 8049-Anwender-Programm zur Adresse 0x007 zur Folge hätte. Da dies nur mit Kenntnis des Inhalts der Stackpointer-Bits S0, S1 und S2 im PSW möglich ist, erfolgt die Modifikation des Stack erst nach Rettung des PSW (siehe "4.1.5 A, T, PSW, R0 retten").
    [SP = 0x01; Stack@SP-1 = 0x031; ReturnAddress = 0x007]

Für die Ausführung jedes im Folgenden beschriebenen Codes durch den 8049 gilt, dass vor jedem Befehl eine Überprüfung der unteren 12 Bit des Adressbusses auf einen der Interrupt-Vektoren erfolgt, erkannte IRQs abgefangen werden und gegebenenfalls der Stack sowie die Rücksprungadresse aus der Monitor-Software korrigiert wird.

4.1.4 Pending Bank Switch - "SEL MBx"

Nach den unter "Monitor-Start" beschriebenen Vorgängen wird ermittelt, ob ein Bankwechsel per "SEL MB0" oder "SEL MB1" gewählt, aber noch nicht durch einen unbedingten Sprungbefehl ("JMP"/"CALL") als Bit11 des PC zur Geltung gekommen ist (pending bank switch). Das geschieht automatisch mit Hilfe eines der bereits ausgeführten unbedingten Sprungbefehle in der Monitor-Software. Es steht ein "SEL MB0" an (MB-FlipFlop ist zurückgesetzt), wenn beim erstmaligen Lesen des Adressbusses das Bit11 gesetzt war und nach einem unbedingten Sprungbefehl zurückgesetzt ist und ein "SEL MB1" (MB-FlipFlop ist gesetzt) steht an, wenn beim erstmaligen Lesen des Adressbusses das Bit11 zurückgesetzt war und nach einem unbedingten Sprungbefehl gesetzt ist. Jeglichen Problemen mit Bankswitching etc. wird während der Ausführung der Monitor-Software aus dem Weg gegangen, indem für alle Monitor-Programmteile, die dynamisch vom Debug-Mikrocontroller in das RAM am 8049 geladen werden, wie beschrieben nur die unteren 8 Bit des PC des 8049 genutzt werden.

Die Bankswitch-Erkennung und -Wiederherstellung in der "DeMon48"-Firmware funktioniert auch im Zusammenhang mit Interrupts, während deren Ausführung (also vor der Ausführung des am Ende der Interrupt-Service-Routine stehenden "RETR"-Befehls (Return And Restore PSW)) durch die im 8049 enthaltene Interrupt-Hardware Bit11 des Programmzählers auf "0" gezwungen wird (in der intel-Literatur wird in diesem Zusammenhang dringend von der Verwendung von "SEL MBx"-Befehlen zum Wechsel der Speicherbank in einer Interrupt-Service-Routine abgeraten: es wird kaum so, wie vom Programmersteller gedacht, funktionieren).

Wird ein "pending SEL MBx" erkannt, wird ein entsprechendes Flag-Bit gesetzt, um beim Verlassen der Monitor-Software sowohl die tatsächlich aktive Speicherbank durch Bit11 des Programmzählers (PC) auszuwählen und auch das Vorbereitungs-FlipFlop ("MB-FlipFlop") in den Zustand vor Aufruf der Monitor-Software zu versetzen.

4.1.5 A, T, PSW, R0 retten

Nun werden nacheinander die Inhalte von des Akkumulators (A), des Timer-/Counter-Registers (T) und des Prozessor-Zustands-Wortes (Program Status Word, PSW) gesichert, indem während der Ausführung der an die Adresse 0x09 des Debug-RAM geladenen, dafür genutzten "MOVX @R0,A"-Anweisung durch den Debug-µC die Adressleitungen A0 bis A7 und A9 bis A16 auf dem Wert 0x1FE09 gehalten werden, so dass, unabhängig vom bisher unbekannten Inhalt des R0-(R0'-)Registers, der Akkumulatorinhalt des 8049 an Adresse 0x09 im Datenbereich des Debugger-RAM gespeichert wird. A8 bleibt über die MMU unter Kontrolle des 8049, da sie zur Umschaltung zwischen Programm- und Datenbereich des Debugger-RAM durch den 8049 µC benötigt wird. Auf dieselbe Art wird anschließend der Inhalt von R0 in der Registerbank 0 gesichert und am Ende dieser Routine steht endlich ein Indexregister, dessen Inhalt schadlos zerstört werden kann - beispielsweise für die Verwendung in einer "MOVX @Ri,A"-Anweisung - zur Verfügung.

Laden :

09 90 MOVX @R0,A ;Put A in 0x1FF09
0A 42 MOV A,T
0B 00 NOP
0C 04 JMP 009
0D 09

Akkumulator retten

  1. D0 bis D7, A8, *RAM_OE, *RAM_WE unter Kontrolle von 8049
    A0 bis A7, A9 bis A16 unter Kontrolle von Debugger
    A0 bis A7, A9 bis A16 = 0x1FE09
  2. Adresse 0x1FE09 (MOVX @R0,A) ausführen
    Der Akkumulatorinhalt wird in Adresse 0x1FF09 des Debugger-RAM geschrieben
  3. Wenn vor der Ausführung des Befehls in Adresse 0x1FE09 ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. Adresse 0x1FE09 ausführen
  4. Adresse 0x1FF09 (Akkumulatorinhalt) lesen und als DBG_A sichern

T-Register retten

  1. A0 bis A7, D0 bis D7, *RAM_OE, *RAM_WE unter Kontrolle von 8049
  2. Adressen 0x0A (MOV A,T) bis 0x0C (JMP 009) ausführen :
    Danach steht der Inhalt des T-Registers in A
  3. D0 bis D7, A8, *RAM_OE, *RAM_WE unter Kontrolle von 8049
    A0 bis A7, A9 bis A16 unter Kontrolle von Debugger
    A0 bis A7, A9 bis A16 = 0x1FE09
  4. Adresse 0x09 (MOVX @R0,A) ausführen
    Der Akkumulatorinhalt (=T-Register) wird in Adresse 0x1FF09 des Debug-RAM geschrieben
  5. Adresse 0x1FF09 (T-Register) lesen und als DBG_T sichern
  6. Wenn vor der Ausführung des Befehls in Adresse 0x09 ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. Adresse 0x09 ausführen
    3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren

PSW retten

  1. Opcode für "MOV A,PSW" (0xC7) in Adresse 0x0A schreiben :
    09 90 MOVX @R0,A ;Put A in 0x1FF09
    0A C7 MOV A,PSW
    0B 00 NOP
    0C 04 JMP 009
    0D 09
  2. A0 bis A7, D0 bis D7, *RAM_OE, *RAM_WE unter Kontrolle von 8049
  3. Adressen 0x0A (MOV A,PSW) bis 0x0C (JMP 009) ausführen :
    Danach steht der Inhalt des PSW in A
  4. Wenn vor der Ausführung eines der Befehle in Adressen 0x0A bis 0x0C ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
    3. Adressen 0x09 bis 0x0C erneut ausführen
  5. D0 bis D7, A8, *RAM_OE, *RAM_WE unter Kontrolle von 8049
    A0 bis A7, A9 bis A16 unter Kontrolle von Debugger
    A0 bis A7, A9 bis A16 = 0x1FE09
  6. Adresse 0x09 (MOVX @R0,A) ausführen
    Der Akkumulatorinhalt (=PSW) wird in Adresse 0x1FF09 des Debugger-RAM geschrieben
  7. Adresse 0x1FF09 (PSW) lesen und als DBG_PSW sichern
  8. Wenn vor der Ausführung des Befehls in Adresse 0x09 ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. Adresse 0x09 ausführen
    3. StackPointer in DBG_PSW inkrementieren
    4. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren

R0RB0 (R0 in Registerbank0) retten

  1. Da jetzt der Zustand des PSW, das das Bit zur Auswahl der aktiven Registerbank "RB" enthält, gesichert ist, darf sein Inhalt durch explizite Auswahl der Registerbank 0 zerstört werden.
    Opcode für "SEL RB0" (0xC5) in Adresse 0x0A und Opcode für "MOV A,R0" (0xF8) in Adresse 0x0B schreiben :
    09 90 MOVX @R0,A ;Put A in 0x1FF09
    0A C5 SEL RB0
    0B F8 MOV A,R0
    0C 04 JMP 009
    0D 09
  2. A0 bis A7, D0 bis D7, *RAM_OE, *RAM_WE unter Kontrolle von 8049
  3. Adressen 0x0A (SEL RB0) bis 0x0C (JMP 009) ausführen :
    Danach steht der Inhalt des R0-Registers, Registerbank 0 in A
  4. Wenn vor der Ausführung eines der Befehle in Adressen 0x0A bis 0x0C ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. StackPointer in DBG_PSW inkrementieren
    3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
    4. Adressen 0x09 bis 0x0C erneut ausführen
  5. D0 bis D7, A8, *RAM_OE, *RAM_WE unter Kontrolle von 8049
    A0 bis A7, A9 bis A16 unter Kontrolle von Debugger
    A0 bis A7, A9 bis A16 = 0x1FE09
  6. Adresse 0x09 (MOVX @R0,A) ausführen
    Der Akkumulatorinhalt (=R0, RB0) wird in Adresse 0x1FF09 des Debug-RAM geschrieben
  7. Adresse 0x1FF09 (R0, RB0) lesen und als DBG_R0RB0 sichern
  8. Wenn vor der Ausführung des Befehls in Adresse 0x09 ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. Adresse 0x09 ausführen
    3. StackPointer in DBG_PSW inkrementieren
    4. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren

PC auf 0x009 setzen

  1. Adressen 0x0A bis 0x0C ausführen, um den Programmzähler (PC) des 8049 wieder auf 0x009 zu setzen
  2. Wenn vor der Ausführung eines der Befehle in Adressen 0x0A bis 0x0C ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. StackPointer in DBG_PSW inkrementieren
    3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren

4.1.6 P1, P2, T0, T1, *INT, F1 lesen

Jetzt können der Zustand von P1, P2, F1 sowie der Zustand der T0-, T1- und INT-Eingänge ausgelesen und über den Datenbereich des Debugger-RAM gerettet werden.

Laden :

;(Read Ports And Bits)
09 B8 MOV R0,#00
0A 00
0B 09 IN A,P1
0C 90 MOVX @R0,A ;Put A in 0x1FF00
0D 18 INC R0
0E 0A IN A,P2
0F 90 MOVX @R0,A ;Put A in 0x1FF01
10 18 INC R0
11 27 CLR A
12 97 CLR C      ;Bit 3 = INT
13 86 JNI 16
14 16
15 A7 CPL C
16 F7 RLC A
17 97 CLR C      ;Bit 2 = F1
18 A7 CPL C
19 76 JF1 1C
1A 1C
1B A7 CPL C
1C F7 RLC A
1D 97 CLR C      ;Bit 1 = T1
1E 46 JNT1 21
1F 21
20 A7 CPL C
21 F7 RLC A
22 97 CLR C      ;Bit 0 = T0
23 26 JNT0 26
24 26
25 A7 CPL C
26 F7 RLC A
27 90 MOVX @R0,A ;Put A in 0x1FF02
28 04 JMP 009
29 09

Nach Ausführen der Befehle in den Adressen 0x09 bis 0x28 hat der Programmzähler den Inhalt 0x009 und es kann der Inhalt von P1 aus RAM-Adresse 0x1FF00, der Inhalt von P2 aus RAM-Adresse 0x1FF01 und der Zustand von INT (Bit3), F1 (User Flag 1) (Bit2), T1 (Bit1) und T0 (Bit0) aus RAM-Adresse 0x1FF02 gelesen werden.

Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x28 ein neuer IRQ erkannt wurde, dann :

  1. PC = 0x009
  2. StackPointer in DBG_PSW inkrementieren
  3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
  4. Adressen 0x09 bis 0x28 erneut ausführen

4.1.7 Internes RAM lesen

Anschließend wird der MCS-48 Code zum Kopieren des internen RAMs (Adresse 0x01 bis 0x7F, Adresse 0x00 wurde ja bereits mit R0, RB0 als DBG_R0RB0 gesichert) in das externe Debug-RAM geladen.

Laden :

;(Copy internal RAM locations 01h to 7Fh to external RAM locations 01h to 7Fh)
09 B8 MOV R0,#7F
0A 7F
0B F0 MOV A,@R0
0C 90 MOVX @R0,A
0D E8 DJNZ R0,0B
0E 0B
0F 04 JMP 009
10 09

Nach Ausführen der Befehle in den Adressen 0x09 bis 0x0F hat der Programmzähler den Inhalt 0x009 und die Inhalte des internen RAMs des 8049 von Adresse 0x01 bis Adresse 0x7F können aus den Adressen 0x1FF01 bis 0x1FF7F des Debugger-Datenbereichs gelesen und anschließend gesichert werden.

Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x0F ein neuer IRQ erkannt wurde, dann :

  1. PC = 0x009
  2. StackPointer in DBG_PSW inkrementieren
  3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
  4. Adressen 0x09 bis 0x0F erneut ausführen

4.1.8 8049-Stack modifizieren

Falls während der Ausführung der Monitor-Software bis zu diesem Zeitpunkt ein Hardware-Interrupt (Timer-/Counter-Interrupt oder L-Pegel am *INT-Anschluss) abgefangen wurde, wird diese Routine ausgeführt.

Laden :

;(Modify Stack locations)
09 B8 MOV R0,#xx
0A xx             ;Load R0 with stack-index
0B 23 MOV A,#xx
0C xx             ;new PCL
0D A0 MOV @R0,A
0E 18 INC R0
0F 23 MOV A,#xx
10 xx             ;new PSW/PCH
11 A0 MOV @R0,A
12 B8 MOV R0,#xx
13 xx             ;Load R0 with previously saved value for R0RB0
14 23 MOV A,#xx
15 xx             ;Load A with with previously saved value for A
16 04 JMP 009
17 09

Dazu wird der vorstehende Code geladen und anschließend durch die Monitor-Firmware an Adresse 0x0A die berechnete Adresse (StackIndex = (StackPointer - 1) * 2 + 8) des unteren Bytes der zuletzt vom 8049 beschriebenen Stack-Speicherstellen, an Adresse 0x0C das niederwertige Byte der Rücksprungadresse in das 8049-Anwenderprogramm und an Adresse 0x10 die restlichen 4 Bit der Rücksprungadresse in das 8049-Anwenderprogramm, mit den höherwertigen 4 Bit des durch den Debugger gespeicherten PSW kombiniert, eingetragen. Schließlich werden noch die vom Debug-µC gesicherten Werte für R0RB0 in Adresse 0x13 und für den Akkumulator in Adresse 0x15 geladen, da die Inhalte dieser beiden durch das Programm zerstört werden.

Nach Ausführen der Adressen 0x09 bis 0x16 ist auf dem Stack die korrekte Rücksprungadresse inklusive PSW-Bits abgelegt und der Programmzähler (PC) des 8049 hat den Inhalt 0x009.

Während der Ausführung des Codes findet keine Überprüfung auf einen neuen IRQ statt - der wurde schließlich bereits erkannt und dadurch wurde die Modifikation des Stacks angestoßen.

4.2.0 Monitor-Metazustand

Es ist nun eine Art "Meta-Zustand" erreicht, in dem der Zustand des 8049 nach dem zuletzt ausgeführten Befehl des Anwenderprogramms vollständig bekannt ist und vor dem Fortsetzen des Anwender-Programms über das GUI und den Debug-µC beliebig geändert werden kann.

4.2.1 Internes RAM beschreiben

Laden :

;(Copy external RAM locations 01h to 7Fh to internal RAM locations 01h to 7Fh)
09 B8 MOV R0,#7F
0A 7F
0B 80 MOVX A,@R0
0C A0 MOV @R0,A
0D E8 DJNZ R0,0B
0E 0B
0F 04 JMP 009
10 09

Nach Laden des Programmcodes, Beschreiben der Adressen 0x1FF01 bis 0x1FF7F des Debugger-RAM (Datenbereich) mit den gewünschten Inhalten des internen RAM des 8049 und Ausführen der Befehle in den Adressen 0x09 bis 0x0F hat der Programmzähler (PC) des 8049 den Inhalt 0x009 und das interne RAM des 8049 ist mit den neuen Inhalten von Adresse 0x01 bis 0x7F beschrieben.

Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x0F ein neuer IRQ erkannt wurde, dann :

  1. PC = 0x009
  2. StackPointer in DBG_PSW inkrementieren
  3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
  4. Adressen 0x09 bis 0x0F erneut ausführen

4.2.2 PBR, DBR, P1, P2 und F1 beschreiben

Laden :

;(Load PBR, DBR, A16Mode, Port2, Port1, User Flag F1)
09 23       MOV A,#xx   ;PBR
0A xx
0B 00/3C/8C NOP/MOVD P4,A (A16=normal)/ORLD P4,A (A16=L)
0C 23       MOV A,#xx   ;DBR
0D xx
0E 00/3D/8D NOP/MOVD P5,A (A16=normal)/ORLD P5,A (A16=L)
0F 23       MOV A,#xx   ;P2
10 xx
11 3A       OUTL P2,A
12 23       MOV A,#xx   ;P1
13 xx
14 39       OUTL P1,A
15 A5       CLR F1
16 00/B5    NOP/CPL F1
17 04       JMP 009
18 09

Nach dem Laden des Programmcodes werden vom Debug-µC in Adresse 0x0A der neue Wert für das Program Bank Register (PBR), in Adresse 0x0D der neue Wert für das Data Bank Register, in Adresse 0x10 der neue Wert für P2, in Adresse 0x13 der neue Wert für P1 und für ein zurückgesetztes User-Flag1 (F1) in Adresse 0x16 der 8049-Opcode für "NOP" (0x00) oder für ein gesetztes User-Flag1 (F1) "CPL F1" (0xB5) eingetragen und anschließend die Befehle von Adresse 0x09 bis 0x17 ausgeführt. Danach hat der Programmzähler (PC) des 8049 den Inhalt 0x009 und Port1 (P1), Port2 (P2), Program Bank Register (PBR), Data Bank Register (DBR) und User-Flag1 (F1) haben die neuen Werte übernommen.

Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x17 ein neuer IRQ erkannt wurde, dann :

  1. PC = 0x009
  2. StackPointer in DBG_PSW inkrementieren
  3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
  4. Adressen 0x09 bis 0x17 erneut ausführen

4.3.0 Monitor-Ausgang

4.3.1 Gerettete und geänderte Inhalte zurückschreiben

Das Zurückschreiben der beim Monitor-Aufruf geretteten Inhalte (Register, Flags, Bits) erfolgt in der umgekehrten Reihenfolge der Rettung. PBR, DBR, P1, P2, F1 und internes RAM werden nur beschrieben, wenn sie zwischenzeitlich durch den Anwender im GUI geändert wurden (siehe oben : "4.2.1 Internes RAM beschreiben" und "4.2.2 PBR, DBR, P1, P2 und F1 beschreiben") oder der Stackinhalt durch die Debugger-Firmware im Debugger-Datenbereich manipuliert werden musste. R0, PSW, A, PC, aktive Speicherbank und der Zustand des MB-Flipflops sind durch die Ausführung der Monitor-Programme durch den 8049 µC stets als "zerstört" anzusehen, so dass sie, im Fall einer Änderung durch den Nutzer im GUI gemeinsam mit dem T-Register, beim Verlassen der Monitor-Software zurück in den 8049 µC geschrieben werden.

4.3.2 R0, PSW, T und A wiederherstellen

Laden :

;(Restore R0, PSW, T, A)
09 B8    MOV R0,#xx   ;R0, RB0
0A xx
0B 23    MOV A,#xx    ;PSW
0C xx
0D D7    MOV PSW,A
0E 23    MOV A,#xx    ;T
0F xx
10 00/62 NOP / MOV T,A
11 23    MOV A,#xx    ;A
12 xx
13 04    JMP 009
14 09

Nachdem der vorstehende Code und der Inhalt des Registers R0 in Registerbank 0 (R0, RB0) aus DBG_R0RB0 in Adresse 0x0A, der Inhalt des Prozessor-Status-Wortes (PSW) aus DBG_PSW in Adresse 0x0C, der Inhalt des Timer-/Counter-Registers (T) aus DBG_T in Adresse 0x0F und der Inhalt des Akkumulators (A) aus DBG_A in Adresse 0x12 des Programmspeicherbereichs im Debugger-RAM geladen und die Befehle in Adresse 0x09 bis 0x13 ausgeführt wurden, sind R0, PSW, T und A mit den im Debug-µC gespeicherten Werten beschrieben und der Programmzähler (PC) des 8049 hat den Inhalt 0x009. Durch Einfügen des 8049-Opcodes für "NOP" (0x00) an Adresse 0x10 kann das Beschreiben des T-Registers durch den Debug-µC unterdrückt werden, wenn keine Änderung des Inhalts des T-Registers durch den Nutzer im GUI vorliegt.

Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x13 ein neuer IRQ erkannt wurde, dann :

  1. PC = 0x009
  2. StackPointer in DBG_PSW inkrementieren
  3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
  4. Adressen 0x09 bis 0x13 erneut ausführen

4.3.3 8049-Stack modifizieren

Siehe "4.1.8 8049-Stack modifizieren".

4.3.4 PC, Timer-/Counter-Konfiguration und MB-FF wiederherstellen

Laden :

;(Restore active MB, TC-Config, PC, pending MB)
09 xx NOP / SEL MBx (restore active memory bank)
0A xx NOP / STRT T / STRT CNT
0B xx JMP ReturnAddress-1 (PC=PC+1 by NOP/SEL MBx in 0D)
0C xx
0D xx NOP / SEL MBx (restore pending SEL MBx)
  1. Wiederherstellung von Bit11 des PC (aktive Speicherbank) vorbereiten :
    Wenn beim Monitor-Aufruf ein "pending SEL MB0"-Befehl festgestellt (Bit11 des PC war beim Monitor-Aufruf gesetzt (Speicherbank 1 aktiv) und nach Ausführung eines unbedingten Sprungbefehls in der Monitor-Software zurückgesetzt) wurde, dann wird an Adresse 0x09 ein "SEL MB1"-Befehl (Opcode 0xF5) zur Festlegung der aktiven Speicherbank für das Anwender-Programm eingetragen.
    Wenn beim Monitor-Aufruf ein "pending SEL MB1"-Befehl festgestellt (Bit11 des PC war beim Monitor-Aufruf zurückgesetzt (Speicherbank 0 aktiv) und nach Ausführung eines unbedingten Sprungbefehls in der Monitor-Software gesetzt) wurde, dann wird an Adresse 0x09 ein "SEL MB0"-Befehl (Opcode 0xE5) zur Festlegung der aktiven Speicherbank für das Anwender-Programm eingetragen.
    Wurde weder ein "pending SEL MB0" noch ein "pending SEL MB1" beim Aufruf der Monitor-Software festgestellt, dann wird an Adresse 0x09 ein "NOP"-Befehl (Opcode 0x00) eingetragen.
  2. Wiederherstellung der Timer-/Counter-Konfiguration vorbereiten :
    Adresse 0x0A erhält je nach Auswahl der Monitor-Optionen im GUI den Opcode für "NOP" (0x00), "STRT T" (Opcode 0x55 : Timer/Counter als Zeitgeber starten) oder "STRT CNT" (Opcode 0x45 : Timer/Counter als Zähler starten).
  3. Wiederherstellung von Bit0 bis Bit10 des PC vorbereiten :
    Adressen 0x0B und 0x0C werden mit einem unbedingten Sprungbefehl ("JMP") auf die um 1 verminderte Monitor-Rückspungadresse geladen, um Bit0 bis Bit10 des Programmzählers (PC) damit wiederherzustellen. Die dort eingetragene Zieladresse muss wegen der Ausführung des in Adresse 0x0D zu hinterlegenden Befehls um 1 kleiner als die tatsächliche Zieladresse im Anwender-Programm sein (s.u. "Zustand des MB-FlipFlops wiederherstellen", "PC Wiederherstellung abschließen") !
  4. Wiederherstellung des MB-FlipFlops vorbereiten :
    Wenn beim Monitor-Aufruf ein "pending SEL MB0"-Befehl festgestellt (Bit11 des PC war beim Monitor-Aufruf gesetzt (Speicherbank 1 aktiv) und nach Ausführung eines unbedingten Sprungbefehls in der Monitor-Software zurückgesetzt) wurde, dann wird an Adresse 0x0D ein "SEL MB0"-Befehl (Opcode 0xE5) eingetragen, bei dessen Ausführung das MB-FlipFlop zurückgesetzt wird.
    Wenn beim Monitor-Aufruf ein "pending SEL MB1"-Befehl festgestellt (Bit11 des PC war beim Monitor-Aufruf zurückgesetzt (Speicherbank 0 aktiv) und nach Ausführung eines unbedingten Sprungbefehls in der Monitor-Software gesetzt) wurde, dann wird an Adresse 0x0D ein "SEL MB1"-Befehl (Opcode 0xF5) eingetragen, bei dessen Ausführung das MB-FlipFlop gesetzt wird.
    Wurde weder ein "pending SEL MB0" noch ein "pending SEL MB1" beim Aufruf der Monitor-Software festgestellt, dann wird an Adresse 0x0D ein "NOP"-Befehl (Opcode 0x00) eingetragen.

Nachdem damit die Vorbereitungen abgeschlossen sind, kann mit der Ausführung des geladenen Programms begonnen werden. Der 8049 hat dabei die Kontrolle über A0 bis A7, D0 bis D7, *RAM_OE, *RAM_WE. A8 bis A16 werden vom Debug-µC auf 0x1FE00 gehalten.

  1. Bit0 bis Bit11 des PC , Timer-/Counter-Konfiguration wiederherstellen :
    Durch Ausführen des Programms von Adresse 0x09 bis zum Befehl in Adresse 0x0B werden Bit11 des PC (aktive Speicherbank), die Timer-/Counter-Konfiguration und Bit0 bis Bit10 des PC wiederhergestellt.
  2. Wenn vor der Ausführung eines der Befehle in den Adressen 0x09 bis 0x0B ein neuer IRQ erkannt wurde, dann :
    1. PC = 0x009
    2. StackPointer in DBG_PSW inkrementieren
    3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
    4. "R0, PSW, T und A wiederherstellen" ausführen (siehe 4.3.2)
    5. "8049-Stack modifizieren" ausführen (siehe 4.1.8)
    6. Adressen 0x0009 bis 0x000B erneut ausführen
  3. Zustand des MB-FlipFlops wiederherstellen :
    Da nun keine unbedingten Sprungbefehle in dem durch den 8049 auszuführenden Teil der Monitor-Software vorkommen, kann jetzt der Zustand des MB-FlipFlops wiederhergestellt werden (andernfalls käme ja durch einen unbedingten Sprung ("JMP"/"CALL") das MB-FlipFlop als Bit11 des PC schon zur Wirkung), wenn beim Monitor-Aufruf ein "pending SEL MBx" erkannt wurde. Zur Ausführung des Befehls an Adresse 0x0D wird der Adress-Bus des 8049 vom als Befehlsspeicher dienenden RAM getrennt (Bit0 bis Bit10 des PC des 8049 zeigen bereits auf die um 1 verminderte Monitor-Rückspungadresse) und der Debug-µC als Quelle aller Adress-Signale genutzt. Jetzt wird der Befehl an Adresse 0x0D (physisch 0x1FE0D) ausgeführt.
  4. Beim Ausführen des Befehls an Adresse 0x0D wird nicht auf externen Interrupt (L-Pegel an *INT) reagiert, wenn ReturnAddress-1 = 0x003, und es wird nicht auf einen Timer-/Counter-Interrupt reagiert, wenn ReturnAddress-1 = 0x007 (*). Dies ist der einzige Zeitpunkt während der Ausführung der Monitor-Software, in der die IRQ-Erkennung scheinbar(!) nur eingeschränkt funktioniert (siehe (*)). Wurde ein zu unterdrückender Interrupt erkannt, wird das entsprechende Status-Bit im Debug-µC durch dessen Firmware wieder gelöscht, um einen eventuellen SingleStep-Befehl vom steuernden PC nicht zu blockieren.
    Wenn vor der Ausführung des Befehls in Adresse 0x0D ein neuer IRQ erkannt wurde :
    1. PC = 0x009
    2. StackPointer in DBG_PSW inkrementieren
    3. Wenn der neu erkannte IRQ ein Timer-/Counter-Interrupt ist, dann DBG_T inkrementieren
    4. "R0, PSW, T und A wiederherstellen" ausführen (siehe 4.3.2)
    5. "8049-Stack modifizieren" ausführen (siehe 4.1.8)
    6. Adresse 0x09 bis 0x0B erneut ausführen
    7. Adresse 0x0D erneut ausführen
  5. Der PC des 8049 hat durch Ausführung des Befehls in Adresse 0x0D den richtigen Wert (nämlich die Adresse des nächsten Befehls im 8049-Anwenderprogramm) angenommen.

(*) : Wenn ReturnAddress-1 = 0x003, dann ist die Adresse des nächsten auszuführenden Befehls im Anwenderprogramm 0x004 - das zweite Byte einer ISR für externe Interrupts (L-Pegel an *INT). Es ist höchst unwahrscheinlich, dass an dieser Stelle ein externer Interrupt auch bei minimalistischster ISR ausgelöst werden soll (ausgenommen, die ISR besteht nur aus einem "RETR" - aber dann ergibt der ganze Interrupt keinen Sinn (s.u.)) :

;ISR für externen Interrupt (L-Pegel an *INT)
003 xx Irgendein Einzelbyte-Befehl
004 93 RETR (darauf zeigt der PC des 8049 als nächsten auszuführenden Befehl)

Versagen würde die Erkennung externer Interrupts (L-Pegel an *INT) nur bei folgender Konstruktion und ReturnAddress=0x004 - und sogar in diesem Fall nur dann, wenn zwischen der Ausführung des "JMP ReturnAddress-1" und des letzten "SEL MBx"-Befehls in der Monitor-Software eine externe Interrupt-Anforderung erzeugt wird und nur sehr kurz anliegt :

;ISR für externen Interrupt (L-Pegel an *INT)
003 93 RETR      ;die denkbar sinnloseste ISR
;Code ausserhalb der "ISR"
004 xx
[...]

Analog dazu beim Timer-/Counter-Interrupt :

;ISR für Timer-/Counter-Interrupt
007 xx Irgendein Einzelbyte-Befehl
008 93 RETR (darauf zeigt der PC des 8049 als nächsten auszuführenden Befehl)

Wenn ReturnAddress-1 = 0x007, dann ist die Adresse des nächsten auszuführenden Befehls im Anwenderprogramm 0x008 - das zweite Byte einer ISR für Timer-/Counter-Interrupts. Es ist höchst unwahrscheinlich, dass an dieser Stelle ein Timer-/Counter-Interrupt auch bei minimalistischster ISR ausgelöst werden soll (ausgenommen, die ISR besteht nur aus einem "RETR" - aber dann ergibt der ganze Interrupt keinen Sinn (s.u.)) :

Versagen würde die Erkennung von Timer-/Counter-Interrupts nur bei folgender Konstruktion und ReturnAddress=0x008 - und sogar in diesem Fall nur dann, wenn zwischen der Ausführung des "JMP ReturnAddress-1" und des letzten "SEL MBx"-Befehls in der Monitor-Software eine Timer-/Counter-Interrupt-Anforderung erzeugt wird und nur sehr kurz anliegt :

;ISR für externen Interrupt (L-Pegel an *INT)
007 93 RETR      ;die denkbar sinnloseste ISR
;Code ausserhalb der "ISR"
008 xx
[...]