#ifndef LINK_PS2_KEYBOARD_H #define LINK_PS2_KEYBOARD_H // -------------------------------------------------------------------------- // A PS/2 Keyboard Adapter for the GBA. // -------------------------------------------------------------------------- // Usage: // - 1) Include this header in your main.cpp file and add: // LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 event) { // // handle event (check example scan codes below) // }); // - 2) Add the required interrupt service routines: (*) // irq_init(NULL); // irq_add(II_VBLANK, LINK_PS2_KEYBOARD_ISR_VBLANK); // irq_add(II_SERIAL, LINK_PS2_KEYBOARD_ISR_SERIAL); // - 3) Initialize the library with: // linkPS2Keyboard->activate(); // - 4) Handle events in the callback sent to LinkPS2Keyboard's constructor! // -------------------------------------------------------------------------- // (*1) libtonc's interrupt handler sometimes ignores interrupts due to a bug. // That causes packet loss. You REALLY want to use libugba's instead. // (see examples) // -------------------------------------------------------------------------- // (*2) The hardware is very sensitive to timing. Make sure that // `LINK_PS2_KEYBOARD_ISR_SERIAL()` is handled on time. That means: // Be careful with DMA usage (which stops the CPU), and write short // interrupt handlers (or activate nested interrupts by setting // `REG_IME=1` at the start of your handlers). // -------------------------------------------------------------------------- // ____________ // | Pinout | // |PS/2 --- GBA| // |------------| // |CLOCK -> SI | // |DATA --> SO | // |VCC ---> VCC| // |GND ---> GND| // -------------------------------------------------------------------------- #include "_link_common.hpp" static volatile char LINK_PS2_KEYBOARD_VERSION[] = "LinkPS2Keyboard/v7.0.0"; /** * @brief A PS/2 Keyboard Adapter for the GBA. */ class LinkPS2Keyboard { private: using u32 = unsigned int; using u16 = unsigned short; using u8 = unsigned char; static constexpr int RCNT_GPIO_AND_SI_IRQ = 0b1000000100000000; static constexpr int RCNT_GPIO = 0b1000000000000000; static constexpr int SI_DIRECTION = 0b1000000; static constexpr int SO_DIRECTION = 0b10000000; static constexpr int SI_DATA = 0b100; static constexpr int SO_DATA = 0b1000; static constexpr int TIMEOUT_FRAMES = 15; // (~250ms) LinkPS2Keyboard() = delete; public: using EventCallback = void (*)(u8 event); /** * @brief Constructs a new LinkPS2Keyboard object. * @param onEvent Function pointer that will receive the scan codes (`u8`). * Check out `LINK_PS2_KEYBOARD_KEY` and `LINK_PS2_KEYBOARD_EVENT` for codes. */ explicit LinkPS2Keyboard(EventCallback onEvent) { this->onEvent = onEvent; } /** * @brief Returns whether the library is active or not. */ [[nodiscard]] bool isActive() { return isEnabled; } /** * @brief Activates the library. */ void activate() { deactivate(); Link::_REG_RCNT = RCNT_GPIO_AND_SI_IRQ; Link::_REG_SIOCNT = 0; bitcount = 0; incoming = 0; parityBit = 0; prevFrame = 0; frameCounter = 0; isEnabled = true; } /** * @brief Deactivates the library. */ void deactivate() { isEnabled = false; Link::_REG_RCNT = RCNT_GPIO; Link::_REG_SIOCNT = 0; } /** * @brief This method is called by the VBLANK interrupt handler. * \warning This is internal API! */ void _onVBlank() { frameCounter++; } /** * @brief This method is called by the SERIAL interrupt handler. * \warning This is internal API! */ void _onSerial() { if (!isEnabled) return; u8 val = (Link::_REG_RCNT & SO_DATA) != 0; u32 nowFrame = frameCounter; if (nowFrame - prevFrame > TIMEOUT_FRAMES) { bitcount = 0; incoming = 0; parityBit = 0; } prevFrame = nowFrame; if (bitcount == 0 && val == 0) { // start bit detected // start bit is always 0, so only proceed if val is 0 bitcount++; } else if (bitcount >= 1 && bitcount <= 8) { // data bits incoming |= (val << (bitcount - 1)); bitcount++; } else if (bitcount == 9) { // parity bit // store parity bit for later check parityBit = val; bitcount++; } else if (bitcount == 10) { // stop bit if (val == 1) { // stop bit should be 1 // calculate parity (including the stored parity bit from previous IRQ) u8 parity = 0; for (u8 i = 0; i < 8; i++) parity += (incoming >> i) & 1; parity += parityBit; if (parity % 2 != 0) // odd parity as expected onEvent(incoming); } bitcount = 0; incoming = 0; parityBit = 0; } } private: bool isEnabled = false; u8 bitcount = 0; u8 incoming = 0; u8 parityBit = 0; u32 prevFrame = 0; u32 frameCounter = 0; EventCallback onEvent; }; extern LinkPS2Keyboard* linkPS2Keyboard; /** * @brief VBLANK interrupt handler. */ inline void LINK_PS2_KEYBOARD_ISR_VBLANK() { if (!linkPS2Keyboard->isActive()) return; linkPS2Keyboard->_onVBlank(); } /** * @brief SERIAL interrupt handler. */ inline void LINK_PS2_KEYBOARD_ISR_SERIAL() { linkPS2Keyboard->_onSerial(); } /** * @brief Key Scan Code list. */ enum LINK_PS2_KEYBOARD_KEY { ESC = 118, F1 = 5, F2 = 6, F3 = 4, F4 = 12, F5 = 3, F6 = 11, F7 = 131, F8 = 10, F9 = 1, F10 = 9, F11 = 120, F12 = 7, BACKSPACE = 102, TAB = 13, ENTER = 90, SHIFT_L = 18, SHIFT_R = 89, SUPER = 97, CTRL_L = 20, SPECIAL_CTRL_R = 224 + 20, ALT_L = 17, SPECIAL_ALT_R = 224 + 17, SPACE = 41, CAPS_LOCK = 88, NUM_LOCK = 119, SCROLL_LOCK = 126, SPECIAL_INSERT = 224 + 112, SPECIAL_DELETE = 224 + 113, SPECIAL_HOME = 224 + 108, SPECIAL_END = 224 + 105, SPECIAL_PAGE_UP = 224 + 125, SPECIAL_PAGE_DOWN = 224 + 122, SPECIAL_UP = 224 + 117, SPECIAL_DOWN = 224 + 114, SPECIAL_LEFT = 224 + 107, SPECIAL_RIGHT = 224 + 116, A = 28, B = 50, C = 33, D = 35, E = 36, F = 43, G = 52, H = 51, I = 67, J = 59, K = 66, L = 75, M = 58, N = 49, O = 68, P = 77, Q = 21, R = 45, S = 27, T = 44, U = 60, V = 42, W = 29, X = 34, Y = 53, Z = 26, NUMPAD_0 = 112, NUMPAD_1 = 105, NUMPAD_2 = 114, NUMPAD_3 = 122, NUMPAD_4 = 107, NUMPAD_5 = 115, NUMPAD_6 = 116, NUMPAD_7 = 108, NUMPAD_8 = 117, NUMPAD_9 = 125, NUMPAD_PLUS = 121, NUMPAD_MINUS = 123, SPECIAL_NUMPAD_ENTER = 224 + 90, NUMPAD_DOT = 113, NUMPAD_ASTERISK = 124, NUMPAD_SLASH = 74 }; /** * @brief Event Scan Code list. */ enum LINK_PS2_KEYBOARD_EVENT { SELF_TEST_PASSED = 0xAA, // Triggered when hot-plugging the keyboard RELEASE = 240, // Triggered before each key release SPECIAL = 224 // Triggered before special keys }; #endif // LINK_PS2_KEYBOARD_H