Keyboard: Reading HW, multiple simultaneous keypresses, redefine, Firmware vs. HW (C & ASM with SDCC)

Pincha aquí para verlo en español

In this tutorial we will see how to handle the keyboard and to use multiple simultaneous keystrokes, for example, to make a game with two players moving and shooting at the same time. We will also see how to redefine the controls, to allow the user to use the keys that want.

In the previous tutorial Snake: Minigame with tile's, keyboard and sprites (C & ASM with SDCC) we saw how to use the firmware to read if there were any key pressed (BB09 KM READ CHAR). This method allows us to see only a keystroke at a time. The Amstrad CPC hardware is able to monitor the status of 80 physical keys (including keyboard and joystick) for this it uses 80 bits (10 bytes), when the particular bit is a 0 means that the key is pressed. The best way to control the entire keyboard is reading the hardware status of the 80 keys at once and so you can refer to it later. To read the keyboard we will use assembler and thanks to CPCWiki (Programming:Keyboard scanning) we have easy. The routine has to be adapted to compile and run in SDCC, being as follows:

#define KEYBOARD_HW_LINES 10

//Array to handle the 80 physical keys bit state
unsigned char aKeyboard[KEYBOARD_HW_LINES];


////////////////////////////////////////////////////////////////////////
//ReadKeyboard()
//Copied and adapted from the function shown in CPCWiki:
//http://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning
////////////////////////////////////////////////////////////////////////
void ReadKeyboard()
{
  __asm
    di
    ld hl, #_aKeyboard
    ld bc,#0xf782
    out (c),c
    ld bc,#0xf40e
    ld e,b
    out (c),c
    ld bc,#0xf6c0
    ld d,b
    out (c),c
    ld c,#0x00
    out (c),c
    ld bc,#0xf792
    out (c),c
    ld a,#0x40
    ld c,#0x4a
    _loop:
    ld b,d
    out (c),a
    ld b,e
    ini
    inc a
    cp c
    jr c,_loop
    ld bc,#0xf782
    out (c),c
    ei
  __endasm;
}
////////////////////////////////////////////////////////////////////////

 

Every time we call ReadKeyboard function, it will store the state of the entire keyboard in aKeyboard array. But how do we relate these 80 bits with the keys / joystick we know? As simple as consulting the user manual:

CODES

 

To identify each key in the code, nothing better than make us a enumeration with all of them:

//Enumeration to identify each physical key
typedef enum _eKey
{
  Key_CursorUp = 0,
  Key_CursorRight,
  Key_CursorDown,
  Key_F9,
  Key_F6,
  Key_F3,
  Key_Enter,
  Key_FDot,
  Key_CursorLeft, //8
  Key_Copy,
  Key_F7,
  Key_F8,
  Key_F5,
  Key_F1,
  Key_F2,
  Key_F0,
  Key_Clr, //16
  Key_BraceOpen,
  Key_Return,
  Key_BraceClose,
  Key_F4,
  Key_Shift,
  Key_BackSlash,
  Key_Control,
  Key_Caret, //24
  Key_Hyphen,
  Key_At,
  Key_P,
  Key_SemiColon,
  Key_Colon,
  Key_Slash,
  Key_Dot,
  Key_0, //32
  Key_9,
  Key_O,
  Key_I,
  Key_L,
  Key_K,
  Key_M,
  Key_Comma,
  Key_8, //40
  Key_7,
  Key_U,
  Key_Y,
  Key_H,
  Key_J,
  Key_N,
  Key_Space,
  Key_6_Joy2Up, //48
  Key_5_Joy2Down,
  Key_R_Joy2Left,
  Key_T_Joy2Right,
  Key_G_Joy2Fire,
  Key_F,
  Key_B,
  Key_V,
  Key_4, //56
  Key_3,
  Key_E,
  Key_W,
  Key_S,
  Key_D,
  Key_C,
  Key_X,
  Key_1, //64
  Key_2,
  Key_Esc,
  Key_Q,
  Key_Tab,
  Key_A,
  Key_CapsLock,
  Key_Z,
  Key_Joy1Up, //72
  Key_Joy1Down,
  Key_Joy1Left,
  Key_Joy1Right,
  Key_Joy1Fire1,
  Key_Joy1Fire2,
  Key_Joy1Fire3,
  Key_Del,
  Key_Max //80
}_ekey;

 

And if we need to display the ASCII character corresponding to a key, we do an array of all ASCII codes:

