Teclado: Leyendo el HW, multiples pulsaciones simultaneas, redefine, Firmware vs. HW (C y ASM con SDCC)

Click here to see in English

En este tutorial vamos a ver como manejar el teclado y poder usar múltiples pulsaciones de tecla simultaneas para, por ejemplo, poder hacer un juego con dos jugadores moviéndose y disparando a la vez. También vamos a ver un caso practico de como se redefinirían los controles, para dejar al usuario usar los que prefiera.

En el tutorial anterior Snake: Minijuego con tiles, teclado y sprites (C y ASM con SDCC) vimos como usar el Firmware para leer si había alguna tecla pulsada (BB09 KM READ CHAR). Este método únicamente nos permite ver una pulsación a la vez, con lo que no nos vale. El hardware del Amstrad CPC es capaz de controlar el estado de pulsación de 80 teclas físicas (que incluyen teclado y joystick) para ello utiliza 80 bits (10 bytes), cuando el bit concreto está a 0 significa que la tecla está pulsada. La mejor manera de poder controlar todo el teclado es leer del hardware el estado de las 80 teclas a la vez y así poder consultarlo después tranquilamente según nos haga falta. Para leer el teclado vamos a usar ensamblador y gracias a CPCWiki (Programming:Keyboard scanning) lo tenemos fácil. Vamos a adaptar la rutina para que compile y funcione en SDCC, quedando de la siguiente manera:

#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;
}
////////////////////////////////////////////////////////////////////////

 

Cada vez que llamemos a la función ReadKeyboard se almacenará el estado de todo el teclado en el array aKeyboard. Pero, ¿como relacionamos estos 80 bits con las teclas/joystick que conocemos? Tan sencillo como consultar el manual de usuario:

CODES

 

Para poder identificar cada tecla en el código, nada mejor que hacernos una enumeración con todas ellas:

//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;

 

Y por si necesitamos mostrar en pantalla el carácter ASCII que corresponde con alguna tecla, nos hacemos un array con los códigos ASCII de todas ellas:

//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
};

 

Vamos a hacer un primer programa, que cada vez que se pulse una tecla muestre en pantalla el código de tecla, su carácter ASCII y que nos muestre el estado de los 80 bits con todo el teclado, el programa completo quedaría así:

////////////////////////////////////////////////////////////////////////
// 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]);
  }
}
////////////////////////////////////////////////////////////////////////

Como vemos, la función GetFirstKeyPressed recorre los 80 bits buscando la primera tecla pulsada (bit a 0) y el programa principal la muestra por pantalla. Ejemplo pulsando varias teclas al azar:

CPC

 

En el ejemplo anterior se buscaba la primera tecla pulsada, pero normalmente necesitaremos conocer el estado de pulsación de una tecla concreta, para ello nada más sencillo que la siguiente función:

////////////////////////////////////////////////////////////////////////
//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;
}
////////////////////////////////////////////////////////////////////////

De manera sencilla la función IsKeyPressed calcula el byte y bit que corresponde con la tecla que queremos consultar y comprueba si está pulsada o no.

 

Vamos a ver otro ejemplo ahora de como manejar los controles de un jugador (4 direcciones y dos disparos) y de permitir al usuario redefinirlos fácilmente. El código fuente completo quedaría así:

////////////////////////////////////////////////////////////////////////
// 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));
}
////////////////////////////////////////////////////////////////////////

El programa monitoriza el estado de pulsación de los controles actuales (por defecto cursores, z, x) y nos permite redefinirlos a nuestro gusto (pulsando la tecla r) así como salir del programa (pulsando la tecla Esc). Un ejemplo de ejecución sería el siguiente:

 

 

Vamos aplicar lo aprendido en un antiguo ejemplo de los tutoriales de Sprites, para mover con los cursores el sprite por la pantalla, pudiendo hacer diagonales gracias a manejar múltiples pulsaciones simultaneas. El código fuente completo quedaría así:

////////////////////////////////////////////////////////////////////////
// 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);
}
////////////////////////////////////////////////////////////////////////

Un ejemplo de ejecución sería el siguiente:

 

Firmware vs. HW

¿Existe alguna alternativa en el Firmware para comprobar estado de pulsación de una tecla sin tener que leer el HW directamente? Pues si, tenemos el comando KM TEST KEY (BB1E), que nos permite comprobar el estado de pulsación de una tecla física, usando como parámetro el identificador de la tecla, que corresponde (lógicamente) con el enum que hemos hecho anteriormente. Para utilizar este comando del firmware desde SDCC, nos hacemos una sencilla función:

////////////////////////////////////////////////////////////////////////
//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;
}
////////////////////////////////////////////////////////////////////////

 

Para probarlo y poder compararlo con la lectura directa del HW, vamos a modificar el programa Keyboard02 de tal manera que podamos conmutar entre que funcione mediante el firmware o mediante la lectura directa de Hw y que mida cuantas lecturas es capaz de manejar por segundo. El código fuente sería el siguiente:

////////////////////////////////////////////////////////////////////////
// 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);
}
////////////////////////////////////////////////////////////////////////

Si ejecutamos Keyboard04.dsk en un emulador, comprobamos que el programa funciona perfectamente, tanto usando el firmware como leyendo directamente del Hw, tambien vemos que cuando no tocamos las teclas (los printf son lentos y falsean la medida) la velocidad del firmware es aproximadamente un 25% más lenta respecto de la lectura directa del Hw del teclado, aunque supongo que si en vez de 9 teclas manejaramos pocas teclas, no habría diferencia:

Como vemos, los dos métodos son perfectamente validos, que cada uno use el que mejor le venga :-)

Podéis bajar un zip con todos ficheros (código fuente, bat's para compilar, binarios y dsk's) aquí: Keyboard.zip

 

www.CPCMania.com 2013