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:
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:
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. HWIs 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 |