intel 8049 (8048, 8748, 8749, 8035, 8039) / MCS-48 Hardware-Debugger & Monitor
Als mir im Jahr 2021 wieder einmal die 8049-Mikrocontroller in die Hand fielen, war schon eine gewisse Neugier vorhanden, ob diesem wirklich archaischen Baustein überhaupt noch irgendwelche Lebenszeichen zu entlocken wären und wie es ist, ihn zu programmieren. Immerhin war die MCS-48 Familie von den späten 1970ern bis zum Ende der 1980er sehr populär.
Es sollte allerdings ein RAM als Programmspeicher eingesetzt werden, um dem umständlichen Hantieren mit (E)EPROMs, Programmier- und Löschgerät (wo sind die eigentlich?) dieses Mal zu entgehen : ein integrierter EPROM-Simulator.
Es wurde nur die Stromversorgung angeschlossen und als einzige On-Chip-Hardware der Oszillator mit einem Quarz und Kondensator bei *RESET=L und EA=H in Betrieb genommen.
Eigentlich wurde erwartet, dass dann nur an den Oszillator-Anschlüssen XTAL1 und XTAL2 des µCs Aktivität zu messen wäre, aber die MCS-48 µC generieren bei EA=H auch im Reset-Zustand Signale an den Anschlüssen *PSEN und ALE und geben kontinuierlich die Adresse 0x000 auf BUS und P2.0 bis P2.3 aus (So steht es auch im "intel 8-Bit Embedded Controllers"-Handbuch von 1990 : "[...]ALE and PSEN (if EA = 1) are active while in Reset." Quelle: "intel 8-Bit Embedded Controllers"-Handbuch (1990); Kap."Single Component MCS48-System" / "2.12 Reset"; S.1-10, was ich offensichtlich überlesen hatte). Leider durchkreuzte das die Idee, das für den Programmspeicher eingesetzte RAM im Reset-Zustand isoliert vom 8049 einfach mit Daten laden zu können.
Bild: *PSEN und ALE bei *RESET=L und EA=H
Die einfachste Möglichkeit für einen weiteren Test war eine "NOP-Schleife" : die CPU findet nur "NOP"-Anweisungen (No OPeration) in ihrem Adressraum vor. Werden diese "NOP"-Befehle durch die CPU ausgeführt, kann das Hochzählen des Befehlszählers (PC/Program Counter), der stets auf die Programmspeicher-Adresse des nächsten auszuführenden Befehls zeigt, an den externen Adress-Signalen beobachtet werden.
Dabei müssen unter anderem der Oszillator, der Programmzähler (PC), die Ausgabe und das Demultiplexen der Adressinformation, das Lesen des Opcodes, der Befehls-Decoder und die interne Zustandsmaschine funktionieren.
Um auch dafür kein EPROM o.ä. programmieren zu müssen und es ja nur um den Opcode für "NOP" (0x00) geht, wurde ein 74HC541 (Octal 3-State Buffer) als festverdrahteter Befehlsspeicher eingesetzt, der während *PSEN=L den Opcode auf die BUS-Leitungen legt.
Statt des von intel vorgeschlagenen "Transparent Latch", 74LS373, für das Zwischenspeichern der unteren 8 Adressbits von BUS wurde, wie auch für das untere Nibble (P2.0 bis P2.3) von P2, ein flankengesteuertes D-FlipFlop mit Tri-State Ausgängen vorgesehen, weil sich so ein störärmerer Adressbus erzielen lässt.
Da die Adressleitungen des RAM während des Ladens mit Daten über das Interface zwischen Personal Computer und der Debugger-/Monitor-Hardware vom 8049 isoliert werden müssen, waren die Tri-State-Ausgänge dieser D-FFs natürlich auch sehr willkommen.
Und weil der bidirektionale Datenbus des RAMs beim Laden mit Daten ebenfalls vom 8049 getrennt werden muss, kam dort ein "Bi-Directional 8-Bit Bus-Transceiver", 74HC245, als Trennstelle auch gleich beim NOP-Schleifen-Test zum Einsatz. *PSEN, *RD, *WR, ALE wurden provisorisch gepuffert, so dass auch bei Unfällen auf diesen Leitungen dem 8049 möglichst kein Schaden drohte.
Bild: Ausführen der NOP-Schleife (D0...D7, P2.0...P2.3, ALE, *PSEN, *RESET)
Bild: Die für den NOP-Test verwendete Schaltung
Um das zukünftig als externen Programmspeicher eingesetzte RAM auch als Datenspeicher, der über die "MOVX @Ri,A"- und
"MOVX A,@Ri"-Befehle und die *WR- und *RD-Signale des µCs beschrieben beziehungsweise gelesen werden
kann, einsetzen zu können, wurden die *PSEN- und *RD-Signale des µCs durch ein UND-Gatter miteinander verknüpft, so dass die *OE-Leitung des RAM immer Low-Pegel annimmt, sobald eines dieser Signale aktiv (Low) wird.
Das *PSEN-Signal des µCs wurde gleichzeitig die 17. Adressleitung, A16, des RAM, so dass sich der externe Programmspeicher-Bereich im RAM von Adresse 0x00000 bis Adresse 0x0FFFF und der externe Datenspeicher-Bereich von Adresse 0x10000 bis Adresse 0x1FFFF (ohne Debugger und ohne memory-mapped-IO) erstreckt.
Der 8049 kann mit den maximal 12 unter seiner Kontrolle stehenden Adressleitungen 4kByte adressieren, die fehlenden Adress-Bits werden durch drei zusätzliche 4Bit-D-FlipFlops in der MMU (Memory Management Unit) des Debugger-/Monitor-Systems bereitgestellt. So ist das Ansprechen von jeweils bis zu 64kByte - unabhängig voneinander für Programm- und Datenspeicherbereich unter Softwarekontrolle auszuwählen - ermöglicht (siehe "3.6.0 DeMon48_128k Speicherorganisation / MMU").
Als Mikrocontroller zur Steuerung des DeMon48_128k-Systems (Debug-µC) und als Interface zum Personal Computer fiel die Wahl auf einen betagten, ebenfalls noch als Restbestände hier vorhandenen PIC16F874. Eine erste Version des Debug-/Monitor-Systems basierte auf serieller Kommunikation zwischen Debug-µC und restlicher Hardware, was aber später zugunsten einer wesentlich höheren Geschwindigkeit des Systems auf parallelen Datenaustausch umgestellt wurde.
Die erste Test-Firmware für den Debug-µC hatte folgende Funktion :
Es ließen sich nach dem Start die gleichen Signale wie beim Test mit dem 74HC541 als "Programmspeicher" auf den Adress- und Datenleitungen des 8049 messen, also wurde jetzt wirklich von unterschiedlichen Adressen des RAMs der korrekte Opcode für "NOP" gelesen.
Dieser Test sollte die Trennung zwischen Programm- und Datenspeicher-Bereichen im RAM nachweisen. Dafür beschreibt das folgende MCS-48 Programm eine 256Byte-Seite im Datenbereich des externen RAM mit dem Komplement der jeweiligen Adresse, also Adresse 0x00 mit 0xFF, Adresse 0x01 mit 0xFE, Adresse 0x02 mit 0xFD bis zur Adresse 0xFF und dem geschriebenen Wert 0x00 :
Wenn diese Trennung nicht funktioniert, überschreibt sich das Programm selbst, was unter anderem durch das Ausbleiben der Low-Impulse auf der *WR-Leitung nachweisbar wäre.
Bild: 256Byte Page Write (D0...D7, P2.0...P2.3, ALE, *PSEN, *RD, *WR)
Bild: 256Byte Page Write (D0...D7, P2.0...P2.3, ALE, *PSEN, *RD, *WR) (Fortsetzung)
Die mit den Bausteinen 74HC74 und 74HC00 realisierte Einzelschritt-Schaltung ist bei H-Pegel am *SET-Eingang des am *SS-Eingang angeschlossenen D-FFs aktiv (siehe Zeichnungssatz). Liegt hier L-Pegel an, wird der Ausgang des D-FFs ständig auf H-Pegel gesetzt. Durch H-Pegel am *SET-Eingang kann der Ausgang des D-FFs durch L-Pegel des invertierten ALE-Signals auf L-Pegel gesetzt werden, wodurch der *SS Eingang aktiviert wird.
Der MCS-48 µC beendet dann die Ausführung des aktuellen Befehls (im Fall von Befehlen, die 2 Befehlszyklen zur Ausführung benötigen, wird das Ende des zweiten Befehlszyklus abgewartet) und signalisiert den aktiven Einzelschritt-Modus durch H-Pegel an ALE. Während dieses Zustands, für den es seitens des MCS-48 µCs keine zeitliche Beschränkung gibt, wird auf BUS und der unteren Hälfte von P2 die Adresse des nächsten auszuführenden Befehls ausgegeben.
Um den nächsten Befehl auszuführen, wird durch eine steigende Flanke am Takteingang des D-FFs der ständig am D-Eingang des D-FFs anliegende H-Pegel in das FF übernommen und erscheint am *SS-Eingang des MCS-48 µCs. Dadurch wird die Einzelschritt-Logik im MCS-48 µC freigegeben, der nächste Befehl wird adressiert und durch den für das Zwischenspeichern der unteren 8-Bit der Adress-Signale entstehenden L-Pegel am ALE-Ausgang des MCS-48 µCs wird das D-FF wieder zurückgesetzt, *SS wird auf L-Pegel gesetzt und das System befindet sich am Ende wieder in der Wartephase, in der an BUS und der unteren Hälfte von P2 die Adresse des nächsten Befehls ausgegeben wird.
Das Verlassen des Einzelschrittmodus kann jederzeit durch L-Pegel am *SET-Eingang des D-FFs erzwungen werden.
Bild: Single-Step Hardware
Bild: Single-Step Aktivierung durch *S=H des D-FFs (letzter Befehl besteht aus 2 ALE-Zyklen)
Bild: Ausführen einzelner Befehle (1&2 Zyklen) durch Impulse am CLK-Eingang des D-FFs
Um den zur Verfügung stehenden, 128kB umfassenden Speicherplatz im statischen RAM des DeMon48_128k-Systems vollständig nutzen zu können, reichen die vom MCS-48 Adresszähler und MB-FF gelieferten 12 Bit Adressinformationen offensichtlich nicht aus (2^12 = 4096dez.).
Daher werden von drei 74HC173 4fach D-FlipFlops unabhängig voneinander 4 Bit für Programmspeicher- und 8 Bit für Datenspeicher-Zugriffe als Adress-Signale bereitgestellt, die das sonst nur für den Betrieb der eher obsoleten 8243-Portexpander (16 zusätzliche I/O-Leitungen in einem großen (600mil) 24pol.-DIL Gehäuse) benötigte "PROG"-Signal nutzen, um den gewünschten Zustand der zusätzlichen Adress-Signale softwaregesteuert herzustellen.
Die 17. Adressleitung A16 wird durch die MMU (Memory Management Unit) in Abhängigkeit der aktuellen Speicherkonfiguration (siehe: "5.0.0 Speicherkonfigurationen") bereitgestellt.
Da die Erzeugung der zusätzlichen Adress-Bits unter voller Kontrolle eines auf dem MCS-48 µC ablaufenden Anwender-Programms erfolgt, können tatsächlich Programme erstellt werden, die bis zu 16 4kByte-Bänke (=64kB) des Programm- und bis zu 256 256Byte-Seiten (=64kB) des Daten-Speicherbereichs nutzen (der Debugger belegt 512 Byte im RAM).
Die Auswahl der aktiven 256Byte-Datenspeicher-Seite erfolgt über das "DBR" (Data Bank Register), das sich aus DBL für die 4 Adressbits A8 bis A11 und DBH für die 4 Adressbits A12 bis A15 zusammensetzt.
Bild: Zugriff auf das DBR via PROG und Wirkung auf A12...A15
Wenn eine 4(8)kByte Memory Map aktiv ist (siehe "5.0.0 Speicherkonfigurationen"), um beispielsweise das "PROG"-Signal für 8243 Portexpander verwenden zu können, kann DBL (A8 ... A11) durch entsprechende Jumperkonfiguration bei jedem ALE-Zyklus automatisch mit dem aktuellen Zustand von P2.0 ... P2.3 geladen werden, so dass über P2.0 ... P2.3 ("OUTL P2,A") und das dann transparente DBL-Register die Auswahl der aktiven 256Byte-Datenspeicher-Seite oder des I/O-Bereichs erfolgt.
Die Auswahl der aktiven 4kByte-Programmspeicher-Bank erfolgt über das "PBR" (Program Bank Register). Bei einem Wechsel der 4kB-Programmspeicherbank können aufgrund der MMU-Hardware 16 Adressbits gleichzeitig beeinflusst werden, so dass zum Beispiel ein unbedingter Sprung von 0x08E7 nach 0x53F0 durchgeführt wird :
Der "SEL MBx"-Befehl zu Beginn der Sequenz ist in anderen Fällen möglicherweise optional, aber die Reihenfolge "MOVD/ORLD" gefolgt von einem Befehl zur Beeinflussung des Befehlszählers ("JMP"/"CALL"/"RET") ist unbedingt einzuhalten !
Bild: Wechsel von der 4kB-Programmspeicherbank 7 zur 4kB-Bank 8
Die MMU unterstützt Unterprogrammaufrufe per "CALL" über 4kB-Bankgrenzen hinweg, allerdings muss die 4kB-Bank, zu der mit "RET" zurückgekehrt werden soll, beispielsweise in einem Register hinterlegt werden :
Soll eine Hardware, die einen oder mehrere 8243-Portexpander nutzt, mit Hilfe des Debuggers getestet werden, kann die Speicherung der zusätzlichen Adressbits A12 bis A15 mit Hilfe des "PROG"-Signals über einen Jumper abgeschaltet werden (siehe "5.0.0 Speicherkonfigurationen"). Das System verhält sich dann beispielsweise wie ein 8049 mit 4kB externem Programm- und 3,75kB bis 4kB (abhängig davon, ob noch ein I/O-Bereich eingeblendet wird) externem Datenspeicher und das "PROG"-Signal ist zur Ansteuerung der 8243-Portexpander frei.
Über die MMU kann erzwungen werden, dass alle Speicherzugriffe des MCS-48 µCs mit A16=Low erfolgen, unabhängig davon, ob es sich um Programm- oder Daten-Speicherzugriffe handelt. Dafür wird über "ORLD Pp,A"-Befehle das Signal DA16 (Data Address 16) auf "Low" gesetzt und etwas normalerweise Unmögliches erreicht : der MCS-48 µC kann seinen eigenen Programm-Speicherbereich unter Softwarekontrolle beschreiben. Das ermöglicht unter anderem das Laden von Programmen durch Software, die auf dem MCS-48 µC selbst läuft.
Natürlich kann DA16 auch durch die Software wieder auf "High" gesetzt werden, so dass bspw. erst ein Programm mit DA16=Low geladen wird und dann während dessen Ausführung durch DA16=High wieder 64kB als Programmspeicher und zusätzlich bis zu 64kB als Datenspeicher zur Verfügung stehen.
Zusätzlich zum PBR und DBR ist an die mit Hilfe des "PROG"-Signals realisierte Schnittstelle zur MMU noch ein 4Bit "Expansion Register" / "EXP" angeschlossen.
Das "DIS_IO"-Bit wird genutzt, um einen eventuell aktiven memory-mapped I/O-Bereich im Adressraum softwaregesteuert aus- (DIS_IO=1) und wieder einblenden (DIS_IO=0) zu können (siehe "3.6.8 Memory-Mapped I/O").
Bit | Belegung |
---|---|
3 | DIS_IO |
2 | Nicht belegt |
1 | Nicht belegt |
0 | Nicht belegt |
DA16 = H, A16 = normal :
MOVD P4,A | Load Data Bank Register Low Nibble (DBL : A8 ... A11) |
MOVD P5,A | Load Data Bank Register High Nibble (DBH : A12 ... A15) |
MOVD P6,A | Load Program Bank Register (PBR : A12 ... A15) |
MOVD P7,A | Load Expansion Register |
DA16 = L, A16 = forced low :
ORLD P4,A | Load Data Bank Register Low Nibble (DBL : A8 ... A11) |
ORLD P5,A | Load Data Bank Register High Nibble (DBH : A12 ... A15) |
ORLD P6,A | Load Program Bank Register (PBR : A12 ... A15) |
ORLD P7,A | Load Expansion Register |
Zugriff auf Programmspeicher :
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MM16 | PBR.3 | PBR.2 | PBR.1 | PBR.0 | PC.11 | PC.10 | PC.9 | PC.8 | PC.7 | PC.6 | PC.5 | PC.4 | PC.3 | PC.2 | PC.1 | PC.0 |
Zugriff auf Datenspeicher und I/O :
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
MM16 | DBH.3 | DBH.2 | DBH.1 | DBH.0 | DBL.3 | DBL.2 | DBL.1 | DBL.0 | Ri.7 | Ri.6 | Ri.5 | Ri.4 | Ri.3 | Ri.2 | Ri.1 | Ri.0 |
Zugriff auf die Programm- und Datenspeicher des Debuggers :
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
"1" | "1" | "1" | "1" | "1" | "1" | "1" | "1" | MM8 | PC.7 | PC.6 | PC.5 | PC.4 | PC.3 | PC.2 | PC.1 | PC.0 |
Ausführen des Befehls in Adresse 0x1FE09 im Debug-Programmspeicher :
A16 | A15 | A14 | A13 | A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
"1" | "1" | "1" | "1" | "1" | "1" | "1" | "1" | MM8 | "0" | "0" | "0" | "0" | "1" | "0" | "0" | "1" |
Signal | Funktion |
---|---|
MM8 | A8 Ausgang der MMU |
MM16 | A16 Ausgang der MMU |
PBR | Program Bank Register |
DBL | Data Bank Register Low Nibble (A8 ... A11) |
DBH | Data Bank Register High Nibble (A12 ... A15) |
PC | Program Counter |
Ri | Inhalt des zur Adressierung in "MOVX"-Befehlen genutzten Indexregisters (R0, R1) |
"1" | Wird vom Debug-µC auf logisch "1" (+5V = H) gehalten |
"0" | Wird vom Debug-µC auf logisch "0" (0V = L) gehalten |
Adressraum der Grundkonfiguration (siehe auch "5.0.0 Speicherkonfigurationen"):
Start (h) | Ende (h) | Bezeichnung |
---|---|---|
00000 | 0FFFF | MCS-48 Programmspeicher |
10000 | 1FCFF | MCS-48 Datenspeicher (extern) |
1FD00 | 1FDFF | Memory Mapped I/O |
1FE00 | 1FEFF | Debug Programmspeicher |
1FF00 | 1FFFF | Debug Datenspeicher |
Mit Hilfe eines "8-Bit Equality Comparator" 74HC688 wird aus den Adress-Signalen A8 bis A16 das Signal IO/*M gewonnen, das das Einblenden von 256 I/O-Adressen in den Adressraum für den externen Datenspeicher erlaubt. Externe I/O-Einheiten können damit wie ein externes RAM über die "MOVX"-Befehle des MCS-48 Befehlssatzes angesprochen werden, so dass 256 * 8 Bit (=2048 Bit) zur Ausgabe und 256 * 8 Bit (=2048 Bit) zur Eingabe adressierbar sind. Das Signal IO/*M führt H-Pegel im I/O-Adressbereich und L-Pegel, wenn das externe RAM angesprochen wird.
Wenn das vom Expansion-Register der MMU bereitgestellte "DIS_IO"-Bit gesetzt oder Jmp2 nicht gesetzt ist, wird der I/O-Bereich deaktiviert und es kann auf den gesamten Programm- und Datenspeicher zugegriffen werden.
Bild: Erzeugung des IO/*M-Signals für memory-mapped-I/O mit DeMon48
Informationen zu den gezeigten Jumpern : siehe "5.0.0 Speicherkonfigurationen".
Das DeMon48-System bietet derzeit die Möglichkeit, einen Hardware-Breakpoint (evtl. kommt später ein Breakpoint-RAM) zu aktivieren, was die Ausführung von Code bei aktiviertem Breakpoint ohne Geschwindigkeitsverlust ermöglicht.
Aufgrund des Multiplexings von DBR und PBR über das *PSEN-Signal ist es notwendig, mit Hilfe zweier RC-Netzwerke das *X_PSEN-Signal zu verzögern und zu verkürzen, so dass der Zeitpunkt an dem der Adressbus abgefragt wird in etwa in die Mitte des ursprünglichen *PSEN-Signals verschoben wird. Andernfalls könnte es zu Glitches kommen, die zur falschen Erkennung des Hardware-Breakpoints führen.
Bild: Hardware-Breakpoint Schaltung
Bild: Verzögertes und verkürztes Gate-Signal für die "74HC688" 8Bit-Komparatoren
Die Erzeugung des 11MHz Taktes für den MCS-48 µC erfolgt über einen separat aufgebauten Quarzoszillator mit daran angeschlossener Umschalteinrichtung, um einen externen Takt (Rechteck, 50% Tastverhältnis, 5Vss) einspeisen zu können. Die Umschalteinrichtung ermöglicht das Umschalten zwischen den beiden Signalquellen, ohne dass es im Umschaltmoment zu unvollständigen Oszillator-Zyklen ("Glitches") am Takteingang des MCS-48 µCs kommt.
Durch den Debug-µC wird während der Ausführung der Monitor-Routinen durch den MCS-48 µC dessen Takteingang automatisch mit dem 11MHz Oszillator und während der Ausführung des Anwender-Programms mit dem externen Takteingang verbunden, wenn das durch den Nutzer über den entsprechenden Schalter in der Umschalteinrichtung ausgewählt ist.
Über diesen Schalter kann alternativ bewirkt werden, dass der Takteingang des MCS-48 µC ständig mit dem 11MHz Oszillator verbunden ist.
Das Timing des Debug-µCs bei der Ausführung des MCS-48 Anwender-Programms im Single-Step-Modus wird an die im GUI eingetragene Frequenz der dann ausgewählten Taktquelle angepasst.
Wenn auf die Möglichkeit einen externen Takt einspeisen zu können verzichtet werden soll, kann der separate Quarzoszillator und die Umschalteinrichtung entfallen und ein 11MHz Quarz mit Kondensator direkt an die Oszillator-Anschlüsse des MCS-48 µCs angeschlossen werden (siehe "2.3.0 Minimalsystem mit externem Programmspeicher").
Bild: Takterzeugung und störungsfreie -umschaltung