8-Bit-Breadboard-Computer auf Basis einer 6502-CPU - Assembler-Programmierung: weitere Funktionen des CoProzessors auf Arduino-Basis
Bisherige Artikel dieser Serie - hier könnt ihr nochmal alle Grundlagen nachlesen, falls ihr jetzt erst einsteigt:- Digitale Logik und Logikgatter einfach erklärt
- Verwendung des 555-Timer als Taktgeber / Clock
- Das Clock-Modul: Taktgeber für unseren Breadboard-Computer
- Speichertypen und Zugriff auf Speicher
- Erste Schritte mit der CPU
- Eine echte WDC W65C02-CPU
- Das Speicher-Modul: Anbindung von RAM und ROM
- Erstes Programm in Maschinensprache: RAM-Test
- Das Sniffer-Modul: Ein Arduino/STM32 zeigt an, was auf dem Bus los ist.
- Erstes Ausgabegerät: Adressierung und Ausgabe auf 8 LEDs
- Programmiersprache-Evolution: von Maschinensprache zu Assembler
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 1: Taktungsprobleme
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 2: 20 Nanosekunden, die nicht sein sollten
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 3: Assembler-Programme
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 4: neue Adressierungsarchitektur mit 74HC688 und 74HC138
- 3-fach 7-Segment-Anzeige als dezimale Ausgabe, Teil 5: Programmierung in Assembler
- 3x16 Zeichen LCD DOGM163 als Textausgabe-Gerät, Teil 1: Breadboard-Aufbau und erste Programmversion
- 3x16 Zeichen LCD DOGM163 als Textausgabe-Gerät, Teil 2: Debugging
- Clock-Modul upgraden: höherer Takt (2 KHz) und Frequenzgenerator (bis 160 KHz)
- Erstes Eingabe-Gerät: ein 4x4 Keypad als Tastatur (wird mit 65C22 abgefragt)
- Erweiterung um eine Debug-Anzeige des Prozessorstatus und der Register auf dem LCD
- Die Tastatur spinnt: Hardware-Debugging, Fake-6522 aus China und mehr
- Assembler-Programmierung: Dezimal Hexadezimal Binär Konverter und Spiel
- Ein Arduino ATmega328p dient dem Breadboard Computer als CoProzessor
- Integration eines CoProzessors auf Arduino-Basis
- Temperatur und Luftfeuchtigkeit per DHT11
- Aktuelles Datum und Uhrzeit dank RTC / Echtzeituhr
- Fernbedienbarkeit über 4 Knöpfe der 433 MHz-Funkfernbedienung

