diff --git a/README.md b/README.md index 4058951..24c09d0 100644 --- a/README.md +++ b/README.md @@ -52,20 +52,14 @@ Cute Mouse Driver is part of FreeDos project and is free and open source. ## Jumper Settings There are two jumpers, which can be used to set different modes. Currently you -can choose between 2- and 3-button mouse modes and select so called remote and -streaming mode. Remote mode sends mouse data all the time and has a better -response times in current implementation. Streaming mode sends only data, when -mouse position or button state changes. Unfortunately, this mode is currently -not in a good shape. It has a higher delay and can result in some timing -issues. However, it is interesting to have it for experimental purpose. Default -values are when the jumpers are unset. - -JP1 | Setting |Description -----|---------|------------------------------ -1-2 | unset | 3-button Logitech mouse -1-2 | set | 2-button Microsoft mouse -3-4 | unset | remote mode -3-4 | set | streaming mode (experimental) +can choose between 2-button, 3-button, and wheel mouse modes. In the table below, O means open or unset, and X means closed or set. + +JP1 |JP2 |Description +-----|-----|------------------------------ + O | O | 3-button Logitech mouse + X | O | 2-button Microsoft mouse + O | X | Wheel mouse (cutemouse driver required) + X | X | Undefined (2-button mode will override) ## Bill of materials diff --git a/firmware/ps2adapter/.gitignore b/firmware/ps2adapter/.gitignore new file mode 100644 index 0000000..b9f3806 --- /dev/null +++ b/firmware/ps2adapter/.gitignore @@ -0,0 +1,2 @@ +.pio +.vscode diff --git a/firmware/ps2adapter/LEDMessenger.cpp b/firmware/ps2adapter/LEDMessenger.cpp new file mode 100644 index 0000000..76f0c97 --- /dev/null +++ b/firmware/ps2adapter/LEDMessenger.cpp @@ -0,0 +1,122 @@ +#include +#include "LEDMessenger.h" +#include "ProMini.h" + + +LEDMessenger::LEDMessenger() { + list = NULL; + length = 0; + m_timerResolution = 10; + m_nextMessageDelay = 0; +}; + +LEDMessenger::~LEDMessenger() { + while (list) { + node *tmp = list; + list = list->next; + delete tmp; + } +}; + +LEDMessenger* LEDMessenger::instance = NULL; + +void LEDMessenger::TimerStatic() { + LEDMessenger::instance->update(); +}; + +typedef void (*voidFuncPtr)(void); +static volatile voidFuncPtr intFunc = NULL; + +ISR(TIMER1_OVF_vect) +{ + TCNT1 = 45535; // Timer Preloading + if (intFunc != NULL) + intFunc(); +} + +void LEDMessenger::start() { + instance = this; + intFunc = TimerStatic; + LED_SETLOW(); + TCCR1A = 0; // Init Timer1A + TCCR1B = 0; // Init Timer1B + TCCR1B |= B00000010; // Prescaler = 64 + TCNT1 = 45535; // Timer Preloading + TIMSK1 |= B00000001; // Enable Timer Overflow Interrupt +}; + +void LEDMessenger::push(int delay, int repeat, int nextMessageDelay) { + node *newNode = new node; + newNode->item.state = HIGH; + newNode->item.stepDelay = delay; + newNode->item.curStepDelay = delay; + newNode->item.repeat = repeat; + newNode->item.nextMessageDelay = nextMessageDelay; + newNode->next = NULL; + if (list) { + node *tmp = list; + while (tmp->next) { + tmp = tmp->next; + } + tmp->next = newNode; + } else { + list = newNode; + } + length++; +}; + +void LEDMessenger::pop() { + if (list) { + node *tmp = list; + list = list->next; + delete tmp; + length--; + } +}; + +LEDMessenger::LEDMessage* LEDMessenger::get() { + if (list) { + return &(list->item); + } else { + return NULL; + }; +}; + +int LEDMessenger::size() { + return length; +}; + +void LEDMessenger::update() { + if (m_nextMessageDelay > 0) { + m_nextMessageDelay -= m_nextMessageDelay; + return; + } + + LEDMessenger::LEDMessage* pItem = get(); + if (pItem) { + LED_SET(pItem->state); + if (pItem->curStepDelay > 0) { + pItem->curStepDelay -= m_timerResolution; + } else { + pItem->curStepDelay = pItem->stepDelay; + pItem->state = HIGH - pItem->state; + // one full HIGH -> LOW -> HIGH transistion + if (pItem->state == HIGH) { + // continous blink + if (pItem->repeat < 0) { + // waiting message + if (size() > 1) { + m_nextMessageDelay = pItem->nextMessageDelay; + pop(); + } + return; + } + pItem->repeat--; + if (pItem->repeat <= 0) { + m_nextMessageDelay = pItem->nextMessageDelay; + pop(); + } + } + } + } +} diff --git a/firmware/ps2adapter/LEDMessenger.h b/firmware/ps2adapter/LEDMessenger.h new file mode 100644 index 0000000..bc8b61d --- /dev/null +++ b/firmware/ps2adapter/LEDMessenger.h @@ -0,0 +1,38 @@ +#pragma once + +class LEDMessenger +{ +public: + LEDMessenger(); + ~LEDMessenger(); + void start(); + void push(int delay, int repeat, int nextMessageDelay); + +private: + int m_timerResolution; + int m_nextMessageDelay; + static LEDMessenger *instance; + + struct LEDMessage { + byte state; + int repeat; + int stepDelay; + int curStepDelay; + int nextMessageDelay; + }; + + typedef struct node + { + LEDMessage item; + node *next; + } node; + + node *list; + int length; + + void update(); + static void TimerStatic(); + void pop(); + LEDMessage* get(); + int size(); +}; diff --git a/firmware/ps2adapter/ProMini.h b/firmware/ps2adapter/ProMini.h new file mode 100644 index 0000000..d8dabb5 --- /dev/null +++ b/firmware/ps2adapter/ProMini.h @@ -0,0 +1,74 @@ +#pragma once +/* + * Created by Jason Hill + * 2023/06/18 + * + * Updated by Peter Edwards + * 2024/01/02 + * + */ + +/**************************************************** + * The following defines assume default pinout + * of Arduino Pro Mini or similiar: + * + * PS2_CLOCK = 2; //PD2 - Port D, bit 2 + * PS2_DATA = 17;//PC3 - Port C, bit 3 + * + * RS232_RTS = 3; //PD3 - Port D, bit 3 + * RS232_TX = 4; //PD4 - Port D, bit 4 + * + * JP12 = 11; //PB3 - Port B, bit 3 + * JP24 = 12; //PB4 - Port B, bit 4 + * LED = 13; //PB5 - Port B, bit 5 + * + * The following defines Pin and Port addressing to + * more quickly set individual pins to their desired + * states. +*/ + +/****************************************************/ +/* PS/2 data pin operations */ +#define PS2_DATA_BIT (_BV(3)) +#define PS2_SETDATAHIGH() ( PORTC |= PS2_DATA_BIT ) +#define PS2_SETDATALOW() ( PORTC &= ~PS2_DATA_BIT ) +#define PS2_SETDATA(val) (val) ? PS2_SETDATAHIGH() : PS2_SETDATALOW() +#define PS2_DIRDATAIN() ( DDRC &= ~PS2_DATA_BIT ) +#define PS2_DIRDATAIN_UP() ( DDRC &= ~PS2_DATA_BIT ) +#define PS2_DIRDATAOUT() ( DDRC |= PS2_DATA_BIT ) +#define PS2_READDATA() ( (PINC &= PS2_DATA_BIT)>>3 ) + +#define PS2_CLOCK_BIT (_BV(2)) +#define PS2_SETCLOCKHIGH() ( PORTD |= PS2_CLOCK_BIT ) +#define PS2_SETCLOCKLOW() ( PORTD &= ~PS2_CLOCK_BIT) +#define PS2_SETCLOCK(val) (val) ? PS2_SETCLOCKHIGH() : PS2_SETCLOCKLOW() +#define PS2_DIRCLOCKIN() ( DDRD &= ~PS2_CLOCK_BIT ) +#define PS2_DIRCLOCKIN_UP() ( DDRD &= ~PS2_CLOCK_BIT ); PS2_SETCLOCKHIGH() +#define PS2_DIRCLOCKOUT() ( DDRD |= PS2_CLOCK_BIT ) +#define PS2_READCLOCK() ( (PIND &= PS2_CLOCK_BIT)>>2 ) + +/****************************************************/ +/* RS-232 pin operations */ +#define RS_SETTXHIGH() (PORTD |=0b00010000) +#define RS_SETTXLOW() (PORTD &=0b11101111) +#define RS_SETTX(val) (val) ? RS_SETTXHIGH() : RS_SETTXLOW() +#define RS_DIRTXOUT() (DDRD |=0b00010000) + +/****************************************************/ +/* JUMPER pin operations */ +#define JP12_SETHIGH() (PORTB |=0b00001000) +#define JP12_DIRIN() (DDRB &=0b11110111) +#define JP12_DIRIN_UP() (DDRB &=0b11110111); JP12_SETHIGH() +#define JP12_READ() ((PINB &=0b00001000)>>3) + +#define JP34_SETHIGH() (PORTB |=0b00010000) +#define JP34_DIRIN() (DDRB &=0b11101111) +#define JP34_DIRIN_UP() (DDRB &=0b11101111); JP34_SETHIGH() +#define JP34_READ() ((PINB &=0b00010000)>>4) + +/****************************************************/ +/* LED pin operations */ +#define LED_SETHIGH() (PORTB |=0b00100000) +#define LED_SETLOW() (PORTB &=0b11011111) +#define LED_SET(val) (val) ? LED_SETHIGH() : LED_SETLOW() +#define LED_DIROUT() (DDRB |=0b00100000) diff --git a/firmware/ps2adapter/Ps2Mouse.cpp b/firmware/ps2adapter/Ps2Mouse.cpp index a5b97ac..7d87751 100644 --- a/firmware/ps2adapter/Ps2Mouse.cpp +++ b/firmware/ps2adapter/Ps2Mouse.cpp @@ -1,22 +1,8 @@ +#include "ProMini.h" #include "Ps2Mouse.h" namespace { -struct Status { - - byte rightButton : 1; - byte middleButton : 1; - byte leftButton : 1; - byte na2 : 1; - byte scaling : 1; - byte dataReporting : 1; - byte remoteMode : 1; - byte na1 : 1; - - byte resolution; - byte sampleRate; -}; - struct Packet { byte leftButton : 1; @@ -30,28 +16,13 @@ struct Packet { byte xMovement; byte yMovement; -}; - -enum class Command { - disableScaling = 0xE6, - enableScaling = 0xE7, - setResolution = 0xE8, - statusRequest = 0xE9, - setStreamMode = 0xEA, - readData = 0xEB, - resetWrapMode = 0xEC, // Not implemented - setWrapMode = 0xEE, // Not implemented - reset = 0xFF, - setRemoteMode = 0xF0, - getDeviceId = 0xF2, // Not implemented - setSampleRate = 0xF3, - enableDataReporting = 0xF4, - disableDataReporting = 0xF5, - setDefaults = 0xF6, // Not implemented + byte wheelData; }; enum class Response { + isMouse = 0x00, + isWheelMouse = 0x03, selfTestPassed = 0xAA, ack = 0xFA, error = 0xFC, @@ -60,177 +31,113 @@ enum class Response { } // namespace -struct Ps2Mouse::Impl { - const Ps2Mouse& m_ref; +static Ps2Mouse* classPtr = NULL; - void sendBit(int value) const { - while (digitalRead(m_ref.m_clockPin) != LOW) {} - digitalWrite(m_ref.m_dataPin, value); - while (digitalRead(m_ref.m_clockPin) != HIGH) {} - } +Ps2Mouse* Ps2Mouse::instance() { - int recvBit() const { - while (digitalRead(m_ref.m_clockPin) != LOW) {} - auto result = digitalRead(m_ref.m_dataPin); - while (digitalRead(m_ref.m_clockPin) != HIGH) {} - return result; - } - - bool sendByte(byte value) const { - - // Inhibit communication - pinMode(m_ref.m_clockPin, OUTPUT); - digitalWrite(m_ref.m_clockPin, LOW); - delayMicroseconds(10); - - // Set start bit and release the clock - pinMode(m_ref.m_dataPin, OUTPUT); - digitalWrite(m_ref.m_dataPin, LOW); - pinMode(m_ref.m_clockPin, INPUT_PULLUP); - - // Send data bits - byte parity = 1; - for (auto i = 0; i < 8; i++) { - byte nextBit = (value >> i) & 0x01; - parity ^= nextBit; - sendBit(nextBit); - } - - // Send parity bit - sendBit(parity); - - // Send stop bit - sendBit(1); - - // Enter receive mode and wait for ACK bit - pinMode(m_ref.m_dataPin, INPUT); - return recvBit() == 0; + if (classPtr == NULL) { + classPtr = new Ps2Mouse(); } + return classPtr; +} - bool recvByte(byte& value) const { +Ps2Mouse::Ps2Mouse() +{ + + m_type = MouseType::threeButton; + m_mouseBits = 0; + m_bitCount = 0; + m_parityBit = 0; + m_bufferTail = 0; + m_bufferHead = 0; + m_bufferCount = 0; + memset(m_buffer, 0, 256); +} - // Enter receive mode - pinMode(m_ref.m_clockPin, INPUT); - pinMode(m_ref.m_dataPin, INPUT); +bool Ps2Mouse::reset() { + + byte reply; - // Receive start bit - if (recvBit() != 0) { + if (!sendCommand(Command::reset)) { return false; - } - - // Receive data bits - value = 0; - byte parity = 1; - for (int i = 0; i < 8; i++) { - byte nextBit = recvBit(); - value |= nextBit << i; - parity ^= nextBit; - } - - // Receive and check parity bit - recvBit(); // TODO check parity - - // Receive stop bit - recvBit(); - - return true; } - template - bool sendData(const T& data) const { - auto ptr = reinterpret_cast(&data); - for (auto i = 0; i < sizeof(data); i++) { - if (!sendByte(ptr[i])) { - return false; - } - } - return true; - } - - template - bool recvData(T& data) const { - auto ptr = reinterpret_cast(&data); - for (auto i = 0u; i < sizeof(data); i++) { - if (!recvByte(ptr[i])) { - return false; - } - } - return true; - } - - bool sendByteWithAck(byte value) const { - while (true) { - if (sendByte(value)) { - byte response; - if (recvByte(response)) { - if (response == static_cast(Response::resend)) { - continue; - } - return response == static_cast(Response::ack); - } - } + if (!getByte(reply) || reply != byte(Response::selfTestPassed)) { return false; - } - } - - bool sendCommand(Command command) const { - return sendByteWithAck(static_cast(command)); - } - - bool sendCommand(Command command, byte setting) const { - return sendCommand(command) && sendByteWithAck(setting); } - bool getStatus(Status& status) const { - return sendCommand(Command::statusRequest) && recvData(status); - } -}; - -Ps2Mouse::Ps2Mouse(int clockPin, int dataPin) - : m_clockPin(clockPin), m_dataPin(dataPin), m_stream(false) -{} - -bool Ps2Mouse::reset() const { - Impl impl{*this}; - if (!impl.sendCommand(Command::reset)) { + if (!getByte(reply) || reply != byte(Response::isMouse)) { return false; } - byte reply; - if (!impl.recvByte(reply) || reply != byte(Response::selfTestPassed)) { + // Determine if this is a wheel mouse + if (!sendCommand(Command::setSampleRate, 200) || + !sendCommand(Command::setSampleRate, 100) || + !sendCommand(Command::setSampleRate, 80) || + !sendCommand(Command::getDeviceId) || + !getByte(reply)) { return false; } - if (!impl.recvByte(reply) || reply != byte(Response::isMouse)) { - return false; + if (reply == byte(Response::isWheelMouse)) { + m_type = MouseType::wheelMouse; } - return disableStreaming() && impl.sendCommand(Command::enableDataReporting); + // switch on data reporting + return setReporting(true); +} + +bool Ps2Mouse::setReporting(bool enable) { + + return sendCommand(enable ? Command::enableDataReporting : Command::disableDataReporting); } -bool Ps2Mouse::enableStreaming() const { - return Impl{*this}.sendCommand(Command::setStreamMode); +bool Ps2Mouse::setStreamMode() { + + return (sendCommand(Command::setStreamMode)); } -bool Ps2Mouse::disableStreaming() const { - return Impl{*this}.sendCommand(Command::setRemoteMode); +bool Ps2Mouse::setRemoteMode() { + + return (sendCommand(Command::setRemoteMode)); } -bool Ps2Mouse::setScaling(bool flag) const { - return Impl{*this}.sendCommand(flag ? Command::enableScaling : Command::disableScaling); +bool Ps2Mouse::setScaling(bool flag) { + + setReporting(false); + bool res = sendCommand(flag ? Command::enableScaling : Command::disableScaling); + setReporting(true); + return res; } -bool Ps2Mouse::setResolution(byte resolution) const { - return Impl{*this}.sendCommand(Command::setResolution, resolution); +bool Ps2Mouse::setResolution(byte resolution) { + + setReporting(false); + bool res = sendCommand(Command::setResolution, resolution); + setReporting(true); + return res; } -bool Ps2Mouse::setSampleRate(byte sampleRate) const { - return Impl{*this}.sendCommand(Command::setSampleRate, sampleRate); +bool Ps2Mouse::setSampleRate(byte sampleRate) { + + setReporting(false); + bool res = sendCommand(Command::setSampleRate, sampleRate); + setReporting(true); + return res; } -bool Ps2Mouse::getSettings(Settings& settings) const { +bool Ps2Mouse::getSettings(Settings& settings) { + Status status; - if (Impl{*this}.getStatus(status)) { + setReporting(false); + bool res = getStatus(status); + setReporting(true); + if (res) { + settings.rightBtn = status.rightButton; + settings.middleBtn = status.middleButton; + settings.leftBtn = status.leftButton; + settings.remoteMode = status.remoteMode; + settings.enable = status.dataReporting; settings.scaling = status.scaling; settings.resolution = status.resolution; settings.sampleRate = status.sampleRate; @@ -239,28 +146,206 @@ bool Ps2Mouse::getSettings(Settings& settings) const { return false; } -bool Ps2Mouse::readData(Data& data) const { +Ps2Mouse::MouseType Ps2Mouse::getType() const { + return m_type; +} - Impl impl{*this}; +void Ps2Mouse::clockInterruptStatic() { - if (m_stream) { - if (digitalRead(m_clockPin) != LOW) { - return false; - } + if (classPtr != NULL) { + classPtr->interruptHandler(); } - else if (!impl.sendCommand(Command::readData)) { - return false; +} + +void Ps2Mouse::startInterrupt() { + + // Enter receive mode + PS2_DIRCLOCKIN(); + PS2_DIRDATAIN(); + m_mouseBits = 0; + m_bitCount = 0; + m_bufferHead = m_bufferTail = m_bufferCount = 0; + memset(m_buffer, 0, 256); + attachInterrupt(0, clockInterruptStatic, FALLING); +} + +void Ps2Mouse::stopInterrupt() { + detachInterrupt(0); + m_mouseBits = 0; + m_bitCount = 0; + m_bufferHead = m_bufferTail = m_bufferCount = 0; + memset(m_buffer, 0, 256); +} + + +bool Ps2Mouse::readData(Data& data) { + + size_t pktSize = sizeof(Packet); + if (m_type != MouseType::wheelMouse) { + pktSize--; } - Packet packet; - if (!impl.recvData(packet)) { + if (m_bufferCount < pktSize) { return false; } + Packet packet = {0}; + if (m_type == MouseType::wheelMouse) { + if (!getBytes((byte*)&packet, sizeof(packet))) { + return false; + } + } else { + if (!getBytes((byte*)&packet, sizeof(packet) - 1)) { + return false; + } + } + data.leftButton = packet.leftButton; data.middleButton = packet.middleButton; data.rightButton = packet.rightButton; data.xMovement = (packet.xSign ? -0x100 : 0) | packet.xMovement; data.yMovement = (packet.ySign ? -0x100 : 0) | packet.yMovement; + data.wheelMovement = (m_type == MouseType::wheelMouse) ? packet.wheelData : 0; + return true; +} + + +void Ps2Mouse::interruptHandler() { + + uint8_t bit = PS2_READDATA(); + + if (m_bitCount == 0) { + // start bit should ALWAYS be 0 + if (bit) { + // LED_SETHIGH(); + // error on start bit + return; + } + // start bit: bit 1 + m_parityBit = 1; + m_mouseBits = 0; + m_bitCount++; + } else if ((m_bitCount > 0) && (m_bitCount < 9)) { + // data bits: bits 2 - 9 + m_mouseBits >>= 1; + m_mouseBits |= (bit << 7); + m_parityBit ^= bit; + m_bitCount++; + } else if (m_bitCount == 9) { + // parity bit: bit 10 + // parity bit should match the calculated parity + // so m_parityBit xor bit will be 0 if parity matches: + // 1 ^ 1 = 0 + // 0 ^ 0 = 0 + // 1 ^ 0 = 1 + // 0 ^ 1 = 1 + m_parityBit ^= bit; + m_bitCount++; + } else { + // stop bit: bit 11 + // if stop bit is 1 and parity 0, then add this byte to the buffer + if (bit && !m_parityBit) { + m_buffer[m_bufferHead] = m_mouseBits; + m_bufferCount++; + m_bufferHead++; + m_bitCount++; + //LED_SETLOW(); + } else { + // LED_SETHIGH(); + } + m_bitCount = 0; + } +} + +bool Ps2Mouse::getByte(uint8_t& data) { + unsigned long count = 0; + while (m_bufferHead == m_bufferTail) { + if (count++ > 5000000) { + return false; + } + } + m_bufferCount--; + data = m_buffer[m_bufferTail++]; + return true; +} + +bool Ps2Mouse::getBytes(uint8_t* data, size_t size) { + + for (size_t i = 0u; i < size; i++) { + if (!getByte(data[i])) { + return false; + } + } return true; } + +void Ps2Mouse::sendBit(int value) const { + + while (PS2_READCLOCK() != LOW) {} + PS2_SETDATA(value); + while (PS2_READCLOCK() != HIGH) {} +} + +bool Ps2Mouse::sendByte(byte value) { + + // Inhibit communication + stopInterrupt(); + PS2_DIRCLOCKOUT(); + PS2_SETCLOCKLOW(); + delayMicroseconds(10); + + // Set start bit and release the clock + PS2_DIRDATAOUT(); + PS2_SETDATALOW(); + PS2_DIRCLOCKIN_UP(); + + // Send data bits + byte parity = 1; + for (auto i = 0; i < 8; i++) { + byte nextBit = (value >> i) & 0x01; + parity ^= nextBit; + sendBit(nextBit); + } + + // Send parity bit + sendBit(parity); + + // Send stop bit + sendBit(1); + + // Enter receive mode and wait for ACK bit + PS2_DIRDATAIN(); + while (PS2_READCLOCK() != LOW) {} + byte ack = PS2_READDATA(); + while (PS2_READCLOCK() != HIGH) {} + + startInterrupt(); + return ack == 0; +} + +bool Ps2Mouse::sendByteWithAck(byte value) { + while (true) { + if (sendByte(value)) { + byte response; + if (getByte(response)) { + if (response == static_cast(Response::resend)) { + continue; + } + return response == static_cast(Response::ack); + } + } + return false; + } +} + +bool Ps2Mouse::sendCommand(Command command) { + return sendByteWithAck(static_cast(command)); +} + +bool Ps2Mouse::sendCommand(Command command, byte setting) { + return sendCommand(command) && sendByteWithAck(setting); +} + +bool Ps2Mouse::getStatus(Status& status) { + return sendCommand(Command::statusRequest) && getBytes((byte*) &status, sizeof(Status)); +} diff --git a/firmware/ps2adapter/Ps2Mouse.h b/firmware/ps2adapter/Ps2Mouse.h index 2bd55d2..24a5f52 100644 --- a/firmware/ps2adapter/Ps2Mouse.h +++ b/firmware/ps2adapter/Ps2Mouse.h @@ -5,38 +5,108 @@ class Ps2Mouse { public: + enum class MouseType { + + threeButton, + wheelMouse, + }; + struct Data { + bool leftButton; bool middleButton; bool rightButton; int xMovement; int yMovement; + int wheelMovement; }; struct Settings { + + bool rightBtn; + bool middleBtn; + bool leftBtn; bool scaling; + bool enable; + bool remoteMode; byte resolution; byte sampleRate; }; - - Ps2Mouse(int clockPin, int dataPin); - bool reset() const; + static Ps2Mouse* instance(); - bool setScaling(bool flag) const; - bool setResolution(byte resolution) const; - bool setSampleRate(byte sampleRate) const; + bool reset(); + bool setReporting(bool enable); - bool getSettings(Settings& settings) const; + bool setStreamMode(); + bool setRemoteMode(); - bool enableStreaming() const; - bool disableStreaming() const; - bool readData(Data& data) const; + bool setScaling(bool flag); + bool setResolution(byte resolution); + bool setSampleRate(byte sampleRate); + + bool getSettings(Settings& settings); + Ps2Mouse::MouseType getType() const; + + bool readData(Data& data); + uint8_t getBufferCount() const { return m_bufferCount; }; private: - struct Impl; + enum class Command { + + disableScaling = 0xE6, + enableScaling = 0xE7, + setResolution = 0xE8, + statusRequest = 0xE9, + setStreamMode = 0xEA, + readData = 0xEB, + resetWrapMode = 0xEC, // Not implemented + setWrapMode = 0xEE, // Not implemented + reset = 0xFF, + setRemoteMode = 0xF0, + getDeviceId = 0xF2, // Not implemented + setSampleRate = 0xF3, + enableDataReporting = 0xF4, + disableDataReporting = 0xF5, + setDefaults = 0xF6, // Not implemented + }; + + struct Status { + + byte rightButton : 1; + byte middleButton : 1; + byte leftButton : 1; + byte na2 : 1; + byte scaling : 1; + byte dataReporting : 1; + byte remoteMode : 1; + byte na1 : 1; + + byte resolution; + byte sampleRate; + }; + + Ps2Mouse(); + + bool getByte(uint8_t& data); + bool getBytes(uint8_t* data, size_t size); + + void sendBit(int value) const; + bool sendByte(byte value); + bool sendByteWithAck(byte value); + bool sendCommand(Command command); + bool sendCommand(Command command, byte setting); + bool getStatus(Status& status); + + void startInterrupt(); + void stopInterrupt(); + static void clockInterruptStatic(); + void interruptHandler(); - int m_clockPin; - int m_dataPin; - bool m_stream; + MouseType m_type; + uint8_t m_mouseBits; + uint8_t m_bitCount; + uint8_t m_parityBit; + volatile uint8_t m_bufferTail, m_bufferHead, m_bufferCount; + uint8_t m_buffer[256]; }; diff --git a/firmware/ps2adapter/platformio.ini b/firmware/ps2adapter/platformio.ini new file mode 100644 index 0000000..5228a98 --- /dev/null +++ b/firmware/ps2adapter/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[platformio] +src_dir = . + +[env:pro16MHzatmega328] +platform = atmelavr +board = pro16MHzatmega328 +framework = arduino +monitor_speed = 115200 + diff --git a/firmware/ps2adapter/ps2adapter.ino b/firmware/ps2adapter/ps2adapter.ino index 79e0a41..4a6b26c 100644 --- a/firmware/ps2adapter/ps2adapter.ino +++ b/firmware/ps2adapter/ps2adapter.ino @@ -1,20 +1,19 @@ +#include "ProMini.h" #include "Ps2Mouse.h" +#include "LEDMessenger.h" -static const int PS2_CLOCK = 2; -static const int PS2_DATA = 17; static const int RS232_RTS = 3; -static const int RS232_TX = 4; -static const int JP12 = 11; -static const int JP34 = 12; -static const int LED = 13; -static Ps2Mouse mouse(PS2_CLOCK, PS2_DATA); -static bool threeButtons = false; +static Ps2Mouse* pMouse; +static bool twoButtons = false; +static bool wheelMouse = false; +static volatile bool doSerialInit = true; +static volatile bool serialInitDone = false; static void sendSerialBit(int data) { // Delay between the signals to match 1200 baud static const auto usDelay = 1000000 / 1200; - digitalWrite(RS232_TX, data); + RS_SETTX(data); delayMicroseconds(usDelay); } @@ -38,6 +37,7 @@ static void sendSerialByte(byte data) { } static void sendToSerial(const Ps2Mouse::Data& data) { + auto dx = constrain(data.xMovement, -127, 127); auto dy = constrain(-data.yMovement, -127, 127); byte lb = data.leftButton ? 0x20 : 0; @@ -45,68 +45,190 @@ static void sendToSerial(const Ps2Mouse::Data& data) { sendSerialByte(0x40 | lb | rb | ((dy >> 4) & 0xC) | ((dx >> 6) & 0x3)); sendSerialByte(dx & 0x3F); sendSerialByte(dy & 0x3F); - if (threeButtons) { - byte mb = data.middleButton ? 0x20 : 0; + if (!twoButtons) { + byte mb; + if (wheelMouse) { + // according to: https://sourceforge.net/p/cutemouse/trunk/ci/master/tree/cutemouse/PROTOCOL.TXT + // for a wheel mouse, the middle button should be reported in bit 0x10 + mb = data.middleButton ? 0x10 : 0; + mb |= (data.wheelMovement & 0x0F); + } else { + // for a 3-button mouse, the middle button should be reported in bit 0x20 + mb = data.middleButton ? 0x20 : 0; + } sendSerialByte(mb); } } static void initSerialPort() { + + // disable the PS/2 mouse data reporting while the serial port is being initialized + pMouse->setReporting(false); + Serial.println("Starting serial port"); - digitalWrite(RS232_TX, HIGH); + RS_SETTXHIGH(); delayMicroseconds(10000); sendSerialByte('M'); - if(threeButtons) { - sendSerialByte('3'); - Serial.println("Init 3-buttons mode"); + if(!twoButtons) { + // if wheelMouse mode has been set by jumper, confirm that the mouse has the capability + if(wheelMouse && (pMouse->getType() == Ps2Mouse::MouseType::wheelMouse)) { + // set wheelMouse mode + sendSerialByte('Z'); + Serial.println("Init wheel mode"); + } else { + // set 3-button (Logitech) mode + sendSerialByte('3'); + Serial.println("Init 3-button mode"); + } + } else { + Serial.println("Init 2-button mode"); } delayMicroseconds(10000); - - Serial.println("Listening on RTS"); - void (*resetHack)() = 0; - attachInterrupt(digitalPinToInterrupt(RS232_RTS), resetHack, FALLING); + // Renable PS/2 mouse data reporting + pMouse->setReporting(true); + // Signal the main loop that the serial port is ready + serialInitDone = true; } static void initPs2Port() { - Serial.println("Reseting PS/2 mouse"); - mouse.reset(); - mouse.setSampleRate(20); + + Serial.print("Reseting PS/2 mouse... "); + + if (pMouse->reset()) { + Serial.println("OK"); + } else { + Serial.println("Failed!"); + } + + if (pMouse->setSampleRate(20)) { + Serial.println("Sample rate set to 20"); + } else { + Serial.println("Failed to set sample rate"); + } Ps2Mouse::Settings settings; - if (mouse.getSettings(settings)) { + if (pMouse->getSettings(settings)) { Serial.print("scaling = "); Serial.println(settings.scaling); Serial.print("resolution = "); Serial.println(settings.resolution); Serial.print("samplingRate = "); Serial.println(settings.sampleRate); + } else { + Serial.println("Failed to get settings"); } } +void resetSerialSignal() { + + // signal the main loop to ignore the PS/2 mouse data until the serial port is ready + serialInitDone = false; + // signal the main loop to initialize the serial port + doSerialInit = true; +} + +LEDMessenger blinker; + void setup() { // PS/2 Data input must be initialized shortly after power on, // or the mouse will not initialize - pinMode(PS2_DATA, INPUT_PULLUP); - pinMode(RS232_TX, OUTPUT); - pinMode(JP12, INPUT_PULLUP); - pinMode(JP34, INPUT_PULLUP); - pinMode(LED, OUTPUT); - digitalWrite(LED, HIGH); - threeButtons = digitalRead(JP12); + PS2_DIRDATAIN_UP(); + RS_DIRTXOUT(); + JP12_DIRIN_UP(); + JP34_DIRIN_UP(); + LED_DIROUT(); + LED_SETHIGH(); + Serial.begin(115200); - initSerialPort(); + blinker.start(); + + twoButtons = (JP12_READ() == LOW); + wheelMouse = (JP34_READ() == LOW); + pMouse = Ps2Mouse::instance(); + + // PS/2 initialization can take > 400ms for reset to complete. + // Init the mouse here, determine its characteristics and then + // wait for the RTS signal to initialize the serial port comms initPs2Port(); + Serial.println("Listening on RTS"); + attachInterrupt(digitalPinToInterrupt(RS232_RTS), resetSerialSignal, FALLING); + Serial.println("Setup done!"); - digitalWrite(LED, LOW); - if (digitalRead(JP34) == LOW) { - Serial.println("Enabling streaming mode"); - mouse.enableStreaming(); + LED_SETLOW(); +} + +enum class SettingState { + ProcessMouse, + SettingEnterDetected, + SettingStateConfirmed, +}; + + +static SettingState settingState = SettingState::ProcessMouse; +static unsigned long lastMillis = 0; +static bool swapButtons = false; + +void processStateMachine() { + + Ps2Mouse::Data data; + bool validData = pMouse->readData(data); + + switch (settingState) { + case SettingState::ProcessMouse: + if (!validData) { + return; + } + if (data.leftButton && data.rightButton && data.middleButton) { + settingState = SettingState::SettingEnterDetected; + lastMillis = millis(); + blinker.push(500, -1, 500); + } else { + if (swapButtons) { + bool tmp = data.leftButton; + data.leftButton = data.rightButton; + data.rightButton = tmp; + } + Serial.println("L: " + String(data.leftButton) + " M: " + String(data.middleButton) + " R: " + String(data.rightButton) + " X: " + String(data.xMovement) + " Y: " + String(data.yMovement) + " W: " + String(data.wheelMovement)); + sendToSerial(data); + } + break; + case SettingState::SettingEnterDetected: + if (validData && !(data.leftButton && data.rightButton && data.middleButton)) { + settingState = SettingState::ProcessMouse; + blinker.push(0, 0, 200); + return; + } + if (millis() - lastMillis > 3000) { + settingState = SettingState::SettingStateConfirmed; + lastMillis = millis(); + blinker.push(300, -1, 500); + } + break; + case SettingState::SettingStateConfirmed: + if (!validData) { + return; + } + + if (millis() - lastMillis > 1000) { + // button swap mode selected + if (data.rightButton && !data.leftButton && !data.middleButton) { + swapButtons = !swapButtons; + Serial.println("Button Swap: " + String(swapButtons)); + settingState = SettingState::ProcessMouse; + blinker.push(200, 3, 1000); + } + } + break; } } void loop() { - Ps2Mouse::Data data; - if (mouse.readData(data)) { - sendToSerial(data); + if (doSerialInit) { + initSerialPort(); + doSerialInit = false; + } + + if (serialInitDone) { + processStateMachine(); } }