Table of Contents (click on a link to jump to that web page)
- Part 1 – What is an IRQ and when would I use it?
- Part 2 – Overview with an example program
- Part 3 – Can my IRQ run without effecting BASIC?
- Part 4 – Simpler ways to use interrupts on the CoCo
- Part 5 – Advanced interrupt settings and using CoCo 3 specific interrupts
Here we show the same IRQ routine as in the previous blog post which shows a clock on the screen in the top left corner. This program will setup the IRQ so that BASIC is still running completely unaware that this IRQ has been added.
BASIC has many IRQs that get used by the ROM so we can’t simply take over an IRQ or FIRQ, we must intercept BASIC’s IRQ jump and point it to our interrupt routine and when our routine is complete we send the processor on it’s way to the BASIC IRQ routine handler.
The program starts here after LOADMing the program and the BASIC EXEC command is used. This section temporarily disables the FIRQ and IRQ so we can make changes without effecting BASIC
START: PSHS D,X,CC * save the registers our setup code will effect ORCC #$50 * = %01010000 this will Disable the FIRQ and the IRQs using the Condition Code register is [EFHINZVC] a high or 1 will disable that value
Since we are going to keep BASIC running and have our code running in the background we are going to have to intercept BASIC’s IRQ. We load the existing IRQ jump pointer information and save it at the Exit location of our routine.
LDA $10C * Get the opcode that is currently used by BASIC STA Exit * save it where our IRQ is going to end (a little self modifying code) LDX $10D * get the address info that is currently being used by BASIC (different ROMs will have a different location) STX Exit+1 * save the address info where our IRQ is going to end (a little self modifying code)
Next we insert a jump to our IRQ routine
LDA #$7E * Jump instruction Opcode STA $10C LDX #IRQ_Start * Load X with the pointer value of our IRQ routine STX $10D * Store the new IRQ address location
Before turning the interrupt routine back on we setup the counter display related code – Initialize the clock to 00:00
LDD #$3030 * ascii '00' STD Minutes1 * Start showing 00 minutes STD Seconds1 * Start showing 00 seconds LDB #VSYNCFreq * VSYNC is triggered 50 (PAL) or 60 (NTSC) times per second STB Jiffy * store it
Our setup is done, go back to BASIC
PULS D,X,CC,PC* Restore the registers and the Condition Code which will reactive the FIRQ and IRQ and return back to BASIC
This is the actual IRQ routine, that is intercepted from BASIC’s IRQ. We don’t know what IRQ is triggered when the processing starts our interrupt request. So we check bit 7 of address $FF03. If this bit is set then we know the IRQ is a VSYNC interrupt so we will execute our routine and then jump to BASIC’s IRQ handling routine. Otherwise if bit 7 of address $FF03 is clear then the IRQ is not triggered by a VSYNC so we simply exit our routine and jump to BASIC’s IRQ handling routine.
IRQ_Start:
TST $FF03 * Check for (Vsync) Interrupt bit 7 of $FF03 is set to 1 when the VSYNC is triggered
BMI UpdateTime * If bit 7 of $FF03 is set then the value is a negative so run our code below
Exit:
RMB 3 * These three bytes will be copied from BASIC's own IRQ jump setting when the setup code is EXECuted
* Using a little self modifying code
* Typically a JMP to some address in ROM, but it could be modified by another program
UpdateTime:
...
That is all there is to it. Below is the full program with lots of comments explaining the code at each step. I hope seeing how to insert your own code into BASIC’s interrupt routine will help you to better understand how interrupts work. Maybe you can use this method to enhance your own BASIC programs…
* Example of setting up your own code to run in the background while BASIC is still running * This will intercept BASIC's IRQ routine and check if the IRQ type is a VSYNC IRQ and if so * Show our clock on the screen ORG $0E00 * Choose the VSYNC frequency PAL EQU 50 * PAL region uses 50hz for VSYNC NTSC EQU 60 * NTSC region uses 60hz for VSYNC VSYNCFreq EQU NTSC * Change this to either NTSC or PAL depending on your region * Clock related pointers Jiffy FCB $00 * Used for the clock, counts down from 50 or 60, every time VSYNC IRQ is entered. When it hit's zero a second has passed Minutes1 FCB $00 * Keep track of minutes Minutes2 FCB $00 * "" Seconds1 FCB $00 * Keep track of seconds Seconds2 FCB $00 * "" * This is the actual IRQ routine * When the IRQ is triggered all the registers are pushed onto the stack and saved automatically. * Including the Program Counter (PC) it's like this command is done for you PSHS D,X,Y,U,CC,DP,PC * * NOTE: If you are using the FIRQ is for "Fast" interrupts the CPU does not push the registers onto the stack except for the CC and PC * You must take care and backup and restore any registers you change in your FIRQ routine. ********************************* IRQ_Start: TST $FF03 * Check for (Vsync) Interrupt bit 7 of $FF03 is set to 1 when the VSYNC is triggered BMI UpdateTime * If bit 7 of $FF03 is set then the value is a negative so run our code below Exit: RMB 3 * These three bytes will be copied from BASIC's own IRQ jump setting when the setup code is EXECuted * Using a little self modifying code * Typically a JMP to some address in ROM, but it could be modified by another program UpdateTime: DEC Jiffy * countdown 1/50 (PAL) or 1/60 (NTSC) of a second BNE Exit * Only continue after counting down to zero from 50 (PAL) or 60 (NTSC), VSYNC runs at 50 or 60Hz * If not zero then exit the IRQ routine * If we get here then Jiffy is now at zero so another second has passed so let's update the screen LDA #VSYNCFreq * Reset Jiffy STA Jiffy * to 50 (PAL) or 60 (NTSC) LDA Seconds2 * check the ones value of the # of seconds CMPA #$39 * if it is a nine then make it a zero BEQ > * make it a zero and add to the tens value of the # of seconds INCA * Otherwise update STA Seconds2 * Save BRA Update * Go copy the data to the screen ! LDA #$30 * set the ones value to zero STA Seconds2 * Save it LDA Seconds1 * Get the tens value of the seconds CMPA #$35 * check if it is a 5 BEQ > * If so then add one to minute value INCA * otherwise add 1 to the tens value of the seconds STA Seconds1 * save it BRA Update * Go copy the data to the screen ! LDA #$30 * Set the tens value of the seconds STA Seconds1 * to a zero LDA Minutes2 * Get the ones value of the minutes CMPA #$39 * check if it is a nine BEQ > * if so then go add one to the tens of the minute value INCA * otherwise increment the ones value of the seconds STA Minutes2 * update the value BRA Update * Go copy the data to the screen ! LDA #$30 * Set the ones value of the minutes STA Minutes2 * to zero INC Minutes1 * add one to the tens value of the minutes * Update the screen Update: LDD Minutes1 * Get the minutes value from RAM STD $400 * Show it on the top left of the screen LDA #': * A now equals the value for a Colon STA $402 * Display the Colon on the screen between the minutes and seconds LDD Seconds1 * Get the seconds value from RAM STD $403 * Show it on the top left of the screen BRA Exit * Jump back to BASIC's IRQ handling code * Program starts here when BASIC EXEC command is used after LOADMing the program * This section temporarily disables the FIRQ and IRQ so we can make changes without effecting BASIC START: PSHS D,X,CC * save the registers our setup code will effect ORCC #$50 * = %01010000 this will Disable the FIRQ and the IRQs using the Condition Code register is [EFHINZVC] a high or 1 will disable that value * If we are going to keep BASIC running and have this our code running in the background we are going to have to intercept BASIC`s IRQ * run our code then pass the IRQ control back to BASIC * Intercept Vectored IRQ ********************************* LDA $10C * Get the opcode that is currently used by BASIC STA Exit * save it where our IRQ is going to end (a little self modifying code) LDX $10D * get the address info that is currently being used by BASIC (different ROMs will have a different location) STX Exit+1 * save the address info where our IRQ is going to end (a little self modifying code) * Next we insert a jump to our IRQ routine LDA #$7E * Jump instruction Opcode STA $10C LDX #IRQ_Start * Load X with the pointer value of our IRQ routine STX $10D * Store the new IRQ address location * This is the counter display related code - Initialize the clock to 00:00 LDD #$3030 * ascii '00' STD Minutes1 * Start showing 00 minutes STD Seconds1 * Start showing 00 seconds LDB #VSYNCFreq * VSYNC is triggered 50 (PAL) or 60 (NTSC) times per second STB Jiffy * store it * setup is done, go back to BASIC PULS D,X,CC,PC* Restore the registers and the Condition Code which will reactive the FIRQ and IRQ and return back to BASIC END START * Tell assembler when creating an DECB ML program to set the 'EXEC' address to wherever the label START is in RAM (above)
That’s it for Part 3, in Part 4 I’ll go over a simpler way to use interrupts on the CoCo.