NED – Project Update #2

This weekend, I set myself a task for writing a kick-ass keyboard routine. But why do you need to write a keyboard routine when there’s one sitting right there inside the ROM? There are a number of reasons.

  1. The ROM routine does not store multiple keys at the same time, so if you press one key before releasing another, it won’t register the second key. All good keyboard routines for typing should support mashing the keyboard.
  2. There are numerous key combinations that I would like to support such as Caps-Shift+Enter and Symbol-Shift+Enter. The ROM routine does not do this.
  3. I want to use Extended Mode (Caps-Shift + Symbol-Shift together) as a shift key so I will have to detect 3 key combos. Again, the ROM routine does not do this.

So how did I go about doing this? First of all, I tried to look for other people’s work to use before I contemplated writing it myself. Thanks to Robin Verhagen-Guest, Michael Ware, Jim Bagley and Allen Albright for helping me in that respects. But their routines either didn’t meet my requirements or they were not near their computers to send me them. I’m an impatient man and I wanted to do this over the weekend. However, their work did give some ideas of how to implement my routines.

Finally, I decided to write my own and this is my story of how I wrote a keyboard routine.

Step 0 – The plan

I sat down and thought about the process that would be required to write my dream keyboard routine.

First of all, I decided that I would have to quickly take a snapshot of the keyboard and be able to compare the current state to the previous state to be able to detect new keys that are pressed. If they were off previously and now they are on, the key was pressed.

Secondly, I would need to detect the shift keys and keep track of them so when I converted a key-press to a key-code, I would have the right information.

Thirdly, I would need a circular buffer to store all the keys detected and use that to return one key at a time.

Fourthly, I wanted to support Extended Mode as a shift and allow the user to signal when they have processed the next key in the buffer.

OK, not much, eh?

Step 1 – Scanning the keyboard

The whole keyboard routine runs during an interrupt (setting this up is beyond the scope of the blog). This means that every 50 or 60 times a second the routine is called. The first thing I need to do is grab a snapshot of the keyboard as quickly as possible, and then use this to work on.

The keyboard requires reading from 8 different I/O ports, with each port giving me information of 5 keys. Below is a table of the different ports and which keys they access:

Port NumberBit 0Bit 1Bit 2Bit 3Bit 4
$fefeCaps-ShiftZXCV
$fdfeASDFG
$fbfeQWERT
$f7fe12345
$effe09876
$dffePOIUY
$bffeEnterLKJH
$7ffeSpaceSym-ShiftMNB

Because there are 8 separate scans before the whole keyboard is read, it is possible that keys can be pressed between the scans. For example, the state “Caps-Shift” can be read before “Caps-Shift+0” is pressed (for Delete), and so only the “0” key is registered. For that reason, I decided to read the ports that refer to the shift keys last, namely ports $fefe and $7ffe.

Keyboard scanning routine

The above routine is quite simple. If you study the port numbers you will see that the low byte is always $fe, and the high byte is always a value with 7 binary ones and only 1 binary zero. The position of that 0 determines which row, out of the 8, you read. I just rotate the high byte to get to all the rows in turn. Before storing the state of a keyboard half-row, I copy the previously stored state to another location. At the end of this I have two snapshots of the keyboard. The current one and the previous one.

Step 2 – Updating the Shift Status

The keyboard has 40 keys, but their state doesn’t help with a text editor. I need to convert that to a key-code. The key-code is usually an ASCII code, but for some other key combinations are mapped to the unused area of 1-31. Below is a table of the first 32 key-codes are what they mapped to.

Key-CodeKey CombinationKey-CodeKey Combination
016Sym-Shift+W
1Sym-Shift+1 (Edit)17Sym-Shift+E
2Sym-Shift+2 (Caps Lock) 18Sym-Shift+I
3Sym-Shift+3 (True Video) 19
4Sym-Shift+4 (Inverse Video) 20
5Sym-Shift+5 (Left Cursor) 21
6Sym-Shift+6 (Down Cursor) 22
7Sym-Shift+7 (Up Cursor) 23
8Sym-Shift+8 (Right Cursor) 24
9Sym-Shift+9 (Graphics) 25
10Sym-Shift+10 (Delete) 26
1127Break (Caps+Space)
1228Sym+Space
13Enter29Caps+Enter
1430Sym+Enter
15Extended-Mode31Ext+Enter

