Chapter 10 I/O Devices

Table of Contents
10.1 Keyboard
10.2 Mouse
10.3 8253 Timer Chip
10.4 Internal Speaker

10.1 Keyboard

10.1.1 Keyboard Interface Hardware

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.

10.1.2 Keyboard Interrupt 9 Handler

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:

  1. 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)

  2. 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.

  3. 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)

  4. "Compose" an ASCII code for digits typed on the numeric keypad while ALT is held down. The code is the number (modulo 256).

10.1.2.1 Shift Status Bytes

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.

10.1.2.2 User-defined Interrupt 9 Handlers

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.

10.1.3 Library Procedures for Keyboard Input

Keyboard routines in LIB291

kbdine

This routine waits for a character to become available, echoes the character to the display, and then returns with the character stored in AL.

kbdin

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:

Example 10-2. Using Interrupt 16h with AH=1

        mov     ah, 1   ; Get the status of keyboard
                        ;  buffer
        int     16h     ; ZF=1 if buffer is empty
        jz      .nochar ; Do something else if buffer is
                        ;  empty
        call    kbdine  ; Get the character
                ¦
                ¦

10.1.4 BIOS Function Calls

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.

AL ← ASCII code
AH ← scan (or second) code

AH=1

Checks (but doesn't wait) for a keypress. Leaves it in buffer.

ZF ← 0 if an entry is present. 1 if the buffer is empty.
If an entry is present, it is copied (not removed) just as above.

AH=2

Copies the first shift status byte to AL.

10.1.5 DOS Function Calls

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
AH=01h
AH=07h
AH=08h

Checks (and waits) for a keypress. Removes it from buffer.

AL ← ASCII code
Function 01h also echoes the character.
AH=06h
(DL=-1)

Checks (but doesn't wait) for a keypress. Leaves it in buffer.

ZF ← 0 if an entry is present. 1 if the buffer is empty.
If an entry is present, it is copied (not removed) just as above.

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.

10.1.6 Extended ASCII Codes

10.1.6.1 Scan Codes

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.

10.1.6.2 Second Codes

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

10.1.7 Applications

10.1.7.1 Monitoring How Long a Key is Pressed

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.

10.1.7.2 "Hot Keys"

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

  1. Get Time-of-Day (BIOS call 1Ah) in CX:DX

  2. Convert to HH:MM:SS format

  3. Save contents of upper right hand corner of screen

  4. Display the current time there

  5. Delay about 2 seconds

  6. Restore original screen contents

  7. Return

Preprocessor (invoked when an interrupt 9 occurs)

  1. Enable interrupts

  2. Save working registers

  3. Maintain/check ALT mode flag

  4. If key #78 and ALT-mode, call Display subroutine

  5. Restore working registers

  6. 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.