← Flood Fill
  Keyboard handling (2)
Sun 18th February 2024   
Keyboard handling
 Part 1 Part 2 

Arrows and space bar are sufficient for most games, but sometimes you need to access the complete keyboard.

In this article we expand on the earlier project by adding support for the entire keyboard while keeping the possibility to check multiple simultaneous key-presses.

The Checklist

In the previous project all we had was a single global variable called gKey and a few defines allowing to check if any of the arrow keys, space bar, shift and comparison keys was being pressed.

This is obviously insufficient for a program if you want to be able to type characters, handle upper and lower case letters, etc...

At a minimum, what we will need is:
  • A way to convert keyboard codes into displayable characters like 'A', '7' or '&'
  • The possibility to access the two possible values of each key, like 'A' and 'a', or '1' and '!'
  • Support for key debouncing1
  • Being able to independently access the status of modifier keys like CTRL and FUNC
Let see how we can provide that.

The Matrix Expanded

As already mentioned, the previous example only needed a single 8bit memory location to keep the status of all the keys on a single row.

This new version of the code expands the ReadKeyboard routine to read the content of each of the 8 rows and stores them in a 8 bytes array, enough to store the ON/OFF status of all 64 possible keys.

So instead of
_gKey .dsb 1
we now use that:
_KeyMatrix .dsb 8
which allows us easily implement this little C function.
unsigned char IsPressed(unsigned char matrix_position)
{
unsigned char bitfield;
unsigned char bitkey;

bitkey=1 << (matrix_position&7);
bitfield=KeyMatrix[matrix_position>>3];
return bitfield & bitkey;
}
Assuming you know the location of a key on the matrix, you can easily query its status:
IsPressed(VKEY_LEFT_CONTROL)
IsPressed(VKEY_LEFT_SHIFT)
IsPressed(VKEY_RIGHT_SHIFT)
IsPressed(VKEY_FUNCTION)
As a reminder from the previous article, here is what the matrix structure looks like:

