8-Bit-Breadboard-Computer auf Basis einer 6502-CPU - 4x4 Keypad als Tastatur mit 65C22 abfragen

Bisherige Artikel dieser Serie - hier könnt ihr nochmal alle Grundlagen nachlesen, falls ihr jetzt erst einsteigt: Es wird endlich Zeit für ein Eingabegerät für unseren Breadboard-Computer. Ich habe mir dazu ein 4x4-Keypad ausgesucht, weil dieses über 8 Anschlüsse gesteuert wird und der 6502 Datenbus ebenso breit ist.

Das Keypad habe ich bereits mit einem STM32 ausgetestet. Das Funktionsprinzip ist ganz einfach: Es gibt vier horizontale Leitungen und 4 vertikale Leitungen, die einmal waagerecht und einmal senkrecht unter den Tasten durchführen. Beim Drücken auf eine Taste wird an dem entsprechenden Punkt wird nun die dort durchlaufenden waagerechte mit der dort durchlaufenden senkrechten Leitung verbunden.



Zur Abfrage wird durch die ersten vier Leitungen (A bis D) nacheinander Strom geschickt und dann geschaut, ob und wo bei 1 bis 4 dieser wieder anliegt. Das Hineinschicken des Stromimpulses ist kein Problem, das kann man ganz einfach mit ein bisschen Bitbanging der unteren 4 Bits machen. Auch das Auslesen sollte kein Problem sein, wenn man während einer Schleife auf eine Taste wartet und abfragt, ob eines der oberen 4 Bits auf High gegangen ist.

Das Problem bei der Sache ist allerdings, dass ein Tastendruck den Datenbus kurzschließt. Wenn ich eine Taste drücke, wird das eine (untere) Bit auf ein anderes (oberes) Bit kopiert. Das ist natürlich fatal, wenn man was aus ROM oder RAM lesen will und der Datenbus ist auf einmal durcheinander. Man müsste deswegen strikte Disziplin walten lassen und wirklich immer nur etwas eingeben, wenn das Programm dazu auffordert, und in der Länge, dass das Programm vorgibt. Soll man etwa zwei Ziffern eingeben und gibt aus Versehen drei ein, dann befindet sich das Programm eventuell schon wieder im Verarbeitsmodus und der Tastendruck schießt dazwischen, ändert was am Datenbus und die Ergebnisse sind falsch oder das Programm stürzt ab.

Mit den verwendeten 74HC374 bzw. 74HC574 Latches kommen wir also diesmal nicht weit. Wir brauchen etwas, dass den Tastendruck entgegennimmt, ihn speichert und erst auf Befehl kurz den Datenbus auf die Eingabe setzt und danach den Datenbus wieder "frei" lässt.

Der 65C22 Versatile Interface Adapter (VIA)

Genau das leistet der 65C22 VIA, ein Chip, der die selben imposanten Ausmaße hat wie der W65C02 selbst, nämlich 40 Pins, dessen Funktion ich hier kurz erläutern will:

65C22 Register Befehle

Register
Number
RS Coding Register
Designation
Description
RS3 RS2 RS1 RS0 Write Read
0 0 0 0 0 ORB/IRBOutput Register "B" Input Register "B"
1 0 0 0 1 ORA/IRAOutput Register "A" Input Register "A"
2 0 0 1 0 DDRB Data Direction Register "B"
3 0 0 1 1 DDRA Data Direction Register "A"
4 0 1 0 0 T1C-LT1 Low-Order Latches T1 Low-Order Counter
5 0 1 0 1 T1C-H T1 High-Order Counter
6 0 1 1 0 T1L-L T1 Low-Order Latches
7 0 1 1 1 T1L-H T1 High-Order Latches
8 1 0 0 0 T2C-L T2 Low-Order Latches T2 Low-Order Counter
9 1 0 0 1 T2C-H T2 High-Order Counter
A 1 0 1 0 SR Shift Register
B 1 0 1 1 ACR Auxiliary Control Register
C 1 1 0 0 PCR Peripheral Control Register
D 1 1 0 1 IFR Interrupt Flag Register
E 1 1 1 0 IER Interrupt Enable Register
F 1 1 1 1 ORA/IRA Same as Reg 1 except no "Handshake"

Ansprache des 6522

