Assembler course part 4
by Wanja Gayk
Hi! I hope you've brought some time with you because now we are slowly arriving
at the point where programming starts being fun. The last part was about
bringing color-effects onto the screen by copying a color-table into the
color-ram, moving this chart to the next position, inserting the color, that
comes out at one end, at the other end and finally copying our new color-table
into the
color-ram again.
So we had a closed loop. Now we want to see the whole thing in a reasonable,
regular speed and... well, we want to show off a bit and to bring this effect
into the background of a Basic-program. Hold it, hoolllld it!! You can't have
THREE Wishes! That's impossible!
[Kendra, Wanja, Guenther: The only thing I could think of to replace this slogan
was a variation on the traditional Genie-in-the-bottle. Old stories say that if
you find a genie in a lamp or bottle and release him, he'll grant you a wish. I
hope this works for you. -Nate]
Actually, it isn't all that impossible, and we certainly don't need any genie to
help us out. All we need is a normal breadbox C64 and a good machine language
monitor!
Also, we need to know what an interrupt is. Then we can confidently forget our
closed loop. Don't worry, you haven't learned everything in vain. But what the
hell is an interrupt?!?
Now let's imagine the following: The C64 builds up its picture 50 or 60 times
every second, which you may be able to see depending on your TV or monitor. In
additon, the computer carries out several service-routines that, for example,
make the cursor flash or wait for an input from the keyboard. To do this, a chip
interrupts the C64's actual work from time to time - so this is simply what
"interrupt" stands for. Now the trick is to latch your own programs to these C64
service-routines, or in some cases, to replace them completely. At the beginning
of each interrupt the computer gets the start address of the service-routine out
of the addresses $0314 and $0315 . There we normally have $31 and EA,
respectively. Inside the storage addresses are always stored exactly backward
from the you would read them. The order is lowbyte, highbyte. That means that
during every interrupt the computer jumps to the address $EA31 ($EA is the
highbyte and $31 the lowbyte). You might be confused a bit now, but don't worry,
things will become clear soon. The addresses $0314 and $ 315, from where the
start-adresses for the service-routines come, are called an interrupt vector.
The word vector actually means "an entity describing both direction and distance
from a starting point." In the case of a computer, it means the direction and
distance from the vector location itself, to jump to. Now we latch our programs
to these routines by directing the interrupt vector to the start-address of our
own program.
If we wanted to put our interrupt routined at address $1100, we simply redirect
the vector by writing the $11 $00 into the interrupt vector at $0314/ $0315, as
shown here:
.A1000 LDA #$00 ; low byte
.A1002 STA $0314
.A1005 LDA #$11 ; high byte
.A1007 STA $0315
.A100A RTS
But wait! What if the computer wants to carry out an interrupt while we're in
the middle of changing the two-byte vector? "Vvrroooomm... SCREEEECH!!! Crash!!"
- The computer comes to a screeching halt. Of course we want to avoid that, so
we must forbid the computer from carrying out any interrupts while we are
working with the interrupt vector. For this we need 2 new commands:
SEI - SEt Interrupt disable flag: This command turns interrupt off.
CLI - CLear Interrupt disable flag: This command restores interrupts.
So our new program is:
.A1000 SEI
.A1001 LDA #$00
.A1003 STA $0314
.A1006 LDA #$11
.A1008 STA $0315
.A100A CLI
.A100B RTS
If you start this now (with SYS 4096 in BASIC or g 1000 in your monitor) there
will still be a crash, because there isn't yet a program at $1100 (where we have
directed the interrupt to)! Patience. In fact, we already have half of it. Now
we will latch a small program to the interrupt, and to definitely avoid a crash
we will jump from the end of our program immediately into the C64's own service
routine again. Now back to work! Let's write a small program to $1100:
.A1100 LDA #$07
.A1102 STA $0400
.A1105 LDA #$0F
.A1107 STA $0401
.A110A LDA #$36
.A110C STA $0402
.A110F LDA #$34
.A1111 STA $0403
.A1114 LDA #$21
.A1116 STA $0404
.A1119 JMP $EA31
Now, enter lines $1000-$1010 from above, and this second part (lines
$1100-$1119), leave the monitor with X and divert the interrupt with SYS 4096.
Now, have a look into the upper left corner of the screen - try to delete or
overwrite what you see there (with Shift-Clear/Home or the delete key, perhaps).
IF your machine doesn't show anything at the top of the screen, try pressing
Home, followed by several spaces (or anything else). This oddity is due to a bug
in some very old C64 ROMs, which would cause characters to be invisible is thier
color locations aren't properly set up. The Home + Spacebar thing takes care of
this.
As you can see, the small "GO64!" in the corner just cannot be killed... unless
you boldly press Run/Stop-Restore. Why? Because every time the computer produces
an IRQ (interrupt), it jumps into our routine which writes the string "GO64!"
into the corner and jumps into the system- routine that would have originally
been carried out. So the C64 acts as if nothing had happened. Bingo! Now it
should be clear, why we mustn't program a closed loop into an interrupt,
otherwise the computer would be trapped inside the
interrupt, constantly trying to start a new interrupt during this endless loop.
After a few seconds, it would be confused and crash.
Alright: An interrupt routine must never contain a closed loop and must be
terminated properly -- that means by jumping into the system routines!! If
everything is clear so far, we are going to the next step. If not, go back and
re-read the text a couple of times - it can sometims take a while for the idea
to really "sink in".
There are a few different kinds of interrupts. The most important interrupt for
the programer is the screen, or "raster" interrupt. What you have to know is,
that the screen is built up one raster line at a time, from top to bottom. With
a screen interrupt we can decide on which screen line the interrupt shall begin.
On NTSC machines, screen line No. 1 is below the bottom line of the text
display, immediately after the "last" line (line 262). On PAL machines, line No.
1 is inside the upper screen frame, while last line (312) is down at the bottom
and inside the screen as well, but out of our sight. As soon as the raster beam
finishes the last line, it immediately begins again at the first line.
We have to tell the computer which kind of interrupt we want and where. For this
the VIC has certain registers. I guess it's the best I show you with a small
program again:
.A1000 SEI ; disable interrupts
.A1001 LDA #$00 ; "bend" interrupt vector to $1100
.A1003 STA $0314
.A1006 LDA #$11
.A1008 STA $0315
.A100B LDA #$60
.A100D STA $D012 ; interrupt starts in screen line $60
.A1010 LDA #$1B
.A1012 STA $D011 ; text screen on and bit 7 is clear.
.AD011 LDA #$F1
.A1017 STA $D01A ; start raster interrupt
.A101A CLI ; re-enable interrupts
.A101B RTS
Line $1010 is particularly interesting: Why must we clear bit 7 in $D011? The
reason is that the screen has more than 256 lines. Since one byte (namely $D012)
can only represent a number from $00 to $FF (255, decimal), one additional bit
is provided in $D011. If the screen beam is beyond or below the 256 mark, this
additional bit will take care to represent this. You may know this from playing
around with sprites, which are also difficult to move to the right margin of the
screen. As long as bit 7 in $D011 is cleared, the C64 knows that you want to
start the interrupt in the area of $00-$ff and not further at the bottom. As
long as you don't have any crazy ideas, just stay at the given value.
But what do we gain from this interrupt? Well, as we know the interrupt always
starts in the line that we want. This gives us a quite regular timing (at least
regular enough for our means), compared to our usual timer-interrupt. And with
this we can achieve our original aim: a color-effect without jerkiness or
jitters. We use the mentioned routine to initialise our interrupt. What is still
missing is our own Interrupt-routine for the color-effect. Let's remind
ourselves: We take a routine to copy the color- table into the color- ram and
another to revolve/ rotate the color-table. And, in an interrupt, we can not
have ANY closed or infinite loops!
I still have to make one peculiarity clear to you: We have to access register
$D019 once while writing, so that a screen interrupt is recognized as finished.
We use the standard way to do this, as the SuperCPU is a bit picky here, and
simply write anything into $D019, e.g. a $01.
Now our program looks like this:
Interrupt- routine:
.A1100 JSR $1180 ; call the "copy table" subroutine, below.
.A1103 JSR $1200 ; call the "revolve table" subroutine
.A1106 LDA #$01
.A1108 STA $D019 ; clear interrupt- register (interrupt finished)
.A110B JMP $EA31 ; continue with regular system- routine
Subroutine: "copy table into colour-ram":
.A1180 LDX #$00 ; X-register to $00
.A1182 LDA $1300,X ; value from table + fetch X
.A1185 STA $D800,X ; value into colour-ram + write X
.A1188 INX ; increase X by 1
.A1189 CPX #$28 ; compare X with $28 (decimal 40)
.A118A BNE $1182 ; if it's not a match, jump to $1182
.A118C RTS ; (end of subroutine)
Subroutine: "revolve table":
.A1200 LDA $1300 ; load value to start into
table
.A1203 PHA ; move onto stack
.A1203 LDX #$00 ; X-register to $00
.A1206 LDA $1301,X ; value from table + read X
.A1209 STA ; value into table + write X
.A120B INX ; increase X by1
.A120C CPX #$27 ; compare X with $27 (dec.39)
.A120E BNE $1206 ; if unequal, jump to $ 1206
.A1210 PLA ; take value from stack
.A1211 STA $1327 ; and write it into end of table
.A1214 RTS ; (end of subroutine)
Color table:
.M1300 06 04 0e 0a 03 0f 0d 07
.M1308 01 01 07 0d 0f 03 0a 0e
.M1310 04 06 0b 0c 0f 01 0f 0c
.M1318 0b 09 02 08 0a 0f 07 01
.M1320 01 0d 03 05 0c 0b 0c 0f
You can start the program if you leave the monitor and enter a normal SYS 4096.
Now you will see, that you have got a colour- effect in the top line of the
screen that works at the same time as the regular BASIC interpreter, and looks
good as well! There we are! In our next installment, I will show you how you can
slow the effect down without a delay loop, because long delay loops are taboo
for the interrupt!! Last but not least I want to show you some other simple, but
nice effects using the VIC- registers $D011 and $D016. There's still so awfully
much to be discovered... so see you!