| ECE291 |
|
Potts, Spring 2001 |
| Assigned | Tuesday, February 27, 2001 |
| Purpose | Text-Mode Graphics, Timer and Keyboard Interrupts |
| Points | 50 |
| 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
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.The Keyboard
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:
QwertyNames
- 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.QwertyShift
- Just like
QuertyNames, but has the values for when shift is pressed. Special keys are identical to original table.SpecialKeys
- Holds
KEY_STRoffsets (word sized) that point to'$'terminated stringsSpecialAscii
- Holds
KEY_STRcharacters (byte sized) which are the ascii the special keys should useKeyLocations
- Holds screen offsets used for drawing each key.
The Strings
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
ATTR_STRING- Draw pieces of the string the user types correctly in
ATTR_NORMAL- Draw mistaken characters in
ATTR_ERRORSimilarly, 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
ATTR_RELEASE- Draw the keys that are currently down in
ATTR_PRESSRather 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).
InstallKeyboard, InstallTimer, DrawKeyboard,
NextString, GetKeyPress, TypeKey, DisplayStats,
RemoveKeyboard, RemoveTimerquit] being set to nonzero. This requires
smart coding of GetKeyPress.DisplayStats and wait for a key press before
resetting with NextString.
DrawKeyNamesKeySkin, drawing them to the screen
starting at KBD_OFF, and looping for 11 rows of 80 characters.DrawKeyNames second, as DrawKeyboard draws
blanks where the names will go.
shift]QwertyNames if [shift]==0, or QwertyShift
otherwise.QwertyNames[scancode] > KEY_STR, just
use the byte value as its appearance.QwertyNames[scancode] <= KEY_STR)
it is a lookup into SpecialKeys, which points to '$'
terminated strings.
errors], [seconds], [ticks], and [fifths]ATTR_NORMAL,
update [currentStr], and draw the new string with ATTR_STRSTR_OFF, IN_OFF, ERR_OFF,
and TIME_OFF appropriatelyStrTable[currentStr] is zero, this is
one past the final string. Reset [currentStr] to zero.
TimerISR
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.
KeyboardISR
HighlightKey, DrawKeyNamesnextKey] to the ascii according to the lookup tables:
like in DrawKeyNames, if QwertyNames[scancode]
> KEY_STR, just use it as the ascii, but use SpecialAscii[scancode]
if QwertyNames[scancode] <= KEY_STR HighlightKey on any scancode < SCAN_MAXshift], and call DrawKeyNames
when it changes
ATTR_PRESSED or ATTR_RELEASED as your
attribute byteDrawKeyNames for documentation on the lookup table
structureSCAN_MAX
kbdinquit]
> 0 (then it can be anything)quit] or
[nextKey]
to be nonzero
CheckKeyIN_OFF+f(bx) CheckKey claims a match, use ATTR_NORMAL,
but use ATTR_ERROR otherwiseCheckKey
says 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..STR_MAX-1)StrTable is a lookup table of string
starting addresses: thus compare [[StrTable+[currentStr]]+bx]
to al, but make it work in assemblyerrors] on any mismatched key
binasc on [errors] and [seconds]
pointing to errorNum and timeNum,
respectivelyerrorMsg and timeMsg
starting at ERR_OFF and TIME_OFF,
respectively, with ATTR_STRerrorMsg is directly before errorNum
and relies on the '$' that binasc places
at the end of errorNumMonitor 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.