If you haven't guessed by now, Assembly is the ultimate strategy game. With endless possibilities to achieve the desired result, we have enjoyed the fact that up until now, we have required very little data manipulation or error checking. Our last program used a hardcoded value in a specific memory location to execute a task. It was in fact, part of the program itself. Sure you could put in whatever you wanted, but garbage in, garbage out. In fact, the only user interaction was running the program itself.
When we open up the program to allow user input, the game changes entirely! We've done this before with our Sprite program, which was quite easy though. It only recognized certain keys, and executed a function for each with no regard for sprite positions. This kept the program short and easy to read.
As a matter of fact, I only created the Binary conversion function to store the COMMAND and CONTROL registers as viewable binary called with (JSR) from another program which requires constant visibility to the command and control bits of the ACIA. It is slightly smaller, slightly harder to follow, and takes advantage of the zero page from the last chapter. Just like our program where we hard coded our value, a process populates that value from another location in memory, and still requires no human intervention.
So, we have our binary converter. We hardcoded a value into OURHEXVALUE and let the program run with it. It converted the value, and unceremoniously terminated. The question becomes, "How much more complex would it be to allow for a user to input the hex value on the keyboard, and then convert it?"
The answer is, much more. Let us look at the easiest scenario! If you can't write it on paper as a concept, you can't program it.
1. We clear the screen
2. We create, starting at the top left small banner that says "Enter a hex value: "
3. We provide a flashing cursor as a prompt for input of two characters.
4. We provide code to ultimately put these two characters into OURHEXVALUE
5. We run our existing code to convert the value, and output it
6. We repeat the process
1. Not much to change here, but there are things we are going to need to add. Let me point out here that any program like this was not written top to bottom. It sort of evolves as you meet specific goals.
Im going to attempt to construct this based on our previous program.
INIT:
lda #$93
jsr CHROUT ; Load 147, hex $93 into A reg and JSR to CHROUT
We've done this clear screen technique before..... but not in this program, were going to add a label up top to our existing labels for CHROUT, note we removed OURHEXVALUE. There is no need for it since the user will be inputing that value. Make sure we add our start routine after init. In this manner INIT will only run once when the program loads, START: Will be the beginning of each instance of the program.
*=$C000 ; SYS 49152 to begin
OURHEXNUM = $033C ; This is where the constant OURHEXVALUE will be stored
TESTBYTE = $0345 ; 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
CHROUT = $FFD2 ; Output character kernel routine
Here we've met our first goal. The screen is now clear.
2. So now that the screen is clear we want to write out a label to the screen to instruct the user. This will once again be using the CHROUT, though there are much more efficient ways to do this however, using a technique we employed to load sprite data, we can use the very same loop to print to the screen.
Consider BASIC:
10 CLS
20 PRINT " ENTER A HEX VALUE: "
After running our clear screen function in step one, the screen is cleared and the cursor is at the top left. Under INIT:, we'll create a label called START:
As previously mentioned, INIT is to be called but once. START is called each time the program runs.
START:
ldx #$00 ; Initialize X to 0 for our label
jsr WRITELABEL ; JSR to WRITELABEL
Were going to need a label generation loop, and data for that loop to use.
WRITELABEL: ; Prints our label and returns
lda HELLO,x
jsr CHROUT
inx
cpx #$13
bne WRITELABEL
rts
HELLO: .text 'enter a hex value: ' ; $13 bytes
The HELLO is sitting just after the end of our program on the bottom just like our Sprite data from previous chapters. Using X as the current letter position, starting at 0, we can load the byte into A and then run a jsr CHROUT to print that letter to the screen. The space after the colon is a valid space byte and will give us an actual space between the text and the cursor starting position. This will loop $13[hex] times, each time incrementing X for the next byte and ultimately RTS back from where it was called in START.
With that, step two is finished.......
3. When executing machine code in the C64, the blinking cursor will shut off by design, saving unnecessarily wasted processor cycles if not needed. However we would like to use it as a prompt, so within the code, we would like to turn it back on. Way down in zero page is BLNSW which controls if the cursor is ON or OFF. A zero value will turn it on.
BLNSW = $CC ; Cursor blink on or off
So we add BLNSW to our top list of labels. We know it needs a value of 0 to turn on. Just before we jumped to WRITELABEL we made X zero already, so why not put the code for this directly above the jsr WRITELABEL.
START:
ldx #$00
stx BLNSW ; Keeps the cursor blinking for inputs in our program
jsr WRITELABEL ; print our label to the screen
So now we have a cleared screen, a label that says ENTER A HEX VALUE: and a blinking cursor. So far we've required no human intervention. That is to say, so far its been pretty easy. What we know right now is that we only want two keystrokes from the user, that being two bytes. We need some sort of counter to be initialized to zero, and increment each time a key is pressed. We can use one byte within the memory to record this. I choose 033F
So lets add another label:
BYTECOUNTER = $033F ; what digit were up to
Since we want the value in BYTECOUNTER to be zero, wouldn't it be great to do what we did last time and store our existing X into BYTECOUNTER just like BLNSW
START:
ldy #$80 ; Out first bit test for bit 7 must be 10000000 $80
sty TESTBYTE ; store our initial test byte here
ldx #$00
stx BLNSW ; Keeps the cursor blinking for inputs in our program
stx BYTECOUNTER ;what keystroke are we up to
jsr WRITELABEL ; print our label to the screen
Lets recap what we have.....
*=$C000 ; SYS 49152 to begin
OURHEXNUM = $033C ; This is where the constant OURHEXVALUE will be stored
BYTECOUNTER = $033F ; what keystroke are were up to
TESTBYTE = $0345 ; 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
CHROUT = $FFD2 ; Output character kernel routine
INIT:
lda #$93
jsr CHROUT ; Load 147, hex $93 into A reg and JSR to CHROUT
START:
ldx #$00
stx BLNSW ; Keeps the cursor blinking for inputs in our program
stx BYTECOUNTER ;what keystroke are we up to
jsr WRITELABEL ;print our label to the screen
WRITELABEL: ; Prints our label and returns
lda HELLO,x
jsr CHROUT
inx
cpx #$13
bne WRITELABEL
rts
HELLO: .text 'enter a hex value: ' ; $13 [hex] bytes
As we are, with our blinking cursor positioned in the desired location, its time to jump to a new location. At this point, we are in a holding pattern, at the mercy of the user, waiting for our two key inputs....... waiting...... and waiting..... and waiting......
The kernel has within it, functions what wait for a keypress, and if finding one, to dump it into the accumulator. This technique is the easiest to demonstrate and only works effectively if the whole program has to stop and wait by design . Lets add these to our labels up top.
SCNKEY = $FF9F ; Scan keyboard for keypress
GETIN = $FFE4 ; Get input from buffer, store it in A
Time for an new block of code:
SCANKBD:
jsr SCNKEY ; our kernel routines for scanning the keyboard
jsr GETIN ; our kernel routine for, is key found dump in Accumulator
jmp SCANKBD
We've just created an endless loop, once we jump to SCANKBD, there is no way out of our program. This is because we haven't defined any way to leave the loop that we got ourselves into. Lets give ourselves a way out of all this.
SCANKBD:
jsr SCNKEY ; our kernel routines for scanning the keyboard
jsr GETIN ; our kernel routine for, is key found dump in Accumulator
cmp #$51 ; If letter Q is entered..........
beq END ; jump to END
Now all we need is some quick and dirty END code to jump to in the event Q is pressed. Throw the following just above WRITELABEL: and forget about it, it does what it is supposed to, it returns to BASIC
END:
rts
So now we have a way out, but things will get tricky here. Since our input values make up a HEX value, they must be 0-9 and A-F only, except for Q, which we defined already.
Our function already checks for Q being HEX $51, but now we would like to constrain our possible valid inputs, starting from lowest to highest just after the beq END in SCANKBD, we want it to ignore all values outside of our desired ones. Assuming Q wasn't pressed the test continues.
When we open up the program to allow user input, the game changes entirely! We've done this before with our Sprite program, which was quite easy though. It only recognized certain keys, and executed a function for each with no regard for sprite positions. This kept the program short and easy to read.
As a matter of fact, I only created the Binary conversion function to store the COMMAND and CONTROL registers as viewable binary called with (JSR) from another program which requires constant visibility to the command and control bits of the ACIA. It is slightly smaller, slightly harder to follow, and takes advantage of the zero page from the last chapter. Just like our program where we hard coded our value, a process populates that value from another location in memory, and still requires no human intervention.
So, we have our binary converter. We hardcoded a value into OURHEXVALUE and let the program run with it. It converted the value, and unceremoniously terminated. The question becomes, "How much more complex would it be to allow for a user to input the hex value on the keyboard, and then convert it?"
The answer is, much more. Let us look at the easiest scenario! If you can't write it on paper as a concept, you can't program it.
1. We clear the screen
2. We create, starting at the top left small banner that says "Enter a hex value: "
3. We provide a flashing cursor as a prompt for input of two characters.
4. We provide code to ultimately put these two characters into OURHEXVALUE
5. We run our existing code to convert the value, and output it
6. We repeat the process
1. Not much to change here, but there are things we are going to need to add. Let me point out here that any program like this was not written top to bottom. It sort of evolves as you meet specific goals.
Im going to attempt to construct this based on our previous program.
INIT:
lda #$93
jsr CHROUT ; Load 147, hex $93 into A reg and JSR to CHROUT
We've done this clear screen technique before..... but not in this program, were going to add a label up top to our existing labels for CHROUT, note we removed OURHEXVALUE. There is no need for it since the user will be inputing that value. Make sure we add our start routine after init. In this manner INIT will only run once when the program loads, START: Will be the beginning of each instance of the program.
*=$C000 ; SYS 49152 to begin
TESTBYTE = $0345 ; 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
CHROUT = $FFD2 ; Output character kernel routine
Here we've met our first goal. The screen is now clear.
2. So now that the screen is clear we want to write out a label to the screen to instruct the user. This will once again be using the CHROUT, though there are much more efficient ways to do this however, using a technique we employed to load sprite data, we can use the very same loop to print to the screen.
Consider BASIC:
10 CLS
20 PRINT " ENTER A HEX VALUE: "
After running our clear screen function in step one, the screen is cleared and the cursor is at the top left. Under INIT:, we'll create a label called START:
As previously mentioned, INIT is to be called but once. START is called each time the program runs.
START:
ldx #$00 ; Initialize X to 0 for our label
jsr WRITELABEL ; JSR to WRITELABEL
Were going to need a label generation loop, and data for that loop to use.
WRITELABEL: ; Prints our label and returns
lda HELLO,x
jsr CHROUT
inx
cpx #$13
bne WRITELABEL
rts
HELLO: .text 'enter a hex value: ' ; $13 bytes
The HELLO is sitting just after the end of our program on the bottom just like our Sprite data from previous chapters. Using X as the current letter position, starting at 0, we can load the byte into A and then run a jsr CHROUT to print that letter to the screen. The space after the colon is a valid space byte and will give us an actual space between the text and the cursor starting position. This will loop $13[hex] times, each time incrementing X for the next byte and ultimately RTS back from where it was called in START.
With that, step two is finished.......
3. When executing machine code in the C64, the blinking cursor will shut off by design, saving unnecessarily wasted processor cycles if not needed. However we would like to use it as a prompt, so within the code, we would like to turn it back on. Way down in zero page is BLNSW which controls if the cursor is ON or OFF. A zero value will turn it on.
BLNSW = $CC ; Cursor blink on or off
So we add BLNSW to our top list of labels. We know it needs a value of 0 to turn on. Just before we jumped to WRITELABEL we made X zero already, so why not put the code for this directly above the jsr WRITELABEL.
START:
ldx #$00
stx BLNSW ; Keeps the cursor blinking for inputs in our program
jsr WRITELABEL ; print our label to the screen
So now we have a cleared screen, a label that says ENTER A HEX VALUE: and a blinking cursor. So far we've required no human intervention. That is to say, so far its been pretty easy. What we know right now is that we only want two keystrokes from the user, that being two bytes. We need some sort of counter to be initialized to zero, and increment each time a key is pressed. We can use one byte within the memory to record this. I choose 033F
So lets add another label:
BYTECOUNTER = $033F ; what digit were up to
Since we want the value in BYTECOUNTER to be zero, wouldn't it be great to do what we did last time and store our existing X into BYTECOUNTER just like BLNSW
START:
ldy #$80 ; Out first bit test for bit 7 must be 10000000 $80
sty TESTBYTE ; store our initial test byte here
ldx #$00
stx BLNSW ; Keeps the cursor blinking for inputs in our program
stx BYTECOUNTER ;what keystroke are we up to
jsr WRITELABEL ; print our label to the screen
Lets recap what we have.....
*=$C000 ; SYS 49152 to begin
OURHEXNUM = $033C ; This is where the constant OURHEXVALUE will be stored
BYTECOUNTER = $033F ; what keystroke are were up to
TESTBYTE = $0345 ; 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
CHROUT = $FFD2 ; Output character kernel routine
INIT:
lda #$93
jsr CHROUT ; Load 147, hex $93 into A reg and JSR to CHROUT
START:
ldx #$00
stx BLNSW ; Keeps the cursor blinking for inputs in our program
stx BYTECOUNTER ;what keystroke are we up to
jsr WRITELABEL ;print our label to the screen
WRITELABEL: ; Prints our label and returns
lda HELLO,x
jsr CHROUT
inx
cpx #$13
bne WRITELABEL
rts
HELLO: .text 'enter a hex value: ' ; $13 [hex] bytes
As we are, with our blinking cursor positioned in the desired location, its time to jump to a new location. At this point, we are in a holding pattern, at the mercy of the user, waiting for our two key inputs....... waiting...... and waiting..... and waiting......
The kernel has within it, functions what wait for a keypress, and if finding one, to dump it into the accumulator. This technique is the easiest to demonstrate and only works effectively if the whole program has to stop and wait by design . Lets add these to our labels up top.
SCNKEY = $FF9F ; Scan keyboard for keypress
GETIN = $FFE4 ; Get input from buffer, store it in A
Time for an new block of code:
SCANKBD:
jsr SCNKEY ; our kernel routines for scanning the keyboard
jsr GETIN ; our kernel routine for, is key found dump in Accumulator
jmp SCANKBD
We've just created an endless loop, once we jump to SCANKBD, there is no way out of our program. This is because we haven't defined any way to leave the loop that we got ourselves into. Lets give ourselves a way out of all this.
SCANKBD:
jsr SCNKEY ; our kernel routines for scanning the keyboard
jsr GETIN ; our kernel routine for, is key found dump in Accumulator
cmp #$51 ; If letter Q is entered..........
beq END ; jump to END
Now all we need is some quick and dirty END code to jump to in the event Q is pressed. Throw the following just above WRITELABEL: and forget about it, it does what it is supposed to, it returns to BASIC
END:
rts
So now we have a way out, but things will get tricky here. Since our input values make up a HEX value, they must be 0-9 and A-F only, except for Q, which we defined already.
Our function already checks for Q being HEX $51, but now we would like to constrain our possible valid inputs, starting from lowest to highest just after the beq END in SCANKBD, we want it to ignore all values outside of our desired ones. Assuming Q wasn't pressed the test continues.
cmp #$30 ; less than the hex value for 0?
bcc SCANKBD ; yes, ignore and keep scanning
So above, if its less than 0, its invalid and should return back to scanning for a key, In order to proceed it must be greater than 0
cmp #$3A ; less than the hex value of 9 but greater than 0?
bcc DIGITCONVERT ; jump to our function to convert a digit
We already know at this point that the value must be 0 or greater, but is it equal to or less than 9. If it is 9 or less then we know that the value entered must reside in the 0-9 range and we have a valid input. so we jump to a function to start our conversion process
But what if its greater than 9, it could still be valid? It could be A-F, remember, we need to keep testing. We know at this point that the value must be greater than 9, or it would have branched off to DIGITCONVERT.
cmp #$41 ; less than the value of A
bcc SCANKBD ; less than the hex value of F but greater than A?
So now we ask, is the value which we know to be greater than 9, less than A, because if it is, its invalid and we need to rescan the keyboard again.
If were still here we now know that the value is A or greater, but were not done, it still needs to be F or less
cmp #$47 ; is the value F or less
bcc LETTERCONVERT ; jump to our function to convert a letter
If the value is F or less we know that we have a valid input in our A-F range. We can branch off to LETTERCONVERT.
Heres our complete SCANKBD:
;;;;;;;;;;;;;;;; Our scan keyboard function, if a key is pressed, it will appear
;;;;;;;;;;;;;;;; In the accumulator, hex value, and tested
SCANKBD:
jsr SCNKEY ; our kernel routines for scanning the keyboard
jsr GETIN ; our kernel routine for, is key found dump in Accumulator
cmp #$51 ; If letter Q is entered..........
beq END ; jump to END
cmp #$30 ; less than the hex value for 0?
bcc SCANKBD ; yes, ignore and keep scanning
cmp #$3A ; less than the hex value of 9 but greater than 0?
bcc DIGITCONVERT ; jump to our function to convert a digit
cmp #$41 ; less than the value of A
bcc SCANKBD ; less than the hex value of F but greater than A?
cmp #$47 ; jump to our function to convert a letter
bcc LETTERCONVERT ; jump to our function to convert a letter
jmp SCANKBD ; should anything fall outside this criteria, ignore
; it and keep scanning 0-9 or A-F or Q
So why two separate branches? To keep it easy to follow for beginners I've cut the code up to process numbers and letters in a way thats easy to see. Because of their HEX values in logical order, A does not appear directly after 9. You will see in a moment
Lets start with LETTERCONVERT:
We want to store the value you see on the keyboard. not its hex value. that is to say the number 5 should be stored as 05. Before we manipulate it though, we want to print the key the user pressed to the screen at our cursor with CHROUT.
The focal point of these two functions is obvious, and over documented here. Subtract $36 from a letter to get its hex value, subtract $2F from a number to get its hex value
LETTERCONVERT:
jsr CHROUT ; Lets print the digit before we mess with it
sbc #$36 ; Subtracts Hex value 2F to attain actual value in memory
jmp MAINCONVERT ; jump to MAINCONVERT after performing the routine
Now we need our function to convert DIGITS 0-9
DIGITCONVERT:
jsr CHROUT ; Lets print the digit to screen before we mess with it
sbc #$2F ; Subtracts Hex value 2F to attain actual value in memory
; The program will flow right to MAINCONVERT after
; finishing this routine, so why not put it just above
; MAINCONVERT and save the program the space and cycles
; of running a jmp MAINCONVERT. LETTERCONVERT doesnt have
; this luxury, but there are 10 numbers and only six letters
; so the logical choice is to keep DIGITCONVERT just above
; MAINCONVERT
Once we have made this change we either jump, or in DIGITCONVERT:'s case simply roll into MAINCONVERT. Its important to know which BYTE we are working with because while the second byte needs no further conversion, the first one does. Ill show you why.
When we type in 3 and then F, what were want is 3F, what we get is 03 and 0F. We want to flip the number of only our first entered byte, in this case 03 and leave the second one alone. If we could turn 03 into 30, we could then add it to 0F and arrive at the sum of 3F. Which is what we want.
Our MAINCONVERT function loads the byte from BYTECOUNTER to see if we are on our first or second byte. if it is the first, we will do the conversion, if it is the second, no conversion will be done and we will proceed to PROCESS:
It just so happens any number from 00 to 0F can easily be flipped with 4 asl operations, watch and see how. We will use the value of 03, that we want to convert to 30.
ASL : Arithmetic Shift left - shifts all the bits to the left and adds a zero to the lowest bit each time
03 = 00000011 ASL -> 1st
06 = 00000110 ASL -> 2nd
12 = 00001100 ASL -> 3rd
24 = 00011000 ASL -> 4th
48 = 00110000 output is 48 (decimal) HEX is 30
MAINCONVERT:
ldy BYTECOUNTER ; Load Y with current BYTE counter
cpy #$01 ; Are we on the second byte , LSB
beq PROCESS ; YES, leave the converted byte alone and go back to PROCESS
asl ;
asl ; Lets shift the binary value to the left four times
asl ; turns 01 into 10, 02 into 20, 03 into 30 etc etc etc
asl ; just a nifty trick, so long as nothing exceeds FF
; which it wont, highest possible total is F0
jmp PROCESS ; jump back to process
Now we jump to PROCESS, we are going to need a place to store these two converted bytes. That means new labels up top
LOWBYTE = $033D ; first digit input storage
HIGHBYTE = $033E ; second digit input storage
From the start we load our BYTECOUNTER into X, we then store our value into LOWBYTE,X
if X=0 the Value will be stored in LOWBYTE
if X=1 the Value will be stored in LOWBYTE+1, which we labeled HIGHBYTE
We actually don't need a label named HIGHBYTE, but its not compiled in so its just a reference.
PROCESS: ; We continue here to store the value in the correct location
ldx BYTECOUNTER ; put out BYTECOUNTER in X
sta LOWBYTE,x ; places our values in LOWBYTE and HIGHBYTE
inx ; We must incremet the BYTECOUNTER by 1
stx BYTECOUNTER ; and store the new value in BYTECOUNTER
cpx #$02 ; if BYTECOUNTER is $02 were done adding values
bne SCANKBD ; Not 2, keep scanning for inputs
;;;;; If we are right here, it means we now have our two bytes, we must
;;;;; Add them together to get our final HEX value to convert to binary
;;;;; We will store final value in OURHEXNUM and jump to SETCONVERT
clc ; clear any latent carry flags just to be safe
lda LOWBYTE ; load the low byte into A
adc HIGHBYTE ; add the high byte to it
sta OURHEXNUM ; store out final value in OURHEXNUM for processing
jmp SETCONVERT ; jump the conversion portion of our program
While were storing bytes we can see if we are on the 1st or second byte. After incrementing X we can see if we are on the first inputed byte or the second. After the first byte processed X=1 so if we CPX #$02, we know that both bytes are in. If X doesn't equal 2, then only 1 byte is in and we go back to the SCANKBD: function and do it again, if 2, we do not branch off, we continue on
4. So now we have, for example typed in 3 and F, converted them to 03 and 0F, flipped the digit on the first one to arrive at 30 and 0F.
Now we.......
Clear the carry flag
Load A with the LOWBYTE value 0F
Add it to the HIGHBYTE value 30
Which gives us 3F
and store our value into OURHEXNUM, This was the original storage point we used in the earlier program where the hex value was hard coded.
5. Notice the first two lines I commented out in the code, we don't need them. All of our efforts were to store a value into OURHEXNUM from the keyboard, and we did that.
SETCONVERT:
;lda OURHEXVALUE ; this will be out test number
;sta OURHEXNUM ; we will store the test number here permanently
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
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
6. Im going to interject here in the middle of the code. Once we entered our second number the binary value will appear towards the bottom of the screen in the middle. If we jumped right back to INIT, we would clear the screen and never see the value that was outputted. There is no need to clear the whole screen, just the two positions we typed our number in.
;;;;;;;;; BLANK THE TWO DIGITS, SET CURSOR TO THE ORIGINAL POSITION OF INPUT
;;;;;;;;;
lda #$20 ; load a blank space value into A
sta $0413 ; store it into our input numbers to blank it out
sta $0414 ; for the next set of inputs
clc ; clear any latent carry flags just to be safe
ldx #$00 ; set X for top line
ldy #$00 ; set Y for first character
jsr PLOT ; move cursor
jmp START ; repeat the whole process again from START
So we load A with the value $20, which is a blank space, and clear out our two locations with it, $0413 and $0414. This is where our two digits were, regrettably, keeping this easy the second digit sets things in motion and almost seems to clear itself. No big deal, maybe in the future we can implement waiting on RETURN to get things going
CLC - The clear carry bit will be required by our last routine, PLOT. By setting X with the X coordinate, and Y with the Y coordinate and jsr to PLOT. we move the cursor to the top left position where we Generate our text ENTER A HEX VALUE:
And so everything starts again........
Our complete code......
; C64 Hex to Binary Converter Program
; 64TASS assembler style code for 6502
; Jordan Rubin 2014 http://technocoma.blogspot.com
;
; Takes the HEX value as an input, converts it to Binary for display
; on the screen as a binary number. Press Q to exit
*=$C000 ; SYS 49152 to begin
OURHEXNUM = $033C ; This is where the constant OURHEXVALUE will be stored
LOWBYTE = $033D ; first digit input storage
HIGHBYTE = $033E ; second digit input storage
BYTECOUNTER = $033F ; what digit were up to
TESTBYTE = $0345 ; 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
CHROUT = $FFD2 ; Output character kernel routine
TEXTSTART = $0400 ; Where we want to start the label for input
SCNKEY = $FF9F ; Scan keyboard for keypress
GETIN = $FFE4 ; Get input
BLNSW = $CC ; Cursor blink on or off
PLOT = $FFF0 ; Set/Read cursor location
nop ; get used to it, i leave it for testing and remove at
; the end of the project
INIT:
lda #$93
jsr CHROUT ; Load 147, hex $93 into A reg and JSR to CHROUT
; (Clears the screen)
START:
ldy #$80 ; Out first bit test for bit 7 must be 10000000 $80
sty TESTBYTE ; store our initial test byte here
ldx #$00 ; Set X to 0
stx BLNSW ; Keeps the cursor blinking for inputs in program
stx BYTECOUNTER ; Make our bytecounter 0, we need 2 bytes
; to perform the operation
jsr WRITELABEL ; print our label to the screen
jmp SCANKBD ; jump to init of scanKDB routune
SETCONVERT:
ldx #$00 ; Initialize X for our loop
CONVERTION:
lda OURHEXNUM ; load our test hex number, this is from user input
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
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
;;;;;;;;; BLANK THE TWO DIGITS, SET CURSOR TO THE ORIGINAL POSITION OF INPUT
;;;;;;;;;
lda #$20 ; load a blank space value into A
sta $0413 ; store it into our input numbers to blank it out
sta $0414 ; for the next set of inputs
clc ; clear any latent carry flags just to be safe
ldx #$00 ; set X for top line
ldy #$00 ; set Y for first character
jsr PLOT ; move cursor
jmp START ; repeat the whole process again from START
;;;;;;;;;;;;;;;; Our scan keyboard function, if a key is pressed, it will appear
;;;;;;;;;;;;;;;; In the accumulator, hex value, and tested
SCANKBD:
jsr SCNKEY ; our kernel routines for scanning the keyboard
jsr GETIN ; our kernel routine for, is key found dump in Accumulator
cmp #$51 ; If letter Q is entered..........
beq END ; jump to END
cmp #$30 ; less than the hex value for 0?
bcc SCANKBD ; yes, ignore and keep scanning
cmp #$3A ; less than the hex value of 9 but greater than 0?
bcc DIGITCONVERT ; jump to our function to convert a digit
cmp #$41 ; less than the value of A
bcc SCANKBD ; less than the hex value of F but greater than A?
cmp #$47 ; jump to our function to convert a letter
bcc LETTERCONVERT ; jump to our function to convert a letter
jmp SCANKBD ; should anything fall outside this criteria, ignore
; it and keep scanning 0-9 or A-F or Q
PROCESS: ; We continue here to store the value in the correct location
ldx BYTECOUNTER ; put out BYTECOUNTER in X
sta LOWBYTE,x ; places our values in LOWBYTE and HIGHBYTE
inx ; We must incremet the BYTECOUNTER by 1
stx BYTECOUNTER ; and store the new value in BYTECOUNTER
cpx #$02 ; if BYTECOUNTER is $02 were done adding values
bne SCANKBD ; Not 2, keep scannig for inputs
;;;;; If we are right here, it means we now have our two bytes, we must
;;;;; Add them together to get our final HEX value to convert to binary
;;;;; We will store final value in OURHEXNUM and jump to SETCONVERT
clc ; clear any latent carry flags just to be safe
lda LOWBYTE ; load the low byte into A
adc HIGHBYTE ; add the high byte to it
sta OURHEXNUM ; store out final value in OURHEXNUM for processing
jmp SETCONVERT ; jump the conversion portion of our program
LETTERCONVERT:
jsr CHROUT ; Lets print the digit before we mess with it
sbc #$36 ; Subtracts Hex value 2F to attain actual value in memory
jmp MAINCONVERT ; jump to MAINCONVERT after performing the routine
DIGITCONVERT:
jsr CHROUT ; Lets print the digit to screen before we mess with it
sbc #$2F ; Subtracts Hex value 2F to attain actual value in memory
; The program will flow right to MAINCONVERT after
; finishing this routine, so why not put it just above
; MAINCONVERT and save the program the space and cycles
; of running a jmp MAINCONVERT. LETTERCONVERT doesnt have
; this luxury, but there are 10 numbers and only six letters
; so the logical choice is to keep DIGITCONVERT
; just above MAINCONVERT
MAINCONVERT:
ldy BYTECOUNTER ; Load Y with current BYTE counter
cpy #$01 ; Are we on the second byte , LSB
beq PROCESS ; YES, leave the converted byte alone and go back to PROCESS
asl ;
asl ; Lets shift the binary value to the left four times
asl ; turns 01 into 10, 02 into 20, 03 into 30 etc etc etc
asl ; just a nifty trick, so long as nothing exceeds FF
; which it wont, highest possible total os F0
jmp PROCESS ; jump back to process
END: ; kills program
rts
WRITELABEL: ; Prints our label and returns
lda HELLO,x
jsr CHROUT
inx
cpx #$13
bne WRITELABEL
rts
HELLO: .text 'enter a hex value: ' ; 13 bytes
NEXT----->
Table of contents
No comments:
Post a Comment