Interrupts

PET VIC-20 C64 C16 Plus/4 C128 X16 M65

XC=BASIC allows you to set up interrupting rules and write routines that handle interrupts. The supported interrupt types are:

  • Timer interrupts, issued after every <N> processor cycles where <N> is a value between 1 and 65535 (supported on all targets)
  • Raster interrupts, issued when the screen raster line reached a certain position (supported on C64 C16 Plus/4 C128 X16 M65)
  • Vertical blank interrupts, issued when the screen is fully rendered (supported on X16)
  • Sprite collision interrupts, issued when two or more sprites collide (C64 C128 X16 M65)
  • Sprite-background collision interrupts, issued when one ore more sprites collide with the background (C64 C128 M65)

Note

Multiple types of interrupts can be enabled at the same time, allowing your program a great flexibility of responding to events. If an interrupt is “missed” (because another one is currently served), it will be fired immediately after the current service routine is finished.

Warning

Enabling multiple types of interrupts is not yet supported on the MEGA65.

Defining interrupt service routines

In order to respond to an interrupt request, you must first define what routine to pass control to when an interrupt is fired. If you don't do this, your program will not know what to do when an interrupt request is issued, so it will go to a random memory address and break. A service routine is nothing but a labelled code point in your program, the same that can be referenced by GOTO or GOSUB. For example:

irqserv:
  ' Do whatever needs to be done when an interrupt request is issued
  RETURN

Once you have a service routine, you can reference it within an ON <event> GOSUB statement:

ON TIMER <cycles> GOSUB irqserv
ON RASTER <line> GOSUB irqserv
ON SPRITE GOSUB irqserv
ON BACKGROUND GOSUB irqserv
ON VBLANK GOSUB irqserv

Enabling and disabling interrupts

Once the service routines are defined and they're referenced in one or more ON <event> GOSUB statements, it is safe to enable interrupts:

TIMER INTERRUPT ON
RASTER INTERRUPT ON
SPRITE INTERRUPT ON
BACKGROUND INTERRUPT ON
VBLANK INTERRUPT ON

If you no longer wish to fire interrupts, use the same commands with the OFF keywords:

TIMER INTERRUPT OFF
RASTER INTERRUPT OFF
SPRITE INTERRUPT OFF
BACKGROUND INTERRUPT OFF
VBLANK INTERRUPT OFF

Warning

Make sure you you don't enable interrupts before the service routine is referenced in the corresponding ON <event> GOSUB statement, otherwise your program may break at the first interrupt.

Enabling or disabling system background tasks

By default, KERNAL runs some “background tasks” that are nothing but a timer interrupt service routine that typically does the following:

  • Flash the cursor
  • Query the keyboard (required by INPUT and GET)
  • Query the joysticks and mouse (X16)
  • Update the jiffy count (required by the TI function)

If your program doesn't require the above, you can turn of the system interrupt service using the following command:

SYSTEM INTERRUPT OFF

As you guessed, to turn it back on, you can use

SYSTEM INTERRUPT ON

Restrictions

Due to the nature of the runtime environment, there are some things that you must avoid in interrupt service routines:

  1. You must not call subs or functions
  2. You must not use floating point arithmetic
  3. You must not use the THIS keyword
  4. You must not enable or disable other interrupts (although changing their service routine using ON <event> GOSUB is allowed).

Note that the above rules only apply to the service routine, not the rest of the program.

You're driving: safe or fast?

Another factor to take into consideration is speed. XC=BASIC reserves a few zero page locations to use as virtual registers. In order to return to the main program flow in a clean state after a service routine is done, the runtime environment must push these virtual registers on the stack before the service routine is entered and pull them back when it finished. This roughly takes 2 times 170 CPU cycles.

You have two options:

  1. You accept this penalty, or
  2. If you're sure that your interrupt service routine doesn't mess up the virtual registers, you can declare OPTION FASTINTERRUPT at the top of your program, which will effectively bypass saving the virtual registers when the routine is entered.

Note

Virtual registers reside on the zero page between addresses $02 and $0D, inclusive. You can use a machine language monitor to find out if these values were altered after an interrupt service routine was quit. If they weren't, you're good to go with OPTION FASTINTERRUPT.

Examples

Timer interrupt example

The following example will display a counter on the top left corner of the screen while the rest of the program is running.

DIM i AS DECIMAL
i = 0000d
DIM a$ AS STRING * 8

ON TIMER 10000 GOSUB irqserv
TIMER INTERRUPT ON

INPUT "what is your name? "; a$
END

' This routine will be executed once in every 10,000 cpu cycles
irqserv:
  TEXTAT 0, 0, i
  i = i + 0001d
  RETURN

Raster interrupt example

' Turn off swapping of virtual registers
' as we don't use them in this example
OPTION FASTINTERRUPT
 
BACKGROUND 0
 
' This will set up the first interrupt
GOSUB irqserv2
 
SYSTEM INTERRUPT OFF
' Go!
RASTER INTERRUPT ON
 
' Loop forever
DO : LOOP WHILE 1
 
irqserv1:
  BORDER 2
  ON RASTER 120 GOSUB irqserv2
  RETURN
 
irqserv2:
  BORDER 1
  ON RASTER 100 GOSUB irqserv1
  RETURN