In the last few chapters we've had to get through a lot of dry, but necessary stuff. I figured since this is chapter 20, well do something fun. Just about every 60th of a second, everything we do it put on hold for the computer to run its own housekeeping chores. Cursor blink, keyboard input check, screen refresh, that sort of stuff. To the user it is transparent, as if the 6502 is multitasking and running code in the background. Arguably it is, but it isn't. But it is the stuff of genius how this is accomplished.
Don't get overly excited, we're not here to do that super cool DEMO's today, actually we're not even causing an interrupt. What we are going to do as our introduction to interrupts will be cool none the less. I will point out that what we are doing for the demonstration, is an almost criminally poor use of processor cycles. Well get the initial code going, we could then optimize it, and maybe add some better features.
The usual events of the processor would be generically explained as:
IRQ signal pulses every 1/60th of a second
Upon receiving the signal at the processor the computer backs up the registers and status flags
It jumps to $EA31 where it runs the usual code for housekeeping
It restores the registers and processor flags
It gives control back to the user
What WE are going to do is reroute the computer to run our code first and then run the normal housekeeping duties. In doing so, our code will run 60 times a second in the background. Wow, 60 times a second, thats a lot of extra code. We will use our super Zero Page optimized binary to hex converter for this. The one we took advantage of zero page for, in this example. We will start by implementing this in the WORST possible manner, so you can see what not to do, then we will add some features, because its fun, then we will talk about how it can be optimized to significantly reduce processor usage.
In chapter 15 Understanding 6502 assembly on the Commodore 64 - (15) The Zero Page, we ran an optimized version of our binary converter, which converted $55 and displayed it on the screen.
; C64 Hex to Binary display converter optimized
; 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.
*=$C000 ; SYS 49152 to begin
OURHEXVALUE = #$55 ; 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
BIT7 = $0708 ; This is the location of the 7th bit, required room for
; 8 contiguous bytes after the starting address
; using 0708 dumps it right to screen ram, bottom center
nop
INIT:
lda OURHEXVALUE ; this will be out test number
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
cmp #$00 ; is the result 00?
bne STORE1 ; No, jsr to STORE1
lda #$30 ; Load the display value of 0 into A
jmp CONTINUE ; jump to CONTINUE
STORE1:
lda #$31 ; Load the display value of 1 into A
CONTINUE:
sta BIT7,x ; Load the display value into A
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
brk
First, I can't fathom NOP running 60 times a second for no reason, were going to remark that one out. also, we learned in the status flags that CMP #$00 directly after AND TESTBYTE in CONVERTION is no longer necessary because AND TESTBYTE already set the flag. This was left in because until you learned about the processor flags, you would have no idea how AND led to BNE.
Good, now that we've made those changes, we can continue with the actual work.
When an interrupt occurs, and everything starts moving along, the system needs to know where the starting point is to execute the housekeeping code. The address for this code happens to be $EA31 on the C64. The system knows this, because it looks in memory locations $314 and $315 for this info.
Having a look in $0314 shows $31, and in $0315 shows $EA. (little endian) for a loading address of EA31.
So if we could change these values to point to our code, instead of EA31, we could have our program execute, instead of the housekeeping code. Then, at the end of our program, have it JMP to $EA31, to continue the housekeeping code when our code is done.
Were going to need some new labels up top.
IRQLO =$0314 ; LSB of interrupt vector
IRQHI =$0315 ; MSB of interrupt vector
These are the areas we will populate our codes memory address with. Well, before we do this, we can't have the interrupts running AS we modify the vector, we will need to shut off the interrupts for a moment, just to add our code, and then turn it back on.
sei ; Disable interrupts
CHANGE OUR INTERRUPT VECTORS
cli ; restore interrupts
rts ; return to BASIC like nothing happened
What's with the RTS you say???? Remember WE are not running the program, the interrupt code is. We want to return to basic and do nothing, having the code execute in the background.
Were going to need to tell the system the exact memory location of the code to execute within our program. However, should we modify our program down the road, the memory locations will all shift, and it will execute at the wrong place, were going to need a new type of labeling convention. and a solid starting point. We will name our starting point for the code IRQ:
IRQ: ; This is the code that we have derailed the sysytem to run.
jsr INIT ; Essentially, run out Binary converter program once.
; program will end and rts will bring it to right here.
jmp $EA31 ; Send the processor back to normally scheduled interrupt.
Within IRQ we say, JSR to INIT (Our actual program). When it returns, JMP to the normal housekeeping code. Thats it! Nothing more. Real Simple.
Remember that interrupt vector? It MUST point to the memory location for the first instruction in IRQ: We could hunt down the exact memory location of IRQ:, but for all of our efforts, that location will change if we ever modify the code. Instead we use #<IRQ to get the LSB and #>IRQ to get the MSB of IRQ:'s location.
sei ; Disable interrupts
lda #<IRQ ; Load the beginning location of our interrupt code into IRQLO
sta IRQLO ; Store in 0314
lda #>IRQ ; Load the end location of our interrupt code into IRQHI
sta IRQHI ; Store in 0315
cli ; restore interrupts
rts ; return to BASIC like nothing happened
Be advised that in our original program we had a BRK at the very bottom, so we could see our results without BASIC erasing it. This would not be a good thing, because it would halt the code, whilst inside an interrupt routine and crash the system, so we replace it with RTS. Since BASIC is just sitting there running, it won't be initialized after our code and clear the screen, it just sits there.
Also, before I forget, OURHEXVALUE is set to #$55. Since this is a constant it will be very boring to look at, updating with the same value 60 times a second. Instead lets use a memory address to load instead of a hex value, a busy memory address, like system clock.
OURHEXVALUE = $A1 ; Enter the Hex value to be converted here
If you want to like in the fast lane, change the value to $A2 instead.
Our Modified Code.....................
; C64 Hex to Binary display converter in an interrupt
; 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.
*=$C000 ; SYS 49152 to begin
OURHEXVALUE = $A1 ; 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 = $0314 ; LSB of interrupt vector
IRQHI = $0315 ; MSB of interrupt vector
BIT7 = $0708 ; This is the location of the 7th bit, required room for
; 8 contiguous bytes after the starting address
; using 0708 dumps it right to screen ram, bottom center
sei ; Disable interrupts
lda #<IRQ ; Load the LSB of IRQ into IRQLO
sta IRQLO ; Store in 0314
lda #>IRQ ; Load the MSB of IRQ into IRQHI
sta IRQHI ; Store in 0315
cli ; restore interrupts
rts ; return to BASIC like nothing happened
IRQ: ; This is the code that we have derailed the sysytem to run
jsr INIT ; Essentially, run out Binary converter program once
; program will end and rts will bring it to right here.
jmp $EA31 ; Send the processor back to normally scheduled interrupt
INIT:
lda OURHEXVALUE ; this will be out test number
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 #$30 ; Load the display value of 0 into A
jmp CONTINUE ; jump to CONTINUE
STORE1:
lda #$31 ; Load the display value of 1 into A
CONTINUE:
sta BIT7,x ; Load the display value into A
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
rts
The problem here should be apparent. What if the value didn't, change, we are still running all of the conversion code 60 times a second!!! I know, we'll deal with that later.
Table of contents
Don't get overly excited, we're not here to do that super cool DEMO's today, actually we're not even causing an interrupt. What we are going to do as our introduction to interrupts will be cool none the less. I will point out that what we are doing for the demonstration, is an almost criminally poor use of processor cycles. Well get the initial code going, we could then optimize it, and maybe add some better features.
The usual events of the processor would be generically explained as:
IRQ signal pulses every 1/60th of a second
Upon receiving the signal at the processor the computer backs up the registers and status flags
It jumps to $EA31 where it runs the usual code for housekeeping
It restores the registers and processor flags
It gives control back to the user
What WE are going to do is reroute the computer to run our code first and then run the normal housekeeping duties. In doing so, our code will run 60 times a second in the background. Wow, 60 times a second, thats a lot of extra code. We will use our super Zero Page optimized binary to hex converter for this. The one we took advantage of zero page for, in this example. We will start by implementing this in the WORST possible manner, so you can see what not to do, then we will add some features, because its fun, then we will talk about how it can be optimized to significantly reduce processor usage.
In chapter 15 Understanding 6502 assembly on the Commodore 64 - (15) The Zero Page, we ran an optimized version of our binary converter, which converted $55 and displayed it on the screen.
; C64 Hex to Binary display converter optimized
; 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.
*=$C000 ; SYS 49152 to begin
OURHEXVALUE = #$55 ; 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
BIT7 = $0708 ; This is the location of the 7th bit, required room for
; 8 contiguous bytes after the starting address
; using 0708 dumps it right to screen ram, bottom center
nop
INIT:
lda OURHEXVALUE ; this will be out test number
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
cmp #$00 ; is the result 00?
bne STORE1 ; No, jsr to STORE1
lda #$30 ; Load the display value of 0 into A
jmp CONTINUE ; jump to CONTINUE
STORE1:
lda #$31 ; Load the display value of 1 into A
CONTINUE:
sta BIT7,x ; Load the display value into A
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
brk
First, I can't fathom NOP running 60 times a second for no reason, were going to remark that one out. also, we learned in the status flags that CMP #$00 directly after AND TESTBYTE in CONVERTION is no longer necessary because AND TESTBYTE already set the flag. This was left in because until you learned about the processor flags, you would have no idea how AND led to BNE.
Good, now that we've made those changes, we can continue with the actual work.
When an interrupt occurs, and everything starts moving along, the system needs to know where the starting point is to execute the housekeeping code. The address for this code happens to be $EA31 on the C64. The system knows this, because it looks in memory locations $314 and $315 for this info.
Having a look in $0314 shows $31, and in $0315 shows $EA. (little endian) for a loading address of EA31.
So if we could change these values to point to our code, instead of EA31, we could have our program execute, instead of the housekeeping code. Then, at the end of our program, have it JMP to $EA31, to continue the housekeeping code when our code is done.
Were going to need some new labels up top.
IRQLO =$0314 ; LSB of interrupt vector
IRQHI =$0315 ; MSB of interrupt vector
These are the areas we will populate our codes memory address with. Well, before we do this, we can't have the interrupts running AS we modify the vector, we will need to shut off the interrupts for a moment, just to add our code, and then turn it back on.
sei ; Disable interrupts
CHANGE OUR INTERRUPT VECTORS
cli ; restore interrupts
rts ; return to BASIC like nothing happened
What's with the RTS you say???? Remember WE are not running the program, the interrupt code is. We want to return to basic and do nothing, having the code execute in the background.
Were going to need to tell the system the exact memory location of the code to execute within our program. However, should we modify our program down the road, the memory locations will all shift, and it will execute at the wrong place, were going to need a new type of labeling convention. and a solid starting point. We will name our starting point for the code IRQ:
IRQ: ; This is the code that we have derailed the sysytem to run.
jsr INIT ; Essentially, run out Binary converter program once.
; program will end and rts will bring it to right here.
jmp $EA31 ; Send the processor back to normally scheduled interrupt.
Within IRQ we say, JSR to INIT (Our actual program). When it returns, JMP to the normal housekeeping code. Thats it! Nothing more. Real Simple.
Remember that interrupt vector? It MUST point to the memory location for the first instruction in IRQ: We could hunt down the exact memory location of IRQ:, but for all of our efforts, that location will change if we ever modify the code. Instead we use #<
sei ; Disable interrupts
lda #<IRQ ; Load the beginning location of our interrupt code into IRQLO
sta IRQLO ; Store in 0314
lda #>IRQ ; Load the end location of our interrupt code into IRQHI
sta IRQHI ; Store in 0315
cli ; restore interrupts
rts ; return to BASIC like nothing happened
Be advised that in our original program we had a BRK at the very bottom, so we could see our results without BASIC erasing it. This would not be a good thing, because it would halt the code, whilst inside an interrupt routine and crash the system, so we replace it with RTS. Since BASIC is just sitting there running, it won't be initialized after our code and clear the screen, it just sits there.
Also, before I forget, OURHEXVALUE is set to #$55. Since this is a constant it will be very boring to look at, updating with the same value 60 times a second. Instead lets use a memory address to load instead of a hex value, a busy memory address, like system clock.
OURHEXVALUE = $A1 ; Enter the Hex value to be converted here
If you want to like in the fast lane, change the value to $A2 instead.
Our Modified Code.....................
; C64 Hex to Binary display converter in an interrupt
; 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.
*=$C000 ; SYS 49152 to begin
OURHEXVALUE = $A1 ; 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 = $0314 ; LSB of interrupt vector
IRQHI = $0315 ; MSB of interrupt vector
BIT7 = $0708 ; This is the location of the 7th bit, required room for
; 8 contiguous bytes after the starting address
; using 0708 dumps it right to screen ram, bottom center
sei ; Disable interrupts
lda #<IRQ ; Load the LSB of IRQ into IRQLO
sta IRQLO ; Store in 0314
lda #>IRQ ; Load the MSB of IRQ into IRQHI
sta IRQHI ; Store in 0315
cli ; restore interrupts
rts ; return to BASIC like nothing happened
IRQ: ; This is the code that we have derailed the sysytem to run
jsr INIT ; Essentially, run out Binary converter program once
; program will end and rts will bring it to right here.
jmp $EA31 ; Send the processor back to normally scheduled interrupt
INIT:
lda OURHEXVALUE ; this will be out test number
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 #$30 ; Load the display value of 0 into A
jmp CONTINUE ; jump to CONTINUE
STORE1:
lda #$31 ; Load the display value of 1 into A
CONTINUE:
sta BIT7,x ; Load the display value into A
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
rts
The problem here should be apparent. What if the value didn't, change, we are still running all of the conversion code 60 times a second!!! I know, we'll deal with that later.
NEXT----->
Table of contents
No comments:
Post a Comment