Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions firmware/gameport-adapter/HidDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ class HidDevice : public PluggableUSBModule {

explicit HidDevice()
: PluggableUSBModule(1, 1, epType) {
PluggableUSB().plug(this);
}

void AppendDescriptor(HIDSubDescriptor *node) {

if (rootNode == nullptr) {
// Initialization is delayed until AppendDescriptor
// is called for the first time. This allows for creating
// HidDevice objects and not initializing them.
PluggableUSB().plug(this);
rootNode = node;
} else {
auto current = rootNode;
Expand Down Expand Up @@ -90,7 +93,7 @@ class HidDevice : public PluggableUSBModule {
total += res;
}

// Reset the protocol on reenumeration. Normally the host should not
// Reset the protocol on reenumeration. Normally the host should not
// assume the state of the protocol due to the USB specs, but Windows
// and Linux just assumes its in report mode.
protocol = HID_REPORT_PROTOCOL;
Expand Down
34 changes: 23 additions & 11 deletions firmware/gameport-adapter/HidJoystick.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ class HidJoystick {
}

m_joystick = joystick;
m_joystickCount = min(joystick->getJoystickCount(), Joystick::MAX_JOYSTICKS);

m_hidDescription = createDescription(*m_joystick);
m_subDescriptor = new HIDSubDescriptor{m_hidDescription.data, m_hidDescription.size};
m_hidDevice.AppendDescriptor(m_subDescriptor);

log("Detected device: %s", joystick->getDescription().name);
for (uint8_t i = 0; i < m_joystickCount; ++i)
{
m_subDescriptor[i] = new HIDSubDescriptor{m_hidDescription.data, m_hidDescription.size};
m_hidDevice[i].AppendDescriptor(m_subDescriptor[i]);
}

log("Detected device: %s, count %d", joystick->getDescription().name, m_joystickCount);
return true;
}

Expand All @@ -43,8 +49,11 @@ class HidJoystick {
return false;
}

const auto packet = createPacket(*m_joystick);
m_hidDevice.SendReport(DEVICE_ID, packet.data, packet.size);
for (uint8_t i = 0; i < m_joystickCount; ++i)
{
const auto packet = createPacket(*m_joystick, i);
m_hidDevice[i].SendReport(DEVICE_ID, packet.data, packet.size);
}
return true;
}

Expand Down Expand Up @@ -133,9 +142,9 @@ class HidJoystick {
return buffer;
}

static BufferType createPacket(const Joystick &joystick) {
static BufferType createPacket(const Joystick &joystick, uint8_t joystickIndex) {

const auto &state = joystick.getState();
const auto &state = joystick.getState(joystickIndex);
const auto &description = joystick.getDescription();
BufferType buffer;
auto filler = BufferFiller(buffer);
Expand All @@ -158,8 +167,11 @@ class HidJoystick {
return buffer;
}

Joystick *m_joystick{};
BufferType m_hidDescription{};
HIDSubDescriptor *m_subDescriptor{};
HidDevice m_hidDevice;
Joystick *m_joystick{};
BufferType m_hidDescription{};
HIDSubDescriptor * m_subDescriptor[Joystick::MAX_JOYSTICKS]{};
HidDevice m_hidDevice[Joystick::MAX_JOYSTICKS];

// used for daisychained joysticks, otherwise 1
uint8_t m_joystickCount{};
};
24 changes: 24 additions & 0 deletions firmware/gameport-adapter/Joystick.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ class Joystick {
public:
static const auto MAX_AXES{16u};

/// Calculate how many usb endpoints are available. USB_ENDPOINTS is the total supported endpoints.
/// The CDC / serial endpoints are first, then any pluggable modules get what is remaining.
/// Normally this leaves 3, but if you disable CDC support (via CDC_DISABLED), then 6 are available.
static const uint8_t AVAILABLE_USB_ENDPOINTS {max(0, (USB_ENDPOINTS - CDC_ENPOINT_COUNT - CDC_FIRST_ENDPOINT))};

/// Maximum number of digital joysticks that can daisy chain off of the same port.
/// Sidewinder gamepad is the only one supported at this time which has a limit of 4.
/// This is then limited by how many USB endpoints are available so this is normally 3.
static const uint8_t MAX_JOYSTICKS{min(4, AVAILABLE_USB_ENDPOINTS)};

/// Device description.
///
/// This structure is used to generate the HID description
Expand Down Expand Up @@ -82,9 +92,23 @@ class Joystick {
/// Gets the State of the Joystick.
virtual const State &getState() const = 0;

/// Override this method to add support for daisy chained joysticks.
virtual const State &getState(uint8_t joystickIndex) const
{
return getState();
}

/// Gets the Description of the Joystick.
virtual const Description &getDescription() const = 0;

/// Returns the number of daisy chained joysticks
/// that are connected. Defaults to 1 as most joysticks
/// implementations don't have this feature.
virtual uint8_t getJoystickCount() const
{
return 1;
}

Joystick() = default;
virtual ~Joystick() = default;
Joystick(const Joystick &) = delete;
Expand Down
99 changes: 70 additions & 29 deletions firmware/gameport-adapter/Sidewinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,50 @@
/// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/sidewinder.c
class Sidewinder : public Joystick {
public:

/// The maximum number of sidewinder gamepads supported.
static const auto MAX_GAMEPADS{min(Joystick::MAX_JOYSTICKS, 4)};

/// Resets the joystick and tries to detect the model.
bool init() override {
log("Sidewinder init...");
m_errors = 0;

m_model = guessModel(readPacket());
uint8_t joystickCount = 1;
m_model = guessModel(readPacket(), joystickCount);
while (m_model == Model::SW_UNKNOWN) {
// No data. 3d Pro analog mode?
enableDigitalMode();
m_model = guessModel(readPacket());
m_model = guessModel(readPacket(), joystickCount);
}
log("Detected model %d", m_model);
m_joystickCount = joystickCount;
log("Detected model %d, count %d", m_model, joystickCount);
return true;
}

bool update() override {
const auto packet = readPacket();
State state;
if (decode(packet, state)) {
m_state = state;

bool firstJoystickOK = false;
for (uint8_t i = 0; i < m_joystickCount; ++i)
{
State state;
if (decode(packet, i, state)) {
m_state[i] = state;

// The logic here is if one of the joysticks in the chain randomly
// didn't respond or the cable got loose, it is better to update at
// least one of them than to fail out.
firstJoystickOK = firstJoystickOK || (i == 0);
}
}
if (firstJoystickOK)
{
m_errors = 0;
return true;
}


m_errors++;
log("Packet decoding failed %d time(s)", m_errors);
if (m_errors > 5) {
Expand All @@ -60,7 +80,15 @@ class Sidewinder : public Joystick {
}

const State &getState() const override {
return m_state;
return m_state[0];
}
const State &getState(uint8_t joystickIndex) const override {
return m_state[joystickIndex];
}

uint8_t getJoystickCount() const override
{
return m_joystickCount;
}

const Description &getDescription() const override;
Expand Down Expand Up @@ -94,12 +122,13 @@ class Sidewinder : public Joystick {
template <Model M>
struct Decoder {
static const Description &getDescription();
static bool decode(const Packet &packet, State &state);
static bool decode(const Packet &packet, uint8_t joystickIndex, State &state);
};

/// Guesses joystick model from the size of the packet.
Model guessModel(const Packet &packet) const {
Model guessModel(const Packet &packet, uint8_t & joystickCount) const {
log("Guessing model by packet size of %d", packet.size);
uint8_t gamepadCount = packet.size / 15;
switch (packet.size) {
case 15:
return Model::SW_GAMEPAD;
Expand All @@ -118,6 +147,12 @@ class Sidewinder : public Joystick {
case 64:
return Model::SW_3D_PRO;
default:
// for daisychained gamepads, the packet will be a multiple of 15
if (gamepadCount > 0 && (packet.size % 15 == 0)) {
// clamp the count to the maximum the firmware supports.
joystickCount = min(MAX_GAMEPADS, gamepadCount);
return Model::SW_GAMEPAD;
}
return Model::SW_UNKNOWN;
}
}
Expand All @@ -137,7 +172,8 @@ class Sidewinder : public Joystick {
DigitalInput<GamePort<14>::pin, true> m_data2;
DigitalOutput<GamePort<3>::pin> m_trigger;
Model m_model{Model::SW_UNKNOWN};
State m_state{};
State m_state[Joystick::MAX_JOYSTICKS]{};
uint8_t m_joystickCount;
uint8_t m_errors{};

/// Enables digital mode for 3D Pro.
Expand Down Expand Up @@ -219,7 +255,7 @@ class Sidewinder : public Joystick {
}

/// Decodes bit packet into a state.
bool decode(const Packet &packet, State &state) const;
bool decode(const Packet &packet, uint8_t joystickIndex, State &state) const;
};

/// Placeholder for Unknown Device
Expand All @@ -231,7 +267,7 @@ class Sidewinder::Decoder<Sidewinder::Model::SW_UNKNOWN> {
return desc;
}

static bool decode(const Packet &, State &) {
static bool decode(const Packet &, uint8_t, State &) {
return false;
}
};
Expand All @@ -245,7 +281,7 @@ class Sidewinder::Decoder<Sidewinder::Model::SW_GAMEPAD> {
return desc;
}

static bool decode(const Packet &packet, State &state) {
static bool decode(const Packet &packet, uint8_t joystickIndex, State &state) {

const auto checksum = [&]() {
byte result = 0u;
Expand All @@ -255,19 +291,24 @@ class Sidewinder::Decoder<Sidewinder::Model::SW_GAMEPAD> {
return result;
};

if (packet.size != 15 || checksum() != 0) {
uint8_t joystickCount = packet.size / 15;
if (joystickCount < (joystickIndex + 1) ||
(packet.size % 15) != 0 ||
(joystickIndex == 0 && checksum() != 0)) {
return false;
}

const uint8_t * data = packet.data + (joystickIndex * 15);

// Bit 0-1: x-axis (10-left, 01-right, 11-middle)
// Bit 2-3: y-axis (01-up, 10-down, 11-middle)
// Bit 4-13: 10 buttons
// Bit 14: checksum
for (auto i = 0u; i < 10; i++) {
state.buttons |= (~packet.data[i + 4] & 1) << i;
state.buttons |= (~data[i + 4] & 1) << i;
}
state.axes[0] = map(1 + packet.data[3] - packet.data[2], 0, 2, 0, 1023);
state.axes[1] = map(1 + packet.data[0] - packet.data[1], 0, 2, 0, 1023);
state.axes[0] = map(1 + data[3] - data[2], 0, 2, 0, 1023);
state.axes[1] = map(1 + data[0] - data[1], 0, 2, 0, 1023);

return true;
}
Expand All @@ -282,7 +323,7 @@ class Sidewinder::Decoder<Sidewinder::Model::SW_3D_PRO> {
return desc;
}

static bool decode(const Packet &packet, State &state) {
static bool decode(const Packet &packet, uint8_t joystickIndex, State &state) {
const auto value = [&]() {
uint64_t result{0u};
for (auto i = 0u; i < packet.size; i++) {
Expand Down Expand Up @@ -351,7 +392,7 @@ class Sidewinder::Decoder<Sidewinder::Model::SW_PRECISION_PRO> {
return desc;
}

static bool decode(const Packet &packet, State &state) {
static bool decode(const Packet &packet, uint8_t joystickIndex, State &state) {

// The packet can be either in 3bit or in 1bit mode
if (packet.size != 16 && packet.size != 48) {
Expand Down Expand Up @@ -410,9 +451,9 @@ class Sidewinder::Decoder<Sidewinder::Model::SW_FORCE_FEEDBACK_PRO> {
return desc;
}

static bool decode(const Packet &packet, State &state) {
static bool decode(const Packet &packet, uint8_t joystickIndex, State &state) {
// Decode is identical between the Force Feedback Pro and the Precision Pro.
return Decoder<Model::SW_PRECISION_PRO>::decode(packet, state);
return Decoder<Model::SW_PRECISION_PRO>::decode(packet, joystickIndex, state);
}
};

Expand All @@ -425,7 +466,7 @@ class Sidewinder::Decoder<Sidewinder::Model::SW_FORCE_FEEDBACK_WHEEL> {
return desc;
}

static bool decode(const Packet &packet, State &state) {
static bool decode(const Packet &packet, uint8_t joystickIndex, State &state) {

// The packet can be either in 3bit or in 1bit mode
if (packet.size != 11 && packet.size != 33) {
Expand Down Expand Up @@ -496,19 +537,19 @@ inline const Joystick::Description &Sidewinder::getDescription() const {
}
}

inline bool Sidewinder::decode(const Packet &packet, State &state) const {
inline bool Sidewinder::decode(const Packet &packet, uint8_t joystickIndex, State &state) const {
switch (m_model) {
case Model::SW_GAMEPAD:
return Decoder<Model::SW_GAMEPAD>::decode(packet, state);
return Decoder<Model::SW_GAMEPAD>::decode(packet, joystickIndex, state);
case Model::SW_3D_PRO:
return Decoder<Model::SW_3D_PRO>::decode(packet, state);
return Decoder<Model::SW_3D_PRO>::decode(packet, joystickIndex, state);
case Model::SW_PRECISION_PRO:
return Decoder<Model::SW_PRECISION_PRO>::decode(packet, state);
return Decoder<Model::SW_PRECISION_PRO>::decode(packet, joystickIndex, state);
case Model::SW_FORCE_FEEDBACK_PRO:
return Decoder<Model::SW_FORCE_FEEDBACK_PRO>::decode(packet, state);
return Decoder<Model::SW_FORCE_FEEDBACK_PRO>::decode(packet, joystickIndex, state);
case Model::SW_FORCE_FEEDBACK_WHEEL:
return Decoder<Model::SW_FORCE_FEEDBACK_WHEEL>::decode(packet, state);
return Decoder<Model::SW_FORCE_FEEDBACK_WHEEL>::decode(packet, joystickIndex, state);
default:
return Decoder<Model::SW_UNKNOWN>::decode(packet, state);
return Decoder<Model::SW_UNKNOWN>::decode(packet, joystickIndex, state);
}
}