Monday, December 15, 2014

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

We've steered of course long enough.  Its time to get back to grass roots.  One of these days I will get all of these chapters in order because addressing modes should definitely be in the first 10.









     The addressing modes are a fundamental part of 6502 programming,  even though we have been using several different addressing modes, we haven't stopped to discuss them. This chapter will be dedicated to them.



NO ADDRESS / IMPLIED - SIZE [1] BYTE

    This would be the easiest to understand.  Not every instruction references and address.  We have used many in this fashion. Note that in our programs, nothing comes after these instructions. They sit alone, the processor will not expect any value after receiving these opcodes.

     The syntax constitutes a single BYTE instruction only.

00 - BRK
60 - RTS
E8 - INX
AA - TAX
EA - NOP (of course)

     I'm not going to list all of them, but you get the point. We can group these in a type of instruction that doesn't address a memory location of any type.  This is not entirely true, but on the surface for our purpose, this is the case.  No memory address is referenced after the instruction.

     Consider TAX transfers the value of A to X.  The movement of the BYTE to the associated location is implied in the opcode itself.  INX, ROL, and other opcodes will most definitely have an effect of the stored value in their registers.

E8 - INX - would effect the value in X
A8 - TAY - would effect the value in Y
2A - ROL - would effect the value in A

Note an exception here, ROL in this mode is 2A.  ROL can exist in other modes but the hex value wont be 2A.  ROL and other opcodes have multiple Hex values based on the mode they use.



IMMEDIATE MODE - SIZE [2] BYTES

     This is another one we've ofter used.  Consider if we want to load a value into A.  That value would not exceed one byte, as the maximum value that came be stored in A,X or Y is 8 bits.  This means anything from $00 to $FF. So were looking at a one byte opcode, and a one byte value.

A9 55 - LDA #$55

Here we have LDA, which obviously takes a argument...........  note the # before the value denotes an actual value and not a memory address.

LOAD A..... Load A with what? LDA can not exist alone, an argument is expected.  In this instance we load A with $55.  Sure LDA can have more than one addressing mode, in this instance, with one byte it is immediately addressed.
Again A9 is LDA when using this mode, as such, A9 will only expect 1 byte after it.  From the programmer perspective, LDA #$55 will compile and produce A9 55 becuause the compiler does that work for us. But its good to know.



 ABSOLUTE MODE - SIZE [3] BYTES

    Just like Jim did, were going to go out of order here (size wise) the reason will become apparent.   In absolute mode we reference a NON zero page address. Not a value!!!!! an address. Consider the following......


AD 00 0C - LDA $0C00

     In this mode we reference a 16 BIT memory address, and in this case we load the contents of 0C00 into the A register.  Just the same, STA 0C00 would store the value in A to $0C00.  We see in this case that LDA is not A9 but AD.


8D 00 0C - STA $0C00


     Remember that the values in hex are little Endian, hence 00 0C and not 0C00.  In both cases, AD and 8D will expect 2 bytes after it.



ZERO PAGE MODE - SIZE [2] BYTES

    Just like absolute mode, zero page mode references a 16 bit memory address, however it does so with only 8 bits.  It does so only because the processor was designed to forgo the first two zeros, as discussed in Chapter 15 - the zero page. This naturally save program size, and processor cycles. 

In the case of......

It would be wasteful and unnecessary, and by many compilers, not allowed to:

AD CE 00 - LDA $00CE   (this was personally painful to write)

when you could simply do:

A5 CE - LDA $CE




     Now it's time to go over the next group, the indexed group.  It is permissible to allow the value after the opcode to be affected by a value in one of the other existing registers. We will go over these as well.  You will be surprised (or not surprised) to note that we have emplyed these too in previous code.



  ABSOLUTE INDEXED MODE - SIZE [3] BYTES
 
      There are rules associated with the following modes, so they are not entirely straight forward, however the concept is easily understood.  we will start with absolute indexed.  Consider the following scenartio where X=05:



BD 00 C0 - LDA 0C00,X


     The first thing you might notice is the fact that with the added information our line is still 3 bytes long.  If you notice the change in the hex for the opcode is now BD.  It is BD that dictates that the value will be indexed by X.  Consider if we were doing the same thing but indexing with Y.


B9 00 C0 - LDA 0C00,Y


Getting back though, in our initial example with X having the value of 5:


