From bf6336f60b3563da9e0d0dc72892fcddd3ab5aed Mon Sep 17 00:00:00 2001 From: Filipp Andjelo Date: Tue, 1 Jun 2021 21:39:30 +0200 Subject: [PATCH] Add Gravis GamePad Pro support (GrIP) --- README.md | 2 + firmware/gameport-adapter/GrIP.h | 127 ++++++++++++++++++ firmware/gameport-adapter/HidGrIP.h | 94 +++++++++++++ .../gameport-adapter/gameport-adapter.ino | 2 + 4 files changed, 225 insertions(+) create mode 100644 firmware/gameport-adapter/GrIP.h create mode 100644 firmware/gameport-adapter/HidGrIP.h diff --git a/README.md b/README.md index c17c86a..85c3b72 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ ThrustMaster | 4 | 3 | 1 | 1010 | Analog, DOS-compatib Sidewinder GamePad | 10 | 2 | 0 | 1110 | Digital protocol Sidewinder 3D Pro | 8 | 4 | 1 | 1110 | Digital protocol Sidewinder Precision Pro | 9 | 4 | 1 | 1110 | Digital protocol +Gravis GamePad Pro | 10 | 2 | 0 | 0001 | Digital protocol (GrIP) Please pay attention how the same switches are used for different Sidewinder devices. This is possible due to fully digital communication. Using this the @@ -98,6 +99,7 @@ Well physically following joysticks were tested so far: * Sidewinder GamePad * Sidewinder 3D Pro * Sidewinder Precision Pro +* Gravis GamePad Pro Those are joysticks, which I had at hand during the development. However, Sidewinder 3D Pro can be switched between analog and digital mode and in analog diff --git a/firmware/gameport-adapter/GrIP.h b/firmware/gameport-adapter/GrIP.h new file mode 100644 index 0000000..0c40cea --- /dev/null +++ b/firmware/gameport-adapter/GrIP.h @@ -0,0 +1,127 @@ +// This file is part of Necroware's GamePort adapter firmware. +// Copyright (C) 2021 Necroware +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "DigitalPin.h" + +/// Class to communicate with Gravis joysticks using GrIP. +/// @remark This is a green field implementation, but it was heavily +/// inspired by Linux Sidewinder driver implementation. See +/// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/grip.c +class GrIP { +public: + + /// Supported Gravis model types. + /// + /// @remark currently inly GamePad Pro is supported + enum class Model { + + /// Unknown model + GRIP_UNKNOWN, + + /// GamePad Pro + GRIP_GAMEPAD_PRO, + }; + + /// Joystick state. + struct State { + uint8_t axis[2]{0}; + uint16_t buttons{0}; + }; + + /// Resets the joystick and tries to detect the model. + void reset() { + m_model = Model::GRIP_UNKNOWN; + while (m_model == Model::GRIP_UNKNOWN) { + if (readPacket()) { + m_model = Model::GRIP_GAMEPAD_PRO; + } + } + } + + /// Gets the detected model. + /// @returns the detected joystick model + Model getModel() const { + return m_model; + } + + /// Reads the joystick state. + /// @returns the state of axis, buttons etc. + /// @remark if reading the state fails, the last known state is + /// returned and the joystick reset is executed. + State readState() { + const auto packet = readPacket(); + if (packet) { + + const auto getBit = [&](uint8_t pos) { + return uint8_t(packet >> pos) & 1; + }; + + m_state.axis[0] = 1 + getBit(13) - getBit(12); + m_state.axis[1] = 1 + getBit(15) - getBit(16); + + m_state.buttons = getBit(8); + m_state.buttons |= getBit(3) << 1; + m_state.buttons |= getBit(7) << 2; + m_state.buttons |= getBit(6) << 3; + m_state.buttons |= getBit(10) << 4; + m_state.buttons |= getBit(11) << 5; + m_state.buttons |= getBit(5) << 6; + m_state.buttons |= getBit(2) << 7; + m_state.buttons |= getBit(0) << 8; + m_state.buttons |= getBit(1) << 9; + } + + return m_state; + } + +private: + + DigitalInput::pin, true> m_clock; + DigitalInput::pin, true> m_data; + Model m_model{Model::GRIP_UNKNOWN}; + State m_state; + + /// Read bits packet from the joystick. + uint32_t readPacket() const { + + // Gravis GamePad Pro sends 24 bits long packages of data all the + // time. Every package starts with a binary tag sequence 011111. + + static const auto length = 24u; + uint32_t result = 0u; + + // read a package of 24 bits + for (auto i = 0u; i < length; i++) { + if (!m_clock.wait(Edge::falling, 100)) { + return 0u; + } + result |= uint32_t(m_data.get()) << i; + } + + // alighn the bits to have the binary tag in front. This code + // was taken almost unchanged from the linux kernel. + for (auto i = 0u; i < length; i++) { + result = (result >> 1) | (result & 1) << (length - 1u); + if ((result & 0xfe4210) == 0x7c0000) { + return result; + } + } + return 0u; + } +}; + diff --git a/firmware/gameport-adapter/HidGrIP.h b/firmware/gameport-adapter/HidGrIP.h new file mode 100644 index 0000000..9edb1d7 --- /dev/null +++ b/firmware/gameport-adapter/HidGrIP.h @@ -0,0 +1,94 @@ +// This file is part of Necroware's GamePort adapter firmware. +// Copyright (C) 2021 Necroware +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "DigitalPin.h" +#include "Driver.h" +#include "GamePort.h" +#include "HidDevice.h" +#include "GrIP.h" +#include "Log.h" + +class HidGrIP : public Driver { +public: + + template struct HidType; + using HidGamePadPro = HidDevice>; + + void init() override { + m_grip.reset(); + switch(m_grip.getModel()) { + case GrIP::Model::GRIP_GAMEPAD_PRO: + log("Detected Gravis GamePad Pro"); + HidGamePadPro::activate(); + break; + case GrIP::Model::GRIP_UNKNOWN: + log("Unknown or not GrIP input device"); + break; + } + } + + void update() override { + const auto state = m_grip.readState(); + switch(m_grip.getModel()) { + case GrIP::Model::GRIP_GAMEPAD_PRO: + sendGamePadPro(state); + break; + case GrIP::Model::GRIP_UNKNOWN: + log("Unknown or not GrIP input device"); + break; + } + } + +private: + + static void sendGamePadPro(const GrIP::State& state) { + const uint16_t data = state.buttons << 4 | state.axis[0] << 2 | state.axis[1]; + HidGamePadPro::send(&data, sizeof(data)); + } + + GrIP m_grip; +}; + +template <> +const byte HidGrIP::HidGamePadPro::description[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x04, // Usage (Joystick) + 0xa1, 0x01, // Collection (Application) + 0x85, id, // Report ID (id) + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x02, // Logical Maximum (2) + 0x75, 0x02, // Report Size (2) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (1) + 0x29, 0x0A, // Usage Maximum (10) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0A, // Report Count (10) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x75, 0x02, // Report Size (2) + 0x95, 0x01, // Report Count (1) + 0x81, 0x03, // Input (Const,Var,Abs) + 0xc0, // End Collection +}; + diff --git a/firmware/gameport-adapter/gameport-adapter.ino b/firmware/gameport-adapter/gameport-adapter.ino index 1be5f1d..d3bbb3a 100644 --- a/firmware/gameport-adapter/gameport-adapter.ino +++ b/firmware/gameport-adapter/gameport-adapter.ino @@ -15,6 +15,7 @@ // along with this program. If not, see . #include "HidCHFlightstickPro.h" +#include "HidGrIP.h" #include "HidJoystickB2A2.h" #include "HidJoystickB4A2.h" #include "HidJoystickB4A3.h" @@ -33,6 +34,7 @@ static Driver* createDriver(byte sw) { case 0b0100: return new HidCHFlightstickPro; case 0b0101: return new HidThrustMaster; case 0b0111: return new HidSidewinder; + case 0b1000: return new HidGrIP; default: return new HidJoystickB2A2; } }