Um unsere KeyPad-Tastatur auszulesen , soll durch A...D (Datenbits 4...7) Strom geschickt werden und dann in 1...4 (Datenbits 0...3) geschaut werden, ob der Strom dort wieder auftaucht. D4...D7 werden also als Output-Leitungen und D0...D3 als Input-Leitungen definiert. Mit diesem Mix brauchen wir nur einen Datenport (A) und haben Datenport B für ein weiteres Gerät frei: D0 IN 1 D1 IN 2 D2 IN 3 D3 IN 4 D4 OUT A D4 OUT B D4 OUT C D4 OUT D

Anpassung der Adressierungsarchitektur

Doch zuerst müssen wir ein Problem lösen: Bisher hört unser RAM auf alles zwischen $0000 und $7FFF. Das hat nichts bei den bisherigen Nur-Schreibbefehlen ausgemacht. Sollte der in ein IO-Output-Gerät geschriebene Wert doch ruhig auch im RAM stehen. Doch beim Lesen würde sich das RAM und ein IO-Input-Gerät ins Gehege kommen. Wer soll jetzt liefern? Das RAM oder das Eingabegerät? Was ankommt, wäre ein Glücksspiel. Wir müssen dem RAM also beibringen, nicht zu reagieren, wenn der Adressbereich ab $7000 angesprochen wird. Das RAM soll sich einfach raushalten und keine Antwort geben. So kommt die Antwort des Eingabegerätes ungehindert durch.

Um die Adressierungs-Architektur möglichst einfach zu halten - meine Erfahrung mit zu vielen kaskadierten Logikgattern habe ich ja bereits gemacht - habe ich mir überlegt, ob mir nicht auch nur 16 KB RAM ausreichen würde. Mit dem Gedanken, dass wohl anzuzeigende, vorgefertigte Texte den meisten Speicherplatz verbrauchen werden und die im ROM liegen, kann ich mir momentan gar nicht vorstellen, was ich alles in 16 KB RAM unterkriegen sollte. Da stehen doch eigentlich nur veränderliche Werte drin. Ich habe darum beschlossen, folgende, simplere Adressierung zu verwenden:

A15 ($8000+)A14 ($4000+)A13 ($2000+)A12 ($1000+)SpeicherbereichVerwendung
H***$8000...$FFFFROM (EPROM/Flash)
LL**$0000...$3FFFRAM (Variablen)
LHLL$4000...IO Output (LED, 7Seg, LCD, ...)
LHLH$5000...IO Output (noch frei)
LHHL$6000...IO Input 6522 #1 (Keypad)
LHHH$7000...IO Input z. B. 6522 #2 (noch frei)
LHHH + {A11...A0}$7800...Coprozessor (Arduino oder STM32), der den Adressbus selbst auf bestimmte IO-Adressen überwacht und dann entsprechend nach Funktion reagiert (RND, RTC, etc.)


Das sollte für den Breadboard Computer an Adressen eigentlich ausreichen. Und wenn nicht, muss ich später auch noch die Adressleitung A11 mit einbeziehen.

