 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!
