Writing PE6502 Assembler code - Useful tips
Posted: Wed May 01, 2019 2:10 pm
Hello everyone,
I really like the PE6502 and am using the opportunity to learn how to program in the most basic level possible. However I've hit several problems while programming. There are some good tutorials out there on how to write 6502 assembly, but not all of it is as helpful as it might appear. explanations on how certain functions work, vary from one 6502 based computer to another. For example a book on the Apple II I found, says the output to monitor register is $FDED, which does nothing on the PE6502. I'll write down a few issues i've had while learing to code for the PE6502. Hopefully some of you will find some part of this usefull! Note that i'm not explaining 6502 opcodes, just my way on how to use them for some cool stuff
Writing text on screen:
The first problem I ran into was how to put some text on the screen. Writing values in registry, manipulating them and moving them around is one thing, but how do you actually print something?
The short anwser for the PE6502 is: $FFEF
Just dump the ASCII value of the character you want to put on the screen in the Accumulator (A) and do a JSR to $FFEF. This will read whatever is in the Accumulator and print it in screen. Like this:
Note, that LDA #'A' is already something that has to be interpreted by the assembler of your choice. I use Win2C64 (http://www.aartbik.com/MISC/c64.html). This is a lightweight assembler for the 65XX family and works well for my needs, but others are CC65 and VASM. Your mileage might vary with the example above. But you could also just do LDA #$C1 (which is equal to the ASCII value of A).
Now, dumping one letter on the screen is nice, but what about a whole sentence, or a carriage return?
Carriage return is easy, just dump $0D to $FFEF and voila!
Now a whole sentence is harder, you can repeat the LDA and JSR commands but that looks horrible and cluttered. Plus what if you want to print something twice? You don't want to dump the same print commands in multiple times!
Now I solve this with a small function that starts reading at a given memory address and keeps printing characters, until it hits a 0. I use this:
And you call for the command using
And here's the sentence itself:
Now this takes a bit of explaining. First of all, I use the .BYTE command in my assembler to define a label/pointer (OPENA) and then put some text in with some Carriage returns in there as well (CR is a simple label for $0D), ending with a 0. When you want to print you have two problems, you don't know where in memory the BYTE command will be stored in the compiled code. The second problem is that you can store values in and from memory but the registers in the 6502 are 8-bits (one byte) and the memory bus is 16-bits (two bytes). This poses a interesting issue on how to find the memory address, store the memory address and restore it in the function to write some text on the screen.
When I want to print the text I run LDX #<OPENA and LDY #>OPENA. Which temporarily stores the low-byte of the memory address where OPENA starts in register X and the high-byte of the memory address of OPENA in register Y.
Then we move to the function. The first part is easy and can be ignored:
This is just my way of creating a function/subroutine, some piece of code that won't run until I want to run it. These two commands just make sure that when the code gets run and it hits the function, i'll just skip right over the whole function. LPRINTZ is a label at the end of the function. There might be better ways to do this, but by clearing the overflow flag in the 6502 with CLV and then check if the overflow flag is cleared with BVC we always branch to label LPRINTZ in this case.
The next part is a little more interesting:
LPRINT is just the label I JSR to when I want to run the code in the function. The next is a pointer definition, telling the assembler that pointer ZPADDR will be equal to memory address $00. In the next two commands take the values we stored in STX (the low-byte) in ZPADDR (which is $0000) and the high-byte from Y using STY and put it in ZPADDR+1 (which is $0001). Now if you were to run this through the PE6502 you might get confused because its in backwards... For example when i assemble this, the text starts at memory address $1164, but i'm storing $6411 in my zero page memory $0000 and $0001. This is intended actually, since the 6502 is a little-endian system, the memory address has to be reversed in this case of it doesn't read the correctly data later on!
The last part we can do in one go:
First we reset the Y register, which I will use in this case as a counter for the memory we have to read. Then we get a label LPRINTA which is nothing more then where our loop will return to while processing all the text. Now we load the text from memory. For this I use the Y register specifically, because I want to use Postindexed Indirect Addressing (Don't ask me to explain it to deeply, I barely understand myself, but read here http://www.chibiakumas.com/6502/ for some excellent explanation!). Basically i'm starting at the memory address located in ZPADDR ($0000 and $0001, but not read that byte specifically, but offset by Y. So in my example the first time it reads the value in $1164, the next loop it reads the value in $1165 and so forth.
Then all it does is compare it to the number 0 and if the value is a 0, it ends the function by branching to LPRINTY using CMP #0 and BEQ LPRINTY. If it keeps on going (value is not 0), it will print the letter on screen (JSR CHROUT, which is a pointer for $FFEF), increment the Y register for the next letter to read and do the same trick with CLV and BVC to branch back to LPRINTA to do the loop again. LPRINTY only has one opcode after it, RTS, just to go back to when I JSR to the function afterwards!
That's it!
A simple function to print whatever text you want, as long as your assembler understands labels and pointers like .BYTE. The exact syntax might vary per assembler!
Returning to WozMon
One last small thing, I have been faced with my code running fine but the PE6502 getting stuck afterwards, not accepting any key combinations and me having to reset the CPU or Propeller with the switches to keep going. I noticed this was because the RAM seems to be full of gabled data. When I run a Apple 1 emulator, like Pom1, all the ram is filled with nice 00's everywhere, but there's random data in the PE6502's RAM everywhere. I hardly think this is by design, and the emulator is probably just too clean.
Just to make sure you can keep using the PE6502 after running your program, try jumping to the starting address of WozMon as your last command. Wozmon starts at $FF00.
That's it, I hope this helps anyone else. If there are no objections i'll add more topics like this for other functions, with capturing keyboard input next!
Take care, Marc
I really like the PE6502 and am using the opportunity to learn how to program in the most basic level possible. However I've hit several problems while programming. There are some good tutorials out there on how to write 6502 assembly, but not all of it is as helpful as it might appear. explanations on how certain functions work, vary from one 6502 based computer to another. For example a book on the Apple II I found, says the output to monitor register is $FDED, which does nothing on the PE6502. I'll write down a few issues i've had while learing to code for the PE6502. Hopefully some of you will find some part of this usefull! Note that i'm not explaining 6502 opcodes, just my way on how to use them for some cool stuff
Writing text on screen:
The first problem I ran into was how to put some text on the screen. Writing values in registry, manipulating them and moving them around is one thing, but how do you actually print something?
The short anwser for the PE6502 is: $FFEF
Just dump the ASCII value of the character you want to put on the screen in the Accumulator (A) and do a JSR to $FFEF. This will read whatever is in the Accumulator and print it in screen. Like this:
- Code: Select all
LDA #'A'
JSR $FFEF
Note, that LDA #'A' is already something that has to be interpreted by the assembler of your choice. I use Win2C64 (http://www.aartbik.com/MISC/c64.html). This is a lightweight assembler for the 65XX family and works well for my needs, but others are CC65 and VASM. Your mileage might vary with the example above. But you could also just do LDA #$C1 (which is equal to the ASCII value of A).
Now, dumping one letter on the screen is nice, but what about a whole sentence, or a carriage return?
Carriage return is easy, just dump $0D to $FFEF and voila!
Now a whole sentence is harder, you can repeat the LDA and JSR commands but that looks horrible and cluttered. Plus what if you want to print something twice? You don't want to dump the same print commands in multiple times!
Now I solve this with a small function that starts reading at a given memory address and keeps printing characters, until it hits a 0. I use this:
- Code: Select all
CLV ;CLEAR OVERFLOW FLAG
BVC LPRINTZ ;BRANCH ON OVERFLOW CLEAR (WHICH IT ALWAYS IS!)
LPRINT
ZPADDR .EQU $00 ;POINTER TO SET THE START BYTE LOCATION FOR THE 2 BYTE MEM ADDRESS (WHICH WILL STORE THE STRINGS MEMORY START AT $0000 AND $0001)
STX ZPADDR ;TEMP STORE START MEM ADDRESS OF TEXT IN $00 AND $01
STY ZPADDR+1
LDY #0 ;RESET Y FOR LOOPING THROUGH THE STRING
LPRINTA
LDA (ZPADDR),Y ;LOAD A STRING CHARACTER FROM ADDRESS STORED IN POINTER ZPADDR AND ZPADDR+1 ($00 AND $01 IN THIS CASE)
CMP #0
BEQ LPRINTY ;IF EQUAL TO 0, DONE PRINTING TEXT
JSR CHROUT ;PRINT ON SCREEN
INY ;NEXT LETTER
CLV
BVC LPRINTA ;ALWAYS JUMP
LPRINTY ;SUBROUTINE TERMINATOR (GO BACK WHERE YOU CAME FROM BEFORE!)
RTS
LPRINTZ ;JUST SKIPPING OVER FUNCTION, NO RTS OTHERWISE BACK TO PROMPT!
And you call for the command using
- Code: Select all
LDX #<OPENA ;PRINT THE INTRO TEXT ON SCREEN
LDY #>OPENA
JSR LPRINT
And here's the sentence itself:
- Code: Select all
OPENA .BYTE "HANGMAN",CR,"REWRITTEN FOR 6502 ASSEMBLER",CR,CR,CR,0
Now this takes a bit of explaining. First of all, I use the .BYTE command in my assembler to define a label/pointer (OPENA) and then put some text in with some Carriage returns in there as well (CR is a simple label for $0D), ending with a 0. When you want to print you have two problems, you don't know where in memory the BYTE command will be stored in the compiled code. The second problem is that you can store values in and from memory but the registers in the 6502 are 8-bits (one byte) and the memory bus is 16-bits (two bytes). This poses a interesting issue on how to find the memory address, store the memory address and restore it in the function to write some text on the screen.
When I want to print the text I run LDX #<OPENA and LDY #>OPENA. Which temporarily stores the low-byte of the memory address where OPENA starts in register X and the high-byte of the memory address of OPENA in register Y.
Then we move to the function. The first part is easy and can be ignored:
- Code: Select all
CLV
BVC LPRINTZ
This is just my way of creating a function/subroutine, some piece of code that won't run until I want to run it. These two commands just make sure that when the code gets run and it hits the function, i'll just skip right over the whole function. LPRINTZ is a label at the end of the function. There might be better ways to do this, but by clearing the overflow flag in the 6502 with CLV and then check if the overflow flag is cleared with BVC we always branch to label LPRINTZ in this case.
The next part is a little more interesting:
- Code: Select all
LPRINT
ZPADDR .EQU $00 ;POINTER TO SET THE START BYTE LOCATION FOR THE 2 BYTE MEM ADDRESS (WHICH WILL STORE THE STRINGS MEMORY START AT $0000 AND $0001)
STX ZPADDR ;TEMP STORE START MEM ADDRESS OF TEXT IN $00 AND $01
STY ZPADDR+1
LPRINT is just the label I JSR to when I want to run the code in the function. The next is a pointer definition, telling the assembler that pointer ZPADDR will be equal to memory address $00. In the next two commands take the values we stored in STX (the low-byte) in ZPADDR (which is $0000) and the high-byte from Y using STY and put it in ZPADDR+1 (which is $0001). Now if you were to run this through the PE6502 you might get confused because its in backwards... For example when i assemble this, the text starts at memory address $1164, but i'm storing $6411 in my zero page memory $0000 and $0001. This is intended actually, since the 6502 is a little-endian system, the memory address has to be reversed in this case of it doesn't read the correctly data later on!
The last part we can do in one go:
- Code: Select all
LDY #0 ;RESET Y FOR LOOPING THROUGH THE STRING
LPRINTA
LDA (ZPADDR),Y ;LOAD A STRING CHARACTER FROM ADDRESS STORED IN POINTER ZPADDR AND ZPADDR+1 ($00 AND $01 IN THIS CASE)
CMP #0
BEQ LPRINTY ;IF EQUAL TO 0, DONE PRINTING TEXT
JSR CHROUT ;PRINT ON SCREEN
INY ;NEXT LETTER
CLV
BVC LPRINTA ;ALWAYS JUMP
LPRINTY ;SUBROUTINE TERMINATOR (GO BACK WHERE YOU CAME FROM BEFORE!)
RTS
LPRINTZ ;JUST SKIPPING OVER FUNCTION, NO RTS OTHERWISE BACK TO PROMPT!
First we reset the Y register, which I will use in this case as a counter for the memory we have to read. Then we get a label LPRINTA which is nothing more then where our loop will return to while processing all the text. Now we load the text from memory. For this I use the Y register specifically, because I want to use Postindexed Indirect Addressing (Don't ask me to explain it to deeply, I barely understand myself, but read here http://www.chibiakumas.com/6502/ for some excellent explanation!). Basically i'm starting at the memory address located in ZPADDR ($0000 and $0001, but not read that byte specifically, but offset by Y. So in my example the first time it reads the value in $1164, the next loop it reads the value in $1165 and so forth.
Then all it does is compare it to the number 0 and if the value is a 0, it ends the function by branching to LPRINTY using CMP #0 and BEQ LPRINTY. If it keeps on going (value is not 0), it will print the letter on screen (JSR CHROUT, which is a pointer for $FFEF), increment the Y register for the next letter to read and do the same trick with CLV and BVC to branch back to LPRINTA to do the loop again. LPRINTY only has one opcode after it, RTS, just to go back to when I JSR to the function afterwards!
That's it!
A simple function to print whatever text you want, as long as your assembler understands labels and pointers like .BYTE. The exact syntax might vary per assembler!
Returning to WozMon
One last small thing, I have been faced with my code running fine but the PE6502 getting stuck afterwards, not accepting any key combinations and me having to reset the CPU or Propeller with the switches to keep going. I noticed this was because the RAM seems to be full of gabled data. When I run a Apple 1 emulator, like Pom1, all the ram is filled with nice 00's everywhere, but there's random data in the PE6502's RAM everywhere. I hardly think this is by design, and the emulator is probably just too clean.
Just to make sure you can keep using the PE6502 after running your program, try jumping to the starting address of WozMon as your last command. Wozmon starts at $FF00.
- Code: Select all
JMP $FF00
That's it, I hope this helps anyone else. If there are no objections i'll add more topics like this for other functions, with capturing keyboard input next!
Take care, Marc