Natürlich muss ich damit auch die Chip Select Leitungen für RAM, 74HC138 und den neuen 6522 anpassen: neuer 74HC688 (rechts neben 7-Segment-Anzeige): RAM Chip Select: bis $3FFF Paar Signal P / Q Bemerkung 0. A15 H/L L \ Adressen ab $0000 bis $3FFF 1. A14 H/L L / lesen und schreiben 2 L L unbenutzt 3 L L unbenutzt 4 L L unbenutzt 5 L L unbenutzt 6. L L unbenutzt 7. L L unbenutzt G L G wird fest auf Low gesetzt 74HC688 (Breadboard oben rechts) Änderung für Output-Geräte: ab $4000 Paar Signal P / Q Bemerkung 0. PHI2 H/L H Der Adressbus ist valide, wenn PHI2 High ist 1. R/W H/L L Der Datenbus ist valide, wenn R/W Low (=write) ist 2. A15 H/L L nur für den Bereich $0*** bis $7*** nicht $8**** bis $F*** (ROM) 3. A14 H/L H \ 4. A13 H/L L } Adressen ab $4000 5. A12 H/L L / 6. L L unbenutzt 7. L L unbenutzt G L G wird fest auf Low gesetzt neuer 74HC688 (rechts neben 6522 Breadboard unten rechts): Input-Geräte: ab $6000 Paar Signal P / Q Bemerkung 0. A15 H/L L nur für den Bereich $0*** bis $7*** nicht $8**** bis $F*** (ROM) 1. A14 H/L H \ 2. A13 H/L H } Adressen ab $6000 3. A12 H/L L / 4 L L unbenutzt 5 L L unbenutzt 6. L L unbenutzt 7. L L unbenutzt G L G wird fest auf Low gesetzt Unsere Speicheraufteilung bekommt damit auch neue Grenzen. Ich habe die sonstigen fest verwendeten Adressen auch gleich mit eingetragen: Adr.(hex) Beschreibung 0000 RAM Zero-Page: schneller Speicherzugriff mit zp-OpCodes, Muss für BIT-Befehle 0 lcdhb: High Byte/Nibble für LCD-Ansteuerung 1 lcdlb: Low Byte/Nibble für LCD-Ansteuerung 2 lcdrs: RS-Status für LCD: 1=char, 0=cmd; (vor jsr lcdByte setzen) ... 00FF 0100 RAM Stack: Hinterlegung von Rücksprungadressen ... 01FF 0200 RAM für in mehreren Programmen verwendete Variablen ... 1000 RAM für programmbezogene Variablen ... 3FFF 4000 Hardware IO Output #1 0 LED Ausgabe 1 linke 7-Segment-Anzeige 2 mittlere 7-Segment-Anzeige 3 rechte 7-Segment-Anzeige 4 DOGM163 LCD mit 3*16 Zeichen 5 frei für Output IO-Gerät, Anschluss an 74HC138 Breadboard oben rechts 6 frei für Output IO-Gerät, Anschluss an 74HC138 Breadboard oben rechts 7 frei für Output IO-Gerät, Anschluss an 74HC138 Breadboard oben rechts 8 frei für Output IO-Gerät, Anschluss an 74HC138 Breadboard oben rechts 5000 Hardware IO Output #2 (zur zukünftigen Verwendung) 6000 Hardware IO Input #1 0 Write: Output Register "B" Read: Input Register "B" 1 Write: Output Register "A" 4x4 KeyPad Read: Input Register "A" 4x4 KeyPad 2 Data Direction Register "B" 3 Data Direction Register "A" 4x4 KeyPad 4 Write: T1 Low-Order Latches Read: T1 Low-Order Counter 5 T1 High-Order Counter 6 T1 Low-Order Latches 7 T1 High-Order Latches 8 Write: T2 Low-Order Latches Read: T2 Low-Order Counter 9 T2 High-Order Counter A Shift Register B Auxiliary Control Register C Peripheral Control Register D Interrupt Flag Register E Interrupt Enable Register F Same as Reg 1 except no "Handshake" 4x4 KeyPad 7000 Hardware IO Input #2 (zur zukünftigen Verwendung) 7800 Coprozessor (zur zukünftigen Verwendung) 8000 ROM (per DIP-Switch eingeblendete Programmbank von 0 bis 15) ... FFFA ROM Sprungvektor für NMI Low-Byte FFFB ROM Sprungvektor für NMI High-Byte FFFC ROM Sprungvektor für RES Low-Byte = Programmstartadresse FFFD ROM Sprungvektor für RES High-Byte = Programmstartadresse FFFE ROM Sprungvektor für BRK/IRQ Low-Byte FFFF ROM Sprungvektor für BRK/IRQ High-Byte

Anpassungen der Programme und Test

Da die IO-Geräte nun hardwaretechnisch neue Adressen haben, müssen natürlich auch die Programme daran angepasst werden. Ich habe das so gelöst, dass ich eine Include-Datei erstellt habe: io-defs.inc ; IO-Geräte-Adressen ; last edit: Oliver Kuhlemann (www.cool-web.de), 2020-08-31 ; --- Konstanten: IO-Adressen --- LED: equ $4000 ; 8-LED-Ausgabe SEGL: equ $4001 ; linke 7-Segment-Anzeige SEGM: equ $4002 ; mittlere 7-Segment-Anzeige SEGR: equ $4003 ; rechte 7-Segment-Anzeige LCD: equ $4004 ; DOGM163 LCD mit 3*16 ZeichenlcdInit: ; LCD initialisieren und diese mit INCLUDE io-defs.inc in die einzelnen Programme einbinde.

Die Programme für die LED und 7 Segment-Anzeige muss ich natürlich auch noch in der Hinsicht anpassen, dass es nun keine RAM-Spiegelung mehr gibt. Da das RAM jetzt nur noch auf Adresse unter $4000 horcht, wird bei STA LED oder STA SEGL nichts mehr ins RAM geschrieben, sondern nur noch in das IO-Gerät. Die verwendeten ROL-Befehle muss ich deshalb anpassen.