BD 00 C0 - LDA 0C00,X


     The effective value would load the value of $0C05 into A.  There's no trickery here, its quite easy to understand, there are however some limitations.  The first should be painfully obvious. X can only hold a value between 00 and FF, so from our initial value, the furthest you could index would be 0CFF. Also, there is no indexing backwards, that is to say there is no negative X, only forwards. 

    Another thing to consider is wraparound.  If the index is such that the final value would exceed FFFF, the count will wrap around to 0000 and keep going.

Consider where X = 5

BD FE FF - LDA FFFE,X

     The address you will arrive at would be 0003.  Another thing worth noting that should the value produced by indexing leave the current 256 byte memory page boundary, it will come at the cost of an extra processor cycle.  Lastly, different opcodes support different registers as an index, not all will support X, or Y or A or a combination of the registers.


 

ZERO PAGE INDEXED MODE - SIZE [2] BYTES

     Very much the same as absolute indexed mode.  Where X =5:

B5 CE - LDA $CE,X

This would result in an address of D3.  One important note for zero page is that any attempts to exceed memory address FF will cause a wraparound to 0000.  For instance, where X = 5:

B5 FE - LDA $FE,X

Will result in a memory location of 03.  The jury is still out on weather this is a feature or a bug.  The wraparound at FFFF is obvious but the one at 00FF, is somewhat less obvious.  it is however possible to use absolute indexed mode on the zero page area to avoid this wraparound as shown below, where X = 5:


BD FE 00 - LDA 00FE,X

The result this time is address 0103.

 

 INDIRECT ABSOLUTE MODE - SIZE [3] BYTES
 
      This mode only exists with JMP, so you will not find it associated with any other opcode.  Its a bit weird, but easy to understand, and can be quite useful.


Consider:

4C 00 0C - JMP 0C00

The program would jump to 0C00 and continue execution, however:

6C 00 0C - JMP (0C00)

Now we have something entirely different!!!!!

     This is entirely dependant on the values located in memory addresses 0C00 and 0C01. So were going to have to assume some values here. Keeping in mind that the value is little endian.  So if:

0C00 has $22
0C01 has $EA

6C 00 0C - JMP (0C00) will take us to address EA22.


     There is, as you might expect a pitfall here to watch out for.  Never use the last byte of a memory page for this instruction, for example:


6C FF 0C - JMP (0CFF)




     This will not use 0CFF and 0D00!!! it will use 0CFF and 0C00, another wraparound to watch out for. Just avoid it.  Just a note, this jump was used by the kernel for our irq vector at $0314




 INDIRECT INDEXED MODE - SIZE [2] BYTES

     This mode represents a combination of the technique found in indirect absolute mode and combines it with indexed mode. Where Y = 5, EA = 11 and EB = 22:

B1 EA - LDA ($EA),Y

     We will get 11 and 22 from EA for a value of $2211 and then add 5 to it for a final memory location of $2216.
 



INDEXED INDIRECT MODE - SIZE [2] BYTES

     This one is similar to indirect indexed mode, but the order of operation is different.  Consider the following where X = 5, EF = 11 and F0 = 22:


A1 EA - LDA ($EA,X)

     We will  add X to EA to arrive at a value of $EF.  If we look at the values in EF and F0 we get 11 and 22 for a final address of $2211




RELATIVE MODE - SIZE [2] BYTES
     We've used this one quite often, this address mode is associated with branching.  It is special because it is a signed number which allows if to go to a memory address in either direction.

   Consider that we have a label called OURROUTINE.  and A is #$05.
We could say:

C9 05 - CMP #$05
F0 ?? - BEQ OURROUTINE

What we are actually doing is going forwards or backwards to where OURROUTINE may be.  6502 doesnt understand OURROUTINE, but the compiler does.

If for example,

CMP #$05 was at C005 and C006 (remember 2 bytes) and...
BEQ OURROUTINE was at C007 and C008 (also 2 bytes) and....
OURROUTINE was located at C00E.....

The jump would be 6 bytes, and calculated by our compiler as

 
C9 05 - CMP #$05
F0 06 - BEQ OURROUTINE

Remember however that this value is a signed number, and instead of a range of 0 to 256 we have a range of -128 to +127.  While gaining the ability to go forwads and backwards, we are only able to branch a maximum of 128 bytes in each direction.

To give an idea how this would work:


01 is +1
30 is +48
50 is +80
7F is +127


80 is -128
81 is - 127
82 is -126
F7 is -9
FC is -4
FF is -1



Sunday, December 14, 2014

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
                

Thursday, December 4, 2014

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