8-Bit-Breadboard-Computer auf Basis einer 6502-CPU - 3x16 Zeichen Anzeige mit DOGM163 LCD

Bisherige Artikel dieser Serie - hier könnt ihr nochmal alle Grundlagen nachlesen, falls ihr jetzt erst einsteigt: Die 3-fach 7-Segment-Anzeige bietet nicht gerade viel Platz für Text, gerade mal 3 Zeichen gleichzeitig sind möglich und dann muss gescrollt werden. Und das erhöht nicht gerade die Ablesegeschwindigkeit und den Komfort bei längeren Texten.

Das DOGM163-LCD, das ich hier schon einmal vorgestellt habe und von denen ich noch ein paar habe, bietet da schon mehr: 3 Zeilen zu 16 Zeichen, macht zusammen 48 Zeichen. Das ist schon brauchbarer zur Textausgabe und für einen Breadboard-Computer gar nicht schlecht.

Ich dachte die Ansteuerung würde nicht allzu schwierig und könnte wieder einen 74HC374 benutzen, indem ich das LCD im 4-Bit-Modus (den ich hier schon einmal mit einem ähnlichen Controller umgesetzt habe) anspreche und die anderen 4 freien Leitungen vom Datenbyte für die Taktung des LCD benutze.

Beim Durchlesen des Datenblattes für den verbauten ST7036-Controller sprang mir aber folgendes ins Auge: RS R/W Operation L L Instruction Write operation (MPU writes Instruction code into IR) L H Read Busy Flag (DB7) and address counter (DB0 ~ DB6) H L Data Write operation (MPU writes data into DR) H H Data Read operation (MPU reads data from DR) Das bedeutet: eigentlich sollte ich immer erst das Busy Flag lesen und überprüfen, bevor ich ein Kommando oder Text in das LCD schreibe. Wenn das Busy Flag gesetzt ist, sollte ich ein paar Warterunden schieben, bis der Weg frei ist und erst dann schreiben.

Das bedeutet aber: ich brauche einen bidirektionalen IO-Chip wie den 65C22, den man zwischen lesen und schreiben umschalten kann. Die 74HC374 kennen aber nur eine Richtung.

Außerdem müsste ich vom IO-Port und nicht aus dem RAM lesen. Und momentan ist das RAM so verdrahtet, dass es auf alles zwischen $0000 und $7FFF reagiert. Es würde dem IO-Gerät also in den Weg kommen und IO-Gerät und RAM würden sich quasi streiten, wer in den Datenbus schreiben darf. Das Chip Enable des RAM müsste ich also auch noch durch einen zusätzlichen Chip absichern, z. B. einem Komparator 74HC688. Das würde aber wieder dazu führen, dass ich den ROL-Trick bei der LED-Ausgabe nicht mehr verwenden könnte, sondern dafür einen Speicherplatz im RAM verwenden müsste. Das macht die Sache ein klitzeklein bisschen komplizierter, wäre aber verkraftbar.

Eine andere Lösung, die nicht ganz so sauber ist, wäre, Warteschleifen einzubauen, so dass zwischen den Schreibvorgängen immer genügend lange Pausen gemacht werden. Bei meinen max. 500 Hertz, die ich am Poti bei meiner jetzigen Clock-Modul einstellen kann, sollte es aber noch keine Probleme geben. Aber später, wenn ich einen 1 oder sogar 14 MHz Quartz verwenden sollte, wird der 6502 wohl zu schnell sein und ich muss die Wartezyklen einprogrammieren bzw. anpassen. Aber mit diesem Konzept könnte ich das LCD im write-only-Mode mit einem 374er ansprechen.

Die Verdrahtung für den 4-Bit-Modus des LCD würde dann ungefähr so aussehen: DB* LCD Bemerkung D0 RESET LOW: LCD zurücksetzen D1 RS Select Register, schaltet zwischen Befehl und Textmodus um D2 R/W Schaltet zwischen Lese (für Busy Flag) und Schreib-Modus um D3 E Enable, startet Read bzw. Write D4 DB4 W:Datenbit 0 D5 DB5 W:Datenbit 1 D6 DB6 W:Datenbit 2 D7 DB7 W:Datenbit 3 R:Busy-Flag *Datenbus 6502 Jedes Text-Byte würde mit einem ROL oder AND Befehl in zwei Nibbles geteilt und die Nibbles dann nacheinander an das LCD geschickt.

