Understanding 6502 assembly on the Commodore 64 - (24) A better way with NMI

     So far, we've found a less than ideal way to implement our interrupt. So much so that I wanted to simply remove the chapter, but decided to keep it so we can make a comparison.  Lets look at a better way to do this.








     In order to keep our housekeeping processes undisturbed, we can rely upon CIA2 instead of CIA1.  CIA1 which is tied to the interrupt is heavily used for the housekeeping functions where CIA2 is tied to NMI and has nothing to do with housekeeping.  This is not to say that CIA2 is completely unused, but will be available for our purposes.  There is a caveat however, it is non maskable and also will immediately start executing our code the instance the flag goes high.


     Because we will not be running into any conflicts with the housekeeping routine, the code will be very easy to explain. 


;;;;;;;;;;;;;;;;;;;; main program memory areas

OURHEXVALUE = $A2     ; Enter the Hex value to be converted here 
OURHEXNUM = $FB       ; This is where the constant OURHEXVALUE will be stored
TESTBYTE = $FC        ; This is where our test byte will be stored for lsr
IRQLO  =$0318         ; LSB of interrupt vector
IRQHI =$0319          ; MSB of interrupt vector


 
     Notice our vector has changed to $0318 and $0319 when an NMI is flagged and jumps to FFFA which jumps to FE43 it is originally directed to FE47.  A quick note about the original routine at FE47

CHECKS IF NMI WAS CAUSED BY RS232 DEVICE
CHECKS IF NMI WAS CAUSED BY RESTORE KEY
CHECKS IF CARGRIDGE IS INSERTED
CHECKS IF STOP KEY WAS PRESSED


     If the NMI was caused by an RS232 device it will bypass all other checks and service it.  Since we are not using RS232, and at the cost of the stop and restore key, we can write the code simply as such.  There is no reason however we cant write more code to do these checks and service them if required.



;;;;;;;;;;;;;;;;;;;; Timer related memory areas

TODHRS = $DD0B             ; TOD Clock Hours in BCD
TODMIN = $DD0A             ; TOD Minutes in BCD
TODSEC = $DD09             ; TOD Seconds in BCD
TODTEN = $DD08             ; TOD Tenths of seconds in BCD
CI2CRB = $DD0F             ; CIA Control register B
CI2ICR = $DD0D             ; Interrupt Control Register



     Note that our memory areas must point to CIA2 and not CIA1. in the DDXX areas and not DCXX.  Please not that the labels have changed as well.


     None of the sprite loading code or setting the TOD clock and alarm changes, except for the few label names.


     Our IRQ code changes however, first of all we must store the processor and status flags ourselves.  The IRQ routine did this for us, NMI does not.  If we load CI2ICR into A and disable every flag except for bit 2, we can see if bit 2 is set, which would give us a value of $04.

     If bit2 is set and we have a value of 4, we jump to INIT to start our routine for the binary decoder.  If not we jump to $FEBC.  We do not have to jump to FEBC, if we wanted to write the restore from interrupt routine in our code itself.


     Here is the code for FEBC in the Kernel, as you can see we can put this into our code and forego the jump.

.C:febc  68          PLA
.C:febd  A8          TAY
.C:febe  68          PLA
.C:febf  AA          TAX
.C:fec0  68          PLA
.C:fec1  40          RTI


     Since the Jump routine will cost us extra cycles, there is a tradeoff between jumping to FEBC versus writing the code in our program,  and that is our program would save a few cycles but be several bytes larger, or extra cycles and smaller code. I chose the latter.




IRQ:            ; This is the code that we have derailed the sysytem to run
;;;;;;;;;;;;;;;;; IRQ code did this for us, with NMI we must do this within code
pha
txa
pha
tya
pha

lda CI2ICR        ; load the interrupt register for CIA2
and #% 00000100    ; disable all but bit 2
cmp #$04          ; is bit 2 TOD ALARM high
beq INIT          ; if so, goto init
jmp $FEBC         ; if not jump to restore from NMI routine




That's it! Program finished.  Here's the final code..........


