home *** CD-ROM | disk | FTP | other *** search
- *********************************************************************
- This article is being presented through the *StarBoard* Journal of
- the FlagShip/StarShip, SIGS (Special Interest Groups) on the
- Delphi and GEnie telecommunications networks. Permission is
- hereby granted to non-profit organizations only to reprint this
- article or pass it along electronically as long as proper credit
- is given to both the author and the *StarBoard* Journal.
- *********************************************************************
-
- INTERRUPTS ON THE COMMODORE 64
-
- By Roy Riggs aka DevilTM
-
-
- INTRODUCTION
- This text file is a detailed explanation of interrupts. It is
- written for an intermediate level machine language programmer as a
- HOW-TO write your own interrupt routines. If you have been programming
- ML this has probably been an area that you have stayed away from.
- Interrupts can be intimidating at first, but after some hands-on
- experience you will find that they are no harder than any other
- programming technique. Hopefully, this file will give you the push you
- need so that you will try writing your own interrupt routines.
-
- WHAT YOU NEED TO KNOW
- Before you continue you should have a fairly solid base in ML
- programming, at least an understanding of all the op codes, the status
- register, the stack, vectors, and common KERNAL routines. If you
- don't, the author personally recommends Jim Butterfield's book of
- Machine Language for Commodore computers. Please note all references,
- memory locations, and specifications in this file are for the Commodore
- 64 or the Commodore 128 if in 64 mode ONLY.
-
- LEARNING THE BASICS
- An interrupt is when the processor is told to stop what it is
- doing. It is temporarily given a new set of instructions to process.
- This new set consists of the interrupt routines. When the routines are
- finished, the original program continutes execution undisturbed. On
- the C64 the special interrupt routines perform such vital functions as
- updating the system clock, checking for the RUN/STOP key, blinking the
- cursor, handling the cassette motor, and checking the keyboard for
- input. The interrupt happens approximately 60 times a second and is an
- important part of the operating system. If the interrupts were ever
- disabled the computer would lock up.
-
- WHAT REALLY HAPPENS
- When you first turn on the C64 the CIA #1 Timer B will start
- triggering an interrupt request (IRQ) every 1/60 of a second. When
- this happens the processor finishes the instruction it is working on,
- saves the program counter and the status register on the stack, then
- JMPs indirectly to the address stored in $FFFE and $FFFF. This is
- normally $FF48. The first thing that the routine does is an SEI to
- keep the interrupt from interrupting itself. Next it saves all the
- registers on the stack in the order of A, X, Y. This is VERY
- important! In order for the original program to run undisturbed, it
- will have to restore these values later. Since this routine is also
- used by the BRK interrupt, the next thing it will do is test to see if
- the interrupt was from a BRK or an IRQ. It does this by checking bit 4
- of the status register (SR). If the B or BRK flag is set then it JMPs
- to the vector stored in $316-$317. This is usually the break routine
- $FE66. Otherwise it is an IRQ and is vectored thru $314-$315 to $EA31,
- the keyboard scan. It will continue through the rest of the routines
- mentioned above until it is done, at which point it will pull the Y, X,
- and A back off the stack and execute a RTI return from interrupt. The
- old program counter PC and the status register SR are restored; control
- is returned to the original program.
-
- WHY DO WE NEED THEM?
- A good question indeed. Like any other programming technique,
- interrupts are designed to make certain tasks easier. A common example
- is split-screen graphics. Try and think of a way to make the top half
- of the screen bit mapped for pictures and the bottom for text. You
- probably can't think of a way to do it without printing letters in
- high-res or drawing pictures with redefined characters. Either way is
- very messy. Raster interrupts will be explained later; they make this
- problem a piece of cake. Since IRQs happen every 1/60 of a second they
- are very useful for timing critical code. This is why so many music
- programs use interrupts, such a perfect way to insure a steady tempo
- regardless of what is happening elsewhere. Finally, interrupts are
- sometimes the only way you can do something; for example, things that
- need to run as background process regardless of what application is
- executed. If you wanted to make the border flash red every 2 seconds
- while running any BASIC program, interrupts would be the only way to
- solve the programming problems.
-
- GETTING BACK TO BUSINESS
- The basic idea when writing an interrupt routine is to make a
- wedge. This means we will make a slight detour through our code (to do
- what we want) before going on with what normally happens. Every time
- an interrupt occurs it will go through our code so we have a choice of
- doing whatever we want. To do this we change the vector at $314-$315
- to point to our routine. When we are finished we usually can just jump
- to the normal interrupt routines. Interrupt wedges are typically
- broken into two parts. The first part is typically very short and is
- used to initialize the wedge and reset the vectors. The second part is
- also pretty short; it is the actual detour. Lengthy code will slow the
- works down because it is run every 1/60 of a second. It must take
- every interrupt, find out what caused it, service it appropriately,
- clear the interrupt, and either terminate by itself, or jump to the
- normal interrupt routines.
-
- IMPORTANT NOTE: Whenever you change an interrupt vector make sure you
- SEI. Otherwise if an IRQ hits in the middle of changing it, the vector
- will point to nowhere.. CRASH! Also don't forget CLI when you are
- done. Quick review, SEI means set interrupt disable. This means that
- no interrupts will occur until a CLI is executed.
-
- LET'S GET STARTED
- Now we know enough to write a simple interrupt routine. The only
- way to learn is by doing, so here is some source code with lots of
- comments. This routine will flash the border every half of a second.
- Don't just skim over this code; make sure you understand the purpose of
- every instruction. Remember, by the time it gets to our routine it has
- already saved the registers. Since we JMP to the normal routine when
- done, we do not have to worry about preserving them.
-
-
- 0100 *interrupt border flasher
- 0200 *by roy riggs aka deviltm
- 1000 .or $033c
- 1010 irq .eq $0314
- 1020 vector .eq $fc
- 1030 timer .eq $fe
- 1040 border .eq $d020
-
- *RESET VECTOR AND INTIALIZE TIMER
-
- 1050 lda irq ;save old vector
- 1060 sta vector ;for when we are done
- 1070 lda irq+1
- 1080 sta vector+1
- 1090 sei ;can't forget this!
- 1100 lda #<start ;point vector to our
- 1110 sta irq ;routine instead of old one
- 1120 lda #>start
- 1130 sta irq+1
- 1140 cli ;safe now
- 1150 lda #30 ;init timer for .5 seconds
- 1160 sta timer
- 1170 rts ;go back
-
- *THIS IS THE ACTUAL WEDGE
-
- 1180 start dec timer ;countdown timer
- 1190 bne cont ;time to change yet?
- 1200 inc border ;yes, so flash it
- 1210 lda #30 ;init timer again
- 1220 sta timer
- 1230 cont jmp (vector) ;continue on with normal IRQ
-
- Hopefully you got through that with little or no problem. As
- mentioned earlier, knowledge of vectors is essential here. Now is a
- good time to brush up on them if they gave you problems. If you are
- really serious about learning this, you will spend the 5-10 minutes it
- takes to type that in. Once you enter it, you will understand more by
- modifying the code than by just reading this file.
-
- OTHER SOURCES OF INTERRUPT
- Besides the CIA #1 Timer B, there are several other things that
- can cause an interrupt. There are the raster interrupt,
- Sprite-Background collision, Sprite-Sprite collision, and the negative
- transition of the light pen. These are not serviced in the normal
- interrupt routines, so we must write our own code to use them.
-
- OK, SO HOW?
- Like everything else, interrupts have special memory locations.
- Logically, the first thing we need is a way to tell exactly what caused
- the interrupt. The story is told in the INTERRUPT STATUS REGISTER,
- which I called INTSTAT for short. You do not want to get this mixed up
- with the normal status register SR. The INTSTAT is stored in $D019 or
- 53273. Each bit of the INTSTAT refers to a different interrupt.
-
- Name Bit Cause of Interrupt
- ---------------------------------
- IRST 0 Raster
- IMDC 1 Sprite-Background
- IMMC 2 Sprite-Sprite
- ILP 3 Light pen
-
- Note: the 7th bit is the IRQ and this bit is set on ALL interrupts.
-
- The SEI command provides an easy way of momentarily disabling ALL
- interrupts. However, sometimes we want to turn off just some of them.
- This can be done with the INTERRUPT ENABLE REGISTER, or INTENAB. It is
- at $D01A or 53274. Its format is identical to the INTSTAT, but it
- performs a totally different function. Care must be taken not to get
- them confused. Remember, the INTSTAT shows what caused the interrupt.
- The INTENAB determines what CAN cause an interrupt. To enable an
- interrupt from a source, its corresponding bit in INTENAB must be
- turned on; (Use the same table above) ie, set bit 2 to enable
- Sprite-Sprite interrupts. [NOTE: The INTSTAT can still be checked
- even if the interrupt for that bit is disabled. If the condition is
- right, the INTSTAT will be set, but it will not trigger an IRQ. Once
- again, you POKE the INTENAB to select which interrupts can occur and
- PEEK the INTSTAT to find what caused the interrupt].
- Also when you write your own routines to service interrupts, you
- must remember to clear the interrupt when you are finished. INTSTAT
- gets 'latched' which means, once a bit is set it stays set until
- cleared. To clear the interrupt you write a 1 to the corresponding bit
- in INTSTAT. This does seem backwards, but that is how it works; ie,
- after you get done handling a raster interrupt (bit 0):
- LDA #$01 STA INTST
- This way if several bits are set you can handle them one at a time.
- Now in the last program we jumped to the normal routine when done;
- this is a very good idea normally. However, if the interrupt was not
- caused by the CIA timer, you should handle the entire interrupt
- yourself. The reason is that those routines expect to be called every
- 1/60 of a second, no more or no less. For example, if you are using
- raster interrupts (explained shortly) and call the normal IRQ routine
- when done every time, they will be executed more often than they
- should. You will notice that the cursor blinks a lot faster and the
- system clock starts flying. This apparent burst of speed is deceptive.
- The actual program that is running is really running slower! To return
- from an interrupt you must restore the registers from the stack with:
- PLA, TAY, PLA, TAX, PLA, and RTI. NOTE: The status register and
- program counter will be pulled off the stack by the PROCESSSOR after
- the RTI.
-
- ADVANCED WEDGE
- The first program used only the normal IRQ. Notice that all of
- the interrupts whether normal, raster, or sprite ALL go to the same
- vector. We will have to make sure we find out why the interrupt
- occurred so we can service it appropriately. To determine the source
- we just have to look at the bits of INTSTAT.
-
- SPRITE INTERRUPTS
- If you have ever written sprite collision routines, you probably
- had a hard time with them overlapping. By the time your program gets
- around to checking them, they are already on top of each other. Now
- with interrupts you will know the very instant they touch. I will
- leave handling collisions up to the reader since interrupts are merely
- another method of detecting the collision. After you find that the IRQ
- was from sprites you can basically use your old sprite collision
- routines. An in depth look at sprites is out of the range of the topic
- at hand.
-
- LIGHT PENS
- Same deal as with sprites really. Actually, it is probably easier
- to read the light pen normally than mess with interrupts.
-
- RASTER INTERRUPTS
- Raster interrupts are kind of tricky, and since they can only be
- accessed from interrupts, most readers are still in the dark. Let's
- cover the basics here first. The picture generated by your television
- or monitor is NOT projected all at once. Look closely at your screen;
- notice how it is made of a bunch of horizontal lines? These lines are
- called scan lines. At the back of the picture tube is an electron gun.
- It is like a machine gun, continually firing electrons at the screen.
- The back of the screen is coated with phosphorus, and whenever an
- electron hits it, it flashes. The gun sweeps back and forth across the
- screen from top to bottom. It moves with such incredible speed that
- before the flash dies out in one spot, another electron will hit it.
- To the slow human eye all this appears to be a solid screen. The
- raster is the current scan line that the gun is shooting at. The value
- of the raster will vary from 0 - 262 on normal television; on a
- European television it goes from 0 - 312. Since these values are
- greater than 255, we can not fit them all in one byte; therefore 9 bits
- are used to store the raster. The lower 8 bits are stored in $D012,
- and the 9th bit is stored in bit 7 of $D011. Note that the top of the
- screen corresponds with a value of 50 and the bottom is at 249. The
- raster value changes so fast that even a machine language program can't
- check it fast enough if it is going to do anything else. Luckily, we
- can tell the hardware to trigger an IRQ every time the raster equals a
- specific value; this is how you write split-screen applications. For a
- screen half high-res and half text, set the screen to high-res mode and
- tell it to interrupt at a point halfway down the screen. Using a wedge
- we an catch it when this happens and set the screen back to text. By
- telling it to interrupt when we get back to the top we can change back
- to high-res. By flipping back and forth like this we achieve the
- desired effect. Our program doesn't even have to check the raster at
- all.
-
- THATS NICE, BUT HOW DO YOU CODE THAT?
- Here is how it is really done. First we have to specifiy the line
- for the interrupt. You do this by storing the value in the raster
- $D012 and bit 7 of $D011. You CANNOT just ignore this extra bit!!
- [NOTE: This is also where the current raster is stored.. You probably
- wonder how it can hold both values at once. Remember we are dealing
- with an IA here not memory. The IA takes the value and will remember
- it]. Next, we must enable the raster interrupts. To do this, set bit
- 0 of the INTENAB. Finally, we must have our own interrupt wedge
- installed. It will have to determine whether this is a normal IRQ or
- from the raster. If normal then JMP to the normal vector. If it is
- raster then do whatever we want: clear the latch, reset where we want
- raster interrupts to occur, then restore the registers ourself and
- return from the interrupt. Ready for the next program? This program
- uses raster interrupts to divide the border into 2 parts determined by
- the cursor location.
-
-
- 1000 *raster border change
- 1005 *by roy riggs aka deviltm
- 1010 .or $c000
- 1020 border .eq $d020
- 1030 raster .eq $d012
- 1050 intstat .eq $d019
- 1060 intenab .eq $d01a
- 1070 irq .eq $0314
- 1080 vector .eq $fd
- 1090 plot .eq $fff0
-
- *TIME TO SET IT ALL UP
-
- 1100 lda irq ;copy old vector
- 1110 sta vector
- 1120 lda irq+1
- 1130 sta vector+1
- 1140 sei ;be careful
- 1150 lda #<start ;wedge our routine in
- 1160 sta irq
- 1170 lda #>start
- 1180 sta irq+1
- 1190 lda #15 ;set the first raster
- 1200 jsr setras
- 1210 lda intenab ;turn raster interrupts on
- 1220 ora #$01
- 1230 sta intenab
- 1240 lda #$00 ;set black border to start
- 1250 sta border
- 1260 cli ;its safe now
- 1270 rts ;go back
-
- *THIS IS THE REAL THING!
-
- 1280 start lda intstat
- 1290 and #$01 ;check sourde of IRQ
- 1300 bne ras ;was is raster?
- 1310 jmp (vector) ;nope, go on to normal
- 1320 ras lda border ;yep
- 1330 eor #$01 ;flip colour
- 1340 and #$01
- 1350 sta border
- 1360 bne switch ;are we at top or bottom?
- 1370 lda #15 ;bottom, so set to top
- 1380 jsr setras
- 1390 jmp sw
- 1400 switch sec ;top
- 1410 jsr plot ;where is cursor?
- 1420 txa
- 1430 asl ;multiply by 8
- 1440 asl ;cuz theres 8 scan lines
- 1450 asl ;per character
- 1460 clc ;top of screen starts at 50
- 1470 adc #50 ;(see page 157 of ref manual)
- 1480 jsr setras
- 1490 sw lda #$01 ;clear latch
- 1500 sta intstat
- 1510 pla ;restore everything
- 1520 tay
- 1530 pla
- 1540 tax
- 1550 pla
- 1560 rti ;and away we go!
- 1570 setras sta raster ;set where the IRQ will occur
- 1580 lda raster-1 ;msb is stored here
- 1590 and #$7f ;sets bit7=0
- 1600 sta raster-1
- 1610 rts
-
-
-
- WRAPPING IT ALL UP
- Here it is again from the top. Interrupts are triggered every
- 1/60 of a second and by certain special conditions. The processor
- stops and runs the interrupts routines vectored through $314-$315.
- These routines perform vital system functions. Once the routines are
- completed, all the registers are restored and the processor returns to
- what it was doing. In order to use the interrupts we need to insert a
- wedge into that vector. (Don't forget, disable interrupts first and
- clear them afterwards!) Wedges consist of two main parts. The first
- part saves the old vector and inserts a new vector pointing to the
- second part of the wedge. It also should initialize anything that the
- second needs and enable the interrupts we choose. The second part is
- the meat of the program. It must determine the cause of the interrupt
- and handle it. Clear latches is necessary and, when finished it should
- return either by itself or through the normal IRQ routine. Below is a
- flow chart for both parts. The flowcharts are the skeleton for any
- interrupt routine you may write. Leave out the branches you don't use
- and flush out the others to suit your purpose.
-
-
- ****PART 1****
-
- SAVE OLD VECTOR
- I
- SEI
- I
- POINT VECTOR TO PART2
- I
- INTIALIZE DATA FOR PART2
- I
- ENABLE INTERRUPTS
- I
- CLI
- I
- RETURN
-
-
- ****PART 2****
-
- START
- I yes
- NORMAL IRQ?------------I
- I I
- Ino I
- I I
- WHAT CAUSED IT? I
- I I I I
- LIGHT PEN? I RASTER? TIMING
- I I I SPECIFIC
- I SPRITE? I CODE
- I I I I
- I I I I
- SERVICE I SERVICE I
- I SERVICE I JUMP TO
- I I I NORMAL IRQ
- I I I
- I I SET RASTER
- I I FOR NEXT TIME
- I I I
- CLEAR INTERRUPT LATCH
- I
- RESTORE REGISTERS
- FROM STACK AND RTI
-
-
-
- I THINK YOU FORGOT SOMETHING
- Yes, I have been ignoring some things about interrupts, mainly
- because theese things are rarely used and your head is already swimming
- with enough stuff. For those that insist on knowing everything,
- continue reading.
- Only the IRQ interrupt has been explained so far, but there are
- two other interrupts: the BRK and the NMI. BRK is triggered whenever
- the BRK command is executed. It does not quite fulfill our definition
- of an interrupt, but it is similar. Normally the BRK is vectored
- through locations $316-$317, which points to the same routine that is
- invoked by hitting RUN/STOP RESTORE. NMI stands for non-maskable
- interrupts; this means it can not be turned off by the SEI command.
- The NMI can be triggered either by the RESTORE key or by CIA #2. When
- triggered, it passes through the vector at $318-$319. This leads to a
- routine that determines the source of the NMI. If it was from CIA #2
- then it jumps to some RS-232 routines. If it was the RESTORE key it
- checks to see if the RUN/STOP key is also pressed. Surely you know
- what this does! If the RUN/STOP key was not pressed, it simply
- returns.
-
-
- The author gives permission for this file to be printed, copied, or
- reproduced in whole or part. Please include the author's name if used.
- All source codes provided are written by the author. These are now
- submitted to the public domain.
-
- The author accepts no responsibility for damages resulting from gross
- negligence of English grammar, spelling, and punctuation, as this
- article was written late at night on his birthday. :)
-
- Roy Riggs
- (DEVIL on GEnie)