Erstellt: 19. Juni 2013 (zuletzt geändert: 29. Oktober 2019)

Ein erster NMI

Den NMI hält keiner auf

C64 Studio, AMCE & TASM

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
NMI und IRQ in Aktion.
NMI und IRQ in Aktion.

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.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (8 Bewertungen | Ø 5,00 von 5 | 100,00%)

Loading...


ZurückWeiter

2 Gedanken zu „Ein erster NMI“

    1. 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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Protected by WP Anti Spam