; C64 Hex to Binary display converter in an interrupt optimized
; Outputs with sprites for a light bulb/ led like display
; 64TASS assembler style code for 6502
; Jordan Rubin 2014 http://technocoma.blogspot.com
;
; Takes the HEX value in OURHEXVAUE, converts it to Binary for display
; on the screen as a binary number. Only does conversion if the value
; in OURHEXVALUE has changed since the last time the program was run
; The output is reflected in the 8 sprites at the bottom, red for 0
; and green for 1.

*=$C000 ; SYS 49152 to begin

;;;;;;;;;;;;;;;;;;;; main program memory areas

OURHEXVALUE = $A2     ; Enter the Hex value to be converted here 
OURHEXNUM = $FB       ; This is where the constant OURHEXVALUE will be stored
TESTBYTE = $FC        ; This is where our test byte will be stored for lsr
IRQLO  =$0318         ; LSB of interrupt vector
IRQHI =$0319          ; MSB of interrupt vector

;;;;;;;;;;;;;;;;;;; Sprite related memory areas

SPRITEDATA = $2000     ; Sprite 0 through 7 data
SPENA = $D015          ; Sprite Enable Register
SSDP0 = $07F8          ; Sprite Shape Data Pointers
SP0X = $D000           ; Sprite 0 Horizontal Position
SP0Y = $D001           ; Sprite 0 Vertical Position
SP0COL = $D027         ; Sprite 0 Color Register
OFFSET = $0C00         ; SPRITE Y OFFSET

;;;;;;;;;;;;;;;;;;;; Timer related memory areas

TODHRS = $DD0B          ; TOD Clock Hours in BCD
TODMIN = $DD0A          ; TOD Minutes in BCD
TODSEC = $DD09          ; TOD Seconds in BCD
TODTEN = $DD08          ; TOD Tenths of seconds in BCD
CI2CRB = $DD0F          ; CIA Control register B
CI2ICR = $DD0D          ; Interrupt Control Register

;;;;;;;;;;;;;;;;;;; This begins our sprite loading and initialization code
;;;;;;;;;;;;;;;;;;; Same data for all sprites, Same color for all sprites
;;;;;;;;;;;;;;;;;;; Sprite data location pointing to the same address for
;;;;;;;;;;;;;;;;;;; all sprites.  Same Y axis for all sprites, and
;;;;;;;;;;;;;;;;;;; X axis starting at $20, staggered each one by $1F

ldx #$00
stx OFFSET            ; Store our inital offset for Y into OFFSET
SPRITELOADER:         ; Load our sprite data into memory
lda LEDDATA,x         ; All 8 sptites will use the same data
sta SPRITEDATA,x
inx
cpx #$40
bne SPRITELOADER
lda #%11111111
sta SPENA             ; Activate all SPRITES
ldx #$00

SPRITESETUP:          ; F2 red F5 green
lda #$F2
sta SP0COL,x          ; turn color red for all sprites
lda #$80
sta SSDP0,x           ; Set the location to find SPRITE0 Data
inx                   ; each sprite will point to the same data location
cpx #$08
bne SPRITESETUP
ldx #$00
ldy #$00

SPRITELOCATE:
lda #$DF
sta SP0Y,x            ; Set Y UP DOWN coordinate for SPRITE0
lda OFFSET
sta SP0X,x            ; Set X left right coordinate for SPRITE0
adc #$1F              ; We offset $1F so the sprites are not on top of each
sta OFFSET            ; other but in a row
inx
inx                   ; skip every other address to accommodate X AND Y
iny                   ; becuause SP0X is D000 and SP0Y is D001 and SP1X is D002
cpy #$08              ; etc etc
bne SPRITELOCATE

;;;;;;;;;;;;;;;;;;;; We now have 8 red sprites on the bottom of the screen

INTERRUPTMOD:
sei              ; Disable interrupts
lda #<IRQ        ; Load the beginning location of our interrupt code into INTLO
sta IRQLO        ; Store in 0318
lda #>IRQ        ; Load the end location of our interrupt code into INTHI
sta IRQHI        ; Store in 0319

