Assembler course part 3
by Wanja Gayk
Hi guys! The programs we've written so far aren't really all too useful, wouldn't you admit? But now we're going to try some cooler stuff. Today I want to show you how to produce a nice color effect on the screen ... Interested? Okay, start your monitors and let's get going!
For starters we're going to let the first line of the screen change color. To be able to do that it's necessary to know how the C64 handles colors. This is how it works in the text mode: if you look at the screen memory you'll notice that the screen code for the character from the upper-left corner can be found in $0400 ... the next character to the right of it is in $0401 and the next chAracter to the right of that can be found in $0402, and so on. The first sign of the second line is in $0428 ($28 hex = 40 decimal), the one right of it in $0429 and so forth. The system's easy enough to understand, isn't it? You can find the screen codes for each of the characters in the C64 manual.
I'd like to illustrate the whole thing on the screen with a simple Assembler program for which we need a new command:
TXA / TAX and TYA / TAY
TXA (Transfer X register to Accumulator), this command copies the value from the X register into the accumulator. To copy a value from the accumulator into the X register you have the command TAX (Transfer Accumulator to X register).
These two commands are of course also available for the Y register, where they are called TYA (Transfer Y register to Accumulator) and TAY (Transfer Accumulator to Y register) respectively.
The following program will show you their effects:
.A 1000 LDX #$00
.A 1002 TXA
.A 1003 STA $0400, X
.A 1006 INX
.A 1007 BNE $1002
.A 1009 RTS
This program loads the values of $00 to $ff into the screen memory. It does a great job in demonstrating which value from the screen memory (screen code) is responsible for which sign. On some older C64's, you won't see anything with this code. The reason is that the older Kernal's internal CLR/Home routine does not fill color memory with the current cursor color, but instead, with the current BACKGROUND color. Any characters output without a specific color (e.g. by POKE or STA) are as invisible as an albino rabbit on a glacier (white on white). Just a few preparations will help solve the problem. Can you imagine which?
This is how the program works: We start the X register at $00 and copy it into the accumulator. Now we load the content of the accumulator into the screen memory at $0400 + X. In the first run X is $00, so that the value of the accumulator (presently $00) is loaded into memory cell $0400. Now the X register is increased by one via the INX. As X does not equal zero, the BNE (Branch if Not Equal) instruction jumps back to $1002 where the value of the accumulator (now $01 thanks to the TXA instruction) is loaded into the screen memory.
This process of copying X into the Accumulator and then storing to memory at $0400 + X continues until X = $ff. Again $ff is loaded into the screen memory at $04ff. The next command is INX, which tries to increase X once more. Since it has already reached $ff, the maximum value, X starts again at zero. Now that X has rolled over to Zero, the Zero-Flag is now set, and BNE does not jump anymore. The program proceeds to RTS and stops. RTS is a new command I'd like to explain briefly:
RTS (ReTurn from Subroutine) - Normally subroutines are called up with SYS from BASIC or with JSR from Assembler. We're going to use JSR later on in this part of the course, where the command is going to be explained. Every time you call up a subroutine, the address to which it must return is stored on the processor stack. RTS takes these return addresses from the stack again and jumps back to them. RTS works comparable to RETURN in BASIC. In BASIC the command GOSUB 100 calls up a subroutine. RETURN then returns to the spot where the GOSUB command left off. So if you jump with SYS from BASIC to an Assembler program RTS will return from Assembler to BASIC.
Well, now you've seen the elements the screen is composed of and know which screen codes represent which characters. But where are the colors I promised you so pretentiously? No problem whatsoever, since now we'll come to the color memory - also called color RAM. It starts at $d800 and is composed of the same elements as the screen memory. Each memory cell of the color RAM represents the color of one character found in the corresponding cell of the screen memory. The colors are treated similar to the border ($d020) and background ($d021) colors. Values from $00 up to $0f set the color of any one character. All values higher than these do not make sense as they only produce the same colors, i.e. $0a = light red, $1a = light red, $2a = light red, and so on. As you can see, only the second, low-order digit of the number - the so-called low-nybble (4-bits) - is important. You'll be best able to see that with the help of a little program with which we'll color the first line of the screen. First, let's set up a little color table:
.M 1100 06 04 0e 0a 03 0f 0d 07
.M 1108 01 01 07 0d 0f 03 0a 0e
.M 1110 04 06 0b 0c 0f 01 0f 0c
.M 1118 0b 09 02 08 0a 0f 07 01
.M 1120 01 0d 03 05 0c 0b 0c 0f
And now the program:
.A 1000 LDX #$00
.A 1002 STX S:D020
.A 1005 STX $D021
.A 1008 LDA $1100, X
.A 100B STA $D800, X
.A 100E INX
.A 100F CPX #$28
.A 1011 BNE $1008
.A 1013 RTS
What to do: just write anything you like in the first line of the screen in the BASIC editor, press SHIFT+RETURN (so the line will be ignored and you'll get no syntax error warning), start the program with SYS 4096 or from the monitor with G 1000, and you'll have a nicely colored first line.
Now, how does this program work? Well by now you probably know, but here goes: First, we initialize X to $00, and store this zero value to the border and background color registers (convenient, isn't it?). Next, we start a loop, in which we first load a value from our table. The location pointer to will be $1100 + X, which will be $1100 since X is zero on the first run. The value is then stored to Color RAM at location $D800 + X, which will be $D800 on the first run, of course. X is then increased by 1 (INX) and compared against the value $28 (40 in decimal). If X is not equal to $28, the BNE instruction will cause a jump back to $1008. If they are not equal, the instruction fails and falls through to the RTS, where the program terminates.
Each time the BNE branches back to $1008, X is increased by 1 and a value is loaded from the table and stored to Color RAM. On the second pass through the loop, X will be 1, and so the byte copied will be from $1101 to $D801, followed by $1102 and $D802, and so on until X reaches $28.
Okay, so now we have a colored first line, but it would be nice to have some motion, wouldn't it? So how are we going to do it? Let's start like this: let's take the first color value from our table and keep it in a temporary storage location. Then we move the whole table one position to the left. The second color value now takes the place of the first, the third that of the second and so on. The last value changes to the second last and leaves an empty space where we can deposit the value we have stored. Now let's bring the colors back onto the screen, which means copying the color table into the color RAM.
In addition to the above routine we now need another one to be able to cycle the color table. And as it would be much too easy to store the first value if the table in the X or Y register, we'll use two new commands:
PHA / PLA (PusH Accumulator, PulL Accumulator)
PHA pushes the accumulator's content onto the processor stack in the same way that the processor pushes return addresses when executing a JSR or SYS command. PLA pulls the first value from the stack and transfers it to the accumulator.
The stack is used to store jump addresses and other values. It works by the LIFO principle (Last In - First Out). That's to say if you push one byte directly after another onto the stack by use of the PHA and JSR instructions, the first PLA takes the byte that was most recently pushed, from the stack, and places it in the Accumulator. The second PLA pulls off the next most recently puched byte. Since return addresses are also stored on the stack the number of bytes PULLed from within a subroutine or program must always equal that of those PUSHed within that same subroutine or program, otherwise you'll get a magnificent crash!
But now let's look at a program that demonstrates the effects of the PHA and PLA commands. This program starts off at the address $1014 - directly following the other program.
.A 1014 LDA $1100
.A 1016 PHA
.A 1017 LDX #$00
.A 1019 LDA $1101, X
.A 101C STA $1100, X
.A 101F INX
.A 1020 CPX #$27
.A 1022 BNE $1019
.A 1024 PLA
.A 1025 STA $1127
.A 1028 RTS
What exactly is the effect of this little routine? It takes the first value from the table (at $1100) and pushes it onto the stack. Then it returns the X register value to $00, takes the second value (from $1101 + X) and puts it in the place of the first (to $1100 + X). X is increased by one and a check is made to see if X has already reached the number of table values (minus one, as the last one has been exchanged for the first one, so you don't need it anymore). If this is not the case, the loop is repeated. Otherwise, BNE will not jump and the program will continue at $1024 where the byte last pushed onto the stack will be pulled off and put in the place of the last value of the table. Now we're finished - you've cycled through the table once. You only have to display it on the screen with the routine you wrote at $1000 (first program). Of course you could write a similar BASIC program looking like this:
10 SYS 4096: SYS 4116: GOTO 10
But this is an Assembler course, isn't it? So for testing purposes let's write a little routine at $1200 just to use it for demonstration. We're going to need one new command for it.
JSR (Jump to SubRoutine)
JSR jumps to a subroutine elsewhere in the computer's memory. JSR $1000, for example, would jump to the program that starts at memory cell $1000. The command has the same effect as GOSUB in BASIC - the equivalent of BASIC's RETURN command (to end a subroutine) is a simple RTS instruction in Assembler, as you already know. That's the reason why RTS jumps back to BASIC every time you call up the machine routine from BASIC with SYS. The command SYS is nothing but a JSR command disguised by the BASIC interpreter.
But now let's finally come to the program. It doesn't return to BASIC because it doesn't have an RTS command at the end (it's an infinite loop, anyway):
.A 1200 JSR $1000
.A 1203 JSR $1014
.A 1206 JMP $1200
A really simple program: it jumps to the routine that displays the color table (JSR $1000) and then to the program that cycles the table ($1014). After that it jumps back to its own start where it branches again to the display routine, forming an infinite loop.
You can easily start the effect on the screen with G1200 or in BASIC with SYS 4608 - to abort you've just got to press RUN / STOP-RESTORE - experts like you already know that of course. You'll see that things run nearly a little too fast (well, yes - there's nearly no stopping our dear little C64 with Assembler programming). Therefore I'm going to show you in the next part how to synchronize the whole thing with the scanning beam that is responsible for the display. Then you'll get really close to the cool stuff: the INTERRUPT.
Okay, stay hot for next time!