********************************************************************* 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 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 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)