Auf die Änderungen gehe ich in folgendem Video ein. Dort seht ihr auch, ob die Hardware / die Programme mit den Adress-Anpassungen noch laufen:



Mit dem 6522 die Tastatur abfragen

Zu allererst müssen wir dem 6522 mitteilen, welchen Port (A oder B) wir verwenden wollen, und welche der 8 Bits Input und welche Output sind. Dabei dürfen wir freudig mixen, auf einem Port dürfen sowohl Input- als auch Output-Bits sein.

Wenn wir die oberen 4 Bits von Port A als Output haben wollen und die unteren als Input, um dann zu sehen, wo das Output beim Input wieder rauskommt bei einem Tastendruck (der die beiden ja kurz schließt), dann schreiben wir in das 3. Befehls-Register %1111000. Eine 1 bedeutet Output, eine 0 Input.

Das dritte Befehlsregister ist für die Datenrichtung (Data Direction) von Port A zuständig. Wir laden hier also %1111000 in unseren Akku und speichern ihn dann an Adresse $6003, womit die Funktion vom 6522er ausgeführt wird.

Idealerweise schreibt man sich eine Include-Datei. Meine heißt io-defs.inc: ; --- 6522er --- VIA: equ $6000 ; 6522 #1: 4x4 Keypad VIA_PORTB: equ VIA+$0 ; Port B VIA_PORTA: equ VIA+$1 ; Port A VIA_DDRB: equ VIA+$2 ; Data Direction Port B VIA_DDRA: equ VIA+$3 ; Data Direction Port A VIA_T1CL: equ VIA+$4 ; Timer 1 Low Order Latches (W) / Counter (R) VIA_T1CH: equ VIA+$5 ; T1 High-Order Counter VIA_T1LL: equ VIA+$6 ; T1 Low-Order Latches VIA_T1LH: equ VIA+$7 ; T1 High-Order Latches VIA_T2CL: equ VIA+$8 ; Timer 2 Low Order Latches (W) / Counter (R) VIA_T2CH: equ VIA+$9 ; T2 High-Order Counter VIA_SR: equ VIA+$A ; Shift Register VIA_ACR: equ VIA+$B ; Auxiliary Control Register VIA_PCR: equ VIA+$C ; Peripheral Control Register VIA_IFR: equ VIA+$D ; Interrupt Flag Register VIA_IER: equ VIA+$E ; Interrupt Enable Register VIA_PA_No_SHAKE: equ VIA+$F ; Same as Reg 1 except no "Handshake" VIA2: equ $5000 ; 6522 #2: für zukünfitge Verwendung Dann kann man die Adressen mit VIA_DDRB ansprechen und nicht mit $6003. Zum Einen ist das besser merk- und lesbar. Und zum Anderen ist der Anpassungsauswand denkbar gering, sollte sich die Adresse durch Architekturänderung doch noch einmal ändern (was ich allerdings derzeit nicht glaube, aber man weiß ja nie): lediglich die $6000 muss angepasst werden. Die anderen Adressen sind davon abhängig.

Mit getKey2: lda #%11110000 sta VIA_DDRA geben wir dem 6522er also über unsere Datenrichtungen Bescheid. Und der verhält sich dann so, wie man es sich wünscht und erwartet:
WDC 65C22 Datasheet, Seite 8

Should data be written into bit positions corresponding to pins which have been programmed as input, the output pins will be unaffected.
Das heißt, wenn ein Datenbit als Input definiert ist, ist egal, wenn wir versuchen, etwas hereinzuschreiben. Es wird ignoriert.

Meine ersten Messungen des 6522 am Oszilloskop haben übrigens ergeben, dass die Datenleitungen auf High gezogen sind, wenn sie keinen definieren Wert haben (Stichwort Pullup-Widerstände).

Darum macht es mehr Sinn, statt durch jeweils eine der vier Output-Leitungen Strom zu schicken (wäre High, wäre Normalzustand), ein Low zu schicken und zu schauen, ob das Low beim Input durch Tastendruck ankommt (Input ist Normalzustand auch alles High).