0 1 2 3 4 5 6 7
0 7 N 5 V 1 X 3
1 J T R F ESCAPE Q D
2 M 6 B 4 CONTROL Z 2 C
3 K 9 ; - \
4 SPACE <
,
>
.
UP LEFT SHIFT LEFT DOWN RIGHT
5 U I O P FUNCT DELETE ] [
6 Y H G E A S W
7 8 L 0 / RIGHT SHIFT RETURN =

From this table we can see that the Control key is located in the row number 4 and column number 2, which is the 4+8*2=20.

To simplify the access to these control keys, you will find a few defines in the header file:
#define VKEY_LEFT_CONTROL     20        // Control key (left one on PC)
#define VKEY_LEFT_SHIFT 36 // Left Shift key
#define VKEY_RIGHT_SHIFT 60 // Right Shift key
#define VKEY_FUNCTION 4 // Function key (right Control key on PC)
which were used in the code sample above.

True Lies

If you look at the actual code, you'll notice that it does not exactly match what I was describing, and instead you can see that:
_KeyMatrix .dsb 4
_KeyRowArrows .dsb 4
this is just an optimization trick that allows us to retain our original single byte check for the arrows:
extern unsigned char KeyMatrix[8];
extern unsigned char KeyRowArrows;
The C part of the code does not know anything about the actual physical layout of things in memory, all it cares about is that the symbols exist, and then it trusts the declaration to be valid.

All I did was basically to split the matrix in two so we could isolate the row with the arrows and the space bar into its own symbol for the linker

Out of Character

Explicitly testing for the state of CONTROL or SHIFT is probably fine, but doing that for all the other normal characters would be a massive pain, so there was a need for an easy way to fetch the actual ASCII2 code of a character so it could be printed out.

The simplest way to achieve that was to use a simple array of 64 bytes containing the character matching each specific location in the matrix:
_KeyAsciiUpper
.asc "7","N","5","V",KEY_RCTRL,"1","X","3"
.asc "J","T","R","F",0,KEY_ESCAPE,"Q","D"
.asc "M","6","B","4",KEY_LCTRL,"Z","2","C"
.asc "K","9",59,"-",0,0,92,39
.asc " ",",",".",KEY_UP,KEY_LSHIFT,KEY_LEFT,KEY_DOWN,KEY_RIGHT
.asc "U","I","O","P",KEY_FUNCT,KEY_DELETE,"]","["
.asc "Y","H","G","E",0,"A","S","W"
.asc "8","L","0","\",KEY_RSHIFT,KEY_RETURN,0,"="

_KeyAsciiLower
.asc "&","n","%","v",KEY_RCTRL,"!","x","#"
.asc "j","t","r","f",0,KEY_ESCAPE,"q","d"
.asc "m","^","b","$",KEY_LCTRL,"z","@","c"
.asc "k","(",59,95,0,0,92,39
.asc " ","<",">",KEY_UP,KEY_LSHIFT,KEY_LEFT,KEY_DOWN,KEY_RIGHT
.asc "u","i","o","p",KEY_FUNCT,KEY_DELETE,"}","{"
.asc "y","h","g","e",0,"a","s","w"
.asc "*","l",")","|",KEY_RSHIFT,KEY_RETURN,0,"="
Since we also want to support SHIFTED and UNSHIFTED characters, we need a second table representing the second status of each of the keys.

Some of the keys are not displayable, so for these the implementation of the display is left for the users using the provided defines to identify each key:
#define KEY_LCTRL       1
#define KEY_RCTRL 2
#define KEY_LSHIFT 3
#define KEY_RSHIFT 4
#define KEY_FUNCT 5

#define KEY_LEFT 8
#define KEY_RIGHT 9
#define KEY_DOWN 10
#define KEY_UP 11

#define KEY_RETURN 13
#define KEY_ESCAPE 27
An implementation example is provided in the C code:
// Given an ASCII code, returns a displayable string representing the key
char* GetKeyName(unsigned char asciiCode)
{
if (!asciiCode)
{
// Nothing pressed
return "<none>";
}
else
if ( (asciiCode>32) && (asciiCode<127) )
{
// Displayable keys (letters, numbers, ...)
static char buffer[2];
buffer[0]=asciiCode;
buffer[1]=0;
return buffer;
}
else
{
switch (asciiCode)
{
// Modifier keys
case KEY_LCTRL: return "LEFT CTRL";
case KEY_RCTRL: return "RIGHT CTRL";
case KEY_LSHIFT: return "LEFT SHIFT";
case KEY_RSHIFT: return "RIGHT SHIFT";
case KEY_FUNCT: return "FUNCTION";

// Arrow keys
case KEY_LEFT: return "LEFT ARROW";
case KEY_RIGHT: return "RIGHT ARROW";
case KEY_DOWN: return "DOWN ARROW";
case KEY_UP: return "UP ARROW";

// Special keys
case KEY_RETURN: return "RETURN";
case KEY_ESCAPE: return "ESCAPE";
case KEY_SPACE: return "SPACE";
case KEY_DELETE: return "DELETE";
}
}
return "<unknown>";
}

The Last Shift

The user can specify the "CAPS LOCK" status (the familiar CAPS on the top right of the Oric BASIC screen) by setting the value of the KeyCapsLock variable to either 0 or 1.

The ReadKey subroutine will then automatically translate the character for you by checking if any of the Shift keys are pressed, as well as the KeyCapsLock variable for letters.

That's about it: "It just works".

For practical reasons, I would suggest to stick with the CTRL-T standard since Oric users are already using this keyboard combination when interacting with the BASIC interpreter.

Text for You

In order to be able to type some text, a new feature was required: Debouncing, which is why the header file contains two ReadKey variants:
extern unsigned char ReadKey();
extern unsigned char ReadKeyNoBounce();
The first one reads a key (single press, but repeating) and returns its ASCII value.

The second one does the same thing, but it returns 0 if the same key is being pressed and has not been released in the mean time.

Using this second function, we can implement a very basic text editor:
char EditField[36*4];
char EditFieldSize=0;

void DisplayInputField()
{
int y;
char* screen;
char* buffer;
buffer=EditField;
screen=(char*)(0xbb80+40*24);
for (y=0;y<4;y++)
{
screen[0] = 16+7;
screen[1] = 0;
memcpy(screen+2,buffer,36);
buffer+=36;
screen+=40;
}
}

void UpdateInputField()
{
char key;

// Check if we have new keys, and do something about it
key = ReadKeyNoBounce();
if (key)
{
if (key==KEY_DELETE)
{
if (EditFieldSize>0)
{
EditFieldSize--;
EditField[EditFieldSize]=' ';
}
}
else
if ( (key>=32) && (key<128) )
{
if ( ( (key=='T') || (key=='t') ) && IsPressed(VKEY_LEFT_CONTROL) )
{
KeyCapsLock=!KeyCapsLock;
}
else
{
EditField[EditFieldSize]=key;
EditFieldSize++;
}
}
DisplayInputField();
}
}

The code is quite simple:
  • The editing is done in a buffer and copied to the screen
  • We call ReadKeyNoBounce to check if there is a new relevant key press
  • If DELETE is pressed, we remove the character from the edit field
  • If it's a displayable character we add it to the buffer...
  • ...except if it was CTRL+T in which case we toggle Caps Lock
  • And finally the buffer is copied to the screen
This is obviously just an example, you could totally edit the screen directly if you wanted.

The Test

You can experiment with the test program to see how all the features are implemented:
  • Like in the previous program the status of the center row is displayed in detail.
  • The matrix is shown on the right with the status of every single key
  • Pressing any of the shift keys shows the second set of characters in the matrix
  • Using CTRL-T toggle the Caps Lock mode on or off
  • An edit field at the bottom allows you to type and delete characters

Complete Keyboard Matrix Read demo
Complete Keyboard Matrix Read demo

The complete source code is available on the source code repository.

And here is the pre-build binary in TAP format
Feel free to comment or ask questions!



Keyboard handling
 Part 1 Part 2 



1. Debouncing helps avoiding inputs repeating multiple times, so if you press 'A' you don't type 'AAAAAAAA...'
2. See ASCII on Wikipedia
comments powered by Disqus

Coverity Scan Build Status