Also any letter with Extended-Mode is mapped to $e1-$fa. These codes are chosen so that if you reset bit 7, you will get the ASCII lowercase letter. Finally, Extended-Mode 0-9 are mapped to $b0-$b9. Again, if you reset bit 7, you will get the ASCII numbers.

So there are four shift combinations we can have. Firstly, no shift key pressed; secondly, caps-shift pressed; thirdly, symbol-shift pressed; and finally, both caps-shift and symbol-shift pressed (extended-mode). So, to convert those keyboard states to a key-code, I require 4 tables, each 40 bytes big (1 byte per key-code). To do the conversion I choose the table I look at and index into it according to which of the 40 keys are pressed. The entry is the key-code.

Figuring which table to look at depending on the shift key states

Step 3 – The Circular Buffer

Each frame, it is possible to register multiple key presses and so I needed a buffer to store them all. I chose a circular buffer, which is a special buffer that can store a limited number of items, but you write to the end of it, but read at the beginning. In algorithm parlance, it’s a FIFO queue. First In, First Out.

Armed with the 2 keyboard snapshots (current and previous) and the conversion tables, I can detect a key-press, convert it to an ASCII character and store it on the buffer.

Filling the buffer

The circular buffer reuses the ZX Spectrum’s printer buffer ($5b00-$5bff). It is implemented by using 2 pointers: the read and write pointer. As you write to the buffer, the write pointer advances and wraps around (having a 256-byte buffer makes this easy). Similarly, as you read from the buffer, the read pointer advances and wraps around. If the write pointer ever meets the read pointer from behind, the buffer is full, we store no more keys in it. If the read buffer catches up to the write buffer, the buffer is empty, and we cannot read any more keys from it.

The circular buffer implementation

Step 4 – Returning the Key-Code

So we’ve scanned the keyboard, translated the raw key-presses to key-codes, and inserted them into a buffer. What now? Well, we need to return the next available key-code to the user. To do this, I take a leaf out of the ROM routine book. I have a memory area to store the key-code and some flags (called Key and KFlags respectively). Bit 0 of KFlags is set whenever a key is available for the user. The protocol, is to check this bit to see if a key is available and if so, read the key-code from Key, then reset bit 0 in KFlags. This tells the keyboard routine that it can fetch another key-code from the circular buffer and present it to the user.

And that’s it!

What’s missing?

There are a couple of things still missing from these routines that I want to add eventually. The most conspicuous absence is the lack of key repeat. That is, when you hold a key down it would be nice if the key repeated. The other less obvious feature that it lacks, is supporting the pressing and releasing of the Extended-Mode key. I’ve reserved a key-code for that (15) and may use it to support some features. It works the same way as the Alt key does in Windows. If you press Alt and a key you fire of a command, but if you just press Alt and release it, you can access the Windows menus.

This update was a bit technical so thanks for bearing with me. Until next time!

4 Comments

  • Rob Pearmain says:

    Great development diary. I like the approach of reading shift last to avoid missing it on first scan. The whole keyboard routing could be useful for multiple applications, text adventures, or command line in a graphical adventure game or similar.

    Looking forward to seeing your solution for the repeat code implementation.

  • Paul Land AKA Jaquen Dax says:

    Great write-up.
    Can I just ask what the difference is between …

    keyscan:
    and
    .no_caps

    Are they both labels still?

  • Matt Davies says:

    In most assemblers, usually the following colon (‘:’) is entirely optional. So both labels can have them or omit them. Also, in most assemblers, prefixing the label with a ‘.’ makes it a local label. That is, this label is only visible to code between the last non-local label (one without a ‘.’ prefix) and the next one. This allows authors to easily write loop code, for example, without making up new names all the time.

  • Paul Land says:

    Thanks Matt that does ring a bell with me.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.