Den NMI hält keiner auf
Wie bereits bei der Einleitung zu den Interrupts (ich gehe davon aus, dass ihr den Beitrag bereits gelesen habt) erwähnt, handelt es sich beim NMI (None Maskable Interrupt) um einen Interrupt, der nicht mit SEI/CLI unterdrückt werden kann. Wie ihr wisst, sind die einzigen Quellen für einen NMI die RESTORE-Taste und der CIA-2.
Die beiden CIAs sind übrigens fast identisch und unterscheiden sich nur durch wenige Register und durch das Auslösen von IRQ (CIA-1) und NMI (CIA-2).
Bevor wir jetzt einen NMI programmieren, sollten wir mal wieder einen Blick auf den ROM-Vector werfen. Der NMI springt zur Adresse $fe43, die wir im ROM-Vector unter $fffa finden. Schauen wir mit WinVICE mal nach, was unter $fe43 genau gemacht wird, so stossen wir auf nur zwei Befehle.
SEI JMP ($0318)
Es werden also erstmal weitere IRQs gesperrt (warum es hier einen SEI gibt ist mir allerdings nicht ganz klar, das I-Flag wurde ja bereits gesetzt, aber doppelt hält wohl einfach besser 😉 ) und dann wird über den RAM-Vector an $0318 zur dort hinterlegten Adresse gesprungen. Wie ihr seht, werden hier, im Gegensatz zum normalen IRQ, weder der Akku, noch das X-/Y-Register gesichert. Darum müssen wir uns bei Bedarf selbst kümmern. Wir können also unter $0318/19 wieder die Adresse für unsere eigene NMI-Routine eintragen.
NMI durch den Timer-A vom CIA-2
Unser kleines Testprogramm wird einen NMI und einen IRQ verwenden. Damit könnt ihr dann kontrollieren, dass der NMI tatsächlich nicht durch ein SEI verhindert werden kann. Der NMI wird über den Timer des CIA-2 den Rahmen blinken lassen, der IRQ verändert die Hintergrundfarbe.
*=$0801 ;*** Startadresse BASIC-Zeile: 2018 SYS 2064:NEW !word main-2, 2018 !byte $9e !text " 2064:" !byte $a2,$00,$00,$00 main ldx $dd0d ;aktuelle IRQs des CIA-2 merken lda #%01111111 ;und dann alle sta $dd0d ;deaktivieren lda #<nmi ;Adresse des NMI sta $0318 ;in den RAM-Vector schreiben lda #>nmi sta $0319 lda #$f4 ;LO-BYTE für sta $dd04 ;Timer-A vom CIA-2 setzen lda #$00 ;und auch das HI-BYTE sta $dd05 ;setzen lda #%00000001 ;Timer-A im CIA-2 sta $dd0e ;starten txa ;gemerkte IRQs vom CIA-2 in den Akku ora #%10000001 ;alle IRQs + dem für Timer-A sta $dd0d ;wieder aktivieren
Wir beginnen wieder mit der BASIC-Zeile 2018 SYS 2064:NEW.
Wir beginnen mit der Einrichtung unseres NMIs. Da wir NMIs nicht mit SEI verhindern können, müssen wir einen anderen Weg für die sichere Einrichtung finden. Sonst droht, wie im vorherigen Beitrag erwähnt, wieder ein Absturz, wenn wir bereits das LSB in $0318 geändert haben und ausgerechnet dann ein NMI auftritt. NMIs lassen sich über das Register 13 des CIA-2 unterbinden. Ihr findet es an der Adresse $dd0d. Wer jetzt denkt, es würde reichen dort einfach eine 0 einzutragen, der irrt. Das Register ist (so wie auch das Gegenstück, an der Adresse $dc0d, vom CIA-1) sehr speziell. Beim Schreiben bestimmt ihr über Bit-7, ob ihr die Bits auf 0 oder 1 setzen möchtet. Dann setzt ihr die Bits 4-0 auf 1 (die BITs 6 & 5 sind unbenutzt), die ihr auf den Wert aus BIT-7 ändern möchtet. Wir merken uns die aktuellen Interrupts des CIA-2 im X-Register und deaktivieren dann alle. Jetzt können wir die Adresse unserer NMI-Routine im RAM-Vector für den NMI ab $0318 eintragen. Wir möchten den Timer-A des CIA-2 verwenden, also legen wir an den Adressen $dd04 / $dd05 das LO- & HI-Byte für unseren Timer ab. Hier könnt ihr später mit anderen Intervallen testen, wie die sich auf die Rahmenfarbe auswirken. Jetzt müssen wir noch den Timer starten, das erreichen wir durchs setzten von BIT-0 im 14. Register $dd0e des CIA-2. Die anderen Bits setzen wir auf 0, somit beginnt unser Timer immer wieder von vorne. Jetzt sind wir fertig und müssen die NMIs wieder erlauben. Also holen wir die gemerkten Interrupts aus dem X-Register in den Akku und setzen BIT-0, damit wird es dem Timer-A erlaubt Interrupts auszulösen (hier NMIs). Denkt dran, dass wir wieder das Register 13 beschreiben und daher auch Bit-7 auf 1 setzen müssen!
Um die Auswirkung von SEI/CLI zu beobachten, richten wir noch einen eigenen IRQ fest.
;*** Einen kleinen 'standard' IRQ zum Vergleich einrichten sei ;IRQs sperren lda #<irq ;LSB unserer Routine sta $0314 ;in den RAM-Vector lda #>irq ;MSB sta $0315 ;in den RAM-Vector lda #%01111111 ;Spalte mit SPACE sta $dc00 ;im CIA-1 wählen ;*** Um den Unterschied NMI <-> IRQ kenntlich zu machen loop lda $dc01 ;warten wir, and #%00010000 ;bis SPACE gedrückt wurde, ;bevor wir die Interrupts bne loop ;mit CLI freigeben cli ;ab jetzt läuft auch unser IRQ rts ;zurück zum BASIC
Wir sperren die Interrupts mit SEI und verbiegen dann den RAM-Vector. Um gleich die SPACE-Taste abzufragen, wählen wir dann noch die richtige „Spalte“ im CIA-1 (s. Tastaturmatrix). Die Schleife ab loop dient nun dazu, zu demonstrieren, dass der NMI läuft (der Rahmen blinkt), der IRQ aber nicht. Was kein Wunder ist, da wir die Interrupts ja noch unterbunden haben. Sobald ihr die SPACE-Taste betätigt, erlauben wir die Interrupts duch CLI wieder und kehren zum BASIC zurück. Hier könnt ihr nun (obwohl es durchs Blinken eigentlich kaum möglich ist) weiterarbeiten und z. B. etwas programmieren.
Wir benötigen noch unsere beiden Interrupt-Routinen, fangen wir mit dem irq an, der besteht nur aus zwei Zeilen.
*=$1000 irq inc $d021 ; jmp irq ;zum Test einkommentieren jmp $ea31
Das solltet nichts Neues für euch sein. Wichtig ist aber wieder, dass wir die Interrupt-Routinen erst ab $1000 oder später ablegen, damit wir etwas Platz für ein BASIC-Programm haben, schaut euch ggf. nochmal den letzten Beitrag an. Wenn ihr wollt, könnt ihr später auch ; jmp irq einkommentieren, um nochmal zu sehen, dass NMIs auch auftreten, während ein IRQ läuft.
Abschließend brauchen wir noch unseren NMI.
nmi pha ;Akku txa ;X-Register pha tya ;und Y-Reg pha ;auf dem Stack merken lda $dd0d ;IRQs bestätigen and #%00000001 ;Timer IRQ? beq exit ;wenn nicht -> ENDE inc $d020 ;sonst Hintergrundfarbe ändern exit pla ;Register vom Stack holen tay pla tax pla rti ;und zurück
Da die ROM-Routine den Akku und die Register nicht sichert, kümmern wir uns zu Beginn darum und legen diese auf den Stack. Dann müssen wir den Interrupt bestätigen, dies geschieht einfach durchs lesen von Register 13 $dd0d. Wir prüfen anschließend, ob es sich um den Timer-Interrupt gehandet hat, falls nicht verlassen wir den NMI, sonst erhöhen wir die Rahmenfarbe. Beim Verlassen holen wir den Akku und die Register vom Stack und kehren per RTI zurück zum unterbrochenen Programm.
Da wir hier nicht zur ROM-Routine springen, werden SYSTEM-NMIs erfolglosbleiben. Beachtet das beim Testen bitte.
Vorsicht beim Turbo Assembler, da wir hier den NMI umbiegen, kommt ihr mit RESTORE nicht mehr zurück zum Sourcecode!
Wenn ihr das Programm nun startet, dann sollte zunächst nur der Rahmenblinken. Sobald ihr SPACE betätigt werden die IRQs erlaubt und auch die Hintergrundfarbe ändert sich. Außerdem kehren wir dann auch zurück ins BASIC.
*=$0801 ;*** Startadresse BASIC-Zeile: 2018 SYS 2064:NEW !word main-2, 2018 !byte $9e !text " 2064:" !byte $a2,$00,$00,$00 main ldx $dd0d ;aktuelle IRQs des CIA-2 merken lda #%01111111 ;und dann alle sta $dd0d ;deaktivieren lda #<nmi ;Adresse des NMI sta $0318 ;in den RAM-Vector schreiben lda #>nmi sta $0319 lda #$f4 ;LO-BYTE für sta $dd04 ;Timer-A vom CIA-2 setzen lda #$00 ;und auch das HI-BYTE sta $dd05 ;setzen lda #%00000001 ;Timer-A im CIA-2 sta $dd0e ;starten txa ;gemerkte IRQs vom CIA-2 in den Akku ora #%10000001 ;alle IRQs + dem für Timer-A sta $dd0d ;wieder aktivieren ;*** Einen kleinen 'standard' IRQ zum Vergleich einrichten sei ;IRQs sperren lda #<irq ;LSB unserer Routine sta $0314 ;in den RAM-Vector lda #>irq ;MSB sta $0315 ;in den RAM-Vector lda #%01111111 ;Spalte mit SPACE sta $dc00 ;im CIA-1 wählen ;*** Um den Unterschied NMI <-> IRQ kenntlich zu machen loop lda $dc01 ;warten wir, and #%00010000 ;bis SPACE gedrückt wurde, ;bevor wir die Interrupts bne loop ;mit CLI freigeben cli ;ab jetzt läuft auch unser IRQ rts ;zurück zum BASIC *=$1000 irq inc $d021 ; jmp irq ;zum Test einkommentieren jmp $ea31 nmi pha ;Akku txa ;X-Register pha tya ;und Y-Reg pha ;auf dem Stack merken lda $dd0d ;IRQs bestätigen and #%00000001 ;Timer IRQ? beq exit ;wenn nicht -> ENDE inc $d020 ;sonst Hintergrundfarbe ändern exit pla ;Register vom Stack holen tay pla tax pla rti ;und zurück
So, dass soll es zu den NMIs gewesen sein. Ihr könnt jetzt mit IRQs und NMIs etwas experimentieren, bevor wir als nächstes endlich zu den Rasterzeileninterrupts kommen.
Wie genau wird das Space abgefragt, wenn wir nur XOR für #ff in 0xdc01 machen? Steige nicht ganz dahinter.
Gut aufgepasst!
Da habe ich es mir wirklich sehr einfach gemacht. In der Tat wird nicht nur auf SPACE reagiert, jede Taste in der Spalte PA7 (s. Tastaturmatix) lässt das Programm weiterlaufen.
Sorry, für die Verwirrung.
Um diese zukünftig zu vermeiden, habe ich das Beispiel angepasst, sodass jetzt wirklich nur die SPACE-Taste beachtet wird.
Gruß,
Jörn