;;;;;;;;;;;;;;;;;;;SET CLOCK
lda CI2CRB
and #%01111111           ; shut off bit 7
sta CI2CRB               ; set time mode for values below, bit 7 is 0
lda #$01
sta TODHRS               ; set timer hours
sta TODMIN               ; set timer minutes
sta TODSEC               ; set timer seconds
sta TODTEN               ; set timer tenths of seconds

;;;;;;;;;;;;;;;;;;; SET ALARM
lda CI2CRB
ora #%10000000          ; set alarm mode for values below, bit 7 is 1
sta CI2CRB              ; set alarm mode for values below, bit 7 is 1
lda #$01
sta TODHRS              ; set timer lowbyte - hours
sta TODMIN              ; set timer highbyte - minutes
sta TODTEN              ; set timer highbyte - tenths of second
lda #$02                ; 2 second
sta TODSEC              ; set timer lowbyte - seconds
lda #%10000100          ; Enable our interrupt on BIT2 and leave all other interrupts alone
sta CI2ICR              ; Store it back
cli
rts                     ; return to BASIC like nothing happened


;;;;;;;;;;;;;;;;;;;; THIS IS THE IRQ. SEE IF THE INTERRUPT CAME FROM OUR TOD CLOCK
                   ; READ CI2ICR, DID WE CAUSE THE INTERUUPT?
                   ; NO ---- AS IT WAS AND JMP TO FEBC
                   ; YES ---- RESET TIME TO 0 AND RUN INIT THEN
                   ; JUMP TO FEBC
IRQ:               ; This is the code that we have derailed the system to run

;;;;;;;;;;;;;;;;;;; IRQ code did this for us, with NMI we must do this within code
pha
txa
pha
tya
pha

lda CI2ICR        ; load the interrupt register for CIA2
and #% 00000100    ; disable all but bit 2
cmp #$04          ; is bit 2 TOD ALARM high
beq INIT          ; if so, goto init
jmp $FEBC         ; if not jump to restore from NMI routine

INIT:
lda CI2CRB
and #%01111111          ; shuf off bit 0 for clock SET mode
sta CI2CRB              ; set time mode for registers below
lda #$01
sta TODHRS              ; set timer lowbyte  01:01:01:01
sta TODMIN              ; set timer highbyte
sta TODSEC              ; set timer lowbyte
sta TODTEN              ; set timer highbyte
lda OURHEXVALUE         ; load current value in OURHEXVALUE
sta OURHEXNUM           ; we will store the test number here permanently
ldy #$80                ; Out first bit test for bit 7 must be 10000000 $80
sty TESTBYTE            ; store our initial test byte here
ldx #$00                ; Initialize X for our loop

CONVERTION:
lda OURHEXNUM         ; load our test hex number, this is a constant
and TESTBYTE          ; mask it with our test byte
bne STORE1            ; No, jsr to STORE1
lda #$F2              ; Load the color value of red into A
jmp CONTINUE          ; jump to CONTINUE

STORE1:
lda #$F5              ; Load the color value of green into A

CONTINUE:
sta SP0COL,x          ; Store the color to SP0COL
inx                   ; Increment X for our loop
lda TESTBYTE          ; load testbyte into A
lsr                   ; divide it by 2
sta TESTBYTE          ; store new testbyte back to its memory area
cpx #$08              ; is X=8?
bne CONVERTION        ; No, LOOP back to CONVERSION
jmp $FEBC             ; recover from NMI

LEDDATA:              ; My deathstar code
.byte $01, $FF, $80, $07, $FF, $E0, $0F, $FF, $F0, $1F, $FF, $F8
.byte $3F, $FC, $FC, $7F, $F8, $7E, $7F, $FC, $FE, $FF, $FF, $FF
.byte $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.byte $FF, $FF, $FF, $FF, $FF, $FF, $7F, $FF, $FE, $7F, $FF, $FE
.byte $3F, $FF, $FC, $1F, $FF, $F8, $0F, $FF, $F0, $07, $FF, $E0
.byte $01, $FF, $80   





NEXT----->

Understanding 6502 assembly on the Commodore 64 - (25) Address modes


Table of contents               

No comments:

Post a Comment