Geneve keyboard control
The Geneve uses a MF1 keyboard (PC-XT). Access to the keyboard is implemented as follows:
XOP interface in GeneveOS
|Joystick Y value (see TI, 8376)
|Joystick X value (see TI, 8377)
|if a new key has been pressed
The scan modes 0-5 are identical to those in the TI console. Scan mode 7 is specifically used to check the BREAK key (EQ set), and scan mode 8 returns the raw scan code in R1 MSB. If no key was pressed, R1 MSB contains 0xFF.
In MDOS mode, the address F118 is used to access the 8-bit shift register in the Gate array which stores the most recent scancode. In TI mode, the address is 8008. The address is not fully decoded; the rightmost three bits are ignored (F118 is the same as F11F).
The shift register is readable throughout the receive period of the scancode; this means you can watch the scancode coming in, shifted from left to right. This also means that you must make use of the available flag to find out whether the shift register contains the full scancode.
The bit at CRU address 0010 (or bit 8 at base 0) is set to 1 by default. The interrupt inputs at the 9901 are all negative logic, so we need a 0 to trigger an interrupt.
It is set to 0 when a scancode is available in the shift register at F118. The input is configured to raise an interrupt which will make MDOS read the scancode and put it in a buffer, unless interrupts are masked out (using LIMI).
The bit is reset to 1 with the next SBZ on the control bit 1.
Use the TB command to get the value:
CLR R12 TB 8 JEQ NONE JNE GOTIT
The Equal bit is set to the value of the CRU bit, so when the bit is 1, EQ is set to one.
Control bit 0
The bit at CRU address 1EF0 is directly routed to the keyboard clock line.
Accordingly, setting and resetting the bit has the following effect:
- SBZ: This pulls down the clock line, which disables the transfer of keycodes to the host. All following keycodes are buffered in the keyboard.
- SBO: Setting the clock line to 1 allows the keyboard to strobe the line, thus transmitting the keycodes.
There is another important effect: When the clock line is pulled down for more than 20 ms, the keyboard performs a reset. This means the states of CapsLock, NumLock, and ScrollLock are set to off, the keyboard performs a self-test, clears the scancode buffer, and sends a AA code to the computer. The AA code can only be transmitted after raising the clock line, so in order to properly reset the keyboard, we need to first set the line to 0, wait 20 ms, then raise to 1.
It follows that this bit cannot be used to suspend the keyboard for a longer time, as the keyboard will do a reset after 20 ms.
Control bit 0 can be changed in this way:
LI R12,>1EF0 SBO 0 SBZ 0
or, when using another base value
LI R12,>1EE0 SBO 8 SBZ 8
(Remember that the bit number is added twice on the base address.)
Control bit 1
The bit at CRU address 1EF2 has two functions:
- SBZ: The shift register is cleared and locked to 0. The available flag is reset (set to 1), the interrupt cleared, and the keyboard data line is released.
- SBO: The shift register is set to receive.
When the data line has been released, the keyboard starts to send the scancode (LSB first, with a leading 0 and a 1 (start bits)). When the shift register has received the full scancode, the data line is held low, while the available flag is asserted (set to 0) and the interrupt is triggered.
Scancode reception is done in the background, without program control. The shift register will accept a scan code at any time after the SBO. This means that the shift register should be checked whether it contains a scancode that has been received in the meantime before setting the control bit 1 to 0, since this will wipe the shift register.
Control bit 1 can be changed in this way:
LI R12,>1EF0 SBO 1 LI R12,>1EE0 SBZ 9
Writing a keyboard driver
MDOS offers XOP operations to query the keyboard which differs significantly from the keyboard of the TI-99/4A. If we are interested in a more low-level access, the following code shows how to get the scan codes.
Get a scancode
GETSC LIMI 0 // Do not disturb LOOP LI R12,>1EE0 // Base address 1EE0 SBZ 9 // Clear shift register, enable keyboard SBO 9 // Set shift register to receive mode CLR R12 // Base address 0000 WAIT TB 8 // Listen on the available flag JEQ WAIT // As long as it is 1 (EQ=1), do the loop. MOVB @>F118,R1 // Get the scan code from the shift register
R1 now has the scan code in its high byte. We are using the CRU base 1EE0, so control bit 0 is bit 8, and control bit 1 is bit 9. The SBZ 9 command releases the clock line of the keyboard so that the keyboard starts transmitting a scan code if there is one in its queue. With SBO 9 the Gate Array switches to receive mode and awaits the incoming bits. We should not waste time here: If the pause between the SBZ and the SBO exceeds 0.2 ms (about 600 CPU cycles) we miss some of the incoming bits, and the scan code is incorrect.
When all 8 bits have been assembled to a byte, this byte is made available via address F118. We know that there is a new byte by monitoring bit 8 (base 0000): The Gate Array pulls down this line, and as the 9901 input is configured as an interrupt input, it will trigger an interrupt and MDOS gets the key code itself. We therefore mask the interrupts away in order to do our own handling.
Note that scan codes are quite different from the keycodes of the TI:
- Some keys create two scancodes in sequence (like E048 for Up Arrow).
- Key releases have a separate scancode which is the key press code plus >80.
Two subsequent scancodes arrive at a 1.3 ms interval which means a maximum rate of 768 scancodes per second, each one having 10 bits (2 start bits, 8 data bits), so that means 7680 bits/second. (Note that a keystroke requires at least two scancodes; one for press, one for release.) Key repetition occurs with about 42 ms intervals, i.e. 23.5 characters per second.
Another observation: When an interrupt has been raised, the data line is held down automatically. This can be seen with multi-code keys (E01D for right Ctrl): You can wait a long time after receiving a scancode, and the keyboard is unable to continue sending.
Initialize the keyboard
INITKB LIMI 0 LI R12,>1EE0 SBO 9 // Enable shift register SBZ 8 // Disable keyboard LI R4,60 // Wait a moment WAIT DEC R4 JNE WAIT SBO 8 // Reset it CLR R12 WAIT0 TB 8 // Wait for incoming code JEQ WAIT0 LI R1,>AA00 CB @>F118,R1 JNE FAILED // If not AA, something went wrong
This is quite similar to the above. The SBO 9 line is not required if we are sure that the last operation on the control bit was not a SBZ. The normal case, as shown above, is to set the control bit to 0 only for short, directly followed by a SBO. If you replace the SBO 9 by a SBZ 9, the shift register will not be loaded with the return code.
Experiments showed that between the SBZ and the SBO a minimum delay time of around 0.17 ms should pass (about 500 cycles), or no reset will occur.
Another thing to consider is that there should be no intermediate scancodes (possibly from key releases). Before the SBZ one should try to poll remaining scancodes from the keyboard.