The keyboard unit contains an Intel 8048 microcontroller that is programmed to scan the keyboard for key presses and releases (each counts as an individual keystroke), debounce the keystrokes, implement the "typematic" (hold-to-repeat) feature, maintain a 16-keystroke buffer, and transmit each keystroke serially to the PC's system unit. There are two bidirectional data lines in the cable connecting the keyboard unit to the system unit: KBD DATA carries either the serial keystroke data from the keyboard or a "clear-and-reenable" handshaking signal from the system unit; KBD CLK carries either the baud rate clock from the keyboard unit or a "clock disable" control signal from the system unit. (Note that when the keyboard clock is disabled, the keyboard does not even respond to CTRL-ALT-DEL.
Keystroke data are transmitted serially at 10,000 baud over the KBD DATA line together with the baud rate clock on the KBD CLK line. Each character transmitted consists of 2 start bits and 8 data bits; there are no stop bits. The line is 0 (low) when idle, the start bits are 1 (high). The data bits are transmitted LSB first; bits 0-6 are the "scan code" which uniquely identifies the key by its position on the keyboard, bit 7 (MSB) is 0 for key press and 1 for key release. Holding a key down for more than half a second invokes the "typematic" action: key press scan codes are sent repeatedly at the rate of 10 per second without intervening key release scan codes, until the key is released.
In the system unit the character received on the KBD DATA line is reconverted to parallel format, gated into port 60h, and a interrupt is sent on IRQ1 to the Interrupt Controller. Since the baud rate clock is transmitted along with the data, the circuit needed to deserialize the data is significantly simpler than a UART; it is essentially a serial-in, parallel-out shift register. The Interrupt Controller triggers interrupt 9 for IRQ1.
The interrupt 9 handler must send an End-of-Interrupt signal to the Interrupt Controller, and on the original IBM PC also needs to acknowledge the reception of the character by sending a clear-and-reenable handshaking signal to the keyboard unit over the KBD DATA line. This is done by setting bit 7 of port 61h to 1 and back to 0. (The other bits of port 61h should be left unchanged since they control other functions. E.g., bits 0 and 1 enable the built-in speaker, and bit 6 disables the keyboard clock.) On more recent machines, this acknowledgment is unnecessary but not harmful.
The use of scan codes together with the key press/release information makes it easy to assign arbitrary meanings to the keys, e.g., to convert the standard QWERTY keyboard layout to the Dvorak layout; to discriminate between different keys having the same labels such as the number keys in the main keyboard and the numeric keypad, the left and right SHIFT keys, etc.; to handle special key combinations for "hot key" applications; and to identify the sequence in which certain keys have been pressed and released. For normal typing and character entry, on the other hand, keystrokes should simply be converted to ASCII codes; this is done by the default interrupt 9 handler in the system BIOS, described below.
KBD_INT, the name given to the default BIOS Keyboard Interrupt 9 Handler, reads the scan code from port 60h, sends the clear-and-reenable handshaking signal to the keyboard unit, processes the scan code, sends an End-of-Interrupt signal to the Interrupt Controller (code 20h to port 20h), and returns from the interrupt.
The scan code processing performed in the KBD_INT routine in BIOS consists of the following tasks:
Intercept the following special key combinations:
CTRL-ALT-DEL (invokes a system reset)
CTRL-BREAK (invokes interrupt 1Bh. By default an immediate IRET is performed unless the user has installed an interrupt 1Bh handler)
CTRL-NUM LOCK (enters a SUSPEND state, i.e. waits in a loop within KBD_INT until any key other than NUM LOCK is pressed)
SHIFT-PRTSC (invokes interrupt 5)
Maintain a record of the state of the SHIFT, CTRL, ALT, CAPS LOCK, NUM LOCK, SCROLL LOCK, and INSERT keys. This invokes monitoring key presses as well as releases, and suppressing the typematic action of the LOCK and INSERT keys to get toggle action.
Convert the scan code for any other key press (and for INSERT) into a two-byte "extended ASCII" code representation and store it in a 16-word circular "type-ahead" buffer KB_BUFFER (or sound a beep if the buffer is full)
"Compose" an ASCII code for digits typed on the numeric keypad while ALT is held down. The code is the number (modulo 256).
Task 2 maintains two "shift mode status" bytes, located at 0040:0017h and 0040:0018h in IBM-compatible BIOS, indicating the following:
Table 10-1. Meaning of Shift Status Byte at 0040:0017h
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| INSERT | CAPSLK | NUMLK | SCRLK | ALT | CTRL | LSHIFT | RSHIFT |
| state active | state active | state active | state active | key down | key down | key down | key down |
Table 10-2. Meaning of Shift Status Byte at 0040:0018h
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| INSERT | CAPSLK | NUMLK | SCRLK | SUSPEND | 0 | 0 | 0 |
| key down | key down | key down | key down | state active |
KB_BUFFER, the type-ahead buffer maintained by Task 3, is independent of the 16-keystroke buffer maintained within the keyboard unit. For every keypress other than shift mode keys (but including INSERT) an entry is made in KB_BUFFER, modified as appropriate by the status of the various shift mode keys. Note that CAPS LOCK affects only the letter keys, NUM LOCK only the number keys in the numeric keypad, and that the effect of either key is reversed when SHIFT is also pressed. Note further that when ALT, CTRL, and SHIFT are pressed in combination (other than the CTRL-ALT-DEL "system reset" case) their precedence is ALT first, CTRL second, SHIFT last.
Writing your own interrupt 9 keyboard handler is relatively straightforward since the hardware does most of the work for you. If you use your own interrupt 9 handler, none of the above functions will happen, but you can check for things the normal handler won't check for.
Your interrupt handler will be a normal interrupt service routine. The only special requirement is that it acknowledges reception of the keyboard event by toggling bit 7 of port 61h to 1 and back to 0. The other bits of port 61h must not be modified, since they control other hardware. This is only required for full original IBM PC compatibility. The following code example is a skeleton of an interrupt 9 handler:
Example 10-1. Interrupt 9 (Keyboard) Handler
KbdInt
push ax ; Save registers
push ds ;
mov ax, cs ; Make sure DS = CS
mov ds, ax ;
in al, 60h ; Get scan code
¦ ;
¦ ; Process event
¦ ;
in al, 61h ; Send acknowledgment without
or al, 10000000b ; modifying the other bits.
out 61h, al ;
and al, 01111111b ;
out 61h, al ;
mov al, 20h ; Send End-of-Interrupt signal
out 20h, al ;
pop ds ; Restore registers
pop ax ;
iret ; End of handler
The procedure for installing an interrupt 9 handler is exactly the same as that for installing an interrupt 1Ch timer interrupt routine, except that the interrupt 9 vector is located at address 0000:0024 (segment address 0000, offset 0024h). Remember to save the old vector and restore it before your program exits.
Keyboard routines in LIB291
This routine waits for a character to become available, echoes the character to the display, and then returns with the character stored in AL.
This routine is the same as KBDINE except the character is not echoed.
One thing to note about the above routines is that they will wait indefinitely until a key is typed. If this is not desired, one should check to see if a key is available before calling kbdine or kbdin. For this you call interrupt 16h with AH=1, which returns with the zero flag ZF=1 if no character has been typed, or ZF=0 if a character is available. An example of how to use this interrupt is shown below:
Several useful BIOS routines for the keyboard can be accessed by using INT 16h with AH=0, 1, or 2.
Subfunction 0 (i.e. AH=0) waits, if necessary, until an entry is present in the BIOS type-ahead buffer KB_BUFFER, then removes the entry from KB_BUFFER into register AX. The scan code (or second code) is in AH and the ASCII code (or 00h) is in AL.
Subfunction 1 returns with the entry at the tail of KB_BUFFER copied into AX (but not removed from the buffer) and the zero flag set/reset if the buffer is/isn't empty. I.e., if ZF=1, a new entry is not available, and the entry copied into AX was typed 16 entries ago; if ZF=0, AX contains the new entry (subfunction 0 must be used to actually remove the entry from KB_BUFFER). Subfunction 1 may be used to "preview" a character such as CTRL-C before it is acted upon.
Subfunction 2 copies the first "shift status" byte (see Table 10-1) into AL.
Subfunction 0 and 1 recognize CTRL-BREAK and invoke interrupt 1Bh. The default interrupt 1Bh handler in BIOS is an IRET. A user-written interrupt 1Bh handler may be installed, but care must be taken to issue End-of-Interrupt commands for all hardware interrupts that happen to be in service when CTRL-BREAK was pressed, and to reset those hardware devices.
Here is a short table summarizing the INT 16h functions above:
| INT 16h Function | Description | ||
|---|---|---|---|
|
AH=0 |
Checks (and waits) for a keypress. Removes it from buffer.
|
||
|
AH=1 |
Checks (but doesn't wait) for a keypress. Leaves it in buffer.
|
||
|
AH=2 |
Copies the first shift status byte to AL. |
In addition to the BIOS calls, there are several DOS function calls which also provide keyboard services. The calls are performed using INT 21h with AH=01h, 06h, 07h, 0Ah, 0Bh, and 0Ch. These DOS function calls use the BIOS calls described above. Some functions recognize CTRL-BREAK and SHIFT-PRTSC; note that CTRL-BREAK from a DOS function invokes interrupt 23h.
DOS functions 01h, 07h, or 08h wait (if necessary), read a new buffer entry, and return the ASCII code in register AL; an ASCII code of 00h indicates that a second DOS function call is needed to get the "second code" into AL. Function 01h also echoes the character to the display, functions 07h and 08h don't. Functions 01h and 08h recognize CTRL-BREAK and SHIFT-PRTSC; function 07h ignores them.
DOS function 06h, when used for input by setting DL = -1, is similar to function 07h but does not wait for an entry: if no entry is ready the function returns with the zero flag set, otherwise the zero flag is reset and the ASCII code is returned in AL.
DOS function 0Ah allows input of an entire input string. The characters are echoed to the display as they are entered; the string is terminated with ENTER. Keys requiring a "second code" are ignored. If the allowed maximum length of the string (including the terminating ENTER) is N characters, an (N+2)-byte input buffer pointed to by DS:DX must be set up, with byte 0 set to N. When the complete string has been input, byte 1 of the buffer will be set to the actual character count (not including the terminating ENTER); the characters of the string, including the ENTER, start in byte 2. A beep sounds if the string is too long, i.e., if the Nth character is not ENTER.
DOS function 0Bh returns 0/-1 in register AL if KB_BUFFER is/isn't empty. This function recognizes CTRL-BREAK.
DOS function 0Ch, with AL = 01h, 06h, 07h, 08h, or 0Ah, first clears KB_BUFFER and then invokes the DOS function specified in AL
Here is a short table summarizing the INT 21h functions above:
| INT 21h Function | Description | |||||
|---|---|---|---|---|---|---|
|
Checks (and waits) for a keypress. Removes it from buffer.
|
|||||
|
Checks (but doesn't wait) for a keypress. Leaves it in buffer.
|
|||||
|
AH=0Ah |
Inputs an entire string. Characters are echoed as they are typed. ENTER terminates the entry. (See above for more information). |
|||||
|
AH=0Bh |
AL ← 0 if the buffer is empty, -1 if it isn't empty. |
|||||
|
AH=0Ch |
With AL=01h,06h,07h,08h,0Ah, performs the same functions as above (function is in AL) but first clears the buffer. |
The KB_BUFFER entries are "extended ASCII" codes consisting of two bytes. For letter, number, and punctuation keys (combined with the status of SHIFT, CAPS LOCK, NUM LOCK, or CTRL) the extended ASCII code consists of the key's scan code in the high byte (with MSB = 0 for a key press) and the corresponding ASCII code in the low byte. Scan codes for the standard and the AT keyboards ae shown below. Key releases are indicated by the scan code MSB = 1 and the low 7 bits set to the scan code.
Table 10-3. Scan Codes Assigned to Keys on the Standard or AT Keyboard
| Key | Scan Code |
|---|---|
| ESCape | 1 |
| 1 2 3 4 5 6 7 8 9 0 - = | 2-13 |
| Backspace | 14 |
| Tab | 15 |
| Q W E R T Y U I O P [ ] | 16-27 |
| ENTER | 28 |
| CTRL | 29 |
| A S D F G H J K L ; ' ` | 30-41 |
| SHIFT (left) | 42 |
| \ | 43 |
| Z X C V B N M , . / | 44-53 |
| SHIFT (right) | 54 |
| Print Screen | 55 |
| ALT (left) | 56 |
| SPACEBAR | 57 |
| CAPS LOCK | 58 |
| F1 to F10 | 59-68 |
| NUM LOCK | 69 |
| SCROLL LOCK | 70 |
| 7 8 9 (Numeric keypad) | 71-73 |
| gray - | 74 |
| 4 5 6 (Numeric keypad) | 75-77 |
| gray + | 78 |
| 1 2 3 (Numeric keypad) | 79-81 |
| 0 (Numeric keypad) | 82 |
| DELETE | 83 |
| SYSTEM REQUEST | 84 |
ASCII codes composed on the numeric keypad with ALT held down return a 00h scancode.
Keys which have no standard ASCII representation (F1, PageUp, Insert, etc.) are stored with an ASCII code of 00h in the low byte and a "second code" (usually, but not always, the scan code) in the high byte, as shown in Table 10-4. Key combinations not shown are ignored.
Table 10-4. "Second Codes" for ASCII Code 00h Key Combinations
| Key | Scan Code |
|---|---|
| NUL character | 3 |
| SHIFT-TAB | 15 |
| ALT-Q,W,E,R,T,Y,U,I,O,P | 16-25 |
| ALT-A,S,D,F,G,H,J,K,L | 30-38 |
| ALT-Z,X,C,V,B,N,M | 44-50 |
| F1 to F10 | 59-68 |
| HOME | 71 |
| UP ARROW | 72 |
| PAGE UP | 73 |
| LEFT ARROW | 75 |
| RIGHT ARROW | 77 |
| END | 79 |
| DOWN ARROW | 80 |
| PAGE DOWN | 81 |
| INSERT | 82 |
| DELETE | 83 |
| SHIFT-F1 to F10 | 84-93 |
| CTRL-F1 to F10 | 94-103 |
| ALT-F1 to F10 | 104-113 |
| CTRL-PRINT SCREEN | 114 |
| CTRL-LEFT ARROW | 115 |
| CTRL-RIGHT ARROW | 116 |
| CTRL-END | 117 |
| CTRL-PAGE DOWN | 118 |
| CTRL-HOME | 119 |
| ALT-1,2,3,4,5,6,7,8,9,0,-,= | 120-131 |
| CTRL-PAGE UP | 132 |
| F11, F12 | 133, 134 |
When the keyboard is to be used for real-time control of a simple sound synthesizer, or the motors in a robot, etc., it may be necessary to monitor not only key presses but also key releases, possibly for several keys at once. Of course, the KBD_INT interrupt 9 handler in BIOS does exactly that for the shift keys, but not for other, arbitrary keys. A substitute interrupt 9 handler would differ from KBD_INT only in the way the scan codes sent from the keyboard unit are processed. It would most likely maintain a "status word" in which individual bits are set or reset according to whether the corresponding keys are pressed or released. This status word can then be monitored by the main program.
A hot key is a key combination that activates a resident program, temporarily suspends whatever application program is running, performs a specific task, and then returns control to the application program. A hot key thus acts like an interrupt, as in SHIFT-PRTSC. E.g., a hot key combination might be used to display the time in the right hand upper corner of the screen for a few seconds.
Key combinations that are unlikely to be used normally are candidates for this purpose, particularly key combinations that are ignored by KBD_INT, such as ALT and gray + (key #78). A solution which is simple, elegant, and does not interfere with the normal operation of KBD_INT, is to write a "preprocessor" for interrupt 9 which intercepts and processes ALT and key #78 scan codes, ignores all others, and then exits to the original KBD_INT routine to send the acknowledge signal to the keyboard, process the scan codes, send E-o-I to the Interrupt Controller, and return from the interrupt. The preprocessor either maintains a flag bit which is set/reset when ALT is pressed/released, or tests the KBD_INT Shift Status byte at 0040:0017h, and invokes the desired hot-key action when the ALT mode is active and key #78 is pressed.
Shown below is the outline of a hot-key routine that displays the current time on the screen for a few seconds whenever ALT & key #78 is pressed. Because hot key routines, like interrupts, may be invoked at any time (in particular, during execution of a DOS function), and DOS is not a reentrant operating system, DOS functions cannot be used in hot-key routines. Hence, BIOS call 1Ah is used to get the Time-of-Day value, rather that the much simpler DOS function 2Ch.
Display subroutine
Get Time-of-Day (BIOS call 1Ah) in CX:DX
Convert to HH:MM:SS format
Save contents of upper right hand corner of screen
Display the current time there
Delay about 2 seconds
Restore original screen contents
Return
Preprocessor (invoked when an interrupt 9 occurs)
Enable interrupts
Save working registers
Maintain/check ALT mode flag
If key #78 and ALT-mode, call Display subroutine
Restore working registers
Exit to old type-9 interrupt vector
The program to install the hot-key routine must "chain" interrupt 9 and then exit to DOS, but leave the preprocessor and all routines required for the hot-key action resident. The old interrupt 9 vector should be saved in a doubleword so the preprocessor can exit to it with a JMP dword [oldvect] before the interrupt 9 vector is set to point to the preprocessor. DOS function 31h is used to "terminate-but-stay-resident" by setting DX to the number of 16-byte paragraphs to be kept resident (including 16 paragraphs for PSP, the Program Segment Prefix), and AL to an exit code that can be examined by batch commands.