Ich habe links unten noch einen kleinen Schuber für die 433 MHz-Fernbedienung mit meinem Anycubic i3 Mega rausgelassen und per Klebeknete befestigt. So kann sie nicht mehr verloren gehen und ich habe alles beisammen.
Viel Platz ist jetzt nicht mehr, vielleicht noch für ein paar Taster. Aber alles in einem hat der Breadboard Computer ja auch alles, was man sich wünschen kann: Eingabe, Ausgabe, Sensoren. Ich habe das Gefühl, hardwaretechnisch haben wir das Projekt jetzt so ziemlich ausgereizt.
Die Software-Schnittstelle zum CoProzessor
Aber heute soll es ja auch mehr um die Software gehen, und dort im speziellen um die noch nicht behandelten Sensoren und das dazu gehörige Bit Banging, denn gerade für die Echtzeituhr musste ich die Bits schon ein bisschen zusammenrücken lassen, damit ich alles in den 16 zur Verfügung stehenden Funktionen unterzubringen.Funktionen:
0 0b0000 = Zufallswert (4 Bit)
1 0b0001 = 433 MHz Fernbedienung DCBA (4 Bits, gedrücktes gesetzt)
DHT11 Temp (Messbereich: 0...50°C, Auflösung 1°C (+/- 2°)
2 0b0010 = DHT11 Temp High (0...50) = 6 Bits
3 0b0011 = Temp Low
DHT11 Luftfeuchte (Messbereich: 20...90% RH, Auflösung 1% (+/- 5%)
4 0b0100 = DHT11 Fcht High (0...90) = 7 Bits
5 0b0101 = Fcht Low
RTC DS1307
6 0b0110 = YYYY Jahr High 1900 + 0...255
7 0b0111 = YYYY Low
8 0b1000 = MMMM Monat (0...12) = 4 Bits
9 0b1001 = DDDD Tag (0...31) = 5 Bits
10 0b1010 = DHHH Stunde (0...23) = 5 Bits
11 0b1011 = HHMM Minute (0...59) = 6 Bits
12 0b1100 = MMMM
13 0b1101 = xxSS Sekunde (0...59) = 6 Bits
14 0b1110 = SSSS
15 0b1111 = idle (Normalzustand 6522 Port)
Abfrage der Fernbedienung
Nachdem wir anfangs den 6522 im ioInit verklickert habe, wie die Pins von Port B behandelt werden sollen, nämlich welche Eingang und welche Ausgang sind (nachzulesen im letzten Teil), sagen wir ihm zur Abfrage einfach, welche Funktion wir aufrufen wollen.Für die Verbedienung ist dies die Funktion 1. Der Funktionsaufruf erfolgt in den oberen 4 Bits, die Rückgabe in den unteren 4 Bits.
Wie praktisch, dass die Fernbedienung ebenfalls 4 Knöpfe hat, da können wir jeden Tastendruck auf ein Bit abbilden.
Ich habe die Abfrage in eine Funktion namens getFernbedienung im Include-File sensors.inc gepackt, wegen der Wiederverwendbarkeit und ÜBersicht:
getFernbedienung: ; liefert das Bitmuster der 433 MHz-Funkfernbedienung
; VIA Port B, Funktion 1 (oberen 4 Bits)
; -> 433 MHz Fernbedienung DCBA (4 Bits, gedrücktes gesetzt)
lda #$10
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
sta fernbd ; sichern
; fertig, der CoProzessor darf wieder faulenzen
lda #$F0
sta VIA_PORTB
lda fernbd ; Rückgabe im Akku
rts
nach einem Aufruf der Funktion 1 lassen wir dem CoProzessor 2 Millisekunden Zeit, zu antworten und schauen uns dann das Ergebnis in den unteren 4 Bits von Port B an. Dieses speichern wir im RAM unter der Adresse $21E, die wir uns aber dann Assembler nicht merken müssen. Wir verwenden einfach immer den Variablennamen fernbd für Fernbedienung. Dies ist ein Byte und hat folgenden Aufbau
Bit 76543210
0000DCBA
Wird auf der Fernbedienung die Taste A gedrückt, sorgt das also dafür, dass das Bit 0 ganz rechts gesetzt wird. Die Bits lassen sich wunderbar über unsere LED-Ausgabe anzeigen und so werden wir das auch in unserem Test- und Beispielprogramm implementieren.Abfrage von Temperatur und Luftfeuchtigkeit
Die Temperatur und die Luftfeuchtigkeit brauchen jeweils ein ganzes Byte als Rückgabe. Das heißt, jedes von beiden braucht zwei Nibbles (Halbbytes) und damit zwei Funktionen.Für die Temperatur sind das 2 und 3; für die Luftfeuchtigkeit 4 und 5. Hier werden einfach die zwei Funktionen nacheinander aufgerufen und dann die zwei Paare zu 4 Bits zu 8 Bits kombiniert, simsalabim fertig ist der 8 Byte Wert, indem schon fertig die richtige Temperatur in Grad Celsius bzw. die Luftfeuchtigkeit in Prozent RH (RH = Relative Humidity = relative Luftfeuchtigkeit).
Hier als Beispiel der Code für die Temperatur:
getTemperature: ; liefert die Temperatur in Grad Celsius in tempC
phy
ldy #sensorTries
tryTemperature:
; VIA Port B, Funktion 2 (oberen 4 Bits) -> ; High-Nibble Temp.
lda #$20
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #10
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
asl A; ins High-Nibble verschieben
asl A
asl A
asl A
sta tempC ; High-Nibble sichern
; VIA Port B, Funktion 3 (oberen 4 Bits) -> ; Low-Nibble Temp.
lda #$30
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #10
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
ora tempC ; das high-Nibble dazu
sta tempC ; Gesamt-Temperatur speichern
; fertig, der CoProzessor darf wieder faulenzen
lda #$F0
sta VIA_PORTB
lda tempC ; Rückgabe im Akku
bne doneTemperature ; temperatur gültig: zurückgeben
;temperatur = 0 = ungültig, warten und neuer versuch
dey
beq doneTemperature ; alle versuche durch, mit 0 zurückgeben
lda #sensorWait
jsr delayMs
bra tryTemperature
doneTemperature:
ply
rts
getHumidity funktioniert ganz analog und liefert in der Variablen humidity zurück.Leider habe ich immer noch das Problem mit den Aussetzern in der Datenkommunikation zwischen ATmega328P und 6522, den ich bisher noch nicht ausfindig machen konnte.
Darum gibt es eine Abfrage, ob nicht eine Null zurückgeliefert wird, was auf einen Aussetzer hindeutet. Dann wartet das Programm ein paar Hundert Millisekunden (einstellbar in der Konstanten sensorWait) und versucht es dann erneut. Ist nach einer bestimmten Anzahl Versuche (sensorTries) immer noch kein Erfolg zu verbuchen, dann wird trotzdem die Null zurückgeliefert, damit das Programm nicht in einer Endlosschleife hängenbleibt. So gibt es zwar Kommunikationsaussetzer, aber die falschen Ergebnisse werde nicht angezeigt.
Abfrage von Datum / Zeit
Wir steigern uns in der Schwierigkeit. Die Werte für Datum und Zeit, die ich aufgeteilt habe auf die Variablen- (century)
- year
- month
- day
- hour
- minute
- second
Die folgende Übersicht zeigt die Bitkodierung von Datum und Zeit vielleicht noch einmal übersichtlicher:

Für den Tag zum Beispiel muss ich Funktion $9 für die Bits 4, 3, 2, 1 und Funktion $10 für das Bit 0 des Tages aufrufen. $10 enthält aber außerdem Infos über die Stunde. Möchte ich den Tag errechnen, muss ich die Bits ausblenden, die nicht zum Tag gehören (AND) und an den richtigen Platz verschieben (ASL und LSR) und mir den Tag so wieder zusammenbasteln.
So bekomme ich alles in den verbleibenden 9 Funktionen unter und habe am Ende sogar noch 2 Bits übrig, die ich vor die Sekunden gesetzt habe, um dort einfacher zu rechnen.
Das ganze Bitgeschubse mit OR, AND, ASL und LSR ergibt sich durch die Schnittstellendefinition und steht in der Funktion getDateAndTime von sensors.inc. Ich gehe auf die Programmierung im Video weiter unten näher darauf ein.
sensors.inc (klicken, um diesen Abschnitt aufzuklappen)
; Unter-Routinen für Sensoren (Temperatur, Luftfeuchtigkeit, 433 MHz-Fb., RTC)
; last edit: Oliver Kuhlemann (www.cool-web.de), 2020-10-06
; Funktionen:
; getFernbedienung: ; liefert das Bitmuster der 433 MHz-Funkfernbedienung
; getTemperature: ; liefert die Temperatur in Grad Celsius
; getHumidity: ; liefert die Luftfeuchtigkeit in % RH
; getDateAndTime: ; liefert Datum und Zeit in year, month, day, hour, minute, second
sensorWait: equ 200 ; Wie lange auf den Sensor warten ?
sensorTries: equ 5 ; Wie oft versuchen
getFernbedienung: ; liefert das Bitmuster der 433 MHz-Funkfernbedienung
; VIA Port B, Funktion 1 (oberen 4 Bits)
; -> 433 MHz Fernbedienung DCBA (4 Bits, gedrücktes gesetzt)
lda #$10
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
sta fernbd ; sichern
; fertig, der CoProzessor darf wieder faulenzen
lda #$F0
sta VIA_PORTB
lda fernbd ; Rückgabe im Akku
rts
getTemperature: ; liefert die Temperatur in Grad Celsius in tempC
phy
ldy #sensorTries
tryTemperature:
; VIA Port B, Funktion 2 (oberen 4 Bits) -> ; High-Nibble Temp.
lda #$20
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #10
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
asl A; ins High-Nibble verschieben
asl A
asl A
asl A
sta tempC ; High-Nibble sichern
; VIA Port B, Funktion 3 (oberen 4 Bits) -> ; Low-Nibble Temp.
lda #$30
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #10
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
ora tempC ; das high-Nibble dazu
sta tempC ; Gesamt-Temperatur speichern
; fertig, der CoProzessor darf wieder faulenzen
lda #$F0
sta VIA_PORTB
lda tempC ; Rückgabe im Akku
bne doneTemperature ; temperatur gültig: zurückgeben
;temperatur = 0 = ungültig, warten und neuer versuch
dey
beq doneTemperature ; alle versuche durch, mit 0 zurückgeben
lda #sensorWait
jsr delayMs
bra tryTemperature
doneTemperature:
ply
rts
getHumidity: ; liefert die Luftfeuchtigkeit in % RH in humidity
phy
ldy #sensorTries
tryHumidity:
; VIA Port B, Funktion 4 (oberen 4 Bits) -> ; High-Nibble Humidity
lda #$40
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #10
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
asl A; ins High-Nibble verschieben
asl A
asl A
asl A
sta humidity; High-Nibble sichern
; VIA Port B, Funktion 5 (oberen 4 Bits) -> ; Low-Nibble Humidity
lda #$50
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #10
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
ora humidity ; das high-Nibble dazu
sta humidity ; Gesamt-Temperatur speichern
; fertig, der CoProzessor darf wieder faulenzen
lda #$F0
sta VIA_PORTB
lda humidity ; Rückgabe im Akku
bne doneHumidity ; temperatur gültig: zurückgeben
; Humidity = 0 = ungültig, warten und neuer versuch
dey
beq doneHumidity ; alle versuche durch, mit 0 zurückgeben
lda #sensorWait
jsr delayMs
bra tryHumidity
doneHumidity:
ply
rts
getDateAndTime: ; liefert Datum und Zeit in year, month, day, hour, minute, second
phy
ldy #sensorTries
tryDateAndTime:
; --- JAHR ---
; VIA Port B, Funktion 6 (oberen 4 Bits) -> ; High-Nibble Jahr (seit 1900)
lda #$60
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
asl A ; ins High-Nibble verschieben
asl A
asl A
asl A
sta year; High-Nibble sichern
; VIA Port B, Funktion 7 (oberen 4 Bits) -> ; Low-Nibble Jahr
lda #$70
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
ora year ; das high-Nibble dazu
sta year ; Gesamtwert speichern
lda #20
sta century
lda year ; test, ob 0 (=ungültig, dann warten und neuer versuch)
bne doneYear ; weiter
dey
jmp doneDateAndTime ; alle versuche durch, mit 0 zurückgeben
lda #sensorWait
jsr delayMs
bra tryDateAndTime
doneYear:
; --- MONAT ---
; VIA Port B, Funktion 8 (oberen 4 Bits) -> ; High-Nibble Monat
lda #$80
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
sta month ; Gesamtwert speichern
lda month ; test, ob 0 (=ungültig, dann warten und neuer versuch)
bne doneMonth ; weiter
dey
jmp doneDateAndTime ; alle versuche durch, mit 0 zurückgeben
lda #sensorWait
jsr delayMs
bra tryDateAndTime
doneMonth:
; --- TAG ---
; VIA Port B, Funktion 9: DDDD (10: DHHH)
lda #$90
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$0F ; Function ausblenden
asl A ; um 1 nach links verschieben
sta day; High-Nibble sichern
; VIA Port B, Funktion 10: DHHH
lda #$A0
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #$08 ; wir brauchen nur das bit 4 (wert=8)
lsr A ; und zwar an der 0. Pos. (= 3x rechts schieben)
lsr A
lsr A
ora day ; das high-Nibble dazu
sta day ; Gesamtwert speichern
lda day ; test, ob 0 (=ungültig, dann warten und neuer versuch)
bne doneDay ; weiter
dey
jmp doneDateAndTime ; alle versuche durch, mit 0 zurückgeben
lda #sensorWait
jsr delayMs
jmp tryDateAndTime
doneDay:
; --- STUNDE ---
; VIA Port B, Funktion 10: DHHH)
lda #$A0
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #%00000111 ; nur die unteren 3 Bits behalten
asl A ; um 2 nach links verschieben (da kommt noch was rechts)
asl A
sta hour; High-Nibble sichern
; VIA Port B, Funktion 11: HHMM
lda #$B0
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #%00001100 ; wir brauchen nur bit 3 und 2
lsr A ; und zwar an der 0. Pos. (= 2x rechts schieben)
lsr A
ora hour ; das high-Nibble dazu
sta hour ; Gesamtwert speichern
; --- MINUTE ---
; VIA Port B, Funktion 11: HHmm
lda #$B0
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #%00000011 ; nur die unteren 2 Bits behalten
asl A ; um 4 nach links verschieben (da kommt noch was rechts)
asl A
asl A
asl A
sta minute; High-Nibble sichern
; VIA Port B, Funktion 12: mmmm
lda #$C0
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #%00001111 ; wir brauchen alle 4 bits
ora minute ; das high-Nibble dazu
sta minute ; Gesamtwert speichern
; --- SEKUNDE ---
; VIA Port B, Funktion 13: xxSS
lda #$D0
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #%00000011 ; nur die unteren 2 Bits behalten
asl A ; um 4 nach links verschieben (da kommt noch was rechts)
asl A
asl A
asl A
sta second; High-Nibble sichern
; VIA Port B, Funktion 12: SSSS
lda #$E0
sta VIA_PORTB
; dem CoProzessor ein bisschen Zeit gönnen zum Antworten
lda #2
jsr delayMs ; Akkuwert lang in Millsekunden warten
; und Ergebnis lesen (in die 4 unteren Bits)
lda VIA_PORTB
and #%00001111 ; wir brauchen alle 4 bits
ora second ; das high-Nibble dazu
sta second ; Gesamtwert speichern
; fertig, der CoProzessor darf wieder faulenzen
lda #$F0
sta VIA_PORTB
doneDateAndTime:
ply
rts
Testprogramm
Die frisch entwickelten Unterroutinen müssen natürlich noch getestet werden. Dazu brauchen wir ein Test- und Beispielprogramm. Diese soll- den Fernbedienungsstatus auf der LED-Ausgabe anzeigen
- Datum, Uhrzeit, Temperatur und uftfeuchtigkeit auf dem LCD anzeigen
10-sensor-test.asm (klicken, um diesen Abschnitt aufzuklappen)
; Test der Sensoren / Abfrage über den CoProzessor
; last edit: Oliver Kuhlemann (www.cool-web.de), 2020-10-06
CHIP W65C02S ; (default)
ORG $8000 ; Programmstartadresse (von der CPU aus gesehen)
startvektor:
jmp reset
; !!! === Hinweise === !!!!
; - max 5.3V, sonst kippende Bits in der LED-Ausgabe (und woanders?)
; - max 2Khz, damit der Sniffer noch funktioniert (mitkommt)
; - immer noch ein ungelöstes Problem: Spannungsabfall zwischen den oberen und
; unteren Breadboards, besonders, wenn Verbraucher wie Sniffer angeschaltet sind
; --- Konstanten: Timing ---
; 40 KHz
; 200 nF Kondensator von 7 Hz bis 2 KHz
; Ein Takt sind 0.5 ms, ein NOP sind zwei Takte sind 1 ms
; 2 KHz NopsPerMs: equ 1 ; wieviele NOPs braucht es, um eine Millisek. voll zu machen?
; 40 KHz / 7 =
TaktfrequenzKHz: equ 40 ; KHz
; wieviele NOPs braucht es, um eine Millisek. voll zu machen?
NopsPerMs: equ 5 ; TaktfrequenzKHz / 8
; --- Makro-Definitionen ---
INCLUDE macros.inc
; --- Speicherort-Definitionen ---
INCLUDE io-defs.inc
INCLUDE zeropage-defs.inc
INCLUDE ram-defs.inc
; --- Standard-Definitionen für IO-Geräte ---
INCLUDE seg-defs.inc
INCLUDE lcd-defs.inc
INCLUDE key-defs.inc
; --- allgemeine Unterprogramme und Makros ---
INCLUDE seg.inc ; für 7-Segment-Anzeige
INCLUDE lcd.inc ; für LCD
INCLUDE key.inc ; für 4x4 Keypad
INCLUDE io.inc ; io_init etc.
INCLUDE convert.inc ; valToDez
INCLUDE debug.inc ; Tools zum Debugging
INCLUDE math.inc ; getRandom etc.
INCLUDE sensors.inc ; get Temperature etc.
; --- Hauptprogramm ---
reset:
jsr ioInit
start:
LCD_CLEAR ; LCD löschen (Makro)
LCD_PRINT msgAnzeige ; Makro-Aufruf, siehe macros.inc
werteAusgeben:
ldx #$FF
werteAusgeben2:
jsr getFernbedienung
lda fernbd
sta LED ; auf LED ausgeben
dex
bne werteAusgeben2 ; der Fernbedienung mehr Zeit einräumen
jsr getTemperature
LCD_POS #32 ; LCD-Cursor positionieren (Makro)
LCD_VAL tempC, 2
jsr getHumidity
LCD_POS #39 ; LCD-Cursor positionieren (Makro)
LCD_VAL humidity, 2
jsr getDateAndTime ; Echtzeit holen
LCD_POS #0
LCD_VAL day, 2
LCD_POS #2
LCD_VAL month, 2
LCD_POS #7
LCD_VAL year, 2
LCD_POS #15
LCD_VAL hour, 2
LCD_POS #18
LCD_VAL minute, 2
LCD_POS #21
LCD_VAL second, 2
; dem CoProzessor eine kleine Pause gönnen
; lda #50
; jsr delayMs
jmp werteAusgeben
stop:
STP ; Hält die CPU an
; --- Unterprogramme ---
; --- im ROM abgelegte Konstanten ---
msgAnzeige:
; 1234567890123456
ASCII __.__.20__
ASCII __:__:__
ASCII __
byte lcd_grad
ASCII C, __ %RH
BYTE 0
; --- Interrupt-Routinen ---
; bei einkommenden IRQ-Signal (Taster an NMI) springt er kurz hier rein und
; macht dann im Code weiter
ORG $FFD0 ; NMI -> Debug-Ausgabe
php
jsr debug
plp
wai
rti
; spring zurück, ohne etwas zu tun
; so kann ich BRK als Breakpoints in den Source einbauen, bei dem die CPU stoppt
ORG $FFE0 ; BRK/IRQ -> Continue
rti
; --- Sprungvektoren ---
ORG $FFFA
word $FFD0 ; NMI bei $FFFA/B
word $8000 ; Programmstartadresse bei $FFFC/D
word $FFE0 ; BRK/IRQ bei $FFFE/F
Das meiste daraus kennen wir ja schon und wäre Wiederholung, darum hier nur der interessante Abschnitt im Detail:
start:
LCD_CLEAR ; LCD löschen (Makro)
LCD_PRINT msgAnzeige ; Makro-Aufruf, siehe macros.inc
werteAusgeben:
ldx #$FF
werteAusgeben2:
jsr getFernbedienung
lda fernbd
sta LED ; auf LED ausgeben
dex
bne werteAusgeben2 ; der Fernbedienung mehr Zeit einräumen
jsr getTemperature
LCD_POS #32 ; LCD-Cursor positionieren (Makro)
LCD_VAL tempC, 2
jsr getHumidity
LCD_POS #39 ; LCD-Cursor positionieren (Makro)
LCD_VAL humidity, 2
jsr getDateAndTime ; Echtzeit holen
LCD_POS #0
LCD_VAL day, 2
LCD_POS #2
LCD_VAL month, 2
LCD_POS #7
LCD_VAL year, 2
LCD_POS #15
LCD_VAL hour, 2
LCD_POS #18
LCD_VAL minute, 2
LCD_POS #21
LCD_VAL second, 2
; dem CoProzessor eine kleine Pause gönnen
; lda #50
; jsr delayMs
jmp werteAusgeben
stop:
STP ; Hält die CPU an
; --- im ROM abgelegte Konstanten ---
msgAnzeige:
; 1234567890123456
ASCII __.__.20__
ASCII __:__:__
ASCII __
byte lcd_grad
ASCII C, __ %RH
BYTE 0
Ganz zu Anfang wird das Gerüst der Meldung auf dem LCD angezeigt. Hier sind alle auszufüllenden Variablen noch mit Unterstrichen belegt. Das Gerüst bleibt die ganze Zeit stehen. Die variablen Werte werden jeweils geholft und per Positionierungsbefehl an die richtige Stelle im Gerüst geschrieben. Das spart Zeit und erspart einem ein Flackern, würde man alles immer wieder löschen und neu schreiben.Danach startet eine Endlosschleife, in der die Daten immer und immer wieder ausgegeben werden.
Der LED-Ausgabe der Fernbedienung (getFernbedienung) wird dabei mehr Zeit eingeräumt, damit man den Tastendruck auch gut erkennen kann. Darum wird dieser 255 mal durchlaufen, bevor danach mit getTemperature, getHumidity und getDateAndTime die restlichen Werte vom CoProzessor geholt und dann an der richtigen Position auf dem LCD angezeigt werden.
Durch Kapselung und Verwendung von Unterroutinen und Makros erhalten wir hier einen übersichtlichen Source-Code für das Hauptprogramm. Assembler muss kein Spaghetti-Code sein.
Vorführung des fertigen Programms und Erklärung
In folgendem Video zeige ich zuerst das fertige Programm. So kann man sich schon mal ein Bild dessen machen, was wir programmieren wollen. Im Anschluss erkläre ich den Source-Code in 6502-Assembler, der das leistet: