#ifndef LINK_PS2_MOUSE_H #define LINK_PS2_MOUSE_H // -------------------------------------------------------------------------- // A PS/2 Mouse Adapter for the GBA. // -------------------------------------------------------------------------- // Usage: // - 1) Include this header in your main.cpp file and add: // LinkPS2Mouse* linkPS2Mouse = new LinkPS2Mouse(2); // - 2) Add the required interrupt service routines: // interrupt_init(); // interrupt_add(INTR_TIMER2, []() {}); // - 3) Initialize the library with: // linkPS2Mouse->activate(); // - 4) Get a report: // int data[3]; // linkPS2Mouse->report(data); // if ((data[0] & LINK_PS2_MOUSE_LEFT_CLICK) != 0) // ; // handle LEFT click // data[1] // X movement // data[2] // Y movement // -------------------------------------------------------------------------- // considerations: // - `activate()` or `report(...)` could freeze the system if not connected: // detecting timeouts using interrupts is the user's responsibility! // -------------------------------------------------------------------------- // ____________ // | Pinout | // |PS/2 --- GBA| // |------------| // |CLOCK -> SI | // |DATA --> SO | // |VCC ---> VCC| // |GND ---> GND| // -------------------------------------------------------------------------- #ifndef LINK_DEVELOPMENT #pragma GCC system_header #endif #include "_link_common.hpp" LINK_VERSION_TAG LINK_PS2_MOUSE_VERSION = "vLinkPS2Mouse/v8.0.3"; #define LINK_PS2_MOUSE_LEFT_CLICK 0b001 #define LINK_PS2_MOUSE_RIGHT_CLICK 0b010 #define LINK_PS2_MOUSE_MIDDLE_CLICK 0b100 /** * @brief A PS/2 Mouse Adapter for the GBA. */ class LinkPS2Mouse { private: using u32 = Link::u32; using u16 = Link::u16; using u8 = Link::u8; using s16 = signed short; 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 TO_TICKS = 17; LinkPS2Mouse() = delete; public: /** * @brief Constructs a new LinkPS2Mouse object. * @param waitTimerId `(0~3)` GBA Timer used for delays. */ explicit LinkPS2Mouse(u8 waitTimerId) { this->waitTimerId = waitTimerId; } /** * @brief Returns whether the library is active or not. */ [[nodiscard]] bool isActive() { return isEnabled; } /** * @brief Activates the library. * \warning Could freeze the system if nothing is connected! * \warning Detect timeouts using timer interrupts! */ void activate() { LINK_READ_TAG(LINK_PS2_MOUSE_VERSION); deactivate(); setClockHigh(); setDataHigh(); waitMilliseconds(20); write(0xFF); // send reset to the mouse readByte(); // read ack byte waitMilliseconds(20); // not sure why this needs the delay readByte(); // blank readByte(); // blank waitMilliseconds(20); // not sure why this needs the delay enableDataReporting(); // tell the mouse to start sending data waitMicroseconds(100); isEnabled = true; } /** * @brief Deactivates the library. */ void deactivate() { isEnabled = false; Link::_REG_RCNT = RCNT_GPIO; Link::_REG_SIOCNT = 0; } /** * @brief Fills the `data` int array with a report. The first int contains * *clicks* that you can check against the bitmasks * `LINK_PS2_MOUSE_LEFT_CLICK`, `LINK_PS2_MOUSE_MIDDLE_CLICK`, and * `LINK_PS2_MOUSE_RIGHT_CLICK`. The second int is the *X movement*, and the * third int is the *Y movement*. * @param data The array to be filled with data. */ void report(int (&data)[3]) { if (!isEnabled) { data[0] = 0; data[1] = 0; data[2] = 0; return; } write(0xEB); // send read data readByte(); // read ack byte data[0] = readByte(); // status bit data[1] = readMovementX(data[0]); // X movement packet data[2] = readMovementY(data[0]); // Y movement packet } private: u8 waitTimerId; volatile bool isEnabled = false; void enableDataReporting() { write(0xF4); // send enable data reporting readByte(); // read ack byte } s16 readMovementX(int status) { s16 x = readByte(); if ((status & (1 << 4)) != 0) // negative for (u32 i = 8; i < 16; i++) x |= 1 << i; return x; } s16 readMovementY(int status) { s16 y = readByte(); if ((status & (1 << 5)) != 0) // negative for (u32 i = 8; i < 16; i++) y |= 1 << i; return y; } void write(u8 data) { u8 parity = 1; setDataHigh(); setClockHigh(); waitMicroseconds(300); setClockLow(); waitMicroseconds(300); setDataLow(); waitMicroseconds(10); setClockHigh(); // (start bit) while (getClock()) ; // wait for mouse to take control of clock // clock is low, and we are clear to send data for (u32 i = 0; i < 8; i++) { if (data & 0x01) setDataHigh(); else setDataLow(); // wait for clock cycle while (!getClock()) ; while (getClock()) ; parity = parity ^ (data & 0x01); data = data >> 1; } // parity if (parity) setDataHigh(); else setDataLow(); while (!getClock()) ; while (getClock()) ; setDataHigh(); waitMicroseconds(50); while (getClock()) ; while (!getClock() || !getData()) ; // wait for mouse to switch modes setClockLow(); // put a hold on the incoming data. } u8 readByte() { u8 data = 0; setClockHigh(); setDataHigh(); waitMicroseconds(50); while (getClock()) ; while (!getClock()) ; // eat start bit for (u32 i = 0; i < 8; i++) { data |= readBit() << i; } readBit(); // parity bit readBit(); // stop bit should be 1 setClockLow(); return data; } volatile bool readBit() { while (getClock()) ; volatile bool bit = getData(); while (!getClock()) ; return bit; } void waitMilliseconds(u16 milliseconds) { u16 ticksOf1024Cycles = milliseconds * TO_TICKS; Link::_REG_TM[waitTimerId].start = -ticksOf1024Cycles; Link::_REG_TM[waitTimerId].cnt = Link::_TM_ENABLE | Link::_TM_IRQ | Link::_TM_FREQ_1024; Link::_IntrWait(1, Link::_TIMER_IRQ_IDS[waitTimerId]); Link::_REG_TM[waitTimerId].cnt = 0; } void waitMicroseconds(u16 microseconds) { u16 cycles = microseconds * TO_TICKS; Link::_REG_TM[waitTimerId].start = -cycles; Link::_REG_TM[waitTimerId].cnt = Link::_TM_ENABLE | Link::_TM_IRQ | Link::_TM_FREQ_1; Link::_IntrWait(1, Link::_TIMER_IRQ_IDS[waitTimerId]); Link::_REG_TM[waitTimerId].cnt = 0; } volatile bool getClock() { Link::_REG_RCNT &= ~SI_DIRECTION; return (Link::_REG_RCNT & SI_DATA) >> 0; } volatile bool getData() { Link::_REG_RCNT &= ~SO_DIRECTION; return (Link::_REG_RCNT & SO_DATA) >> 1; } void setClockHigh() { Link::_REG_RCNT |= SI_DIRECTION; Link::_REG_RCNT |= SI_DATA; } void setClockLow() { Link::_REG_RCNT |= SI_DIRECTION; Link::_REG_RCNT &= ~SI_DATA; } void setDataHigh() { Link::_REG_RCNT |= SO_DIRECTION; Link::_REG_RCNT |= SO_DATA; } void setDataLow() { Link::_REG_RCNT |= SO_DIRECTION; Link::_REG_RCNT &= ~SO_DATA; } }; extern LinkPS2Mouse* linkPS2Mouse; #endif // LINK_PS2_MOUSE_H