Introduction to Keyboard Handlers

by Alioth / O.M.D
Any problems, mail me at karalioth@geocities.com





The keyboard system is a relatively complex beast to wield. It may look simple in that you are probably used to using things such as "getch()", but the lower you look, the more confusing it gets.

You may have needed a keyboard handler for your game or something else before, and had one placed right in front of you - all you had to do was reuse the code. Here however, I'm going to tell you how and why this keyboard handler works. The purpose of the keyboard handler I will build is as follows: to find out when keys are pressed and released at any time, allowing to detect multiple key presses. Also, to get rid of that annoying keyboard buffer which keeps beeping everytime it's full and to stop the typematic rate and delay from interfering.

The above keyboard handler has enough functionality to be used in a very high quality game. Sure, there are some nuances such has the fabled detection of the Pause/Break key but that's not really needed anyway (if you're prepared to ignore formalities). So, all you keyboard gurus out there, don't complain about the absence of the Ex scancodes!

So, how do we set about doing this? Well, to begin with, you need to know what happens AS SOON as a key is pressed on the keyboard. If you press the key 'A' on your keyboard the 8031 controller inside the keyboard sends a KScan code value of 1Ch over the keyboard serial link. The 8042 controller on the motherboard receives the value and translates it into the system scan code value 1Eh. This value is then placed in its output buffer. The motherboard controller then issues an interrupt request indicating that data is available. The interrupt request calls the interrupt 9 handler, the keyboard BIOS and the scan code is further converted into it's ASCII counterpart, 61h. Note that this is the code for 'a', the lowercase value - this is because we are assuming that no shift key is being pressed at the same time. What happens after this is not important to us as we want to perform our own actions.

But hang on, don't we want to detect a key release too? Well, yes, but the actions taken by the keyboard are not much different. Instead of sending the KScan code of 'A', 1Ch over the serial link, it will this time send the release code F0h, followed then by 1Ch. The 8042 receives the two bytes and translates them to 1Eh like above, but this time, it sets bit 7 high to indicate that the key was released. So this time, the system scan code will be 9Eh.

Now, after the above in both processes have been carried out, the BIOS takes control and provides you with the services you've been probably taking for granted (I know I did). It's even so kind as to offer you the 16 byte FIFO buffer that beeps EVERYTIME it can't accept any more keys!!! Well, we can tackle that problem right now and prepare for the later problems. How? Well, we make our own keyboard Interrupt Service Routine that is called everytime a key is pressed. In other words, we replace the BIOS function for handling key presses with our own! Sounds difficult but it's actually quite easy. First, we have to save the location of the BIOS routine which handles the key presses. The location of this is provided by the C library function "_dos_getvect" (without the leading underscore in some compilers cases and sometimes without the "_dos_" as evidenced with TC++). Create a pointer to the old BIOS function like so...


void (_interrupt _far *OldKeyboard)(); /* C's notation for function pointers */
...then, using the getvect function, point to the BIOS's original function...
OldKeyboard = _dos_getvect(9); /* get the address of the BIOS keyboard interrupt function, 9 */


So, we have saved the location of the original (soon to be old) BIOS keyboard interrupt function. Now we need to replace it. But before we get carried away, we need a function to replace it with! Our function (which does nothing as yet) should look like this...
void _interrupt _far MyKeyboard(void)
{
}


Now that we have saved the original BIOS keyboard function, we can replace it with our one. To do so, we must use the C library function "_dos_setvect" which varies in name from compiler to compiler, like so...

_dos_setvect(9, MyKeyboard);  /* Replace the old one with your one */


