From: ervin@pinbot.enet.dec.com (Joe Ervin) Newsgroups: comp.sources.hp48 Subject: v03INF1: Reading the keyboard from ML (Doc) v1.0 Date: 6 Jan 92 03:26:56 GMT Followup-To: comp.sys.hp48 Organization: Univ. of North Carolina @ Wilmington Checksum: 1104234437 (verify with brik -cv) Submitted-by: Joe Ervin Posting-number: Volume 3, Info 1 Archive-name: keybrd_input BEGIN_DOC keybrd_input.doc HP48SX Keyboard Input A Guide for the Machine Language Programmer Copyright 1991 Joe Ervin 1 INTRODUCTION In this document, we will examine the workings of the HP48 keyboard hardware and how to do your own keyboard input on the HP48 from machine language. Additionally, we will describe how the HP48 keyboard input scheme works during normal operation, and how you can disable the normal operation and take over direct control of the keyboard. 2 OVERVIEW OF HP48 KEYBOARD INPUT During normal operation, the CPU scans the keyboard for key presses every 1ms. This 1ms keyboard scan is performed directly by the CPU hardware with no involvement from software, and therefore has a negligible effect on CPU performance. During this automatic keyboard scan, the entire keyboard is scanned in a single operation. If no keys are currently being pressed, then nothing else happens and the CPU continues executing instructions normally. If, however, this automatic keyboard scan indicates that one or more keys are being pressed, then the hardware interrupts the CPU. The HP48's interrupt handler then determines the exact key or keys which are being pressed and updates the keyboard data structures in memory to reflect the new state of the keyboard. The keyboard data structures are described later in this document. In addition to determining which specific keys are pressed, the HP48's interrupt handler also scans all other possible sources of interrupts and services them appropriately. In the case of pressed keys, the interrupt handler additionally schedules a timer interrupt for 1/16 of a second into the future before returning to the interrupted program. When the timer expires, another interrupt is generated, and the whole operation repeats. As a result, the CPU is interrupted 16 times per second, for as long as a key is held down. The use of the timer interrupt is necessary because the keyboard interrupt hardware only generates interrupts when keys are pressed, Page 2 i.e. no interrupts are generated directly as a result of keys being released. Therefore, by continually scheduling the timer interrupt to retrigger an interrupt in the near future, the interrupt handler can effectively "poll" the keyboard 16 times per second, checking for keys being released. This is done so that the "key-released" event can be recorded in the keyboard data structures, described below. Machine language applications can then check these data structures to determine the exact state of each key on the keyboard. 3 SCANNING THE KEYBOARD FROM MACHINE LANGUAGE Unlike the automatic 1ms keyboard scan performed by the CPU's hardware, the interrupt handler must force keyboard scans from software to determine the exact state of the keyboard. This is necessary for the interrupt handler because any interrupts generated by the 1ms keyboard scan indicate only that at least one key has been pressed. The interrupt handler then must scan the keyboard row by row to determine exactly what keys are being pressed. This same approach can be used from machine language programs to scan the keyboard. Scanning the keyboard from software happens in two general phases; an output phase and an input phase. During the output phase, software causes the HP48 to output signals to the keyboard which indicate the exact row or rows which are being scanned. Then, during the input phase, the software reads the keyboard to see which of the scanned rows have keys pressed. The keyboard is wired in a matrix as shown in Figure 1. Page 3 IN #20 #10 #08 #04 #02 #01 OUT (bit) 5 4 3 2 1 0 #100 8 B C D E F #080 7 PRG CST VAR up NXT #040 6 STO EVL <<< dwn >>> #020 5 COS TAN sqt pwr inv #010 4 ON* ENT +/- EEX DEL <== #008 3 alp SIN 7 8 9 / #004 2 yel MTH 4 5 6 x #002 1 blu A 1 2 3 - #001 0 ' 0 . SPC + (*) The ON key is actually in a column of its own. The ON key is represented in bit 15 of the data returned from an IN.4 instruction. #xxx refers to the keyboard scan bits driven with the OUT instruction. Figure 1 3.1 The OUT Instruction. During the first phase of the keyboard scan, software executes the OUT.X C instruction, which loads the HP48's output register with the contents of C.x, and then drives those contents into the key matrix. The bits in OUT<8:0> are connected to keyboard ROWS <8:0> respectively. Each "1" bit in OUT<8:0> therefore drives a HIGH voltage level out to the corresponding keyboard row. If any key in that row is currently pressed, then the HIGH level propagates to the bit of the input register which corresponds to the COLUMN of the key being pressed. Normally a HIGH voltage level on the input port causes an interrupt, but we will show later how to disable this interrupt so that the machine language program will be unhindered by the HP48's interrupt handler. Page 4 3.2 The IN.4 Instruction During the second phase of the keyboard scan, software executes the "IN.4 C" instruction to see if any keys in the scanned rows were pressed. In this way, a machine language program can scan the keyboard to test whether specific keys are being pressed. With the exception of the ON key, the keys which are "visible" to the input port is a function of the bit pattern driven during the OUT instruction. For example, if the output register contains 1FFh, then all keyboard rows are visible to the IN register. If, say, the [<-] key is pressed (or any other key or keys in that column), then after the "IN.4 C" instruction executes, bit 0 of the C register will be set, indicating that at least one key in the rightmost column of the scanned rows was pressed. Note that if more than one keyboard row is being scanned, then the data in IN<5:0> does not indicate a specific key, but rather a specific column of any of the scanned keyboard rows, as depicted by Figure 1. The state of the ON key is always reflected in bit 15 of the data returned from the IN.4 instruction. Because of the row/column wiring of the HP48 keyboard, in order to determine exactly which keys are pressed it is necessary to perform multiple OUT/IN pairs in which each "OUT.x C" scans only a single keyboard row. For example, "OUT.x C" with C.x=001h scans only the bottom row of the keyboard; C=002h scans the second row from the bottom; C=004h scans the third row from the bottom, and so on. In this way, any set bits in the data returned by the IN.4 instruction indicate that a specific key is being pressed. This is what the HP48's interrupt handler does to determine the exact status of the keyboard after the 1ms keyboard scan has generated an interrupt due to one or more keys being pressed. During normal operation, the interrupt handler then updates the "KeyBuf" and the "KeyState" in memory (described below) to indicate the key status to the rest of the RPL system. 3.3 Interrupt Woes In the process of servicing other I/O devices, checking battery voltage, etc., the interrupt handler executes many hundreds of instructions which have nothing to do with the keyboard, making the handling of keyboard input through this mechanism very inefficient in terms of CPU utilization. More significantly, however, the interrupt handler debounces the keyboard by capturing the state of the entire keyboard repeatedly, waiting 2ms between samples, until it sees exactly the same keyboard state for 5 consecutive samples. Because of this, the interrupt handler is guaranteed to require over 10ms to detect a single keystroke. Furthermore, the keyboard service routine in the interrupt handler is implemented as a loop which synchronizes itself to the 16Hz timer (TIMER1). If the keyboard service routine detects any keys being held down, then after updating the keyboard data structures (see below) it Page 5 checks to see whether there is enough time before the next 16Hz tick to do another full pass through the keyboard service routine and if so, the code loops back to the top of the keyboard service routine. If the next 16Hz clock tick is less than approximately 17ms into the future, then the keyboard service routine exits, and the interrupt handler completes. However, since the interrupt handler scheduled TIMER1 to interrupt on the next 16Hz tick, the CPU will bounce back into the interrupt service routine in just a few milliseconds. Thus, holding down a key while the HP48's normal keyboard interrupt service is in operation causes the keyboard interrupt service routine to hog approximately 75% of the CPU, leaving the remaining 25% for your application. When writing machine language programs that do not require much CPU power, this may not be a concern and it may be desirable to allow the interrupt system to handle keyboard input normally. In this case, the machine language program can retrieve keyboard status from the two keyboard data structures described below. However, for CPU-intensive applications it is generally desirable to disable the HP48's normal keyboard interrupts and perform keyboard I/O directly in the application. 3.4 HP48 Keyboard Data Structures There are two main keyboard-related data structures that the HP48 keeps in memory. The first is called the "KeyBuf", and occupies 34 nibbles starting at #704EA. The second is called the "KeyState" and occupies 13 nibbles starting at #704DD. Additionally, there are three other datum kept in memory which the interrupt handler uses in conjunction with the keyboard. These are the "ORshadow" (Output Register shadow), which occupies 3 nibbles at #704C3, "KBdisable", which is a single nibble at #704DC, and a two nibble value at #706C3 representing the state of the display annunciator flags at 10B/10C. These data structures are described below. 3.4.1 The KeyBuf The first two nibbles of the KeyBuf are the "get" and "put" pointers, respectively, and the remaining 32 nibbles comprise a 16-entry key buffer, with each entry occupying one byte. The "get" pointer provides an index into the KeyBuf for the next available key code. Similarly, the "put" pointer provides a index to the next entry in the KeyBuf to be written. If the "get" and "put" pointers are equal, then the buffer is empty. The "get" and "put" pointers point to byte locations within the buffer. You may have noticed how your calculator beeps at you defiantly when you have exceeded this 16 entry type-ahead buffer. This is the interrupt handler telling you that there is no more room in the KeyBuf. Page 6 The key codes which are used to represent keys in the KeyBuf are different than the scan patterns read in during the OUT/IN procedure. Each key is given a unique 1-byte key code as follows: key codes are numbered sequentially from the upper left of the keyboard counting across and down. Thus a key code of 1 represents the [A] key, 2 for [B], 3 for [C]..., #19h for [ENTER],... #1Fh for [7],... #31h for [+]. Note that four keys do not obey this ordering: [alpha] is #80h, [leftshift] is #40h, and [rightshift] is #C0h, and [ON] has no key code. The ON key is handled as a special case by the interrupt handler and does not appear in KeyState or in the KeyBuf. If the keyboard service routine detects the presence of [alpha], [leftshift], or [rightshift] at the same time as another key, then the key code for the "shift" key is ORed into the keycode for any non-"shift" keys which may also be pressed, and the resulting keycodes are inserted into the KeyBuf. For example, the "right-shifted" keycode for the [A] key is [rightshift]![A] = C0h!01h = C1h. Similarly, the keycode for a "alpha-shifted" [ENTER] is [alpha]![ENTER] = 80h!19h = 99h. If one of the shift keys is detected alone, then it's keycode is simply inserted into the KeyBuf. The code example in Appendix A shows a simple way to remove keys from the key buffer. In addition, this code example shows how to put the calculator into "light sleep" awaiting a key press. 3.4.2 The KeyState The 13 nibbles at location #704DD provide a bit pattern which reflects the status of the keyboard, with each bit representing the state of a specific key. This bit pattern is updated whenever a key is pressed, and is also updated when keys are released via a timer interrupt as described above. When a key is pressed, its corresponding bit is set to 1. There are 13 nibbles, 4 bits each, making 52 bits. The [ON] key is not represented in KeyState. There are 48 keys remaining, so four bits are unused. The low bit is unused, the next bit corresponds to the bottom rightmost key [+], the one following corresponds to SPC, then comes period [.], [0], ['], after which immediately follows the next row: [-], [3], and so forth, up to the upper leftmost key, which is [B]. (See Figure 1 above for the physical layout of the keyboard). Below is a table of all key bit codes. More than one bit is set if more than one key is being held down simultaneously. Page 7 Key 704E7 704E2 704DD Key 704E7 704E2 704DD B | 1 | | | <== | | 1 | | C | 8 | | | alp | | 8| | D | 4 | | | SIN | | 4| | E | 2 | | | 7 | | 2| | F | 1 | | | 8 | | 1| | PRG | 8| | | 9 | | |8 | CST | 4| | | / | | |4 | VAR | 2| | | yel | | |2 | up | 1| | | MTH | | |1 | NXT | |8 | | 4 | | | 8 | STO | |4 | | 5 | | | 4 | EVL | |2 | | 6 | | | 2 | <<< | |1 | | x | | | 1 | dwn | | 8 | | blu | | | 8 | >>> | | 4 | | A | | | 4 | COS | | 2 | | 1 | | | 2 | TAN | | 1 | | 2 | | | 1 | sqt | | 8 | | 3 | | | 8 | pwr | | 4 | | - | | | 4 | inv | | 2 | | ' | | | 2 | ENT | | 1 | | 0 | | | 1 | +/- | | 8 | | . | | | 8| EEX | | 4 | | SPC | | | 4| DEL | | 2 | | + | | | 2| Figure 2 KeyState 3.4.3 KBdisable And ORshadow As with any computer system that utilizes interrupts, the servicing of interrupts must be transparent to the currently running application, aside from the time delay associated with servicing the interrupt. In order to do this, the entire state of the CPU is saved at the beginning of the interrupt handler, and restored again at the end. Unfortunately, the contents of the OUT register is not readable, so it is not possible to directly save its contents. Since the interrupt handler invariably modifies the contents of the OUT register, the HP48 interrupt system requires applications to maintain a copy of the OUT register in RAM. The 3 nibbles stored at #704C3, designated as the "ORshadow", is used for this purpose. The ORshadow is used by applications to shadow the contents of the OUT register. It is the responsibility of any application which intends to use the OUT register for its own purposes to modify the ORshadow along with the OUT register. By doing this, the interrupt handler is provided with a readable copy of the contents of the OUT register so that if an interrupt should occur while the application is using the OUT register, the interrupt handler will restore the OUT register to Page 8 its correct contents before returning control to the application. The KBdisable flag is a single nibble which applications can write with a nonzero value to indicate to the interrupt handler that the keyboard is currently being scanned by an application. When this nibble is nonzero, the interrupt handler will not run the keyboard service routine. This is very useful for applications which have their own keyboard input routines as it allows the programmer to prevent the high CPU utilization by the interrupt handler as discussed above. The programmer should be aware, however, that the annunciator flags will still be updated when the corresponding keys are pressed, unless interrupts are disabled altogether as described below. 3.4.4 Utilizing The Built-in Data Structures Using the KeyBuf and KeyState data structures for your keyboard input is very straightforward. The routine provided in Appendix A shows how to access the KeyBuf, and accessing the KeyState is trivial. An application need only examine the appropriate bits in KeyState to determine if any given keys are being held down. For applications which can tolerate the additional CPU load of the interrupt handler, it probably makes sense to just allow the normal interrupt mechanism to service the keyboard, and then for the application to utilize the KeyState and KeyBuf data structures. It is generally only for applications which perform CPU intensive or time-critical operations that manual keyboard scanning techniques are required. The following sections will discuss this issue further, as well as how to write your own keyboard input routines. 4 CUSTOM KEYBOARD I/O The main motivation for writing your own keyboard input routines is to steal back valuable CPU time from the interrupt system for applications that need it. In order to recover the CPU time that the HP48 normally uses up in the keyboard interrupt service routine, you will need to write your own keyboard I/O routines, or you can just cut/paste the routines given later in this document. 4.1 Controlling The HP48 Interrupt System Writing your own keyboard I/O routine does little good unless one also disables the normal keyboard interrupt mechanism. Fortunately, there are a few options open to the machine language programmer in this regard. Described below are some general approaches to disabling normal keyboard servicing. Page 9 4.1.1 The Big Hammer The first method, which we will call "The Big Hammer", is to clear bit 15 of the status register. This bit is checked at the top of the interrupt handler, and if it is clear then the interrupt handler disables further interrupts and returns to the interrupted program. Thus, clearing ST<15> effectively shuts off all I/O on the calculator, namely the keyboard. The interrupt system also sets ST<14> to indicate that an interrupt request has been posted but was not serviced. One disadvantage to the "big hammer" approach are that once you clear ST<15>, if a code bug causes your program to "hang" with ST<15>=0, you have no control over the calculator. You can't turn it off; you can't do [ON]-[C]; you can't do [ON]-[A][F]. The only thing you _can_ do is to pull off the rubber foot hiding the reset button and jam a paper clip in the hole. Not much of a way to quit your application. Another problem is that preventing certain interrupts from being serviced for extended periods of time can lead to problems in the calculator. Since the interrupt handler does nothing if ST<15> is clear, none of the possible interrupt sources in the HP48 will be serviced. This includes, among other things, the low battery detect circuitry. Hence if a machine language program which has cleared ST<15> is left running, it can drain the batteries completely, resulting in total loss of memory. Normally the low-battery detect circuitry would interrupt the CPU, allowing the interrupt handler to safely shut the system down into a very low power consumption state, thus preserving RAM. With ST<15> clear, however, this safety net is removed. For programs which run for durations of less than several hours, however, this should not be a problem. Other sources of interrupts such as timer rollover and serial I/O activity will be totally ignored as long as ST<15> is clear. However, the lack of interrupt-driven serial I/O capabilities may not be an issue for many applications, and interrupt requests due to rollover of the 32-bit hardware timer (TIMER2) can be ignored for 72 hours without effecting the calculator's sense of time. An example of when clearing ST<15> can be particularly useful is when the keyboard is actually being scanned. In this way, software can avoid the need to shadow the OUT register in ORshadow. This works because no interrupts are possible while ST<15>=0. The application should then set ST<15>=1 after completing the keyboard scan to allow other interrupts, if desired. See the ENABLE_INTR routine in Appendix B for more information on how to re-enable interrupts after disabling them via ST<15>. 4.1.2 The Little Hammer Another approach to disabling the keyboard interrupts is to shut off keyboard scanning at the source by executing an INTOFF instruction. Page 10 This disables the automatic 1ms keyboard keyboard scan described above. As a result, interrupts no longer occur due to key presses, with the exception of [ON] which always causes an interrupt. Furthermore, since we have not disabled I/O altogether such as is the case when ST<15> is cleared, we can still abort our ML application by doing [ON]-[C] if the need arises. The only minor pitfall to this technique is that the INTOFF instruction prevents _only_ the keyboard interrupts. If one of the other devices in the system causes an interrupt, then the interrupt handler will still execute. As it turns out, the vast majority of the time spent in the interrupt handler is due to the keyboard service routine. Therefore, aside from the keyboard service routine in the interrupt handler, allowing interrupts does not cost very much in terms of CPU time. Fortunately, the keyboard service routine can be completely disabled by writing the KBdisable nibble to a nonzero value, so that if an interrupt should occur, the keyboard service routine will not be executed. The programmer should note that the [alpha], [leftshift], and [rightshift] keys are polled outside the keyboard service routine and the corresponding display annunciators updated. Unfortunately, there is no way to prevent this from happening aside from turning off interrupts altogether via ST<15>. This is generally not necessary, however, since in applications which do not use serial I/O, interrupts should not occur. The programmer should be aware that the interrupt service routines for the serial I/O contain INTON instructions, so if serial I/O is used, then INTOFF instruction will need to be repeatedly executed to prevent keyboard interrupts from occurring. 4.1.3 Recommendations Because of the different features built into the HP48 interrupt service routine, there are several approaches that a ML programmer can take to disable normal keyboard operations. While each technique has it's own strengths and weaknesses, there are some general programming practices that can lead to "cleaner" solutions. Below are a few hints to keep in mind with respect to programming for custom keyboard input. 1. Use INTOFF as a general technique to disabling keyboard interrupts, with critical "uninterruptable" sections of code protected by clearing ST<15>. This generally works well, although there are a few things to keep in mind. - After re-enabling interrupts by setting ST<15>=1, you need to check ST<14> to determine whether an interrupt was requested while interrupts were disabled. ST<14>=1 means an interrupt was requested but has not yet been serviced. Basically what happened is that an interrupt occurred, but since ST<15> was clear the interrupt handler immediately terminated via a RET instruction Page 11 rather than servicing the interrupt and terminating with an RETI instruction, as it does normally when ST<15>=1. The interrupt handler sets ST<14>=1 to inform the application that an interrupt is "pending". The implication of this is that the HP48 believes that it is still executing in the interrupt handler (since no RETI has been executed since the last interrupt) and will not allow further interrupts until an RETI instruction is executed. See the ENABLE_INTR routine in Appendix B for an example of how to handle this. - The Ticking Clock display or any user alarms which come due will be serviced whenever interrupts are enabled. This can lead to the INTON instruction being executed in the interrupt handler. Once this occurs, then it is possible for keystrokes to cause interrupts. - If the Keyboard interrupt service routine has been disabled via the KBdisable flag, then keypresses should not make it into the KeyBuf, even if interrupts occur. If the application does not modify the KBdisable flag, then the KeyBuf may need to be periodically flushed to remove any keypresses that sneak into the KeyBuf. - Because any interrupts may lead to the execution of the INTON instruction, the application should periodically execute an INTOFF instruction. 2. Use "ST<15>=0" sparingly. Try to keep sections of code which are protected from interrupts via the clearing of ST<15> as small as possible, and try to keep the "CLRB 15, ST" and "SETB 15, ST" instructions as local to each other as possible. Remember, if you make a mistake and leave ST<15>=0, all keyboard control is lost. Try to keep "uninterruptable" sections of code as small as possible, such as in the code example of Appendix B when the OUT register is being modified. 4.2 Example Keyboard Input Routines The code example in Appendix B shows how custom keyboard I/O routines can be written. The code shown in this example is actually the keyboard scanner process which runs in the game program "Vaders". Vaders was written using MPE, a machine language multiprogramming environment for the HP48. The keyboard scanner process in Appendix B runs concurrently with the other processes which make up the Vaders game, making the keyboard scanning transparent to the other processes which comprise the game. The example code maintains two data structures which reflect the Page 12 status of the keyboard in a manner similar to the "KeyBuf" and "KeyState" data structures maintained by the HP48's interrupt system. APPENDIX A KEYBUF CODE EXAMPLE ;;+ ;; ;; Keyboard Interface. ;; ;; Keyboard scan codes are numbered 1 for [A], 2 for [B], 3 [C]... ;; #19h [ENTER]... #1Fh [7]... #31h [+]. [alpha] is #80h, [yellow] is ;; #40h, and [blue] is #C0h. ON has no scan code. ;; ;; This program reads the keyboard buffer. If a key is present, it ;; returns it in A.A. If no key is present, it enters light sleep and ;; waits for one. ;; ;; kb_poll polls the keyboard buffer, carry set if non-empty, key in A.A. ;; kb_get does the same, but waits until a key is pressed. ;; ;; Jan Brittenson, April 1991 ;; This program is in the Public Domain ;; ;;- radix ^d16 event_mask = 10e ;; Poll keyboard buffer kb_poll: move.5 keybuf+1, d0 ; KB Put ptr move.s @d0, a ; A.S = put ctr dec d0 move.s @d0, c ; C.S = get ctr breq.s c, a, $100 ; Ctrs are equal - buffer empty move c.15, p ; P = get ctr inc.s c ; Remove key move.s c, @d0 swap c, d0 add p+1, c add p+1, c ; C += get ctr, in bytes KEYBUF CODE EXAMPLE Page A-2 clr p move c, d0 ; D0 = &next key clr.a a move.b @d0, a ; A.A = key retsetc $100: clr.a a retclrc ;; Wait for a key to become pressed, then return scan code in ;; A.B. Uses C.A and B.B. kb_get: call kb_poll ; Get key, if any retcs ; Return if there was a key in the buffer ; No keys are down - enter light sleep move.5 event_mask, d0 move.p1 8, c move.1 c, @d0 rsi shutdn ; Go asleep move.p1 0xc, c ; Restore event mask move.1 c, @d0 jump kb_get ; Check buffer again APPENDIX B CUSTOM KEYBOARD I/O ROUTINES. ;********************************************************************* ;********************************************************************* ; This process is responsible for scanning the keyboard and updating ; the KEY_SCAN and NEW_KEYS data structures. The routines GET_KEYS ; and GET_NEW_KEYS are used to check these two data structures, ; respectively, for key presses. keybuf = ^x704EA ; keyboard buffer. DO_IN_4 = ^x1160 ; Does IN.4 C. KEY_SCAN: DATA.w 0 ; This data structure holds a shadow ; of the status of the keyboard. Each ; bit represents a key, although some ; bits are unused. NEW_KEYS: DATA.w 0 ; This word indicates whether any keys ; have been newly pressed. Each bit ; represents one key, although some ; bits are unused. GET_KEYS: ; This routine just looks in the key_scan data for ; the presense of any of the keys specified in the ; key mask in C.w. addr key_scan, d0 ; Address of key_scan data in D0. move.w @d0, a ; Get key status. and.w a, c ; Allow only the selected keys. ret GET_NEW_KEYS: ; This routine checks the new_key data for new key ; presses. The calling procedure must supply a key ; mask in C.w. Any new keys selected by the mask ; are cleared from the new_key data. A nonzero value ; is returned in C.w representing which of the selected ; keys were pressed. addr new_keys, d0 ; Trashes A. CUSTOM KEYBOARD I/O ROUTINES. Page B-2 move.w @d0, a ; Get new_keys. and.w a, c ; Allow only selected keys. not.w c ; Invert result and apply to A and.w c, a ; to clear selected keys from new_keys. move.w a, @d0 ; Write updated new_keys data. not.w c ; Invert result and apply to A ret ENABLE_INTR: ; This routine turns interrupt servicing back ; on after an interrupts were disabled by ; clearing ST<15>. This routine checks for ; pending interrupts and ensures that they are ; serviced. setb ^d15, st ; Reenable IO interrupt service. We want to ; do this so that any important interrupts in ; the system will still be recognized. brbc ^d14, st, $1 ; If ST<14> is set, then we have missed an ; interrupt while ST<15> was clear, so we ; need to re-enable interrupts. clrb ^d14, st ; Clear the pending interrupt flag. RSI ; Reset the keyboard interrupt state machine. $1: RETI ; Re-enable interrupt servicing. If there is ; an pending interrupt, it will be serviced ; now. PROCESS8_INIT: ; R0: Holds current key status. ; R1: Holds the old key status. ; R2: ; R3: ; R4: cur_process_start process8_code addr new_keys, d1 ; D1 holds pointer to new_keys data. addr key_scan, d0 ; Get address of the key scan bit pattern. clr.w a move.w a, r0 move.w a, r1 ; Initialize current and old key status. move.w a, @d0 ; Clear the keyboard scan pattern. move.w a, @d1 ; Clear new_keys. PROCESS8_CODE: intoff ; Shut off the system 1ms keyboard scan, again. ; This is necessary because ; if other interrupts have occurred, then the ; INTON instruction may have been executed in ; the interrupt handler, re-enabling keyboard ; interrupts. CUSTOM KEYBOARD I/O ROUTINES. Page B-3 move.5 keybuf+1, d0 ; Point at "put" pointer. move.1 @d0, c ; Get the "put" pointer. dec d0 ; Point at "get" pointer. move.1 c, @d0 ; Flush the keybuf just in case any characters ; have snuck into the keybuffer. addr key_scan, d0 ; Get address of the key scan bit pattern. ; First we just want to check to see if any keys are being pressed. clrb ^d15, st ; Shut off IO interrupt service for now. This ; needs to be done here so that any interrupts ; generated in the process of scanning the ; keyboard will be ignored by the system. ; Keyboard interrupts are possible because ; if other interrupts have occurred, then the ; INTON instruction may have been executed in ; the interrupt handler, re-enabling keyboard ; interrupts. clr.w c move.p3 ^x1FF, c ; OUT value for entire keyboard. out.x c call.a do_in_4 clr p brnz.w c, $1 ; If no keys are pressed we just fall through. clr.w c move.w c, @d0 ; Zero out the key_scan scoreboard. move.w r0, a ; Old "current" key status. move.w a, r1 ; R1 holds the old key status. move.w c, r0 ; R0 holds the current key status. jump.4 process8_sched ; Go reschedule the process. $1: ; Now we need to scan the keyboard and build up the key scoreboard. addr new_keys, d1 ; D1 holds pointer to new_keys data. clr.w a ; Temp storage for scanned keys. clr.w c move.p3 ^x100, c move.w c, d ; Leave a copy in D, where it will be shifted. $3: move.w d, c ; Put scan pattern into C. out.x c ; scan the next row. call.a do_in_4 ; Read the keys. add.w a, a ; Shift the data in A 6 bits to the left. add.w a, a add.w a, a add.w a, a add.w a, a add.w a, a OR.w C, A ; Add in the new keys to the data in A. srb.w d ; Shift to look at next keyboard row. brnz.w d, $3 ; Do another row if not done yet. move.w a, @d0 ; Write out the scanned keyboard data. move.w r0, c ; Old "current" key status. move.w c, r1 ; R1 holds the old key status. move.w a, r0 ; R0 holds the current key status. CUSTOM KEYBOARD I/O ROUTINES. Page B-4 not.w c ; Invert the old key status... and.w a, c ; and AND in the new keys. The resulting word ; in C indicates the "new" key presses. move.w @d1, a ; Get existing value of NEW_KEYS data. or.w a, c ; Add in the new keys without destroying any ; "new" keys that haven't been serviced yet. move.w c, @d1 ; D1 points to New_keys data. clr.w c move.p3 ^x1FF, c ; OUT value for entire keyboard. out.x c ; Just to reset things so when we exit to ; the RPL environment the system can still ; read the keyboard. This is needed because ; the 1ms keyboard does not reload the OUT ; register. PROCESS8_SCHED: call.4 save_context ; Save the process context. call.4 enable_intr ; Re-enables I/O by setting ST<15>=1 and ; executes an RETI instruction to allow ; interrupt servicing. CLR.W c MOVE.P3 ^xFF, c ; Reschedule this process to look at the ; keyboard roughly 30 times per second. call.4 resch_cur ; Reschedule the current process. PROCESS8_EXIT: JUMP.4 TO_SCHEDULER ; Return control to the scheduler. ; Thus ends process #8. END_DOC