//Array to associate each physical key to an ASCII character
const unsigned char aKeyAscii[Key_Max] =
{ 
  0xF0, 0xF3, 0xF1, 0x80, 0x80, 0x80, 0x80, 0x80,
  0xF2, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x10, 0x5B, 0x0D, 0x5D, 0x80, 0x80, 0x5C, 0x80,
  0x5E, 0x2D, 0x40, 0x70, 0x3B, 0x3A, 0x2F, 0x2E,
  0x30, 0x39, 0x6F, 0x69, 0x6C, 0x6B, 0x6D, 0x2C,
  0x38, 0x37, 0x75, 0x79, 0x68, 0x6A, 0x6E, 0x20,
  0x36, 0x35, 0x72, 0x74, 0x67, 0x66, 0x62, 0x76,
  0x34, 0x33, 0x65, 0x77, 0x73, 0x64, 0x63, 0x78,
  0x31, 0x32, 0x80, 0x71, 0x09, 0x61, 0x80, 0x7A,
  0x0B, 0x0A, 0x08, 0x09, 0x58, 0x5A, 0x80, 0x7F
};

 

Let's do a first program, every time a key is pressed the program displays the key code, ASCII character and show us the state of the 80-bit entire keyboard, the entire program would look like:

////////////////////////////////////////////////////////////////////////
// Keyboard01.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//Enumeration to identify each physical key
typedef enum _eKey
{
  Key_CursorUp = 0,
  Key_CursorRight,
  Key_CursorDown,
  Key_F9,
  Key_F6,
  Key_F3,
  Key_Enter,
  Key_FDot,
  Key_CursorLeft, //8
  Key_Copy,
  Key_F7,
  Key_F8,
  Key_F5,
  Key_F1,
  Key_F2,
  Key_F0,
  Key_Clr, //16
  Key_BraceOpen,
  Key_Return,
  Key_BraceClose,
  Key_F4,
  Key_Shift,
  Key_BackSlash,
  Key_Control,
  Key_Caret, //24
  Key_Hyphen,
  Key_At,
  Key_P,
  Key_SemiColon,
  Key_Colon,
  Key_Slash,
  Key_Dot,
  Key_0, //32
  Key_9,
  Key_O,
  Key_I,
  Key_L,
  Key_K,
  Key_M,
  Key_Comma,
  Key_8, //40
  Key_7,
  Key_U,
  Key_Y,
  Key_H,
  Key_J,
  Key_N,
  Key_Space,
  Key_6_Joy2Up, //48
  Key_5_Joy2Down,
  Key_R_Joy2Left,
  Key_T_Joy2Right,
  Key_G_Joy2Fire,
  Key_F,
  Key_B,
  Key_V,
  Key_4, //56
  Key_3,
  Key_E,
  Key_W,
  Key_S,
  Key_D,
  Key_C,
  Key_X,
  Key_1, //64
  Key_2,
  Key_Esc,
  Key_Q,
  Key_Tab,
  Key_A,
  Key_CapsLock,
  Key_Z,
  Key_Joy1Up, //72
  Key_Joy1Down,
  Key_Joy1Left,
  Key_Joy1Right,
  Key_Joy1Fire1,
  Key_Joy1Fire2,
  Key_Joy1Fire3,
  Key_Del,
  Key_Max //80
}_ekey;

//Array to associate each physical key to an ASCII character
const unsigned char aKeyAscii[Key_Max] =
{ 
  0xF0, 0xF3, 0xF1, 0x80, 0x80, 0x80, 0x80, 0x80,
  0xF2, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x10, 0x5B, 0x0D, 0x5D, 0x80, 0x80, 0x5C, 0x80,
  0x5E, 0x2D, 0x40, 0x70, 0x3B, 0x3A, 0x2F, 0x2E,
  0x30, 0x39, 0x6F, 0x69, 0x6C, 0x6B, 0x6D, 0x2C,
  0x38, 0x37, 0x75, 0x79, 0x68, 0x6A, 0x6E, 0x20,
  0x36, 0x35, 0x72, 0x74, 0x67, 0x66, 0x62, 0x76,
  0x34, 0x33, 0x65, 0x77, 0x73, 0x64, 0x63, 0x78,
  0x31, 0x32, 0x80, 0x71, 0x09, 0x61, 0x80, 0x7A,
  0x0B, 0x0A, 0x08, 0x09, 0x58, 0x5A, 0x80, 0x7F
};

#define KEYBOARD_HW_LINES 10

//Array to handle the 80 physical keys bit state
unsigned char aKeyboard[KEYBOARD_HW_LINES];