Now, to gather our code, we can make a function which sets up the keyboard and then releases the keyboard afterwards (so that we don't have the BIOS jumping to a non-existant function whenever a key is pressed after the program shuts down)...


void InitKeyboard(void)
{
   OldKeyboard = _dos_getvect(9);  /* Save the old keyboard function */
   _dos_setvect(9, MyKeyboard);  /* Replace old function with our new one */
}

void RestoreKeyboard(void)
{
   _dos_setvect(9, OldKeyboard);  /* Replace our function with the original
                                     BIOS function */
}


Now everytime the 8042 controller issues an interrupt 9 (i.e. a key is pressed or released), the function "MyKeyboard" is called. However, seeming as there is no code in the function to do things with the keyboard, nothing happens! That's one task completed so far - because the FIFO buffer "which beeps" is a BIOS creation it effectively no longer exists since we are using our own function to manage key presses, so there is now no more beeping!!!

On a side note, all the functions get and setvect do is to either look up the segment and offset of a function in the interrupt vector table (stored at 0000:0000) and return a pointer to the function, or replace this value with the segment and offset of the given function. When an interrupt is issued, this table is searched for the appropriate entry and then "vectors" to the give address in the table and begins to execute the code.

Now, seeming as we've got rid of the BIOS's "incarnation" of a keyboard function, it's time to write our own so that we can control the keyboard to a greater extent. To do this, we will make use of the port 60h. Port 60h belongs to the keyboard controller and can be used to send commands to the keyboard such as turn an LED on or set key attributes but this isn't important now - what is important, is the fact that we can read from this port. The byte read from this port is the keyboard scan code of a key being pressed or released which is currently stored in the controller's buffer. Once the scan code has been read, all that's left to do is issue an EOI (End of Interrupt) to make it known that the keyboard interrupt has finished. This is done by writing the value 20h to the port 20h which is the interrrupt controllers control register. I was really surprised that I had to do this since the compiler apparently should have generated the code to do so because of the C _interrupt function type. It still puzzles me to do this day. Here's our function so far...

void _interrupt _far MyKeyboard(void)
{
   unsigned key;

   key = inp(0x60);  /* Retrieve the scan code of the pressed or released
                        key */
   out(0x20, 0x20);  /* Issue EOI */
}


OK, so now we can read a byte from the controller whenever a key is pressed. The question you are probably thinking about now is, what do we do with this byte? Well, there are still a couple of things to do yet. We have to both get rid of the typematic rate/delay and allow multiple keypresses. These are both difficult to solve because firstly, typematic rate/delay is controlled through hardware and secondly, it is impossible to send multiple values simultaneously down a serial connection! You people out there who have already implemented a keyboard handler might be laughing at this "difficult" claim but I think that the following solution is very clever and hasn't been given the praise it deserves (I've no doubt anyhow that those of you laughing copied the code and don't fully understand how and why it works). You see, addressing the first problem; typematic rate/delay will ALWAYS be there, there is no way you can get rid of it. You can speed it up, yes, but this still is not good enough for proper games. To understand the solution you will have to "change your frame of reference" (doing this usually solves a lot of problems, simplifying them in the case). Instead of thinking about how many times the keyboard simulates a press when you hold it down for a period of time, think of when the key is initially pressed down, and when the key is finally released. For an example; have a variable "keypress" which is initially 0. When say, the ENTER key is pressed, the value in "keypress" will change to 1 and will stay at 1 until the ENTER key is released. In which case, a 0 will be written to it. Now, instead of checking whether the key is pressed, check to see whether the value in "keypress" is 1. If it is, then the ENTER is held down and how many times the 1 has been written to "keypress" doesn't matter at all because it will ALWAYS be one as long as the ENTER key isn't released! Note that from now on, I will refer to the pressed code being the "make" code, and the released code, the "break" code.

"_" is where keypress = 1
"." is where a scancode is sent to the motherboard

      ____________________________________________________________
      .    . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        ^                     ^
        |                     |
        +--- Typematic delay  |
                              +--- Typematic rate (defined in cps)

Here's some pseudo-code...

   begin
      key = getscancode;
      if (key is make code of enter) then keypress = 1
      if (key is break code of enter) then keypress = 0
   end
   
   main
      if (keypress is equal to 1) then do stuff
   end


This may all look really simple but getting to this stage probably required a little bit of lateral thinking (although I'm sure the concept of make and break codes helped in the derivation of it). So, we have eradicated typematic delay and pumped up the typematic rate to almost lightspeed! :0)

Already you can probably foresee how we are going to tackle the problem of multiple keypresses. Well, simple - just create an array which is big enough to hold 128 bytes. Each byte will represent the scan code of the chosen key. If, for example, the key 'A' is pressed, the scan code 1Eh will be reported. Then, your interrupt handler which check to see if bit 7 is set - if it isn't then your new table at position 1Eh will be filled with the value 1. When the key 'A' is released, the scan code 9Eh will be reported and the check to see if bit 7 is set will be made. In this case it is (releasing key) and 80h is subtracted from the reported value to give the reference (9Eh - 80h = 1Eh) and a 0 is written to the table at position 1Eh to signify that the key has been released. So, finally, here's my "incarnation" of the function...


void _interrupt _far MyKeyboard(void)
{
   unsigned key;

   key = inp(0x60);  /* Retrieve the scan code of the pressed or released
                        key */
   out(0x20, 0x20);  /* Issue EOI */

   if (key & 0x80)    /* Is bit 7 set? */
   {
      keytab[key - 0x80] = 0;    /* Yes, therefore the key has been released */
   }
   else
   {
      keytab[key] = 1;    /* No, therefore the key has been pressed */
   }
}


That's all there is to making the keyboard handler! You will have to know the scancode of all the keys on your keyboard to be able to use it. Here's and example of it's use...

void main(void)
{
   unsigned int value = 0;

   InitKeyboard();
   while (!keytab[1])   /* Loop until ESC pressed (1 is Escapes make code) */
   {
      if (keytab[0x48] && value != 65536) /* If the up cursor arrow is      */
      {                                   /* pressed, increase "value" and  */
         value++;                         /* display the value on-screen    */
         printf("%d\n", value);
      }
      if (keytab[0x50] && value != 0)     /* If the down cursor arrow is    */
      {                                   /* pressed, decrease "value" and  */
         value--;                         /* display the value on-screen    */
         printf("%d\n", value);
      }
   }
   ReleaseKeyboard();
}


To simplify things even more, you could use #definitions to replace the keyboard scan codes, for example...

#define KEY_UPARROW   0x48
#define KEY_DOWNARROW 0x50


...and then check if a key is held down like so...

   if (keytab[KEY_UPARROW]) { }


And there you have it, all of our above goals have been completed!

The above example doesn't show you the full power of this new keyboard handler. If you want, I've provided a little example in the ZIP - along with the text version of this tutorial which contains the full list of key codes.

There's a lot more to learn about the keyboard itself, rather than the implementations you can come up with. A tutorial on how the keyboard works would take a long time to compile, such is complexity of this apparently quiet "backseater".