TICT TUTORIAL SERIES 1 - Part II (c) TI-Chess Team 2000
Utilizing An Own Interrupt Handler as Clock v 1.00

Focus Of This Tutorial

This tutorial will show how to utilize an own interrupt handler as clock.

It's a pity that the TI89/TI92plus calculators have no built-in real-time clock. Many programs would be easier to make if there would be such a device. Heaven thanx our calculators have a built-in (programmable) timer which can be utilized for our purposes. It will not be absolutely accurate, but it will be accurate enough for almost all purposes.

Necessary Software

TIGCC Development Environment for Windows9x/NT - Version 0.8

http://www.ticalc.org/pub/win/asm/tigcc.zip

Basic Technical Terms

Before I will start with the real tutorial I want to explain some technical terms for everyone who is not comfortable with hardware technology.

CPU

The brain of your calculator. The CPU executes your program.

INTERRUPT

Event which will force the CPU to stop immediately what it is doing now, let it execute the corresponding interrupt handling routine and after it has finished this (special) routine it will continue at the point it was stopped previously. Interrupts are generated (triggered) by other parts of the hardware telling the CPU that there is an important job waiting. For example: Interrupt 6 is triggered by the keyboard if the [ON] key gets pressed by the user.

INTERRUPT HANDLING ROUTINE (interrupt handler) / INTERRUPT VECTOR TABLE

Routine, which is called if the corresponding interrupt is generated (triggered). Each interrupt has an interrupt handler assigned by default. The addresses of these handlers are stored in the interrupt vector table, which is just a list of routine addresses. If, let's say, interrupt 6 is generated the CPU will look up the address of the handler routine at position 6 in the interrupt vector table and will execute this routine.

OSCILLATOR(s)

The oscillator can be seen as the heart of your calculator. It "beats" at a given frequency. The TI89/TI92plus has 2 built-in oscillators. One which generates a fast clock signal for the CPU and a second oscillator which generates a slower clock signal for other parts of the calculator like the programmable timer.

PROGRAMMABLE TIMER

An 8-bit counter which counts up from a given initial value with a specified speed until it reaches 255. This counting is done absolutely independently from the CPU. After value 255 it reaches value 0 due to its 8-bit size. At value 0 it triggers an interrupt (interrupt 5) and starts counting again from its initial value.

Standard Library Functions (and similar) for Interrupt Treatment

Since the TIGCC standard library version 2.2 an interrupt handler can be defined like this:

DEFINE_INT_HANDLER(myinthandler) {
   // do something here ....
}
To get the address of an installed interrupt handler from the interrupt vector table you can use function:
INT_HANDLER GetIntVec(long IntVec)
and to install an own interrupt handler the standard library supplies function
void SetIntVec(long IntVec, INT_HANDLER Handler)
To make the handling of the offsets into the vector table easier, enum AutoInts defines the offsets of the most used entries like this:
enum AutoInts {
   AUTO_INT_1 = 0x64, AUTO_INT_2 = 0x68, AUTO_INT_3 = 0x6C,
   AUTO_INT_4 = 0x70, AUTO_INT_5 = 0x74, AUTO_INT_6 = 0x78, 
   AUTO_INT_7 = 0x7C 
}
Last, but not least, routine
void ExecuteHandler (INT_HANDLER Handler)
have to be used if you want to call an other interrupt handler from within your interrupt handler routine. Calling an other interrupt handler from within your handler may be a good idea in some cases.

Simple Counter - Example 1 (timer1.c)

This example is taken from the TIGCC Standard Library documentation (thanx, Zeljko).

You can compile it with: tigcc -O2 timer1.c

It utilizes an own interrupt handler for interrupt 5 which is triggered periodically by the programmable timer.

#define SAVE_SCREEN

#include <tigcclib.h>

int _ti89, _ti92plus; // produce code for TI89 and TI92plus

INT_HANDLER oldint5 = NULL;

//-------------------------------------------------------------
// NOTE that variable counter is defined "volatile" !!!
//
// "volatile" means that the compiler shouldn't touch it in any 
// way for optimization reasons.
//
// if this variable is not defined "volatile" the compiler
// may completely remove it, because it thinks it will change
// during routine (the compiler doesn't know anything about
// interrupts, so this may fool it).
//-------------------------------------------------------------
volatile int counter = 0;


