How to setup and use IRQs on the TRS-80 Color Computer – Part 3 – Can the IRQ run without effecting BASIC?

Table of Contents (click on a link to jump to that web page)

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.

This entry was posted in CoCo Programming. Bookmark the permalink.

Leave a comment