Nur zu schade, dass sich Datenbit 3 und das Busy-Flag dieselbe Leitung teilen. Hätte man die Datenbits auf DB0 bis DB3 gelegt statt auf DB4 bis DB7, dann könnte ich die DB7-Leitung getrennt behandeln und als read-only an einen extra 74HC374 klemmen, um dieses Bit dann über eine eigenen Geräte-Adresse abzufragen. Das wäre auch sauber gewesen. So allerdings geht für eine saubere Lösung kein Weg um einen bidirektionalen Chip wie den 65C22 herum.

Quick and Dirty ist sauber genug

Nachdem selbst der offizielle Code für das DOGM163 auf die Abfrage des Busy-Flags verzichtet, habe ich mich für die Pausen-Variante entschieden, weil diese mit weniger Aufwand zu realisieren ist. Bei der maximalen Taktfrequenz liegen sowieso immer ein paar Taktzyklen zwischen den einzelnen Aufrufen. Dies sollten beim WDC W65C02 sein:
  • 2 volle Takte: LDA ($A9)
  • 4 volle Takte: STA Wert speichern
Das macht bei einer Taktfrequenz von derzeit max. 500 Hz (mit dem Poti einstellbar) 2 ms pro Takt und min. 6 * 2 = 12 ms Abstand zwischen den Schreibzugriffen.

Laut dem Datenblatt für das DOGM163 braucht es nach dem Reset des LCD und vor dem ersten Befehl eine Pause von 40 ms, nach dem ersten Initialisierungsbefehl braucht es eine Pause von 2 ms und nach jedem weitere Befehl eine Pause von 26.3 µs.

Wobei meine Tests mit dem Display auf dem Arduino abweichende, folgende sichere Werte ergeben haben:
  • Pause nach Reset: 50 ms
  • Pause zwischen Befehlen der Initalisierungs-Sequenz: 2 ms
  • Pause im Betrieb zwischen Befehlen: 50 µs
Das heißt, ich muss nur zu Anfang ein paar Taktzyklen (50 ms) warten, bevor ich mich aufs LCD stürze und der Rest dauert eh lang genug, um die Pause einzuhalten. Da mache ich mir keine Sorgen, denn zu Anfang (Init) werde ich die LED-Ausgabe und die 7-Segment-Ausgabe ausschalten und dann erst das LCD bedienen. Bis dahin sollten die 50 ms lange vergangen sein. Später - sollte ich höhere Taktfrequenzen benutzen - muss ich allerdings die Pause einbauen.

Da ich noch ein paar Geräte-Adressen frei habe ($7004 bis $7007), brauche ich nur noch einen 74HC374 oder 74HC574 für das Latching und den richtigen Assembler-Source-Code, um das LCD zum Laufen zu bringen. Den W65C22 sparen wir uns also noch eine Runde auf.

74HC574 statt 74HC374


Da ich beim DOGM163-LCD alle Anschlüsse auf einer Seite habe, entscheide ich mich diesmal für ein 74HC574 statt des bisher genutzen 74HC374.

Der eine Chip ist so gut wie der Andere, nur hat der 574er alle Eingänge auf einer Seite und alle Ausgänge auf der Anderen. Das macht die Verkabelung einfacher. Ansonsten nehmen sich die beiden ICs nichts. Zur Not geht auch ein 374er mit alternativer Verkabelung, die vielleicht ein bisschen weniger optimal ist.

Vorbereitung des Breadboards

Da das DOGM163-Display die gesamte Höhe des Breadboards benötigt, muss ich die Kabel unter dem LCD herausführen. Darum wird es gesockelt, damit darunter noch ein wenig Platz ist. Außerdem schützen die Sockelleisten die dünne Pins des LCDs.



Die Verkabelung ist analog zu meinen Tests des DOGM163 im 4-Bit-Modus auf dem Arduino, nur dass ich diesmal auch die Reset-Leitung verkabelt habe, um das LCD bei einem CPU-Reset mit zurücksetzen zu können.

Die roten Kabel sind +5V, die schwarzen führen an GND, die grünen an die obersten 4 Datenbits (D4 bis D7) und die violetten an die unteren 4 Datenbits (D0 bis D3, Steuerleitungen, Belegung siehe Tabelle oben). Die 8 Bits kommen aus einem 74HC574, dessen Triggerleitung über das braune Kabel an Y4 des 74HC138, der die IO-Adresse $7000 bis $7007 über seine Y-Leitungen triggern kann.

Die beiden zweipoligen unteren Sockel sind lediglich für die Stromversorgung des Hintergrund-LEDs des Displays da und über 47 Ohm-Widerstände angeschlossen.

Auf die Sockel kommt nach vollzogener Verkabelung das platzsparende Display. So haben wir evtl. rechts davon noch Platz für einen Teil einer späteren Tastatur.



Die Software in Assembler

Das zur Ansteuerung nötige Programm programmiere ich live und in Farbe in folgendem Video:



