www.pudn.com > timsrc > TIMER.C


/*====================================================================*/ 
/*                                                                    */ 
/*  NOTE: Ensure this program is compiled for LARGE model.            */ 
/*        Non-ANSI stuff is pertinent to Microsoft QuickC v2.5.       */ 
/*        Porting to another compiler should not be too hard!         */ 
/*                                                                    */ 
/*====================================================================*/ 
 
/*  What we are going to demonstrate here is the use of two methods of  */ 
/*  hooking into the timer interrupt mechanism. All PCs have a timer    */ 
/*  chip with 3 channels. 1 channel is programmed to provide an tick    */ 
/*  interrupt 18.2 times a second. This is generally refered to as the  */ 
/*  hardware timer. This timer tick is serviced by an interrupt routine */ 
/*  accessed by vector 0x08. DOS typically provides the service routine */ 
/*  so that it can update the DOS clock, etc. What DOS also does is to  */ 
/*  also route each timer tick through another interrupt vector 0x1C.   */ 
/*  This is called the user timer. It toos counts at 18.2 times a sec   */ 
/*  with the idea that it is indenpendant of vector 0x08.               */ 
/*                                                                      */ 
/*  What we will do is to use the user vector at 0x1C to control a very */ 
/*  simple animated cursor (text mode) while the hardware timer at      */ 
/*  vector 0x08 is re-programmed to provide a faster interrupt rate     */ 
/*  that we will manage with our own interrupt handler before passing   */ 
/*  on control to the original DOS handler, and therefore our 0x1C      */ 
/*  handler, whenever we deem it appropriate.                           */ 
 
#include  
#include  
#include  
#include  
#include  
 
#define FALSE         0 
#define TRUE          1 
 
typedef unsigned char BOOL; 
typedef unsigned char BYTE; 
typedef unsigned int  WORD; 
 
BYTE    *pVideo=(BYTE *)0xB8000000L;       /*  Ptr to base of VGA text RAM  */ 
 
/*  As with all interrupt service routines any variables used should be   */ 
/*  declared either globally or statically. It is NOT a good idea to use  */ 
/*  variables directly off of the stack - we won't know the state of the  */ 
/*  stack when we are invoked...                                          */ 
 
/*  Global place holders for original vector routines  */ 
 
void (interrupt *OldDirectTimer)( void ); 
void (interrupt *OldUserTimer)( void ); 
 
long lCounter;              /*  Simple counter for more accurate delays    */ 
int  nTickRate;             /*  The actual rate we are getting interrupts  */ 
int  nTickLimit;            /*  The number of ticks before we call DOS ISR */ 
int  nTickCount;            /*  Current DOS tick counter                   */ 
int  nCursorX,nCursorY;     /*  Cursor position                            */ 
 
/*===================== Our Interrupt Service Routines ======================*/ 
 
void interrupt OurDirectTimer( void ) 
{ 
    /*  Decrement our count down timer  */ 
 
    if ( lCounter ) lCounter--;         /*  Don't go negative though!  */ 
 
    /*  Determine if we call the original DOS handler  */ 
 
    if ( --nTickCount<=0 ) 
    { 
        nTickCount = nTickLimit;        /*  Reset ticks to wait  */ 
        _chain_intr( OldDirectTimer );  /*  Go to old handler!   */ 
    } 
 
    /*  If we haven't called the original handler, we find ourselves here.    */ 
    /*  The DOS handler would take care of telling the Programable Interrupt  */ 
    /*  Controller (PIC) that the interrupt has been serviced. This is called */ 
    /*  acknowledging the interrupt and MUST be done. If not, the PIC cannot  */ 
    /*  generate another interrupt for the timer chip or anything else...     */ 
 
    outp( 0x20,0x20 ); 
} 
 
void interrupt OurUserTimer( void ) 
{ 
    /*  This routine is guaranteed to be called at approx 18 times per sec.  */ 
    /*  We will use that fact to control the timing of our animated cursor   */ 
 
#define NUM_OF_CURSORS      4       /*  4 cursors to display      */ 
#define FRAME_DELAY         2       /*  Approx 1/8 sec per frame  */ 
 
    static  BYTE    cbFrame=0, cbDelay=0, cbColour=0; 
    static  BYTE    cbCursors[NUM_OF_CURSORS]={ '-','/','|','\\' }; 
    static  WORD    wOffset; 
 
    /*  Are we ready to animate next frame...  */ 
 
    if ( cbDelay--==0 ) 
    { 
        cbDelay = FRAME_DELAY; 
        if ( ++cbFrame>=NUM_OF_CURSORS ) cbFrame=0; 
        if ( ++cbColour>15 )             cbColour=1; 
        wOffset = nCursorY*160 + nCursorX*2; 
        *(pVideo+wOffset)   = cbCursors[cbFrame]; 
        *(pVideo+wOffset+1) = cbColour; 
    } 
 
    /*  Call the original handler...  */ 
 
    _chain_intr( OldUserTimer ); 
} 
 
