23 June 2004 Scan codes, part 2 I tried again to get my primitive keyboard-reading code to work. I've written code that gets the scan code from the keyboard controller and displays both the key's name and whether the key was pressed or released. This code works — most of the time. It prints out "H MAKE" when you press the H key and "H BRK" when you let the key up. However, it doesn't work 100% perfectly with keys that generate multiple-byte scan codes, such as PAGE UP. Set 1 scan codes Here is some information that I probably should have included in my last entry. There are two kinds of scan code. When you press a key on the keyboard, the keyboard controller spits out a make code. When you remove your finger from the key, the keyboard controller spits out a break code. Each key on the keyboard has its own make code and its own break code. The keyboard can generate scan codes from any of up to three sets. The keyboard controller, by default, generates scan codes from Set 1. I haven't written code to make the keyboard controller generate either Set 2 or Set 3 scan codes, so I'm assuming that the keyboard controller is giving me Set 1 scan codes. Set 1 provides most keys with a one-byte scan code. Certain other keys — the cursor keys, INSERT, DELETE, PAGE UP, and others — have a two-byte scan code; the first byte is 0xE0. Two keys have odd scan codes: The PRINT SCREEN key generates a four-byte scan code beginning with 0xE0, and the PAUSE key generates a six-byte scan code beginning with 0xE1. The make code and the break code for the same key differ by one bit — bit 7 in a one-byte scan code, or bit 7 in the byte (or bytes) following 0xE0 in a multiple-byte scan code. The make code leaves the bit clear (putting the byte in the range 0x00..0x7F); the break code sets the bit (putting the byte in the range 0x80..0xFF). Thus if the make code for the J key is 0x24, the break code for the J key is 0xA4. The make code for the END key is 0xE0 0x4F, so the break code for the END key is 0xE0 0xCF. Thus you can tell when a key is being held down and when it has been released. (Theoretically I could switch to using Set 3 scan codes, because they are easier to handle — each key without exception has a one-byte make code. However, I've read in places that not all keyboards can generate Set 3 scan codes, but they can be counted on to generate Set 1 codes.) Code My latest project simply waits for a key event, then grabs the name of the key from a table and prints it on the screen, then displays "MAKE" if the key is being pressed, or "BRK" if the key has been released. It also dumps the last byte of the scan code to the screen. The relevant code is in BOOT.ASM, in the section marked SYSTEM LOADER. First, I print a message for the user: Hit a key to see its name below. call cls ; Clear the screen. call tclear ; Empty the T register. _lit msg _lit len call tapps ; Copy the message into the T register. _lit 1 call tprint ; Print the message to row 1 on the screen. call refresh ; Copy the screen buffer into video RAM. Then I enter the get_key loop. First, I reserve a space on the top of the stack, so that I can use the top-of-stack register (EAX, of which AL is a part) to fish bytes out of the keyboard controller ports. get_key: _dup Then I set EAX to zero, so that I can change AL, and then use the whole of EAX as an offset into the key-name table later. xor eax, eax Now I wait for the user to press a key. call wait_for_key_byte I have the first byte of the key's scan code in AL. If this byte is 0xE1, then the user pressed the PAUSE key, which generates a six-byte make code (but no break code). In this case, I just fish out the other five bytes, discard them, and substitute a make code of 0x00. cmp al, 0xE1 jne .2 mov bx, 5 .1: call wait_for_key_byte dec bx jnz .1 xor eax, eax jmp access_key_table If the byte is 0xE0, then the scan code contains a second byte, which I'll have to retrieve. .2: cmp al, 0xE0 jne .3 call wait_for_key_byte I save this byte into make_or_break (so I can test it later to determine if the key was pressed or released). mov [make_or_break], al Then I force bit 7 on, to produce a fake one-byte scan code in the range 0x80..0xFF. or al, 0x80 jmp access_key_table If the byte is neither 0xE0 nor 0xE1, then I have the complete scan code in AL. I save it into make_or_break (again, to test it later), and I force bit 7 off, to ensure that I have a scan code in the range 0x00..0x7F. .3: mov [make_or_break], al and al, 0x7F I save the altered scan code in offset, so that it will show up when I dump the memory to the screen later. access_key_table: mov [offset], al I clear out the T register. Then I grab the four-character string corresponding to the key code in AL, and I append it to the T register. call tclear mov eax, [key_table+eax*4] call tapp4 I add a space to the T register, to separate the key name from the following string. _lit ' ' call tapp1 Now I can test the scan code I saved to make_or_break. If bit 7 is clear, then the scan code was a make code, so I put the word MAKE in EAX; otherwise the code was a break code, so I load BRK into EAX instead. _lit 'MAKE' mov bl, [make_or_break] test bl, 0x80 jz .1 mov eax, 'BRK ' I append the contents of EAX (MAKE or BRK) to the T register. .1: call tapp4 I print the T register to row 3. _lit 3 call tprint I dump sixteen bytes, starting at make_or_break, to row 5. (Make_or_break is the first byte dumped; offset is the second. The rest of the row shows the first few bytes of the key-name table and can be ignored.) _lit 5 _lit make_or_break call dump16 I refresh the screen and repeat the whole process. call refresh jmp get_key Now here is what the wait_for_key_byte routine does. It tests bit zero of port 0x64 repeatedly. If the bit is zero, then there is no data yet, but if the bit is one, I can grab the byte of data from port 0x60 and return. wait_for_key_byte: .1: in al, 0x64 test al, 1 jz .1 in al, 0x60 ret Key table The key table is an array of 256 four-character key names, padded with spaces. Obviously I had to abbreviate the real names of many keys to get them to fit into four characters — "Paus" (offset 0x00) is the Pause key, and "BKSP" (offset 0x0E) is the Backspace key. key_table: ; (Uses Set 1 scan codes; ; two-byte code E0 xx --becomes-- one-byte code xx+0x80) dd 'Paus','ESC ','1 ','2 ' ; 0x00 dd '3 ','4 ','5 ','6 ' dd '7 ','8 ','9 ','0 ' dd '- ','= ','BKSP','TAB ' "ENTR" (offset 0x1C) is the Enter key (the one above the right Shift key, not the "tall" one on the numeric keypad. "LCtl" (offset 0x1D) is the left Ctrl key. dd 'Q ','W ','E ','R ' ; 0x10 dd 'T ','Y ','U ','I ' dd 'O ','P ','[ ','] ' dd 'ENTR','LCtl','A ','S ' I wasn't sure how well the semicolon, the double-quote mark, and the "tick" (the grave-accent mark made with the key that's usually above the Tab key and to the left of the "1" key) would show up on the screen, so I just designated these keys with "semi", "quot", and "tick" respectively. "LShf" is the left Shift key. dd 'D ','F ','G ','H ' ; 0x20 dd 'J ','K ','L ','semi' dd 'quot','tick','LShf','\ ' dd 'Z ','X ','C ','V ' "RShf" is the right Shift key. "KP" indicates a key in the numeric keypad on the right side of most fullsized keyboards (laptops might not have these keys). "KP *" thus indicates the star or multiplication key on the keypad. "LAlt" is the left Alt key, "Spac" is the space bar, and "CAPS" is the Caps Lock key. dd 'B ','N ','M ',', ' ; 0x30 dd '. ','/ ','RShf','KP *' dd 'LAlt','Spac','CAPS','F1 ' dd 'F2 ','F3 ','F4 ','F5 ' "NumL" and "ScrL" are the Num Lock and Scroll Lock keys respectively. dd 'F6 ','F7 ','F8 ','F9 ' ; 0x40 dd 'F10 ','NumL','ScrL','KP 7' dd 'KP 8','KP 9','KP -','KP 4' dd 'KP 5','KP 6','KP +','KP 1' I used "null" in any offset that didn't correspond with any of the keys listed on the Set 1 page. Some keyboards come with extra keys for getting your mail or surfing the Web; such keys might produce strange scan codes that result in the word "null" being printed to the screen. dd 'KP 2','KP 3','KP 0','KP .' ; 0x50 dd 'null','null','null','F11 ' dd 'F12 ','null','null','null' dd 'null','null','null','null' dd 'null','null','null','null' ; 0x60 dd 'null','null','null','null' dd 'null','null','null','null' dd 'null','null','null','null' dd 'null','null','null','null' ; 0x70 dd 'null','null','null','null' dd 'null','null','null','null' dd 'null','null','null','null' Two-byte scan codes that begin with 0xE0 are converted into one-byte scan codes in the range 0x80..0xFF. Entries for such scan codes begin here. dd 'null','null','null','null' ; E0 00 dd 'null','null','null','null' dd 'null','null','null','null' dd 'null','null','null','null' A number of keys given here are multimedia keys that aren't present on many keyboards, such as "PREV" (previous track) and "NEXT" (next track). Others should be present on almost every keyboard, such as "KPEn" (the Enter key on the numeric keypad) and "RCtl" (the right Ctrl key). dd 'PREV','null','null','null' ; E0 10 dd 'null','null','null','null' dd 'null','NEXT','null','null' dd 'KPEn','RCtl','null','null' "PrSc" is the Print-Screen key of course. (This key actually produces two scan codes, one after the other. The first one is 0xE0 0x2A, and the second is 0xE0 0x37. Thus "PrSc" appears twice in this table.) "V_DN" is the Volume-Down key. dd 'MUTE','CALC','PLAY','null' ; E0 20 dd 'STOP','null','null','null' dd 'null','null','PrSc','null' dd 'null','null','V_DN','null' "V_UP" is "Volume Up". "RAlt" is the right Alt key. A key that begins with a "W" is a "Web" key that Windows interprets as a browser command: "WHom" brings up the Home page in the browser. dd 'V_UP','null','WHom','null' ; E0 30 dd 'null','KP /','null','PrSc' dd 'RAlt','null','null','null' dd 'null','null','null','null' The four arrow keys are "->UP", "-> L", "-> R", and "->DN" respectively. dd 'null','null','null','null' ; E0 40 dd 'null','null','null','HOME' dd '->UP','PgUP','null','-> L' dd 'null','-> R','null','END ' "LGUI" and "RGUI" are the two keys (on newer keyboards) that have the Microsoft "flying windows" or "Windows flag" logo on them. "APPS" is the key (next to the right GUI key) with an icon suggesting a popup menu. "POWR" and "SLEE" are the Power and Sleep keys (on still newer keyboards). dd '->DN','PgDN','INS ','DEL ' ; E0 50 dd 'null','null','null','null' dd 'null','null','null','LGUI' dd 'RGUI','APPS','POWR','SLEE' The keys here: Wake (stop hibernation), WWW Search, WWW Favorites, WWW Refresh, WWW Stop, WWW Forward, WWW Back, My Computer, E-Mail, and Media Select respectively. dd 'null','null','null','WAKE' ; E0 60 dd 'null','WSea','WFav','WRef' dd 'WStp','WFor','WBac','MyCo' dd 'MAIL','MEDI','null','null' dd 'null','null','null','null' ; E0 70 dd 'null','null','null','null' dd 'null','null','null','null' dd 'null','null','null','null' Results I've tested this code on the computer I have at work and got strange results: Most of the keys that are supposed to produce multibyte scan codes beginning with 0xE0 worked OK when I pressed them — for example, pressing the up-arrow key displays "->UP MAKE" as I intended. However, when I released the key, I'd get "PrSc BRK" — and the dump line beneath showed that the last byte of the break code was 0xAA. This happened only on my computer at work; my laptop at home produced the "->UP BRK" I expected. From this, I am guessing that some keyboards produce a four-byte scan code not only for the PRINT SCREEN key but also for keys like INSERT and HOME and PAGE UP. I am guessing that, for such keys, the make code begins with 0xE0 0x2A, and the break code ends with 0xE0 0xAA. I've found some evidence for this, in a text file claiming that 0xE0 0x2A is the "make code prefix for keyboard internal numlock," and that 0xE0 0xAA is the corresponding "break code suffix for keyboard internal numlock." (I suppose this means that whenever the keyboard thinks NumLock is on, it will generate these extra scan codes when certain keys are pressed — see also here.) I've also found a page claiming that 0xE0 0x2A is the make code for a "fake" left-shift key, inserted by some keyboards "in order to fool old programs." So I'll add a few lines of code later to treat the scan codes 0xE0 0x2A and 0xE0 0xAA as meaningless. This would leave the "real" make code for PRINT SCREEN as 0xE0 0x37. Relevance for the future I'm doing this for two reasons: (1) I'll want to experiment with macros by assigning them to keys (so that pressing a key runs a macro). (2) I'll want to set aside 1KB for a table that can handle up to 256 keys. Not only that, but I'll also set aside another 1KB for each "control" key I'll want to use — Shift, Ctrl, and so on. This seems terribly wasteful, and certainly it's very far from the way Chuck Moore handles the keyboard (he wrote code to handle no more than 27 out of the hundred or so keys on a keyboard). However, I will eventually want to experiment with the extra keys on multimedia keyboards. Final note: Finding video RAM I forgot to add last time: I added a few lines of code to the boot sector to get the true address of the video RAM. I had to do this in order to test my code on my computer at work. The lines are: ; ------ Find the physical address of the video RAM. ; (Uses 256 bytes at end of segment zero) mov cx, 0x111 ; mode number mov di, 0xFF00 ; pointer to table mov ax, 0x4F01 int 0x10 mov eax, [0xFF28] ; PhysBasePtr field in table mov [scr$.vr], eax Check the index for other entries. |