DEFINE_INT_HANDLER (myint5handler) {
    counter++;
    //-----------------------------------------------------
    // we don't want to replace the old interrupt handler
    // completely, but want that it will be executed at the
    // end of our own handler
    //-----------------------------------------------------
    ExecuteHandler(oldint5);
}


void _main(void) {
    //-------------------------------------------------------
    // clear screen and set counter variable to 0, otherwise 
    // the counter would start at its last value if you start 
    // the program again and the program is not archived!
    //-------------------------------------------------------
    ClrScr();
    counter = 0;

    //---------------------------------------------------
    // get the address of the default interrupt 5 handler
    // and store it in the global variable oldint5.
    // this is necessary so we can call this handler within
    // our own handler and that we can restore it at the
    // end of our program
    //---------------------------------------------------
    oldint5 = GetIntVec(AUTO_INT_5);

    //--------------------------------------
    // install now our own handler ....
    //--------------------------------------
    SetIntVec(AUTO_INT_5, myint5handler);

    //------------------------------------------------------------------
    // ... loop and print the counter value until a key gets pressed ...
    //------------------------------------------------------------------
    while (!kbhit()) {
        if (TI89) printf_xy(50, 50, "Counter=%d  ", counter);
        else      printf_xy(70, 64, "Counter=%d  ", counter);
    }

    //----------------------------------------------------------
    // install the previous handler again (removing our own one)
    //----------------------------------------------------------
    SetIntVec(AUTO_INT_5, oldint5);
    
    //-------------------------------------------------------
    // empty the keyboard buffer and return.
    // its a really good idea to empty the keyboard buffer
    // and this point. otherwise the pressed key will remain
    // in the buffer and will be handed over to the AMS.
    // if the pressed key is the [ENTER] key you program
    // would get started immediately again.
    //-------------------------------------------------------
    GKeyFlush();
}

Simple Clock - Example 2 (timer2.c)

Our next example will extend the previous code to become a real clock.

If the parameters of the programmable timer are not changed by a program the timer will generate 19 interrupts per second (one interrupt every 52.63 milliseconds). Examine the code of the interrupt handler below to see how this fact is used to build up a clock.

Note, that during the execution of the interrupt handler some variables may contain invalid values in the short term (for example: seconds == 60), but this doesn't matter, because the rest of your program will never "see" such invalid values.

You can compile it with: tigcc -O2 timer2.c

#define SAVE_SCREEN

#include <tigcclib.h>

int _ti89, _ti92plus;  // produce code for TI89 and TI92plus

INT_HANDLER oldint5 = NULL;

volatile int mseconds50 = 0; // counts in 50ms steps (not exact 50ms, but 1000/19 ms)
volatile int seconds    = 0; // counts seconds
volatile int minutes    = 0; // counts minutes
volatile int hours      = 0; // counts hours
volatile int resettime  = 1; 


DEFINE_INT_HANDLER (myint5handler) {
    //----------------------------------------------
    // if resettime is not 0 all clock variables are
    // reset to 0
    //----------------------------------------------
    if (resettime) {
        mseconds50=seconds=minutes=hours=resettime=0;
    }
    else {
        mseconds50++;
        if (mseconds50 == 19) {
            mseconds50 = 0;
            seconds++;
            if (seconds == 60) {
                seconds = 0;
                minutes++;
                if (minutes == 60) {
                   minutes = 0;
                   hours++;
                   if (hours == 24) hours = 0;
                }
            }
        }
    }
    ExecuteHandler(oldint5);
}


void _main(void) {
    ClrScr();
    resettime = 1;   

    oldint5 = GetIntVec(AUTO_INT_5);
    SetIntVec(AUTO_INT_5, myint5handler);

    while (!kbhit()) {
        if (TI89) printf_xy(50, 50, "%02d:%02d:%02d.%03d",hours,minutes,seconds,mseconds50*50);
        else      printf_xy(70, 64, "%02d:%02d:%02d.%03d",hours,minutes,seconds,mseconds50*50);
    }

    SetIntVec(AUTO_INT_5, oldint5);
    GKeyFlush();
}

