The basic low level keyboard handler function shown on the previous page is fine for most purposes. Let's make it better by adding a few features to it and enhancing its capabilities. As it stands, the low level keyboard handler has 128 flags called 'keyIsPressed' and 'keyWasPressed' and the low level keyboard handler code will set each flag to one if it detects a key being pressed and reset it to zero if the key was released.
The first thing we need to do is install the low level keyboard handler so that it will actually be called by the system whenever a key is pressed or released. As of right now, this function is never called by the hardware and in order to use it and write more code around it, we must install it so that it will be called by the hardware. In order to do that, we have to know what hardware interrupt vector handles the keyboard changes so that interrupt vector can be set to point to our low level keyboard handler. On a PC, the interrupt vector for the keyboard is 9 and the interrupt vector table starts at address 0000:000016. It is possible to set the pointer in the interrupt vector table directly to point to our ISR, but it is better at this point to use a DOS call to ensure that the interrupt vector is set properly and atomically.
In assembly language, this is done by calling interrupt 2116, DOS function 2516 (AH set to 2516) with DS set to the segment address of the low level keyboard handler and DX set to the offset address of the low level keyboard handler and AL set to 9 (the interrupt vector number). Here's an example of setting an interrupt vector using the DOS call:
mov ax,segment _KeyboardInterruptHandler ; Get segment address of handler mov ds,ax ; Set DS to segment address mov dx,offset _KeyboardInterruptHandler ; Get offset address of handler mov al,9h ; Set AL to vector number mov ah,25h ; DOS function 25h int 21h ; Call DOS function to set vector
In C code, the calling of the DOS function to set the interrupt vector is a lot easier by including the DOS.H library functions 'getvect' and 'setvect' to get and set the interrupt vectors. To set the interrupt vector in C, just use the setvect function like so:
setvect(9, KeyboardInterruptHandler); /* Set the keyboard interrupt vector */
Before we change the interrupt vector to point to our own keyboard handler, we must save the old keyboard handler's address. That way, we can restore the old keyboard handler prior to exiting our program. It is absolutely imperative the the old keyboard vector be restored prior to our program exiting, even if the program crashes due to a logic error within the program that is written. To save the address of the BIOS's interrupt handler, first we must create a function pointer used to store the vector address like so:
void interrupt (far *OldKeyboardHandler)();
Then we use the 'getvect' function to retreive the address and save it in our function pointer like so:
OldKeyboardHandler = getvect(9); /* Get the vector address from DOS */
In assembly language, this is accomplished by using DOS call 3516 at interrupt 2116. Upon return from the DOS call, register ES will contain the segment address of the vector address and register BX will contain the offset address. Here is an example of how this is done:
mov ah,35h ; Set DOS function 35h mov al,9 ; Get interrupt vector 9 int 21h ; Call DOS function ; ES:BX contains address to interrupt vector 9.
To make installing and removing the low level keyboard handler easier, let's write a couple of functions that wrap up these tasks for us.
static void interrupt (far *OldKeyboardHandler)() = 0; void InstallKeyboardHandler(void) { if (OldKeyboardHandler != 0) return; /* Get BIOS's keyboard handler and store it */ OldKeyboardHandler = getvect(9); /* Set the interrupt vector to the low level keyboard handler */ setvect(9, KeyboardInterruptHandler); } void RemoveKeyboardHandler(void) { if(OldKeyboardHandler == 0) return; /* Restores BIOS's keyboard handler */ setvect(9, OldKeyboardHandler); OldKeyboardHandler = 0; }
Now that we know how to change the interrupt vector table so that our low level keyboard handler will execute whenever a key is pressed or released, now we can write code to detect changes on the keyboard and also detect multiple key presses without filling up the keyboard buffer. The easiest way to detect key presses is to check the 'keyIsPressed' array for a one or zero.
if (keyIsPressed[80]) { /* Check for down arrow */ /* Down arrow pressed, do something... */ } else { /* Down arrow not pressed, do something */ }
It is possible that a key can be pressed and released while our programs are busy doing other things. For this reason there is another array called 'keyWasPressed.' When a key is pressed, the low level keyboard handler sets the corresponding index of both the 'keyIsPressed' and 'keyWasPressed' arrays. However, when a key is released, the low level keyboard handler only resets that corresponding index of the 'keyIsPressed' array. This enables user programs to check to see if a particular key was pressed regardless of the fact that it may have been released prior to the program checking the status. Therefore, the 'keyIsPressed' array represents the true state of the keyboard while the 'keyWasPressed' array allows for user programs to be busy doing other things without losing important messages from the user.
if (keyWasPressed[80]) { /* Check for down arrow */ /* Down arrow was pressed, do something... */ } else { /* Down arrow was never pressed */ } /* Reset the was pressed state */ keyWasPressed[80] = 0;
After checking the 'keyWasPressed' array, you should reset it so that the next time you check it, it would not falsely tell you the a key was pressed. To help ease the use of the arrays, let's write a couple of functions that will help automate these tasks.
/* Returns the state of the keyboard */ int KeyPressed(int scanCode) { int retVal; scanCode &= 0x7F; /* Keep scanCode valid */ retVal = keyIsPressed[scanCode] | keyWasPressed[scanCode]; keyWasPressed[scanCode] = 0; return retVal; } /* Return the true state of the keyboard */ int KeyPressedNow(int scanCode) { return keyIsPressed[scanCode & 0x7F]; }
That second function could probably be written inline since it consists of a single line of code. Both functions return a zero if the keys are not being pressed, or a non-zero true value if the keys are being pressed. The main differences between the 'KeyPressed' and 'KeyPressedNow' functions are that the 'KeyPressed' function returns the state of the keyboard since the last time it was called, whereas the 'KeyPressedNow' function always returns the true state of the keyboard...
Send your questions, comments, or ideas here.