Programmmiertechnisch ist das kaum mehr Aufwand ldx #4 ; 4 Output-Leitungen nacheinander mit LOW versorgen... nextKeyOut: lda keyOut sta VIA_PORTA ... rol keyOut ; bits eins nach links schieben = nächste Leitung dex beq getKey2 ; wenn 4x durch, dann nochmal von vorne bra nextKeyOut sollte den Job erledigen. Jetzt müssen wir nur noch innerhalb der Schleife schauen, ob eine der Leitungen Low geworden ist, weil eine Taste gedrückt wurde und das Low aus dem Output-Pin durchschlägt (weil ja kurz geschlossen): ; und schauen, ob das bei einer der 4 Input-Leitungen ankommt... lda VIA_PORTA sta keyIn ; ein unteres Bit auf 0? Dann Taste gedrückt and #%00001111 cmp #$0F ; $0F = Standardwert = nichts gedrückt bne getKeyDone ; es wurde etwas gedrückt Wie wir sehen, können wir VIA_PORTA sowohl mit STA, also auch mit LDA ansprechen, sprich etwas hineinschreiben oder etwas herauslesen. Was davon geschieht, entscheidet die R/W Leitung, die wir ja auch angeschlossen haben und die die 6502 CPU für uns automatisch setzt.

Lassen wir die Schleife nun laufen, dann wandert das Low-Bit vom Output von rechts nach links durch (macht das ROL). Drücken wir dann eine Keypad-Taste, leitet der Kurzschluss das Low weiter zu einem Input-Bit, was wir nachfolgend auslesen. In diesem Augenblick speichern wir den Zustand in keyIn und verlassen die Schleife. Dort können wir noch warten, bis die Taste wieder losgelassen wurde und haben dann in keyIn das Bitmuster der gedrückten Taste.

Tastendrucke analysieren ...

Wie genau das Keypad jetzt angeschlossen ist, kann uns eigentlich egal sein, Hauptsache ist, für jede Taste kommt ein anderes Bitmuster heraus. Das lasse ich übrigens auf die LED-Ausgabe ausgeben.

Linken wir unser Assembler-Programm einmal, flashen es auf unser EPROM und schauen, was unser Breadboard-Computer an Bitmustern ausspuckt:



Im Test drücke ich jede Taste einmal und notiere mir die Bitmuster: Taste Bitmuster Hex 1 0111 0111 77 2 1011 0111 B7 3 1101 0111 D7 A 1110 0111 E7 4 0111 1011 7B 5 1011 1011 BB 6 1101 1011 DB B 1110 1011 EB 7 0111 1101 7D 8 1011 1101 BD 9 1101 1101 DD C 1110 1101 ED E 0111 1110 7E 0 1011 1110 BE F 1101 1110 DE D 1110 1110 EE Ich habe die "wandernde" Null einmal gelb markiert, damit klar wird, wie das Muster zustande kommt.

Nun kann ich wieder - ähnlich der Definition für die 7-Segment-Anzeigen - eine Tabelle mit 256 Werte im ROM anlegen, um der Taste auch eine Wertigkeit zuzuweisen. Dabei möchte ich natürlich die Reihenfolge 0123456789ABCDEF für die Werte von Null bis Fünfzehn bzw. den Wert für ein Halbbyte / Nibble. 0 1 2 3 4 5 6 7 8 9 A B C D E F BE 77 B7 D7 7B BB DB 7D BD DD E7 EB ED EE 7E DE

... und brauchbar machen

Nun brauche ich nur noch eine 256 Byte-Tabelle, in die ich überall $FF schreiben, bis auf an die gültigen Positionen. Das sind die, die wir gerade herausgefunden haben. Dort schreibe ich dann den Wert von $01 bis $0F. Durch diese Hilfstabelle muss ich dann einfach nur noch das Bitmuster auf die Startadresse der Hilfstabelle KEY_VAL addieren (indexierte Adressierung) und bekommen den Wert für das eingegebene Halbbyte.

KEY_VAL (klicken, um diesen Abschnitt aufzuklappen)
KEY_VAL: BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $01, $FF, $FF, $FF, $04, $FF, $07, $0E, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $02, $FF, $FF, $FF, $05, $FF, $08, $00, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $03, $FF, $FF, $FF, $06, $FF, $09, $0F, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $0A, $FF, $FF, $FF, $0B, $FF, $0C, $0D, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF

Statt der Zahlenwerte kann man natürlich auch die ASCII-Codes hinterlegen, will man nicht damit rechnen, sondern diese auf dem LCD ausgeben:

