Update: All needed files for this tutorial can be found in the GitHub repo linked in the bottom of this page.
Now that I started writing about C-64 programming, I decided to write one more article about it. If you want more, please comment so I know if this is an interesting topic.
This article is all about writing code for the heart of the Commodore 64, the 6502 microprocessor. It’s function is to control the C-64, so you can think of it as the C-64s brain.
You control the brain by giving it instructions. These instructions are given to the processor by writing the 6502 machine language.
In the previous article about C-64 we created a simple program that changed the background of the border and the main screen to a lot of different random colors. In this tutorial, we are going more deep into the general 6502 microprocessor language.
Programming the 6502 Microprocessor
The 6502 microprocessor language consists of many different instructions and commands, that all work together to give functionality and graphics to your own programs. You can move data between registers (a register is basically a container(variable) where you can store data), and execute commands. You can move data into registers, and from registers to memory locations.
We got three registers in the 6502 that can be used to move data.
Register | Decimal | Hex | Binary |
A | 0-255 | 00 – FF | 00000000 -11111111 |
X | 0-255 | 00 – FF | 00000000 -11111111 |
Y | 0-255 | 00 – FF | 00000000 –11111111 |
Table 1 – 6502 registers
In table 1, we can see the different registers. We got the A register (accumulator), and the X and Y registers. Each of them can hold one byte (0-255 in decimal, 00-FF in hex and 00000000-11111111 in binary).
To move data to/from a register, we can use various move-instructions:
Instruction | Function |
STA | Move the byte in A into a memory location |
LDA | Stores a byte into A |
STX | Move the byte in X into a memory location |
LDX | Stores a byte into X |
STY | Move the byte in Y into a memory location |
LDY | Stores a byte into Y |
Let’s write a program that changes the background color of both the border and the main screen into a specific color.
First we will need to tell DASM that we are writing a program for the 6502 processor, and that our program should start in the memory located at $1000.
processor 6502
org $1000
Next we create the loop that changes the color of our border/main screen into a cyan color. First we create the loop label, and load the Accumulator register with the value of 3. If you write a hex number with the # symbol in front of it, it will load the Accumulator (A) with the hex number as a value. If you load it without the # symbol, it will load A with the content of the memory location.
loop: lda #$03
Next, we will move the value in the A register into the register that holds the color for the background/main screen.
sta $d021
sta $d020
And then we jump back to the loop label, so the program doesn’t end in a split second.
jmp loop
The complete listing of the program should be like this:
Listing 2.1 – Change screen to a given color.
processor 6502
org $1000
loop: lda #$03
sta $d021
sta $d020
jmp loop
Now, compile this code by browsing to the source code file and writing the command
”dasm example2.asm –oexample2.prg”. Then load this into the emulator by writing the command “x64 example2.prg”.
Now that the program is loaded, run it from the emulator by writing “SYS 4096” and hit [Enter].
The result should be like this:
Pretty nice, right?
Next, let’s do some more work with moving data between the registers.
Instead of using the A register, you could have used the X or Y register instead. So in all, you can use all three registers to move or copy bytes between registers and memory locations.
Relative addressing
Let’s put the X-register to use. Let’s create a new program that displays the exact same result as the example above, but instead we use relative addressing.
Listing 2.2 – Relative addressing
processor 6502
org $1000
loop: ldx #$20
lda #$03
sta $d000,X
sta $d001,X
jmp loop
As you can see, the code for this program is quite different, but the result is the same as above. First we store the value of $20 in the X register, and the value of $03 in the A register (cyan). Then we copy the value of A (color $03) into the memory location $d000+X that equals $d000+$20 (=$d020, the border color), and then we copy A into $d001+$20 (=$d021, the main screen). The result is the same as above, but we use relative addressing when storing data into the memory.
We can also move data between registers by using instructions named TXA, TYA, TAX and TAY. TXA transfers the data in X to A, TAX moves the data in A to X and so on. Only the register that the data is moved to will be affected by the transfer.
We have seen that the INC command can increase the value in a memory location by 1 (same as memorylocation += 1, or memorylocation = memorylocation + 1). You can also decrease it using DEC. It is also possible to increase and decrease the X and Y registers by using INX, INY, DEX, DEY.
In the next example, we are making a program that colors the border with the cyan color (#$03) and then colors the main screen with the cyan color + 1 (#$04) = purple.
Listing 2.3 – Increase/decrease registers
processor 6502
org $1000
loop: ldy #$03
sty $d020
iny
sty $d021
jmp loop
In this example, we simply load #$03 into the Y register, and store in into the border memory location $d020. Then we increase Y = #$03 + #$01 = #$04 = the code for purple, and store that into the main screen memory $d021.
The result can be seen in the image below.
And that’s how far this tutorial goes. Until next time!
Downloads
Download the source from GitHub:
https://github.com/petriw/Commodore64Programming/tree/master/2-6502%20Microprocessor%20Basics
Whoa! Flashbacks from my 6510 and 6502 programming back in the early eighties! Actually, the C64 had the 6510 microprocessor and the VIC-20 had the 6502, but they are both based on the same instruction set.
Pingback: Windows Client Developer roundup 064 for 3/21/2011 - Pete Brown's 10rem.net
Please continue with more
I am so thrilled to have discovered this
Wow… this brought back some serious hackin memories… I was so entangled in the moment with vivid recollections that I ended up doing some asm coding… it all came back like it was yesterday…
processor 6502
org 828
; screenloop.asm
;
; puts a lines of char’s across the screen starting from pos 0(upper left) for the
; the count of (num); As we print a char the char is incremented for each pos
; we move(different char for each position). E.g ABCDEF…
;
SCREEN equ $0400 ;start video address (1024)
start:
lda #1 ;the char were going to print (A)
ldx #0; ;start counter at at last char position of line
loop: sta SCREEN,x ; start video address (1024), + x index
inx ; inc the screen position starting from the right.
adc #1 ; inc the char were going to print next
cpx num ; check to see if we printed all the chars we wanted. (num = x?)
beq myend ; break on zf
jmp loop ; x != 0 so loop
myend:
sta SCREEN,x ; print last char, since we branched on zero.
rts
; variables
num .BYTE 254 ; the number of chars starting from the start of scrn we
; are going to print.
Cool tutorial! I like it a lot.
In the table with the move instructions, aren’t they the other way around, like LDA stores a byte into A, and STA copies the byte in A to a given memory location?
Just a comment:
in the table, you mixed up the LD. and the ST. instructions and what they do.
You can delete this comment, if you fixed it 🙂
What a coincidence 🙂 … i opened the page before frigo commented the same thing … and all this after a month of silence.
Thanks for the feedback frigo and Parker! 🙂 Great to get typos and errors fixed so the tutorial won’t be missleading. Thanks again! 🙂
I am sooo damn lost in nostalgia now.
I feel sad, I miss the days of the c64, back when you and the machine were intimate, rather than diametrically opposed as with any windows os.
Im bookmarking this ffr.
and Please, write more articles, you would be surprised how many ppl still adore the old 6502.
Ah, the felling of false nostalgia!
I want more !!!!! TNKS A LOT !!!!! from San Telmo, Buenos Aires, Argentina.
This is really great stuff! All I did in a programming capacity when I was younger with my old c64 was some long winded BASIC programs, and nothing fancy with loops and variables or anything, just line after line of print commands, clearing the screen after each line to simulate animated text effects. I wished I knew how to do this assembler stuff back then! Oh what a different person I’d be now! Again, thanks for posting, this is really great! More people should be so selfless in informing others on topics like this, in simple to understand plain language instructions, as you have!
Thanks for both great feedback and motivating Words. The C64 was, and still is a great and fun computer 😃
The cmd window Shows Errors. It says, that it doesn’t know jmp, lda, … HELP
Good tutorial! 🙂 I’m very interested in C64 and NES programming, but I love Python as well. I’m sure the formatting will be butchered with this, but here goes:
#! /usr/local/bin/python
processor = “6502”
code_start = “$1000”
foreground_color = “$d021”
background_color = “$d020”
def initc64():
s = “\tprocessor\t” + processor + “\n”
s += “\torg\t” + code_start + “\n\n”
return s
def screw_around():
# s = “\tinc\t” + foreground_color + “\n”
s = “\tlda\t#$03\n”
s += “\tsta\t” + foreground_color + “\n”
s += “\tsta\t” + background_color + “\n”
return s
def loop():
s = “loop:\n”
s += screw_around()
s += “\tjmp\tloop\n”
return s
def main():
print initc64() + loop()
if __name__ == ‘__main__’:
main()
I just thought I’d share the idea of using Python to format and generate the assembly, possibly for different assemblers (and with a call to os.system(), you could even assemble it).
So glad I found this page and instruction. I played my ass of back in the 80s and early 90s on the C64 and C128. I miss those days, and regret never getting into the programming side of things. Now here I am, 40 years old, and trying to learn how to make a game on the C64.
for those of us still programming using winvice, it is refreshing that the code still works without jmp loop. kind of odd that the assemblers of the day did not allow label