How to setup and use IRQs on the TRS-80 Color Computer – Part 2 – Overview with an example program

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

In this article I’m going to cover an example of setting up and using an IRQ for the Color Computer 1, 2 and the CoCo 3.  The CoCo 1 & 2 are the same, the CoCo 3 can be setup the same as a CoCo 1 but if you are going to write code specific for the CoCo 3 then it’s best to use the method specific to the CoCo 3.  This will be explained in later chapter

First we are going to go over how to setup IRQs for the CoCo 1,2 and 3.  On a CoCo 1 or CoCo 2 the ROM is coded to jump to certain addresses whenever an IRQ is triggered, the main interrupts to use for programming are:

The IRQ at Address $FFF8 & $FFF9 these bytes point to address $010C
The FIRQ (Fast IRQ) at address $FFF6 & $FFF7 these bytes point to address $010F

Below I’ll go through a real world example of setting up and using an IRQ that will work on the CoCo 1,2 or 3.  It shows a clock (minutes and seconds) on the top left of the screen that will update itself in the background while your main program carries on as if the IRQ doesn’t exist.  I’ll break up the program in parts and then show the program listing in it’s entirety.  I have commented each line of code to make it clear as to what is happening in the program at each step.

First we have to stop any currently running Interrupts from happening and make sure that our IRQ will be used.  This is done with the ORCC #$50 command which ORs the Condition Code register with $50 which in binary is 01010000

   Binary OR $50 = 01010000
CC register flags [EFHINZVC]

See above we set the CC register’s F and I flags high (or ones).  That tells the CPU not to use the FIRQ and IRQ anymore.  Of course you can turn off just the FIRQ with ORCC #$40 = binary 01000000 or just the IRQ with ORCC #$10 = binary 00010000.  But it’s safer to just turn them both off and do your IRQ setup with ORCC #$50

START:
    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

Next we use two special bytes in the CoCo to set whether our IRQ is going to be triggered using the HSYNC, the VSYNC or both.  The two bytes are $FF01 and $FF03.  The HSYNC is controlled by $FF01 and VSYNC is controlled by $FF03.

Bit 0 of $FF01 enables or disables the HSYNC IRQ

Bit 0 of $FF03 enable or disable the VSYNC IRQ
If bit 0 is set to 1 that IRQ is then enabled and that IRQ will be triggered.  If bit 0 is set to 0 that IRQ is then disabled and wont trigger an IRQ request.

    LDA     $FF01   * PIA 0 side A control reg - PIA0AC
    ANDA    #$FE    * Clear bit 0 - HSYNC IRQ Disabled
    STA     $FF01   * Save settings

    LDA     $FF03   * PIA 0 side B control reg - PIA0BC
    ORA     #$01    * VSYNC IRQ enabled
    STA     $FF03   * Save Settings

As per the information at the start of this article, if we want to use the IRQ we change the code at address $010C-$010E from JMP $xxxx to the address we want to use for our own IRQ.  So we now change the opcode in RAM at address $010C to a Jump instruction which is an $7E (it probably already is a Jump, but just in case it’s not it’s a good idea to do it yourself).  Next we change the Jump location (pointer) in RAM at $010D to point to our IRQ starting location in RAM.

*********************************
* Setup Vectored IRQ using VSYNC
*********************************
   LDA    #$7E     * Jump instruction Opcode
   STA    $10C     * Store it at IRQ jump address 
   LDX    #IRQ_Start  * Load X with the pointer value of our IRQ routine
   STX    $10D     * Store the new IRQ address location

At this point the IRQ is setup and ready to go.  Until we change the Condition Code register and enable the IRQ bit to a zero the IRQ wont be used by CPU.

Before activating the IRQ we want to initialize some of the RAM that the clock IRQ is going to use.  I’m not going to go over this here as it’s described in the full listing down below and it uses labels for RAM locations that make it hard to follow without seeing where those Labels are and it’s not very complicated.

After our IRQ routine is initialized we are ready to fire it into action with the ANDCC instruction as shown here.

* This is where we enable the IRQ so the CPU will use it 
    ANDCC   #$EF        * = %11101111 this will Enable the IRQ to start

There is one last thing to note about using an IRQ.  When the CPU triggers and IRQ it will jump to your IRQ routine and inside your IRQ routine you must also re-trigger or acknowledge the IRQ so that it will be acted upon when it’s triggered again by the CPU.

In our example we are using the VSYNC IRQ and we must acknowledge the VSYNC IRQ inside our IRQ routine by activating memory location $FF02.  You could use TST  $FF02, or CLR  $FF02 or LDA/LDB $FF02.  I use the LDA $FF02 or LDB $FF02 since it only takes two CPU cycles.  Remember we don’t care what is loaded into the A accumulator we just want to “tickle” the memory location.

LDA     $FF02       * PIA 0 side B data register - PIA0BD We need to either CLR  $FF02, TST  $FF02 or LDA,LDB  $FF02 to activate the IRQ now and every time inside the IRQ too.
                    * I use LDA because it is only 2 CPU cycles. "Acknowledge the VSYNC IRQ"    