KEY_ASC (klicken, um diesen Abschnitt aufzuklappen)
KEY_ASC: BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $31, $FF, $FF, $FF, $34, $FF, $37, $45, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $32, $FF, $FF, $FF, $35, $FF, $38, $30, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $33, $FF, $FF, $FF, $36, $FF, $39, $46, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $41, $FF, $FF, $FF, $42, $FF, $43, $44, $FF BYTE $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF

Die beiden Tabellen packe ich in das Include-File key-defs.inc.

Damit habe ich eigentlich auch schon alles zusammen, um den Tastendruck auch anzeigen zu lassen, sei es als Wert, z. B. auf die 7-Segment-Anzeige oder als ASCII-Zeichen für das DOGM163-LCD.

Source-Code Assembler

Die Funktion getKey kommt in das Include-File key.inc. Außerdem schreibe ich noch die Funktionen keyToLCD, keyToSeg und keyToVal, um die Ausgabe einfacher zu machen:

key.inc (klicken, um diesen Abschnitt aufzuklappen)
; Tastendrücke vom 4x4 Keypad entgegennehmen ; last edit: Oliver Kuhlemann (www.cool-web.de), 2020-09-02 ; ----------- ; Funktionen: ; ----------- ; getKey: Wartet auf Tastendruck (+loslassen) und schreibt Tasten-Muster nach keyIn ; in: - ; out: Key-Bitmuster in Var. keyIn ; keyToLCD: gibt die gedrückte Taste als ASCII auf dem LCD aus ; in: - (keyIn) ; out: - ; keyToSeg: gibt die gedrückte Taste auf der 7-Segment-Anzeige aus ; in: - (keyIn) ; out: - ; keyToVal: gibt die gedrückte Taste als Wert im Akku zurück ; in: - (keyIn) ; out: TastenWert ($0...$F) in Akku ; ------------------------------------------------------------------------------------------ keyToLCD: ; --- gibt die gedrückte Taste als ASCII auf dem LCD aus phx ; Register X auf Stack retten lda #1 ; ist ein zeichen sta lcdrs ; ASCII-Zeichen für Key-Muster holen ("0"..."9" und "A"..."F") ldx keyIn lda KEY_ASC, x jsr lcdByte plx ; Register X wieder herstellen rts keyToSeg: ; --- gibt die gedrückte Taste auf der 7-Segment-Anzeige aus phx ; Register X auf Stack retten lda #1 ; ist ein zeichen sta lcdrs ; Wert für Key-Muster holen ($0...$F bzw. 0 bis 15) ldx keyIn lda KEY_VAL, x ; erst das Key-Bitmuster zuordnen tax lda SEG, x ; dann dafür das Segment-Muster holen sta SEGR plx ; Register X wieder herstellen rts keyToVal: ; --- gibt die gedrückte Taste als Wert im Akku zurück phx ; Register X auf Stack retten lda #1 ; ist ein zeichen sta lcdrs ; Wert für Key-Muster holen ($0...$F bzw. 0 bis 15) ldx keyIn lda KEY_VAL, x ; erst das Key-Bitmuster zuordnen plx ; Register X wieder herstellen rts getKey: ; --- Wartet auf Tastendruck (+loslassen) und schreibt Tasten-Muster nach keyIn phx ; Register X auf Stack retten ; oberen 4 Bits Port A Output, unteren 4 Bits Port Input ; standardmäßig sind die Leitungen auf High, wir müssen also auf low prüfen lda #%11110000 sta VIA_DDRA getKey2: lda #%11101111 sta keyOut ldx #4 ; 4 Output-Leitungen nacheinander mit LOW versorgen... nextKeyOut: lda keyOut sta VIA_PORTA ; und schauen, ob das bei einer der 4 Input-Leitungen ankommt... lda VIA_PORTA sta keyIn ; ein unteres Bit auf 0? Dann Taste gedrückt and #%00001111 cmp #$0F ; $0F = Standardwert = nichts gedrückt bne getKeyDone ; es wurde etwas gedrückt rol keyOut ; bits eins nach links schieben = nächste Leitung dex beq getKey2 ; wenn 4x durch, dann nochmal von vorne bra nextKeyOut getKeyDone: ; Ergebnis steht in keyIn ; warten, bis die Taste wieder losgelassen wurde loslassen: NOP NOP NOP NOP NOP lda VIA_PORTA and #%00001111 cmp #$0F ; $0F = Standardwert = nichts gedrückt bne loslassen plx ; Register X wieder herstellen rts ; gedrückte Taste steht in keyIn