////////////////////////////////////////////////////////////////////////
//ReadKeyboard()
//Copied and adapted from the function shown in CPCWiki:
//http://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning
////////////////////////////////////////////////////////////////////////
void ReadKeyboard()
{
  __asm
    di
    ld hl, #_aKeyboard
    ld bc,#0xf782
    out (c),c
    ld bc,#0xf40e
    ld e,b
    out (c),c
    ld bc,#0xf6c0
    ld d,b
    out (c),c
    ld c,#0x00
    out (c),c
    ld bc,#0xf792
    out (c),c
    ld a,#0x40
    ld c,#0x4a
    _loop:
    ld b,d
    out (c),a
    ld b,e
    ini
    inc a
    cp c
    jr c,_loop
    ld bc,#0xf782
    out (c),c
    ei
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetFirstKeyPressed
////////////////////////////////////////////////////////////////////////
enum _eKey GetFirstKeyPressed()
{
  unsigned char nKeyLine = 0;
  unsigned char nBit = 0;
  
  for(nKeyLine = 0; nKeyLine < KEYBOARD_HW_LINES; nKeyLine++)
  {
    for(nBit = 0; nBit < 8; nBit++)
    {
      if(((unsigned char)1 << nBit & aKeyboard[nKeyLine]) == 0)
        return (enum _eKey)(nKeyLine * 8 + nBit);
    }
  }
  
  return Key_Max;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//Main
////////////////////////////////////////////////////////////////////////
void main()
{
  enum _eKey eKey = Key_Max;

  while(1)
  {
    ReadKeyboard();
    eKey = GetFirstKeyPressed();
    
    if(eKey != Key_Max)
      printf("(%d, %c) %x %x %x %x %x %x %x %x %x %x\n\r", eKey, aKeyAscii[eKey],
              aKeyboard[0], aKeyboard[1], aKeyboard[2], aKeyboard[3], aKeyboard[4],
              aKeyboard[5], aKeyboard[6], aKeyboard[7], aKeyboard[8], aKeyboard[9]);
  }
}
////////////////////////////////////////////////////////////////////////

As we see, the function GetFirstKeyPressed check the 80 bit looking down the first key pressed (bit 0) and the main program displays the result on screen. Example pressing several keys at random:

CPC

 

In the above example the program looked for the first key was pressed, but usually we will need to know the status of a particular key, we can do it with the following function:

////////////////////////////////////////////////////////////////////////
//IsKeyPressed
////////////////////////////////////////////////////////////////////////
unsigned char IsKeyPressed(enum _eKey eKey)
{
  unsigned char nKeyLine = eKey / 8;
  unsigned char nBit = eKey % 8;
  
  if(((unsigned char)1 << nBit & aKeyboard[nKeyLine]) == 0)
    return 1;
  
  return 0;
}
////////////////////////////////////////////////////////////////////////

Easily IsKeyPressed function calculates the byte and bit that corresponds with the key and checks whether it is pressed or not.

 

Let's look at another example now of how to handle a player controls (4-way and two shots) and allow the user to redefine them easily. The complete source code would look like this:

////////////////////////////////////////////////////////////////////////
// Keyboard02.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//Enumeration to identify each physical key
typedef enum _eKey
{
  Key_CursorUp = 0,
  Key_CursorRight,
  Key_CursorDown,
  Key_F9,
  Key_F6,
  Key_F3,
  Key_Enter,
  Key_FDot,
  Key_CursorLeft, //8
  Key_Copy,
  Key_F7,
  Key_F8,
  Key_F5,
  Key_F1,
  Key_F2,
  Key_F0,
  Key_Clr, //16
  Key_BraceOpen,
  Key_Return,
  Key_BraceClose,
  Key_F4,
  Key_Shift,
  Key_BackSlash,
  Key_Control,
  Key_Caret, //24
  Key_Hyphen,
  Key_At,
  Key_P,
  Key_SemiColon,
  Key_Colon,
  Key_Slash,
  Key_Dot,
  Key_0, //32
  Key_9,
  Key_O,
  Key_I,
  Key_L,
  Key_K,
  Key_M,
  Key_Comma,
  Key_8, //40
  Key_7,
  Key_U,
  Key_Y,
  Key_H,
  Key_J,
  Key_N,
  Key_Space,
  Key_6_Joy2Up, //48
  Key_5_Joy2Down,
  Key_R_Joy2Left,
  Key_T_Joy2Right,
  Key_G_Joy2Fire,
  Key_F,
  Key_B,
  Key_V,
  Key_4, //56
  Key_3,
  Key_E,
  Key_W,
  Key_S,
  Key_D,
  Key_C,
  Key_X,
  Key_1, //64
  Key_2,
  Key_Esc,
  Key_Q,
  Key_Tab,
  Key_A,
  Key_CapsLock,
  Key_Z,
  Key_Joy1Up, //72
  Key_Joy1Down,
  Key_Joy1Left,
  Key_Joy1Right,
  Key_Joy1Fire1,
  Key_Joy1Fire2,
  Key_Joy1Fire3,
  Key_Del,
  Key_Max //80
}_ekey;

//Array to associate each physical key to an ASCII character
const unsigned char aKeyAscii[Key_Max] =
{ 
  0xF0, 0xF3, 0xF1, 0x80, 0x80, 0x80, 0x80, 0x80,
  0xF2, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x10, 0x5B, 0x0D, 0x5D, 0x80, 0x80, 0x5C, 0x80,
  0x5E, 0x2D, 0x40, 0x70, 0x3B, 0x3A, 0x2F, 0x2E,
  0x30, 0x39, 0x6F, 0x69, 0x6C, 0x6B, 0x6D, 0x2C,
  0x38, 0x37, 0x75, 0x79, 0x68, 0x6A, 0x6E, 0x20,
  0x36, 0x35, 0x72, 0x74, 0x67, 0x66, 0x62, 0x76,
  0x34, 0x33, 0x65, 0x77, 0x73, 0x64, 0x63, 0x78,
  0x31, 0x32, 0x80, 0x71, 0x09, 0x61, 0x80, 0x7A,
  0x0B, 0x0A, 0x08, 0x09, 0x58, 0x5A, 0x80, 0x7F
};

#define KEYBOARD_HW_LINES 10

//Array to handle the 80 physical keys bit state
unsigned char aKeyboard[KEYBOARD_HW_LINES];


////////////////////////////////////////////////////////////////////////
//ReadKeyboard()
//Copied and adapted from the function shown in CPCWiki:
//http://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning
////////////////////////////////////////////////////////////////////////
void ReadKeyboard()
{
  __asm
    di
    ld hl, #_aKeyboard
    ld bc,#0xf782
    out (c),c
    ld bc,#0xf40e
    ld e,b
    out (c),c
    ld bc,#0xf6c0
    ld d,b
    out (c),c
    ld c,#0x00
    out (c),c
    ld bc,#0xf792
    out (c),c
    ld a,#0x40
    ld c,#0x4a
    _loop:
    ld b,d
    out (c),a
    ld b,e
    ini
    inc a
    cp c
    jr c,_loop
    ld bc,#0xf782
    out (c),c
    ei
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetFirstKeyPressed
////////////////////////////////////////////////////////////////////////
enum _eKey GetFirstKeyPressed()
{
  unsigned char nKeyLine = 0;
  unsigned char nBit = 0;
  
  for(nKeyLine = 0; nKeyLine < KEYBOARD_HW_LINES; nKeyLine++)
  {
    for(nBit = 0; nBit < 8; nBit++)
    {
      if(((unsigned char)1 << nBit & aKeyboard[nKeyLine]) == 0)
        return (enum _eKey)(nKeyLine * 8 + nBit);
    }
  }
  
  return Key_Max;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//IsKeyPressed
////////////////////////////////////////////////////////////////////////
unsigned char IsKeyPressed(enum _eKey eKey)
{
  unsigned char nKeyLine = eKey / 8;
  unsigned char nBit = eKey % 8;
  
  if(((unsigned char)1 << nBit & aKeyboard[nKeyLine]) == 0)
    return 1;
  
  return 0;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetCursor
////////////////////////////////////////////////////////////////////////
void SetCursor(unsigned char nColum, unsigned char nLine)
{
  __asm
    ld h, 4 (ix)
    ld l, 5 (ix)
    call #0xBB75 ;TXT SET CURSOR
  __endasm;
}
////////////////////////////////////////////////////////////////////////

enum _eKey aPlayerKeys[6] = { Key_CursorUp, Key_CursorDown, Key_CursorLeft, Key_CursorRight, Key_Z, Key_X};
const char aPlayerKeyText[6][6] = { "Up   ", "Down ", "Left ", "Right", "Fire1", "Fire2"};

////////////////////////////////////////////////////////////////////////
//Redefine
////////////////////////////////////////////////////////////////////////
void Redefine()
{
  unsigned char nKey = 0;
  enum _eKey eKey = Key_Max;
    
  SetMode(1);
  
  for(nKey = 0; nKey < 6; nKey++)
  {
    SetCursor(1, 3 + nKey);
    printf("Press a key for %s", aPlayerKeyText[nKey]);
    
    do
    {
      ReadKeyboard();
      eKey = GetFirstKeyPressed();
    }
    while(eKey == Key_Max);
    
    aPlayerKeys[nKey] = eKey;

    do
    {
      ReadKeyboard();
      eKey = GetFirstKeyPressed();
    }
    while(eKey != Key_Max);
  }
}
////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////
//Main
////////////////////////////////////////////////////////////////////////
void main()
{
  unsigned char bPrintAll = 1;
  unsigned char aLastKeyState[6];

  //sdcc v3.2.0 fails to initialize global arrays not constant, so we initialize again in execution
  aPlayerKeys[0] = Key_CursorUp;
  aPlayerKeys[1] = Key_CursorDown;
  aPlayerKeys[2] = Key_CursorLeft;
  aPlayerKeys[3] = Key_CursorRight;
  aPlayerKeys[4] = Key_Z;
  aPlayerKeys[5] = Key_X;
 
  do
  {
    unsigned char nKey = 0;
    
    ReadKeyboard();

    if(bPrintAll)
    {
      SetMode(1);   
      SetCursor(1, 1);
      printf("Press Esc to exit, R to redefine");
    }
    
    for(nKey = 0; nKey < 6; nKey++)
    {
      if(bPrintAll || IsKeyPressed(aPlayerKeys[nKey]) != aLastKeyState[nKey])
      {
        aLastKeyState[nKey] = IsKeyPressed(aPlayerKeys[nKey]);
        SetCursor(1, 3 + nKey);
        printf("%s (%c): %s", aPlayerKeyText[nKey], aKeyAscii[aPlayerKeys[nKey]],
               aLastKeyState[nKey] ? "Pressed    ": "Not Pressed");
      }
    }
    
    bPrintAll = 0;
    
    if(IsKeyPressed(Key_R_Joy2Left))
    {
      bPrintAll = 1;
      Redefine();
    }
  }
  while(!IsKeyPressed(Key_Esc));
}
////////////////////////////////////////////////////////////////////////

The program monitors the status of current controls (by default cursors, z and x) and allows us to redefine them (by pressing r) and exit the program (by pressing Esc). An example of execution is as follows:

 

 

Finally we apply what we learned in an old example of Sprites tutorials, with the cursor keys to move the sprite around the screen, being able to handle diagonals thanks to simultaneous keypresses. The complete source code would look like this:

////////////////////////////////////////////////////////////////////////
// Keyboard03.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Nick.h"

#define MAX_X 79
#define MAX_Y 199


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
    __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < NUM_COLORS; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//PutSpriteMode0
////////////////////////////////////////////////////////////////////////
void PutSpriteMode0(unsigned char *pAddress, unsigned char nWidth, unsigned char nHeight, unsigned char *pSprite)
{
  __asm
    LD L, 4(IX) 
    LD H, 5(IX) 
    LD C, 6(IX) 
    LD B, 7(IX)            
    LD E, 8(IX) 
    LD D, 9(IX) 

    _loop_alto:
       PUSH BC
       LD B,C
       PUSH HL
    _loop_ancho:
       LD A,(DE)
       LD (HL),A
       INC DE
       INC HL
       DJNZ _loop_ancho
       POP HL
       LD A,H
       ADD #0x08
       LD H,A
       SUB #0xC0
       JP NC, _sig_linea
       LD BC, #0xC050
       ADD HL,BC
    _sig_linea:
       POP BC
       DJNZ _loop_alto
  __endasm;
}
////////////////////////////////////////////////////////////////////////


//Enumeration to identify each physical key
typedef enum _eKey
{
  Key_CursorUp = 0,
  Key_CursorRight,
  Key_CursorDown,
  Key_F9,
  Key_F6,
  Key_F3,
  Key_Enter,
  Key_FDot,
  Key_CursorLeft, //8
  Key_Copy,
  Key_F7,
  Key_F8,
  Key_F5,
  Key_F1,
  Key_F2,
  Key_F0,
  Key_Clr, //16
  Key_BraceOpen,
  Key_Return,
  Key_BraceClose,
  Key_F4,
  Key_Shift,
  Key_BackSlash,
  Key_Control,
  Key_Caret, //24
  Key_Hyphen,
  Key_At,
  Key_P,
  Key_SemiColon,
  Key_Colon,
  Key_Slash,
  Key_Dot,
  Key_0, //32
  Key_9,
  Key_O,
  Key_I,
  Key_L,
  Key_K,
  Key_M,
  Key_Comma,
  Key_8, //40
  Key_7,
  Key_U,
  Key_Y,
  Key_H,
  Key_J,
  Key_N,
  Key_Space,
  Key_6_Joy2Up, //48
  Key_5_Joy2Down,
  Key_R_Joy2Left,
  Key_T_Joy2Right,
  Key_G_Joy2Fire,
  Key_F,
  Key_B,
  Key_V,
  Key_4, //56
  Key_3,
  Key_E,
  Key_W,
  Key_S,
  Key_D,
  Key_C,
  Key_X,
  Key_1, //64
  Key_2,
  Key_Esc,
  Key_Q,
  Key_Tab,
  Key_A,
  Key_CapsLock,
  Key_Z,
  Key_Joy1Up, //72
  Key_Joy1Down,
  Key_Joy1Left,
  Key_Joy1Right,
  Key_Joy1Fire1,
  Key_Joy1Fire2,
  Key_Joy1Fire3,
  Key_Del,
  Key_Max //80
}_ekey;


#define KEYBOARD_HW_LINES 10

//Array to handle the 80 physical keys bit state
unsigned char aKeyboard[KEYBOARD_HW_LINES];


////////////////////////////////////////////////////////////////////////
//ReadKeyboard()
//Copied and adapted from the function shown in CPCWiki:
//http://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning
////////////////////////////////////////////////////////////////////////
void ReadKeyboard()
{
  __asm
    di
    ld hl, #_aKeyboard
    ld bc,#0xf782
    out (c),c
    ld bc,#0xf40e
    ld e,b
    out (c),c
    ld bc,#0xf6c0
    ld d,b
    out (c),c
    ld c,#0x00
    out (c),c
    ld bc,#0xf792
    out (c),c
    ld a,#0x40
    ld c,#0x4a
    _loop:
    ld b,d
    out (c),a
    ld b,e
    ini
    inc a
    cp c
    jr c,_loop
    ld bc,#0xf782
    out (c),c
    ei
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//IsKeyPressed
////////////////////////////////////////////////////////////////////////
unsigned char IsKeyPressed(enum _eKey eKey)
{
  unsigned char nKeyLine = eKey / 8;
  unsigned char nBit = eKey % 8;
  
  if(((unsigned char)1 << nBit & aKeyboard[nKeyLine]) == 0)
    return 1;
  
  return 0;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//Main
////////////////////////////////////////////////////////////////////////
void main()
{
  int nX = 40;
  int nY = 100;

  SetMode(0);
  SetPalette(NickPalette);
 
  do
  {
    ReadKeyboard();

    if(IsKeyPressed(Key_CursorUp))
      nY -= 2;

    if(IsKeyPressed(Key_CursorDown))
      nY += 2;

    if(IsKeyPressed(Key_CursorLeft))
      nX -= 1;
    
    if(IsKeyPressed(Key_CursorRight))
      nX += 1;

    if(nX <= 0)
      nX = 0;

    if(nY <= 0)
      nY = 0;
    
    if(nX >= (MAX_X - NICK_WIDTH))
      nX = MAX_X - NICK_WIDTH;

    if(nY >= (MAX_Y - NICK_HEIGHT))
      nY = MAX_Y - NICK_HEIGHT;
    
    //paint
    PutSpriteMode0((unsigned char *)(0xC000 + ((nY / 8u) * 80u) + ((nY % 8u) * 2048u) + nX), NICK_WIDTH, NICK_HEIGHT, NickSprite);
  }
  while(1);
}
////////////////////////////////////////////////////////////////////////

An example of execution is as follows:

 

Firmware vs. HW

Is there any Firmware alternative to check the status of a key without reading the HW directly? We have the command KM TEST KEY (BB1E), which allows us to check the status of a physical key, using as a parameter the identifier of the key that corresponds (logically) with the enum we've done before. To use this firmware command from SDCC, we do a simple function:

////////////////////////////////////////////////////////////////////////
//IsKeyPressedFW()
////////////////////////////////////////////////////////////////////////
char nKeyPressed;
unsigned char IsKeyPressedFW(unsigned char eKey)
{
  __asm
    LD HL, #_nKeyPressed
    LD (HL), #0
    LD A, 4 (IX)
    CALL #0xBB1E ;KM TEST KEY
    JP Z, _end_IsKeyPressed
    LD HL, #_nKeyPressed
    LD (HL), #1
    _end_IsKeyPressed:
  __endasm;
  
  return nKeyPressed;
}
////////////////////////////////////////////////////////////////////////

 

To test and compare firmware with the direct reading of keyboard HW, we will modify the program Keyboard02.c so that we can switch between work using the firmware or by direct reading and measuring how many readings can handle per second. The source code is as follows:

////////////////////////////////////////////////////////////////////////
// Keyboard04.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//Enumeration to identify each physical key
typedef enum _eKey
{
  Key_CursorUp = 0,
  Key_CursorRight,
  Key_CursorDown,
  Key_F9,
  Key_F6,
  Key_F3,
  Key_Enter,
  Key_FDot,
  Key_CursorLeft, //8
  Key_Copy,
  Key_F7,
  Key_F8,
  Key_F5,
  Key_F1,
  Key_F2,
  Key_F0,
  Key_Clr, //16
  Key_BraceOpen,
  Key_Return,
  Key_BraceClose,
  Key_F4,
  Key_Shift,
  Key_BackSlash,
  Key_Control,
  Key_Caret, //24
  Key_Hyphen,
  Key_At,
  Key_P,
  Key_SemiColon,
  Key_Colon,
  Key_Slash,
  Key_Dot,
  Key_0, //32
  Key_9,
  Key_O,
  Key_I,
  Key_L,
  Key_K,
  Key_M,
  Key_Comma,
  Key_8, //40
  Key_7,
  Key_U,
  Key_Y,
  Key_H,
  Key_J,
  Key_N,
  Key_Space,
  Key_6_Joy2Up, //48
  Key_5_Joy2Down,
  Key_R_Joy2Left,
  Key_T_Joy2Right,
  Key_G_Joy2Fire,
  Key_F,
  Key_B,
  Key_V,
  Key_4, //56
  Key_3,
  Key_E,
  Key_W,
  Key_S,
  Key_D,
  Key_C,
  Key_X,
  Key_1, //64
  Key_2,
  Key_Esc,
  Key_Q,
  Key_Tab,
  Key_A,
  Key_CapsLock,
  Key_Z,
  Key_Joy1Up, //72
  Key_Joy1Down,
  Key_Joy1Left,
  Key_Joy1Right,
  Key_Joy1Fire1,
  Key_Joy1Fire2,
  Key_Joy1Fire3,
  Key_Del,
  Key_Max //80
}_ekey;

//Array to associate each physical key to an ASCII character
const unsigned char aKeyAscii[Key_Max] =
{ 
  0xF0, 0xF3, 0xF1, 0x80, 0x80, 0x80, 0x80, 0x80,
  0xF2, 0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
  0x10, 0x5B, 0x0D, 0x5D, 0x80, 0x80, 0x5C, 0x80,
  0x5E, 0x2D, 0x40, 0x70, 0x3B, 0x3A, 0x2F, 0x2E,
  0x30, 0x39, 0x6F, 0x69, 0x6C, 0x6B, 0x6D, 0x2C,
  0x38, 0x37, 0x75, 0x79, 0x68, 0x6A, 0x6E, 0x20,
  0x36, 0x35, 0x72, 0x74, 0x67, 0x66, 0x62, 0x76,
  0x34, 0x33, 0x65, 0x77, 0x73, 0x64, 0x63, 0x78,
  0x31, 0x32, 0x80, 0x71, 0x09, 0x61, 0x80, 0x7A,
  0x0B, 0x0A, 0x08, 0x09, 0x58, 0x5A, 0x80, 0x7F
};

#define KEYBOARD_HW_LINES 10

//Array to handle the 80 physical keys bit state
unsigned char aKeyboard[KEYBOARD_HW_LINES];


////////////////////////////////////////////////////////////////////////
//ReadKeyboard()
//Copied and adapted from the function shown in CPCWiki:
//http://www.cpcwiki.eu/index.php/Programming:Keyboard_scanning
////////////////////////////////////////////////////////////////////////
void ReadKeyboard()
{
  __asm
    di
    ld hl, #_aKeyboard
    ld bc,#0xf782
    out (c),c
    ld bc,#0xf40e
    ld e,b
    out (c),c
    ld bc,#0xf6c0
    ld d,b
    out (c),c
    ld c,#0x00
    out (c),c
    ld bc,#0xf792
    out (c),c
    ld a,#0x40
    ld c,#0x4a
    _loop:
    ld b,d
    out (c),a
    ld b,e
    ini
    inc a
    cp c
    jr c,_loop
    ld bc,#0xf782
    out (c),c
    ei
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetFirstKeyPressed
////////////////////////////////////////////////////////////////////////
enum _eKey GetFirstKeyPressed()
{
  unsigned char nKeyLine = 0;
  unsigned char nBit = 0;
  
  for(nKeyLine = 0; nKeyLine < KEYBOARD_HW_LINES; nKeyLine++)
  {
    for(nBit = 0; nBit < 8; nBit++)
    {
      if(((unsigned char)1 << nBit & aKeyboard[nKeyLine]) == 0)
        return (enum _eKey)(nKeyLine * 8 + nBit);
    }
  }
  
  return Key_Max;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//IsKeyPressed
////////////////////////////////////////////////////////////////////////
unsigned char IsKeyPressed(enum _eKey eKey)
{
  unsigned char nKeyLine = eKey / 8;
  unsigned char nBit = eKey % 8;
  
  if(((unsigned char)1 << nBit & aKeyboard[nKeyLine]) == 0)
    return 1;
  
  return 0;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetCursor
////////////////////////////////////////////////////////////////////////
void SetCursor(unsigned char nColum, unsigned char nLine)
{
  __asm
    ld h, 4 (ix)
    ld l, 5 (ix)
    call #0xBB75 ;TXT SET CURSOR
  __endasm;
}
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
//IsKeyPressedFW()
////////////////////////////////////////////////////////////////////////
char nKeyPressed;
unsigned char IsKeyPressedFW(unsigned char eKey)
{
  __asm
    LD HL, #_nKeyPressed
    LD (HL), #0
    LD A, 4 (IX)
    CALL #0xBB1E ;KM TEST KEY
    JP Z, _end_IsKeyPressed
    LD HL, #_nKeyPressed
    LD (HL), #1
    _end_IsKeyPressed:
  __endasm;
  
  return nKeyPressed;
}
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
//GetTime()
////////////////////////////////////////////////////////////////////////
unsigned char char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////


enum _eKey aPlayerKeys[6] = { Key_CursorUp, Key_CursorDown, Key_CursorLeft, Key_CursorRight, Key_Z, Key_X};
const char aPlayerKeyText[6][6] = { "Up   ", "Down ", "Left ", "Right", "Fire1", "Fire2"};

////////////////////////////////////////////////////////////////////////
//Redefine
////////////////////////////////////////////////////////////////////////
void Redefine()
{
  unsigned char nKey = 0;
  enum _eKey eKey = Key_Max;
    
  SetMode(1);
  
  for(nKey = 0; nKey < 6; nKey++)
  {
    SetCursor(1, 3 + nKey);
    printf("Press a key for %s", aPlayerKeyText[nKey]);
    
    do
    {
      ReadKeyboard();
      eKey = GetFirstKeyPressed();
    }
    while(eKey == Key_Max);
    
    aPlayerKeys[nKey] = eKey;

    do
    {
      ReadKeyboard();
      eKey = GetFirstKeyPressed();
    }
    while(eKey != Key_Max);
  }
}
////////////////////////////////////////////////////////////////////////



////////////////////////////////////////////////////////////////////////
//Main
////////////////////////////////////////////////////////////////////////
void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char bPrintAll = 1;
  unsigned char nKey = 0;
  unsigned char aLastKeyState[6];
  unsigned char aActualKeyState[6];
  unsigned char bExit = 0;
  unsigned char bRedefine = 0;
  unsigned char bUseFW = 1;
  

  //sdcc v3.2.0 fails to initialize global arrays not constant, so we initialize again in execution
  aPlayerKeys[0] = Key_CursorUp;
  aPlayerKeys[1] = Key_CursorDown;
  aPlayerKeys[2] = Key_CursorLeft;
  aPlayerKeys[3] = Key_CursorRight;
  aPlayerKeys[4] = Key_Z;
  aPlayerKeys[5] = Key_X;
 
  do
  {
    //Reading keyboard, either using the HW or FW
    if(!bUseFW)
      ReadKeyboard();
    
    for(nKey = 0; nKey < 6; nKey++)
      aActualKeyState[nKey] = bUseFW ? IsKeyPressedFW(aPlayerKeys[nKey]) : IsKeyPressed(aPlayerKeys[nKey]);
    
    bExit = bUseFW ? IsKeyPressedFW(Key_Esc) : IsKeyPressed(Key_Esc);
    bRedefine = bUseFW ? IsKeyPressedFW(Key_R_Joy2Left) : IsKeyPressed(Key_R_Joy2Left);
      
    if(bUseFW ? IsKeyPressedFW(Key_S) : IsKeyPressed(Key_S))
    {
      bUseFW = !bUseFW;
      bPrintAll = 1;
    }
    //End of Reading keyboard

    if(bPrintAll)
    {
      SetMode(1);   
      SetCursor(1, 1);
      printf("Press Esc to exit, R to redefine and");
      SetCursor(1, 2);
      printf("S to switch between FW and HW");
      
      SetCursor(1, 14);
      printf("Reading the keyboard using %s", bUseFW ? "Firmware" : "Hardware");
    }

    for(nKey = 0; nKey < 6; nKey++)
    {
      if(bPrintAll || aActualKeyState[nKey] != aLastKeyState[nKey])
      {
        aLastKeyState[nKey] = aActualKeyState[nKey];
        SetCursor(1, 5 + nKey);
        printf("%s (%c): %s", aPlayerKeyText[nKey], aKeyAscii[aPlayerKeys[nKey]],
                              aLastKeyState[nKey] ? "Pressed    ": "Not Pressed");
      }
    }
    
    bPrintAll = 0;
    
    if(bRedefine)
    {
      bRedefine = 0;
      bPrintAll = 1;
      Redefine();
    }
    
        nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      SetCursor(10, 16);
      printf("%u keys/s  ", nFPS * 9); //we have nine key readings for loop

      nTimeLast = GetTime();
      nFPS = 0;
    }
  }
  while(!bExit);
}
////////////////////////////////////////////////////////////////////////

If we load Keyboard04.dsk on an emulator, we found that the program works perfectly using the firmware or reading directly from Hw, we also see that when we do not touch the keys (the printf are slow and distort the measure) firmware speed is about 25% slower than reading directly from HW keyboard, but I guess if instead of 9 keys were few keys, there would be no performance difference:

As we see, the two methods are perfectly valid, that each one use that suits you best :-)

 

You could download a zip with all files (source code, bat to compile, binary and dsk's) here: Keyboard.zip

 

www.CPCMania.com 2013