Skip to content

Commit

Permalink
Add Gravis GamePad Pro support (GrIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
Filipp Andjelo committed Jun 1, 2021
1 parent aa0dfea commit bf6336f
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
127 changes: 127 additions & 0 deletions firmware/gameport-adapter/GrIP.h
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

#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<GamePort<2>::pin, true> m_clock;
DigitalInput<GamePort<7>::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;
}
};

94 changes: 94 additions & 0 deletions firmware/gameport-adapter/HidGrIP.h
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

#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 <GrIP::Model M> struct HidType;
using HidGamePadPro = HidDevice<HidType<GrIP::Model::GRIP_GAMEPAD_PRO>>;

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
};

2 changes: 2 additions & 0 deletions firmware/gameport-adapter/gameport-adapter.ino
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#include "HidCHFlightstickPro.h"
#include "HidGrIP.h"
#include "HidJoystickB2A2.h"
#include "HidJoystickB4A2.h"
#include "HidJoystickB4A3.h"
Expand All @@ -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;
}
}
Expand Down

0 comments on commit bf6336f

Please sign in to comment.