Das Alles kann ich jetzt komfortabel in meinem Hauptprogramm nutzen, um Tastendrücke entgegenzunehmen und auszugeben.

7-Keypad-Test.asm (klicken, um diesen Abschnitt aufzuklappen)
; Meldung auf DOGM163-LCD ausgeben und Tastendrücke entgegennehmen und anzeigen ; last edit: Oliver Kuhlemann (www.cool-web.de), 2020-09-02 CHIP W65C02S ; (default) ORG $8000 ; Programmstartadresse (von der CPU aus gesehen) ; --- Konstanten: Timing --- ; 200 nF Kondensator von 7 Hz bis 2 KHz ; Ein Takt sind 0.5 ms, ein NOP sind zwei Takte sind 1 ms NopsPerMs: equ 1 ; wieviele NOPs braucht es, um eine Millisek. voll zu machen? INCLUDE io-defs.inc INCLUDE zeropage-defs.inc INCLUDE ram-defs.inc init: ; LED-Ausgabe löschen lda #0 sta LED ; Segment-Ausgabe löschen lda #$FF sta SEGL sta SEGM sta SEGR init_LCD: ; Reset an LCD (Bit 0 auf Low für ein paar ms) lda #0 sta LCD lda #5 jsr delay ; wieder auf high und min 50 ms warten lda #1 sta LCD lda #10 jsr delay jsr lcdInit start: ldx #0 eingabeaufforderung: print: ldy MSG1,x ; X. Buchstaben aus Message in Y-Register laden beq eingabe lda #1 ; ist ein zeichen sta lcdrs tya jsr lcdByte inx bra print eingabe: ; Cursor blinken lassen lda #0 ; ist ein Befehl sta lcdrs lda #opLCD_CursorOn jsr lcdByte keypadEingabe: jsr getKey jsr keyToLCD ; auf LCD ausgeben jsr keyToSeg ; auf 7-Segment-Anzeige ausgeben ; Wert holen und auf LED diesen auf LED ausgeben jsr keyToVal ; liefert in Akku zurück sta LED bra keypadEingabe ; und das Spielchen wieder von vorne stop: STP ; Hält die CPU an ; --- Unterprogramme --- delay: ; 1 delay ca. 5 ms NOP ; Zeit verschwenden, NOP = 2 Taktzyklen NOP dec A bne delay rts ; für LCD INCLUDE lcd.inc ; für 4x4 Keypad INCLUDE key.inc ; --- im ROM abgelegte Konstanten MSG1: ; 1234567890123456 Spaces am Ende gelten auch, auch wenn man sie nicht sieht ASCII Dr BYTE lcd_ue ASCII cke eine ASCII Taste: BYTE 0 ; Standard-Definitionen für IO-Geräte INCLUDE seg-defs.inc INCLUDE lcd-defs.inc INCLUDE key-defs.inc ; --- Sprungvektoren ORG $FFFA word $8000 ; BRK/IRQ bei $FFFA/B word $8000 ; Programmstartadresse bei $FFFC/D word $8000 ; NMI bei $FFFE/F

Im an- und abschließenden Video gehe ich noch einmal auf die neuen Assembler-Programmteile ein und teste das Ganze, ob die Ausgabe der eingegebenen Zeichen auch gut funktioniert:



Wie man sieht, kann man bei 2 KHz Taktfrequenz schon ganz komfortabel etwas eintippen. Man muss zwar ein-, zweihundert Millisekunden auf der Taste bleiben, damit sie gut erkannt wird, aber wer wird denn hetzen wollen? Brauchbar ist die Tastatur so schon allemal. Und wenn ich später den Takt hochstelle, dann schafft sie sicher auch den schnellsten Tipper.

Ausblick

Der nächste Teil wird wohl wieder ein Software-only-Part. Ich will mit den jetzt gegebenen Möglichkeiten (Eingabe von der Tastatur, Ausgabe auf mehrere IO-Geräte) etwas halbwegs sinnvolles programmieren, vielleicht ein kleines Spielchen.

Ich habe bei der Entwicklung des Spielchens bemerkt, dass man doch mehr Fehler beim Assembler Programmieren macht, als einem lieb ist. Zu schnell schleicht sich ein Fehler ein. Darum habe ich zuerst ein weiteres Debug-Instrument eingebaut.