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" ); }