Teclado: Leyendo el HW, multiples pulsaciones simultaneas, redefine, Firmware vs. HW (C y ASM con SDCC) 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:
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:
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 |