Full Featured Clock - Example 3 (timer3.c)

The last example in this tutorial shows how the above code (with minor modifications) may be used to measure time intervals.

Keys used in this program:

You can compile this program with: tigcc -O2 timer3.c
#define SAVE_SCREEN

#include <tigcclib.h>

int _ti89, _ti92plus;  // produce code for TI89 and TI92plus

INT_HANDLER oldint5 = NULL;

volatile int mseconds50 = 0; // counts in 50ms steps (not exact 50ms, but 1000/19 ms)
volatile int seconds    = 0; // counts seconds
volatile int minutes    = 0; // counts minutes
volatile int hours      = 0; // counts hours
volatile int resettime  = 1; // used to signal a reset clock
volatile int running    = 1; // used to signal running or stopped state


DEFINE_INT_HANDLER (myint5handler) {
    if (resettime) {
        mseconds50=seconds=minutes=hours=resettime=0;
    }
    //------------------------------------------
    // count interrupts only if running is not 0
    //------------------------------------------
    else if (running) {
        mseconds50++;
        if (mseconds50 == 19) {
            mseconds50 = 0;
            seconds++;
            if (seconds == 60) {
                seconds = 0;
                minutes++;
                if (minutes == 60) {
                   minutes = 0;
                   hours++;
                   if (hours == 24) hours = 0;
                }
            }
        }
    }
    ExecuteHandler(oldint5);
}


void _main(void) {
    int leave_program = 0;
    
    ClrScr();
    running   = 1;
    resettime = 1;   

    oldint5 = GetIntVec(AUTO_INT_5);
    SetIntVec(AUTO_INT_5, myint5handler);

    //-------------------------------------------
    // print some usage information to the screen
    //-------------------------------------------
    printf_xy(0, 0, "[ESC]   end");
    printf_xy(0,10, "[ENTER] run/stop");
    printf_xy(0,20, "[CLEAR] reset");

    //----------------------------------------------
    // loop until leave_program variable is set to 1
    //----------------------------------------------
    while (!leave_program) {
        //-----------------------------------------------------
        // NOTE that it is not very efficient to draw the clock
        // each time even it has not changed, but we don't
        // bother about this in our simple example !!!
        //-----------------------------------------------------
        if (TI89) printf_xy(50, 50, "%02d:%02d:%02d.%03d",hours,minutes,seconds,mseconds50*50);
        else      printf_xy(70, 64, "%02d:%02d:%02d.%03d",hours,minutes,seconds,mseconds50*50);

        if (kbhit()) {
            int input = ngetchx();
            switch(input) {
                //----------------------------------------------------
                // set leave_program to 1 if user has pressed [ESCAPE]
                //----------------------------------------------------
                case KEY_ESC: 
                    leave_program = 1;
                    break;
                //----------------------------------------------------
                // toggle running state if user has pressed [ENTER]
                //----------------------------------------------------
                case KEY_ENTER: 
                    running = !running;
                    break;
                //------------------------------------------------------------
                // set the resettime variable to 1 if user has pressed [CLEAR]
                // to signalize the interrupt handler to reset the clock
                // variables
                //------------------------------------------------------------
                case KEY_CLEAR:
                    resettime = 1;
                    break;
            }
        }
    }

    SetIntVec(AUTO_INT_5, oldint5);
    GKeyFlush();
}

The Credits go to:

Contact TI-Chess Team Members

Check the TICT HQ Website at http://tict.ticalc.org for more tutorials and software.

More useful tips, tricks and hints can also be found at our Messageboard at: http://pub26.ezboard.com/btichessteamhq.

Suggestions, bug reports and similar are VERY welcome (use our Messageboard for this!!).

How to Thanx the Author?

Like Xavier from the Doors Team I like it to get postcards from all over the world. If you want to thank me, just send me an postcard with greetings on it. Thats enough.

My address is:

Thomas Nussbaumer
Heinrichstrasse 112a
A-8010 Graz
Austria

... and please: no mail bombs if one of my programs had crashed your calculator!


Thomas Nussbaumer Graz,Austria 31/10/2000