void SetTimerRate( int nNewRate ) 
{ 
    /*  Reprogram the timer chip to give interrupts at the new rate.  */ 
    /*  Bear in mind that 18.2 times per sec is the minimum...        */ 
 
    _asm   cli              /*  Disable interrupts while we change these  */ 
                            /*  variables. They are used within the ISR   */ 
                            /*  itself so may cause problems!             */ 
 
    if ( nNewRate<18 ) nNewRate=18; 
 
    nTickRate    = nNewRate; 
    nTickLimit   = nNewRate/18;         /*  Number of ticks to give original  */ 
                                        /*  18 ticks per second               */ 
    nTickCount   = 0; 
 
    /*  Calculate the 'count down' value for the timer chip itself...  */ 
 
    nNewRate     = (nNewRate==18) ? 0 : ((unsigned int) ( 1193180L / ((unsigned long)nNewRate) )); 
 
    /*  Re-program the timer chip...  */ 
 
    _asm    mov    dx, 43H 
    _asm    mov    al, 34H 
    _asm    out    dx, al 
    _asm    mov    dx, 40H 
    _asm    mov    ax, nNewRate 
    _asm    out    dx, al 
    _asm    mov    al, ah 
    _asm    out    dx, al 
 
    /*  Re-enable interrupts before we finish...  */ 
 
    _asm    sti 
} 
 
void GetCursorPosition( int *col, int *row ) 
{ 
    /*  Ask the video BIOS to give us the current position of the cursor  */ 
 
    unsigned char r=0, c=0; 
 
    _asm    mov    ah, 03H 
    _asm    mov    bh, 00H 
    _asm    int    10H 
    _asm    mov    r, dh 
    _asm    mov    c, dl 
 
    *row = (int) r; 
    *col = (int) c; 
} 
 
/*========================= main program ====================================*/ 
 
void main( void ) 
{ 
    int         i; 
 
    printf( "\n\nTimer Demo\n==========\n\nPress ESCape to abort, or\n\n" ); 
    printf( "Press 0 to set timer to 18.2Hz\n" ); 
    for ( i=1; i<10; i++ ) 
    { 
        printf( "Press %d to set timer to %dHz\n",i,i*1000 ); 
    } 
    printf( "\n" ); 
 
    /*  Determine the current position of the cursor. We only really want the */ 
    /*  row since we shall show it in the centre of the line...               */ 
 
    GetCursorPosition( &nCursorX,&nCursorY ); 
    nCursorX = 39;                              /*  Set to centre column  */ 
 
    /*  Set things up by installing our handlers  */ 
 
    OldDirectTimer  = _dos_getvect( 0x08 ); 
    OldUserTimer    = _dos_getvect( 0x1C ); 
 
    SetTimerRate( 18 );         /*  Initialise our variables  */ 
 
    _dos_setvect( 0x08,OurDirectTimer ); 
    _dos_setvect( 0x1C,OurUserTimer ); 
 
    /*  Away we go  */ 
 
    while ( 1 ) 
    { 
        /*  Look for a get out  */ 
 
        i=0; 
        if ( kbhit() && (i=getch())==27 ) break; 
 
        /*  Has something been pressed  */ 
 
        if ( i>='0' && i<='9' ) 
        { 
            if ( i=='0' ) SetTimerRate(18); else SetTimerRate( (i-'0')*1000 ); 
            lCounter = (long)nTickRate; 
        } 
 
        /*  If the counter has reached zero, reset it...  */ 
 
        if ( lCounter==0L ) lCounter=(long)nTickRate; 
 
        /*  Display message showing counter counting down and confirmation  */ 
        /*  of tick rate. Note, that \r is used to not cause a line feed... */ 
 
        printf( "\r%-8lu Timer Rate: %-6d",lCounter,nTickRate ); 
    } 
 
    /*  Reinstate the original handlers  */ 
 
    SetTimerRate( 18 );             /*  Ensures correct rate!  */ 
 
    _dos_setvect( 0x08,OldDirectTimer ); 
    _dos_setvect( 0x1C,OldUserTimer ); 
 
    printf( "\n\nTimer Demo Coded by CyberFrog 8:)\n\n" ); 
}