Wie man sieht, bleibt das Display beim ersten Versuch leider dunkel und die Software funktioniert nicht auf Anhieb. Es wird also ein Debugging nötig sein, in dem ich die Fehlerursachen eine nach der anderen einkreise und eliminiere, bis der Code dann läuft.

Den Quelltext werde ich dann hier veröffentlichen, wenn das Debugging abgeschlossen ist. Einen nicht funktionierenden Quelltext würde nur verwirren. Trotzdem ist das Video obige sehenswert, denn dort wird viel erklärt, zum Beispiel zu den neu benutzten Befehlen:
  • INCLUDE Datei: Hiermit können wir dem 6502-Assembler mitteilen, dass wir einen Teil des Codes in eine andere Datei ausgelagert haben. So können wir den Source-Code organisieren und immer wieder gebrauchte Dateien in Includes auslagern und schnell in mehreren Sources einbinden. Und: wir müssen bei Änderungen nur die eine Include-Datei aktualisieren und der neue Code wird in allen Assembler-Programme aktiv, die die Include-Datei einbinden. Ich entscheide mich für .inc als Dateiendung und update meine Breadboard Assistenz-Software dementsprechend.

  • ORG $FFFA: Desweiteren organisiere ich den Start-Vektor neu. Bisher hatte mein Breadboard Assistent immer an die entsprechende Stelle (am Ende der ROM-Bank) "00 80" eingetragen, damit die 6502 CPU wusste, an welcher Speicheradresse (nämlich $8000) sie das Programm findet, dass sie starten soll. Jetzt aber überlasse ich die Arbeit dem Compiler, der automatisch allen leeren Speicher mit $FF auffüllt bis $FFFA und dann die Sprunkvektoren bis $FFFF schreibt, weil ich sie so definiert habe: ORG $FFFA word $8000 ; NMI bei $FFFA/B word $8000 ; Programmstartadresse bei $FFFC/D word $8000 ; BRK/IRQ bei $FFFE/F Dadurch erstellt der Linker jetzt die kompletten 32 KB für das ROM-Bank-File und ich habe mehr Kontrolle über die Sprungvektoren, möchte ich sie für ein Programm einmal anpassen.

  • STP: STP für Stop ist ein erweiterter Befehl für die W65C02-CPU von WDC. Er bewirkt, dass die CPU keinen Takt mehr entgegennimmt und sich schlafen legt. Das spart Strom und außerdem rauscht die Sniffer-Debug-Anzeige nicht weiter durch, so wie es war, als wir mit einer Endlosschleife die CPU zum "stoppen" gebracht haben. Aufgeweckt werden kann die CPU nur wieder durch einen Reset.

  • ASL: ASL steht für Arithmetic Shift Left und funktioniert ähnlich wie das ROL, das wir schon einmal hatten. Es schiebt alle Bits eins nach links. Doch anstatt das Carry Bit aus dem Status Register in das frei gewordene Bit 0 zu schieben (ROL, und das Bit 7 ins Carry Bit) schiebt es eine 0 hinein (ASL, und das Bit 7 ins Carry Bit).
    Das Gegenstück ist übrigens das LSR (Logical Shift Right), das nach rechts schiebt. Hier verschiebt sich alles um ein Bit nach rechts. Eine 0 wird in das freigewordene Bit 7 eingetragen und das herausgeschobene Bit 0 kommt ins Carry Bit. Bei ROR wird übrigens statt der 0 das Carry-Bit links eingeschoben.

  • SMB und RMB: Dies sind ebenfalls wie das STP WDC-only Befehle. Sie manipulieren direkt ein Bit einer Speicheradresse in der Zero Page (also RAM-Bereich $0000 bis $00FF). Dabei setzt SMB (Set Memory Bit) ein Bit und RMB (Remove Memory Bit) löscht ein Bit. Das Bit, das gemeint ist, gehört mit zum Befehl. Das unterste Bit ist die 0, das oberste die 7. RMB0 $00 setzt also das niederwertigste Bit an Speicheradresse $00 auf 0. Diese Befehle sind nicht nur schnell, sondern auch praktisch, wenn einzelnen Bits zu manipulieren sind. Dann muss man sich nicht mit ORA (OR), EOR (XOR) und AND (AND) und entsprechenden Bitschablone herumschlagen. Außerdem bleibt bei SMB/RMB der Akkumulator aus dem Spiel.
Im nächsten Teil werde ich mich dann an das Debuggen des Codes und die Überprüfung der Schaltung machen, versuchen den oder die Fehler zu finden und das Display hoffentlich zum Laufen bringen.

Dort wird dann auch der fehlerbereinigte Source-Code zu finden sein.