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
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
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:
.A1001 LDA #$00
.A1003 STA $0314
.A1006 LDA #$11
.A1008 STA $0315
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
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:
.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
.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)
.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!