diff --git a/README.md b/README.md index 48798e3..2c85c9a 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,390 @@ ### EMARS 4.7 * Switched to Gradle build system with Kotlin support -* Added `--open` option to open a file on start +* Added `--open` command-line option to open a file on start +* Added **Keyboard++**: Enhanced keyboard events +* Refactored many classes in Kotlin ## FAQ **How to run:** Install Java JDK >11 and double-click on the jar. **How to compile:** `gradle shadowJar` +## Keyboard++ + +![Demo](img/bitmap_display_plus_plus.png) + +Mars' MMIO Keyboard can only send one key event at any given time, and key repeats are handled by the operating system running Java, which is very bad for controlling a game because of the delay and inconsistency introduced. + +So I developed the enhanced **Keyboard++** with additional features: + +* **14 simultaneous key events!** +(MARS 4.5 can read only 1 key at a time) +* **Fine control of separate key-down and key-up events** +(MARS 4.5 can only read keys typed) +* **Use 2-byte key code** instead of 1-byte ASCII char +(Non-character keys like arrows & F1-F12 can now be read) +* **Filter out system key repeats** +(Pressing a key for longer will not repeat any event) +* **Use "clear byte"** to indicate if the events should be cleared +(MARS 4.5 clears the key byte whenever it is read) + +### Specification + +![Specification](img/img.png) + +From the initial address, the first 4 words (16 bytes) represents key-down events (when a key is first pressed). The next 4 words (16 bytes) represent key-up events. + +For each event type, the 0x0 byte stores an unsigned integer `n`, ranging from `0` to `7`, representing how many key events are pooled. + +The 0x1 byte tells the keyboard handler whether the events are read by your MIPS program and should be reset. If 1 is written to the 1st byte, then all keys in the current event cycle will be cleared to 0. + +From the 0x2 byte to the 0xF byte stores `n` unsigned 2-byte key codes received, in chronological order. (e.g. the 0x2 half-word stores the first key that's pressed after the last reset). The keycodes are the same with the keycodes used in Java's KeyEvent class. I've attached a list of keycodes extracted from the class below. + +### Sample + +Here's a sample MIPS code that reads events from Keyboard++: + +
+ Example MIPS Program using Keyboard++ + +```asm +.data + +# The addresses of Keyboard++ +ADDR_KEY_DOWN: .word 0xffff0010 +ADDR_KEY_UP: .word 0xffff0020 + +# Example variables +PAUSED: .word 1 +KEY_MOVE_DIRECTION: .byte 0 + +.text + +# """ +# read_keyboard() +# +# Listen to keyboard events +# """ +read_keyboard: + + # Save items onto the stack: ra, s0, s1 + sw $ra, -4($sp) + sw $s0, -8($sp) + sw $s1, -12($sp) + addi $sp, $sp, -12 + + # 1. Check for key down event + lw $s0, ADDR_KEY_DOWN + lbu $s1, 0($s0) + beq $s1, 0, key_down_none + + # In a loop, read the keys that are down (pressed) + # while (s1 > 0) + while_1_0: + sub $t9, $s1, $zero + blez $t9, while_done_1_0 + # { + # Move offset to the next half-word + addiu $s0, $s0, 2 + + # Read half-word keycode + lhu $t2, 0($s0) + + # if (t2 == 0x51): Q, quit + bne $t2, 0x51, else_2_0 + li $v0, 10 + syscall + else_2_0: + + # if (t2 == 0x20): Space, lauch ball + bne $t2, 0x20, else_2_2 + # if (PAUSED == 1) + lw $t9, PAUSED + bne $t9, 1, else_2_2 + jal launch + else_2_2: + + # if (t2 == 0x25): Left arrow, set flag that we're moving left + bne $t2, 0x25, else_2_3 + li $t9, -1 + sw $t9, KEY_MOVE_DIRECTION + else_2_3: + + # if (t2 == 0x27): Right arrow, set flag that we're moving right + bne $t2, 0x27, else_2_4 + li $t9, 1 + sw $t9, KEY_MOVE_DIRECTION + else_2_4: + + # Next iteration + addi $s1, $s1, -1 + # } + j while_1_0 + while_done_1_0: + + # When we're done, write 1 to offset 1 to clear events + lw $s0, ADDR_KEY_DOWN + li $s1, 1 + sb $s1, 1($s0) + + key_down_none: + + # 2. Check for key up event + lw $s0, ADDR_KEY_UP + lbu $s1, 0($s0) + beq $s1, 0, key_up_none + + # In a loop, read the keys that are up (released) + # while (s1 > 0) + while_1_1: + sub $t9, $s1, $zero + blez $t9, while_done_1_1 + # { + addiu $s0, $s0, 2 + lhu $t2, 0($s0) + + # Left arrow released, unset flag + # if (t2 == 0x25 && KEY_MOVE_DIRECTION == -1) + bne $t2, 0x25, else_2_5 + lw $t9, KEY_MOVE_DIRECTION + bne $t9, -1, else_2_5 + li $t9, 0 + sw $t9, KEY_MOVE_DIRECTION + else_2_5: + + # Right arrow released, unset flag + # if (t2 == 0x27 && KEY_MOVE_DIRECTION == 1) + bne $t2, 0x27, else_2_6 + lw $t9, KEY_MOVE_DIRECTION + bne $t9, 1, else_2_6 + li $t9, 0 + sw $t9, KEY_MOVE_DIRECTION + else_2_6: + + addi $s1, $s1, -1 + # } + j while_1_1 + while_done_1_1: + + # When we're done, write 1 to offset 1 to clear events + lw $s0, ADDR_KEY_UP + li $s1, 1 + sb $s1, 1($s0) + + key_up_none: + + # Retrieve items from the stack: ra, s0, s1 + lw $s1, 0($sp) + lw $s0, 4($sp) + lw $ra, 8($sp) + addi $sp, $sp, 12 + jr $ra +``` + +
+ +### List of Keycodes + +
+ List of all key codes + +```python +ENTER = 0xA +BACK_SPACE = 0x8 +VK_TAB = 0x9 + +# Regex used to extract keys: (?<=VK_)([A-Z0-9_]+) += +?([0-9A-Fa-fx]+)(?=;) +CANCEL = 0x03 +CLEAR = 0x0C +SHIFT = 0x10 +CONTROL = 0x11 +ALT = 0x12 +PAUSE = 0x13 +CAPS_LOCK = 0x14 +ESCAPE = 0x1B +SPACE = 0x20 +PAGE_UP = 0x21 +PAGE_DOWN = 0x22 +END = 0x23 +HOME = 0x24 +LEFT = 0x25 +UP = 0x26 +RIGHT = 0x27 +DOWN = 0x28 +COMMA = 0x2C +MINUS = 0x2D +PERIOD = 0x2E +SLASH = 0x2F +0 = 0x30 +1 = 0x31 +2 = 0x32 +3 = 0x33 +4 = 0x34 +5 = 0x35 +6 = 0x36 +7 = 0x37 +8 = 0x38 +9 = 0x39 +SEMICOLON = 0x3B +EQUALS = 0x3D +A = 0x41 +B = 0x42 +C = 0x43 +D = 0x44 +E = 0x45 +F = 0x46 +G = 0x47 +H = 0x48 +I = 0x49 +J = 0x4A +K = 0x4B +L = 0x4C +M = 0x4D +N = 0x4E +O = 0x4F +P = 0x50 +Q = 0x51 +R = 0x52 +S = 0x53 +T = 0x54 +U = 0x55 +V = 0x56 +W = 0x57 +X = 0x58 +Y = 0x59 +Z = 0x5A +OPEN_BRACKET = 0x5B +BACK_SLASH = 0x5C +CLOSE_BRACKET = 0x5D +NUMPAD0 = 0x60 +NUMPAD1 = 0x61 +NUMPAD2 = 0x62 +NUMPAD3 = 0x63 +NUMPAD4 = 0x64 +NUMPAD5 = 0x65 +NUMPAD6 = 0x66 +NUMPAD7 = 0x67 +NUMPAD8 = 0x68 +NUMPAD9 = 0x69 +MULTIPLY = 0x6A +ADD = 0x6B +SEPARATER = 0x6C +SUBTRACT = 0x6D +DECIMAL = 0x6E +DIVIDE = 0x6F +DELETE = 0x7F +NUM_LOCK = 0x90 +SCROLL_LOCK = 0x91 +F1 = 0x70 +F2 = 0x71 +F3 = 0x72 +F4 = 0x73 +F5 = 0x74 +F6 = 0x75 +F7 = 0x76 +F8 = 0x77 +F9 = 0x78 +F10 = 0x79 +F11 = 0x7A +F12 = 0x7B +F13 = 0xF000 +F14 = 0xF001 +F15 = 0xF002 +F16 = 0xF003 +F17 = 0xF004 +F18 = 0xF005 +F19 = 0xF006 +F20 = 0xF007 +F21 = 0xF008 +F22 = 0xF009 +F23 = 0xF00A +F24 = 0xF00B +PRINTSCREEN = 0x9A +INSERT = 0x9B +HELP = 0x9C +META = 0x9D +BACK_QUOTE = 0xC0 +QUOTE = 0xDE +KP_UP = 0xE0 +KP_DOWN = 0xE1 +KP_LEFT = 0xE2 +KP_RIGHT = 0xE3 +DEAD_GRAVE = 0x80 +DEAD_ACUTE = 0x81 +DEAD_CIRCUMFLEX = 0x82 +DEAD_TILDE = 0x83 +DEAD_MACRON = 0x84 +DEAD_BREVE = 0x85 +DEAD_ABOVEDOT = 0x86 +DEAD_DIAERESIS = 0x87 +DEAD_ABOVERING = 0x88 +DEAD_DOUBLEACUTE = 0x89 +DEAD_CARON = 0x8a +DEAD_CEDILLA = 0x8b +DEAD_OGONEK = 0x8c +DEAD_IOTA = 0x8d +DEAD_VOICED_SOUND = 0x8e +DEAD_SEMIVOICED_SOUND = 0x8f +AMPERSAND = 0x96 +ASTERISK = 0x97 +QUOTEDBL = 0x98 +LESS = 0x99 +GREATER = 0xa0 +BRACELEFT = 0xa1 +BRACERIGHT = 0xa2 +AT = 0x0200 +COLON = 0x0201 +CIRCUMFLEX = 0x0202 +DOLLAR = 0x0203 +EURO_SIGN = 0x0204 +EXCLAMATION_MARK = 0x0205 +INVERTED_EXCLAMATION_MARK = 0x0206 +LEFT_PARENTHESIS = 0x0207 +NUMBER_SIGN = 0x0208 +PLUS = 0x0209 +RIGHT_PARENTHESIS = 0x020A +UNDERSCORE = 0x020B +WINDOWS = 0x020C +CONTEXT_MENU = 0x020D +FINAL = 0x0018 +CONVERT = 0x001C +NONCONVERT = 0x001D +ACCEPT = 0x001E +MODECHANGE = 0x001F +KANA = 0x0015 +KANJI = 0x0019 +ALPHANUMERIC = 0x00F0 +KATAKANA = 0x00F1 +HIRAGANA = 0x00F2 +FULL_WIDTH = 0x00F3 +HALF_WIDTH = 0x00F4 +ROMAN_CHARACTERS = 0x00F5 +ALL_CANDIDATES = 0x0100 +PREVIOUS_CANDIDATE = 0x0101 +CODE_INPUT = 0x0102 +JAPANESE_KATAKANA = 0x0103 +JAPANESE_HIRAGANA = 0x0104 +JAPANESE_ROMAN = 0x0105 +KANA_LOCK = 0x0106 +INPUT_METHOD_ON_OFF = 0x0107 +CUT = 0xFFD1 +COPY = 0xFFCD +PASTE = 0xFFCF +UNDO = 0xFFCB +AGAIN = 0xFFC9 +FIND = 0xFFD0 +PROPS = 0xFFCA +STOP = 0xFFC8 +COMPOSE = 0xFF20 +ALT_GRAPH = 0xFF7E +BEGIN = 0xFF58 +UNDEFINED = 0x0 +``` + +
+ + ## License [MIT][2]. Chech the [LICENSE][3] file. All the credits go to the original developers. diff --git a/img/bitmap_display_plus_plus.png b/img/bitmap_display_plus_plus.png new file mode 100644 index 0000000..d1b9853 Binary files /dev/null and b/img/bitmap_display_plus_plus.png differ diff --git a/img/img.png b/img/img.png new file mode 100644 index 0000000..155f1b8 Binary files /dev/null and b/img/img.png differ