||Potts, Spring 2001|
|Assigned||Tuesday, February 27, 2001|
|Purpose||Text-Mode Graphics, Timer and Keyboard Interrupts|
|Due Date||March 21 - 5:00pm|
Ever used a typing tutor to learn how to type? Ever think of doing so? We're about to create one, but don't expect it to be as good as Mavis Beacon.
Our typing tutor has to do three main things:
- Show the current state of the keyboard (to help the typist look at the screen)
- Show a string for the typist to type, showing the typist's progress and mistakes
- Track the time and number of mistakes the typist makes, and display this at the end
To do these somewhat efficiently, we're going to hook into the computer's keyboard and timer interrupts, to run some of the code asynchronously from the main loop. Asynchronously means, in this sense, that you never know ahead of time when it will run. You could have two move opcodes, or a compare and a conditional jump, and either could be interrupted to run the Interrupt Service Routine (ISR). More on this shortly.
Interrupt Service Routines
Hardware Interrupts are generated when various hardware events occur. Examples include the timer, which triggers 18.2 times per second, the keyboard, which triggers for every key press and release, and the mouse which triggers for any movement or button press or release. These interrupts are the hardware's way of saying "I need attention," so that the rest of the time the CPU can completely ignore such devices, and just execute code.
When the interrupt happens, the CPU is informed. The current opcode finishes execution, and then the current program is interrupted. This is when your Interrupt Service Routine (ISR) is executed. As it happens between two effectively random lines of your program, the ISR needs to save all registers and the flags. The hardware takes care of part of this (by pushing of the flags); the rest you must take care of by using IRET instead of RET (to pop the flags), and saving your registers like normal.
The timer will be used to keep track of how long it takes the typist to type the sentences. While we could probably be saving these scores for charting improvement, we'll just use it to report back to the user immediately. Every time your timer ISR is called, increment the tick counter, [
ticks]. Then, every 18 ticks increment the seconds counter and the fifths counter, [
seconds] and [
fifths] respectively, and reset the tick counter to 0. Finally, every 5 seconds, decrement the time counter (to make up for the .2 of the 18.2 ticks per second) and reset the second counter to 0.
When your keyboard handler is called, you know a key has been pressed or released. Unfortunately, you don't know what key, whether it was pressed or released, and whether the press is a true press or an auto-repeat press. There's no way for us find out the last (except keeping track yourself), but as for which key and press or release, you can (and must) find this out by asking the keyboard for the scancode. See the lab manual, page 75 or thereabouts, for information on retrieving these scancodes. The pages after that describe how scancodes work.
The Keyboard Lookup Tables
If you look at the scancodes, and you look at your ascii table, the first thing you'll probably notice is that there's no connection. If you then look down at the keyboard, however, you may notice a general correlation between the scancode and the location of the key, with ESC being scancode 1, and the numbers following an increasing pattern.
While theoretically we could try to come up with some arbitrary math procedure to translate from scancode to ascii code, it's much easier and much faster to just use a lookup table. And since there are several bits of information we want to handle, using multiple lookup tables is definitely the choice.
The tables we'll be using are as follows:
- Mostly an ascii lookup table, 128 bytes long, one byte per character.
- If the value is under or equal to
KEY_STR, it is a "special" character, and takes a second lookup.
- Just like
QuertyNames, but has the values for when shift is pressed. Special keys are identical to original table.
KEY_STRoffsets (word sized) that point to
KEY_STRcharacters (byte sized) which are the ascii the special keys should use
- Holds screen offsets used for drawing each key.
As the user types a string, it needs to be compared against the correct string. As the user makes mistakes, they need to be highlighted. When the user hits backspace, we need to back up the user version. All these things need to be tracked, and drawn in the appropriate colors.
- Draw the string the user has to match in
- Draw pieces of the string the user types correctly in
- Draw mistaken characters in
Similarly, the keyboard must be updated to match the current state of the keyboard (yes, there are some keys that make it look like shift was pressed when it wasn't. Don't worry about them; just treat the shift scancode as you always would).
- Draw the keyboard border and all normal keys in
- Draw the keys that are currently down in
Rather than redrawing the entire keyboard every time, just use
HighlightKeyto change the attribute of a given key by scancode (for easy use of the lookup table). But do redraw the entire keyboard (not changing the attributes) using the Shift tables, if [
shift] changes to true (or the normal tables if it changes to false).
quit] being set to nonzero. This requires smart coding of
DisplayStatsand wait for a key press before resetting with
KeySkin, drawing them to the screen starting at
KBD_OFF, and looping for 11 rows of 80 characters.
DrawKeyboarddraws blanks where the names will go.
QwertyNamesif [shift]==0, or
KEY_STR, just use the byte value as its appearance.
KEY_STR) it is a lookup into
SpecialKeys, which points to
ticks], and [
ATTR_NORMAL, update [
currentStr], and draw the new string with
currentStr] is zero, this is one past the final string. Reset [
currentStr] to zero.
ticks] every time, [
seconds] and [
fifths] every 18 ticks, and decrement [
seconds] every five fifths.
ticks] and [
fifths] after they trigger an increment or decrement, respectively.
nextKey] to the ascii according to the lookup tables: like in
KEY_STR, just use it as the ascii, but use
HighlightKeyon any scancode <
shift], and call
DrawKeyNameswhen it changes
ATTR_RELEASEDas your attribute byte
DrawKeyNamesfor documentation on the lookup table structure
quit] > 0 (then it can be anything)
quit] or [
nextKey] to be nonzero
CheckKeyclaims a match, use
ATTR_NORMAL, but use
CheckKeysays we're done, or the user presses enter
IN_ROW, column bx (See http://www.ctyme.com/intr/int-10.htm)
ZF= 1 if and only if key matches (don't worry about during end case; user can't press 00h)
CF= 1 if and only if we're at the end of the string (zero terminated)
currentStr] is the index of the string (0..
StrTableis a lookup table of string starting addresses: thus compare [[
currentStr]]+bx] to al, but make it work in assembly
errors] on any mismatched key
errors] and [
seconds] pointing to
TIME_OFF, respectively, with
errorMsgis directly before
errorNumand relies on the
binascplaces at the end of
Monitor the newsgroup and this on-line section for revisions to the MP or to the write-up
1. Demonstrate your MP3.EXE to a TA. You may be asked to recompile and demo the program. Your program must work with all given input.
2. Be prepared to answer questions about any aspect of the operation of your program. The TAs will not accept an MP if you cannot fully explain your code and your implementation. Delayed MPs will be subject to late penalties as described in the course syllabus (10/pts per day).
3. The TA will complete the code submission procedure.