Understanding 6502 assembly on the Commodore 64 - (23) The Ubiquitous Interrupt Request

     Conceptually, the interrupt at its core is easy to understand, yet rarely understood.   Before discussing an interrupt on the computer we must understand being interrupted in general.  To make this apparent we should assume that we are being interrupted from something we're doing or not doing.  We could consider not doing, the act of doing something, which is doing nothing.   Either way,  the interrupt comes as a distraction to what we were doing before we were interrupted.






Important Notice!!!!
 
     Please understand that this chapter does not belong in any beginners guide for 6502 assembly.  Originally it was supposed to.  It was supposed to be a logical continuation from using the system interrupt to generating our own.   The constraints in which ive designed it, keeping the computer functionality up and running while our counter updates in the background, is fundamentally flawed.

     I had decided to completely remove the chapter altogether, but then instead, decided to leave it included, showing why it didn't work, and how in fact it could be fixed. Consider this chapter to be more of an interesting exploration into the C64 interrupt and not a defacto standard for how to write programs for it.

     We'll discuss how and why it works, why it dies, and how it could be made to work.  I will keep the chapter in its original form and add to the bottom of it.

 continuing......





      This assumes however, that you will want, or allow the interrupt to interrupt you.  The interrupt as we have worked with, simply requests that you be interrupted, like a phone you don't answer.


  • I'M DOING SOMETHING.
  • THE PHONE RINGS.
  • I IGNORE IT.
  • I GO BACK TO WHAT I WAS DOING.


     I have chosen not to respond to the interrupt.


  • I'M DOING SOMETHING.
  • THE PHONE RINGS.
  • I ANSWER IT. HAVE A NICE CHAT.
  • I GO BACK TO WHAT I'M DOING.


     I have chosen to respond to the interrupt.



     What we have done thus far is not an interrupt...... we were just stuffing our code into an already existing interrupt.   Consider that yesterday my package arrived with replacement chips for my Apple ][ RGB card.  I know that once it arrived, I would stop whatever I was doing and replace the bad ram in the computer.   Until it arrived though, I'd just continue writing yesterdays chapter.  Following this logic, our program as written, would do the following.



  • IM WRITING THE CHAPTER.
  • SOMETHING TICKS 60 TIMES A SECOND CAUSING AN INTERRUPT TO HAPPEN.
  • WHATEVER THAT SOMETHING IS, ALONG WITH IT, I RUN TO THE DOOR TO SEE IF THE PACKAGE ARRIVED.
  • IF IT DIDNT I GO BACK TO WRITING THE CHAPTER


     I would have to be awfully fast to respond to this.  You will imagine that in almost every instance that I opened the door, nothing was there.   In that demonstration, I wasn't being interrupted, I was actively polling.  It would be a lot better if I were to do this:


  • IM WRITING THE CHAPTER
  • THE DOORBELL RINGS
  • IS IT MY PACKAGE?
  • IF YES, GO PUT THE CHIPS IN.
  • TEST THE APPLE II, GO BACK TO WRITING THE CHAPTER.
  • IF NO, CONTINUE WRITING THE CHAPTER.


     This seems to make a lot more sense.  Responding to something happening, is better then constantly checking if something is going to happen.   Considering all things now played out before us, we arrive at some conclusions.



  • I DON'T CAUSE THE INTERRUPT:

      I want something else to cause an interrupt..... I want to respond to an interrupt.



  • I WANT SOMETHING TO BE DONE WHEN THE INTERRUPT HAPPENS:

     I want to check the door when the doorbell rings and get my package if its there.



  • I CHOOSE TO DO WHAT I WAS DOING BEFORE, WHEN IM FINISHED RESPONDING:

    Nobody says I must to go back to what I was dong before the interrupt, I choose to.


     A great analogy would be an alarm clock, that we deliberately set for a particular time, to go off.  When it does, we respond to it.  Doing something, before the alarm went off.  Not only is this a good analogy, its plausible, because we can do this on our computer.  You see, it would be difficult to see if the computer caused an interrupt in an abstract way, however, the C64 does have a clock, and an alarm, and an interrupt, that occurs when the alarm goes off!!!!!

    I should note at this point that it was quite interesting writing this chapter, you see, what I wanted to do was have an interrupt that was generated and handled exactly as originally intended on the C64.    That may seem strange, but consider for a moment that with so many different ways to handle interrupts, only one way is analogous to the KERNEL, with regard to IRQ. (Not BREAK or NMI).  From this point forward, Our IRQ will follow the routine as expected by the KERNEL and written as such.



  • A MASKABLE (NOT NMI) INTERRUPT REQUEST OCCURS
  • THE PROCESSOR JUMPS TO $FFFE
  • THIS JUMPS RIGHT TO $FF48
  • THE CODE AT $FF48 STORES OUR REGISTERS AND PROCESSOR FLAGS
  • IT ALSO CHECKS TO SEE IF BRK CAUSED THE INTERRUPT
  • THE CODE JUMPS TO THE ADDRESS STORED IN $314 AND $315
  • THIS IS SET AS DEFAULT TO $EA31 OUR HOUSEKEEPING ROUTINE.


What we need is......


  • A MASKABLE (NOT NMI) INTERRUPT REQUEST OCCURS
  • THE PROCESSOR JUMPS TO $FFFE
  • THIS JUMPS RIGHT TO $FF48
  • THE CODE AT $FF48 STORES OUR REGISTERS AND PROCESSOR FLAGS
  • IT ALSO CHECKS TO SEE IF BRK CAUSED THE INTERRUPT
  • THE CODE JUMPS TO THE ADDRESS STORED IN $314 AND $315
  • THIS IS SET AS OUR CODE TO SEE IF THE INTERRUPT THAT WAS CAUSE WAS THE ONE WE WANTED.
  • IF IT IS, ACKNOWLEDGE IT, EXECUTE OUR CODE AND JUMP TO $EA31 WHEN FINISHED
  • IF IT ISNT JUMP TO $EA31 OUR HOUSEKEEPING ROUTINE.
  • EA31 RESTORES THE PROCESSOR STATUS AND REGISTERS FOR US.


Heres the kernel code for IRQ..........


.C:ff48  48          PHA               ;Store registers and processor flags
.C:ff49  8A          TXA
.C:ff4a  48          PHA
.C:ff4b  98          TYA
.C:ff4c  48          PHA
.C:ff4d  BA          TSX
.C:ff4e  BD 04 01    LDA $0104,X
.C:ff51  29 10       AND #$10          ;Was the irq caused by BRK
.C:ff53  F0 03       BEQ $FF58         ;No, jump FF58, which jumps to 0314
.C:ff55  6C 16 03    JMP ($0316)       ;Yes jump to 0316 and do BRK stuff



This is how the KERNEL of the C64 does it. As you can see, all roads take us to $0314 as far as our interrupt is concerned.  This kind of puts us back at where we started.

We can add a different memory location in $314 and $315 like we did before with our own code and jump back to $EA31. The code will be different though.  We will simply see if the Interrupt received was one of ours and act accordingly.  Since we are testing conditions, we can test for more than 1 type of interrupt and have it jump to more than one instance of code

At 314 we could say


WAS IT CAUSED BY INTERRUPT A ------> JUMP TO CODE 1 ------> JUMP TO EA31

WAS IT CAUSED BY INTERRUPT B ------> JUMP TO CODE 2 ------> JUMP TO EA31

WAS IT CAUSED BY INTERRUPT C ------> JUMP TO CODE 3 ------> JUMP TO EA31

WAS IT CAUSED BY NONE OF OUR INTERRUPTS ------> JUMP TO EA31






TOD CLOCK


     Lets talk first about what will cause our interrupt before we continue.  The TOD clock is quite nifty, it exists on the 6526 CIA chip and stores the TOD values in Decimal format. Yes its human readable!!!!

We are only going to cover what pertains to our clock, the CIA is a lot more than just a TOD.

It has an input for AM/PM, HOURS, MINUTES, SECONDS, and 10ths OF SECONDS.


TODHRS = $DC0B     ; TOD Clock Hours in BCD
TODMIN = $DC0A     ; TOD Minutes in BCD
TODSEC = $DC09     ; TOD Seconds in BCD
TODTEN = $DC08     ; TOD Tenths of seconds in BCD 

     These values not only correspond to the actual time, but the alarm as well,  just like your regular clock, you will set the alarm time with the same buttons that you set the actual time.  Your clock knows the difference because you hold down the alarm button while setting the alarm time, and you do not hold down the alarm button while setting the regular time.  The only difference on the CIA is, while you can view the regular time, by reading the locations above, you can view the alarm time, only set it.


So where is that alarm button to set the alarm time........


CIACRB = $DC0F     ; CIA Control register B

If bit 7 of this register is 0, any input into the above memory locations sets the time....
If bit 7 of this register is 1, any input into the above memory locations sets the alarm.... 


(Important Note, that the book "Mapping the Commodore 64" lists this backwards, there is a typo.)


For our purposes, we want to set the clock to 1 H, 1 M, 1 SEC, 1 Tenths.

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


     We want to set the alarm, two seconds after our set time................ 
;;;;;;;;;;;;;;;;;;; SET ALARM  
lda #%10000000
sta CIACRB          ; set alarm mode for values below, bit 7 is 1
lda #$01
sta TODHRS          ; set timer lowbyte - hours
sta TODMIN          ; set timer highbyte - minutes
lda #$02            ; 2 second
sta TODSEC          ; set timer lowbyte - seconds
lda #$01
sta TODTEN          ; set timer highbyte - tenths of second

Before we leave our startup code, we want to tell the CIA to cause an interrupt for TOD alarm


lda #%10000100      ; enable the CIA to generate an interrupt for alarm
sta CIAICR          

CIAICR is weird like this,   BIT 7 set to 1 will let us set an interrupt based on the other bits we choose, ours is bit 2 for TOD alarm.

If BIT 7 is set to 0, any other bit set to 1 will be turned to 0, effectively disabling that interrupt.


Lets look at our entire interrupt mod, and new labels

LABELS:


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

TODHRS = $DC0B     ; TOD Clock Hours in BCD
TODMIN = $DC0A     ; TOD Minutes in BCD
TODSEC = $DC09     ; TOD Seconds in BCD
TODTEN = $DC08     ; TOD Tenths of seconds in BCD
CIACRB = $DC0F     ; CIA Control register B
CIAICR = $DC0D     ; Interrupt Control Register


INTERRUPTMOD:



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

;;;;;;;;;;;;;;;;;;;SET CLOCK
lda #$01
sta CIACRB          ; set time mode for values below, bit 7 is 0
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 #%10000000
sta CIACRB          ; set alarm mode for values below, bit 7 is 1
lda #$01
sta TODHRS          ; set timer lowbyte - hours
sta TODMIN          ; set timer highbyte - minutes
lda #%02            ; 2 second
sta TODSEC          ; set timer lowbyte - seconds
lda #$01
sta TODTEN          ; set timer highbyte - tenths of second

;;;;;;;;;;;;;;;;;;; Enable interrupts

lda #%10000100      ; Enable our interrupt on BIT2 and leave all other interrupts alone
sta CIAICR          ; Store it back
cli
rts                 ; return to BASIC like nothing happened


Our new IRQ code at 0314, 0315 will simply test the interrupt and act accordingly.

WAS IT CAUSED BY OUR INTERRUPT? 
      YES: Jump to PRETEST
      NO: Jump to $EA31


IRQ:            ; This is the code that we have derailed the system to run

 ;;;;;;;;;;;;;;;;;;; displays the clock on the screen
lda TODMIN     
adc #$30
sta $0708
lda TODSEC
adc #$30
sta $070a
lda TODTEN
adc #$30
sta $070c
lda CIAICR            ; ack the interrupt
sta $0C01
cmp #%10000001        ; was it caused by housekeeping
beq $EA31   
jmp INIT



Funny thing, checking CIAICR will acknowledge our interrupt and cause it to clear,  it will also clear any other interrupt.  Ultimately CIAICR will be the downfall of the program, well see why later.






Our Final (broken) 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
OLDBYTE = $FD              ; Store our old byte here to test against new one
IRQLO  =$0314              ; LSB of interrupt vector
IRQHI =$0315               ; MSB of interrupt vector
CHROUT = $FFD2             ; Character out

;;;;;;;;;;;;;;;;;;; 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 = $DC0B          ; TOD Clock Hours in BCD
TODMIN = $DC0A          ; TOD Minutes in BCD
TODSEC = $DC09          ; TOD Seconds in BCD
TODTEN = $DC08          ; TOD Tenths of seconds in BCD
CIACRB = $DC0F          ; CIA Control register B
CIAICR = $DC0D          ; 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 #$20
stx OFFSET            ; Store our inital offset for Y into OFFSET
ldx #$00

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 accomidate 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 0314
lda #>IRQ            ; Load the end location of our interrupt code into INTHI
sta IRQHI            ; Store in 0315

;;;;;;;;;;;;;;;;;;;SET CLOCK
lda CIACRB
and #%01111111           ; shut off bit 7
sta CIACRB               ; 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 CIACRB
ora #%10000000          ; set alarm mode for values below, bit 7 is 1
sta CIACRB              ; set alarm mode for values below, bit 7 is 1
lda #$01
sta TODHRS              ; set timer lowbyte - hours
sta TODMIN              ; set timer highbyte - minutes
lda #$02                ; 2 second
sta TODSEC              ; set timer lowbyte - seconds
lda #$01
sta TODTEN              ; set timer highbyte - tenths of second
lda #%10000100          ; Enable our interrupt on BIT2 and leave all other interrupts alone
sta CIAICR              ; Store it back
sta $0C01
cli
rts                     ; return to BASIC like nothing happened


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

 ;;;;;;;;;;;;;;;;;;; displays the clock on the screen
lda TODMIN     
adc #$30
sta $0708
lda TODSEC
adc #$30
sta $070a
lda TODTEN
adc #$30
sta $070c
lda CIAICR            ; ack the interrupt
sta $0C01
cmp #%10000001        ; was it caused by housekeeping
beq $EA31   
jmp INIT


INIT:
lda CIACRB
and #%01111111          ; shuf off bit 0 for clock SET mode
sta CIACRB              ; 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 perminantly
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 $EA81

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   



The code itself is not broken, its implementation, within the C64 kernel is flawed.  Consider that when executed, it will work according to plan, and suddenly die.  

Why?

It would seem the kernel code for housekeeping at EA31 is the culprit.  Remember, the goal was to run our code when the IRQ for TOD was flagged, but we wanted to leave the basic system running.  As the housekeeping code ends, at address EA7E, the following is executed.

EA7E.   LDA DC0D

As housekeeping ends, it acknowledges the interrupt that caused it to execute in the first place.  Unfortunately, it acknowledges ALL interrupts. 

So what happens is our TOD alarm interrupt happens while the housekeeping code is executed? The interrupt will be cleared and not serviced, the clock never resets and keeps going, never to hit the alarm again for at least another day......

Inevitably, this will happen, and that is where our updates stop.  In order to illustrate this, I've rewritten the kernels housekeeping code into the program, and the kernel code is never called.  Then I made one change to it where EA7E used to be, I've replaced the code. We take CIAICR and store it in memory address $0C01.  Instead of EA7E loading CIAICR into LDA, we load $0C01. The section HOUSEKEEPING includes the same code the kernel uses for this function.




; 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
OLDBYTE = $FD         ; Store our old byte here to test against new one
IRQLO  =$0314         ; LSB of interrupt vector
IRQHI =$0315          ; 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 = $DC0B         ; TOD Clock Hours in BCD
TODMIN = $DC0A         ; TOD Minutes in BCD
TODSEC = $DC09         ; TOD Seconds in BCD
TODTEN = $DC08         ; TOD Tenths of seconds in BCD
CIACRB = $DC0F         ; CIA Control register B
CIAICR = $DC0D         ; 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 #$20
stx OFFSET            ; Store our inital offset for Y into OFFSET
ldx #$00

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 accomidate 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

sta IRQLO          ; Store in 0314
lda #>IRQ       ; Load the end location of our interrupt code into INTHI
sta IRQHI          ; Store in 0315

;;;;;;;;;;;;;;;;;;;SET CLOCK
lda CIACRB
and #%01111111         ; shut off bit 7
sta CIACRB             ; 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 CIACRB
ora #%10000000          ; set alarm mode for values below, bit 7 is 1
sta CIACRB              ; set alarm mode for values below, bit 7 is 1
lda #$01
sta TODHRS              ; set timer lowbyte - hours
sta TODMIN              ; set timer highbyte - minutes
lda #$02                ; 2 second
sta TODSEC              ; set timer lowbyte - seconds
lda #$01
sta TODTEN              ; set timer highbyte - tenths of second
lda #%10000100          ; Enable our interrupt on BIT2 and leave all other interrupts alone
sta CIAICR              ; Store it back
sta $0C01
cli
rts                     ; return to BASIC like nothing happened


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

 ;;;;;;;;;;;;;;;;;;; displays the clock on the screen
lda TODMIN      
adc #$30
sta $0708
lda TODSEC
adc #$30
sta $070a
lda TODTEN
adc #$30
sta $070c
lda CIAICR            ; ack the interrupt
sta $0C01
cmp #%10000001        ; was it caused by housekeeping
beq HOUSEKEEPING   
jmp INIT

HOUSEKEEPING: ; from the kernel code at EA31 , modified lda dc0d to lda 0C01 on last line
jsr $FFEA
lda $CC
bne jump1
dec $CD
bne jump1
lda #$14
sta $CD
ldy $D3
lsr $CF
ldx $0287
lda ($D1),y
bcs jump2
inc $CF
sta $CE
jsr $EA24
lda ($F3),y
sta $0287
ldx $0286
lda $CE

jump2: ; EA5C
eor #$80
jsr $EA1C

jump1: ;EA61
lda $01
and #$10
beq jump5
ldy #$00
sty $C0
lda $01
ora #$20
bne jump3
jump5:; EA71

lda $C0
bne jump4
lda $01
and #$1F

jump3: ; EA79
sta $01

jump4: ; EA7B
jsr $EA87
lda $0C01
jmp $EA81

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; END KERNEL HOUSEKEEPING CODE

INIT:
lda CIACRB
and #%01111111            ; shuf off bit 0 for clock SET mode
sta CIACRB                ; 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 perminantly
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 $EA81

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 - (24) A better way with NMI


Table of contents

No comments:

Post a Comment