If you are using the HSYNC IRQ then you want to “tickle” memory location $FF00 inside your IRQ routine with a LDA   $FF00

Where inside your  IRQ you decide to “Acknowledge the IRQ” is important.  If you place it at the beginning of your IRQ routine then if enough time passes the IRQ could be triggered again before the current one it finished.  There might be reasons you want this to happen but for simple IRQ routines probably not.

IRQ_Start:
    LDA     $FF02       * PIA 0 side B data register - PIA0BD We need to either CLR  $FF02, TST  $FF02 or LDA,LDB  $FF02 to activate the IRQ now and every time inside the IRQ too.
                        * I use LDA because it is only 2 CPU cycles. "Acknowledge the VSYNC IRQ"    

At the end of your IRQ routine you always use the RTI (Return from Interrupt) instruction.  Again you don’t have to worry about the accumulators and registers the CPU will automatically do a PULS D,X,Y,U,CC,DP,PC so that all everything is restored.  Unless you are using the FIRQ, which I explained above.

IRQ_End:
    RTI

That’s it for the summary.  Below you will find the entire code including a little demo code at the end of the example to show a little program is running while the IRQ is being triggered in the background updating the clock on the screen.

* Example of setting up an IRQ to run in the background of your own machine language program
    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:
    LDA     $FF02       * Acknowledge the VSYNC IRQ, this makes the IRQ happen again.  Since it's at the start of the IRQ here
                        * it's possible for another IRQ trigger to happen while we are still doing this routine.
                        * If you want to be sure that your IRQ isn't triggered again until you are finished this IRQ then
                        * place this instruction just before the RTI at the end of your IRQ
                        * We are fine here as this IRQ code is very short and will complete in less then 1/60 of a second.
    DEC     Jiffy       * countdown 1/50 (PAL) or 1/60 (NTSC) of a second
    BNE     IRQ_End     * 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
* All done the IRQ so we can now leave...
* Leaving an IRQ will automatically do a PULS D,X,Y,U,CC,DP,PC so that all registers are restored
*
* NOTE: If you are leaving an FIRQ which is "Fast" it does not restore all the registers it only restores the CC and the PC.
*       You must make sure you save and restore any registers you modify in your routine.
*********************************
IRQ_End:
    RTI
* Program starts here when BASIC EXEC command is used after LOADMing the program
* This section disables the HSYNC IRQ and enables the VSYNC IRQ
START:
    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
    LDA     $FF01   * PIA 0 side A control reg - PIA0AC
    ANDA    #$FE    * Clear bit 0 - HSYNC IRQ Disabled
    STA     $FF01   * Save settings
    LDA     $FF00   * PIA 0 side A data register - PIA0AD
    LDA     $FF03   * PIA 0 side B control reg - PIA0BC
    ORA     #$01    * VSYNC IRQ enabled
    STA     $FF03   * Save Settings
    LDA     $FF02   * PIA 0 side B data register - PIA0BD We need to either CLR  $FF02, TST  $FF02 or LDA,LDB  $FF02 to activate the IRQ now and every time inside the IRQ too.
                    * I use LDA because it is only 2 CPU cycles. "Acknowledge the VSYNC IRQ"
*********************************
* Setup Vectored IRQ using VSYNC
*********************************
    LDA     #$7E        * Jump instruction Opcode
    STA     $10C        * Store it at IRQ jump address 
    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 or 60 times a second
    STB     Jiffy       * store it

* This is where we enable the IRQ so the CPU will use it 
    ANDCC   #$EF        * = %11101111 this will Enable the IRQ to start
    LDA     $FF02       * PIA 0 side B data register - PIA0BD We need to either CLR  $FF02, TST  $FF02 or LDA,LDB  $FF02 to activate the IRQ now and every time inside the IRQ too.
                        * I use LDA because it is only 2 CPU cycles. "Acknowledge the VSYNC IRQ"

*
* Your program code goes here and the little clock IRQ will run in the background without any effect from the code below.
*********************************
* Little demo code to show the IRQ is running in the background
BigLoop:
    LDU     #$A000      * Show junk on the screen data copied from ROM
MidLoop:
    LDX     #$406       * Point X beside clock on screen
!   LDA     ,X+         * Get value in move X to the right
    STA     -2,X        * Store the value to left of where it was read from
    CMPX    #$600       * see if X is at the end of screen
    BNE     <           * if not keep going
    LDY     #$1000      * Delay
!   LEAY    -1,Y        * Delay countdown
    BNE     <           * Delay check if zero, if not loop
    LDA     ,U+         * get a byte from ROM
    ORA     #$80        * Make the value semi graphics characters
    STA     $5FF        * store it on screen
    CMPU    #$C000      * check if we are at the end of ROM
    BLO     MidLoop     * If not loop
    BRA     BigLoop     * Go start the process again...
    END    START        * Tell assembler when creating an ML program to set the 'EXEC' address to wherever the label START is in RAM (above)

That’s it for Part 2.  In Part 3 I’ll show you how to do a similar thing but with BASIC still running.

See you soon, Glen

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

Leave a comment