diff --git a/firmware/gameport-adapter/HidDevice.h b/firmware/gameport-adapter/HidDevice.h index d98eb66..133727f 100644 --- a/firmware/gameport-adapter/HidDevice.h +++ b/firmware/gameport-adapter/HidDevice.h @@ -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; @@ -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; diff --git a/firmware/gameport-adapter/HidJoystick.h b/firmware/gameport-adapter/HidJoystick.h index 5445ed7..0e5951a 100644 --- a/firmware/gameport-adapter/HidJoystick.h +++ b/firmware/gameport-adapter/HidJoystick.h @@ -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; } @@ -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; } @@ -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); @@ -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{}; }; diff --git a/firmware/gameport-adapter/Joystick.h b/firmware/gameport-adapter/Joystick.h index d6546dd..b946cf8 100644 --- a/firmware/gameport-adapter/Joystick.h +++ b/firmware/gameport-adapter/Joystick.h @@ -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 @@ -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; diff --git a/firmware/gameport-adapter/Sidewinder.h b/firmware/gameport-adapter/Sidewinder.h index 28ab7b0..5533a37 100644 --- a/firmware/gameport-adapter/Sidewinder.h +++ b/firmware/gameport-adapter/Sidewinder.h @@ -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) { @@ -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; @@ -94,12 +122,13 @@ class Sidewinder : public Joystick { template 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; @@ -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; } } @@ -137,7 +172,8 @@ class Sidewinder : public Joystick { DigitalInput::pin, true> m_data2; DigitalOutput::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. @@ -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 @@ -231,7 +267,7 @@ class Sidewinder::Decoder { return desc; } - static bool decode(const Packet &, State &) { + static bool decode(const Packet &, uint8_t, State &) { return false; } }; @@ -245,7 +281,7 @@ class Sidewinder::Decoder { 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; @@ -255,19 +291,24 @@ class Sidewinder::Decoder { 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; } @@ -282,7 +323,7 @@ class Sidewinder::Decoder { 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++) { @@ -351,7 +392,7 @@ class Sidewinder::Decoder { 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) { @@ -410,9 +451,9 @@ class Sidewinder::Decoder { 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::decode(packet, state); + return Decoder::decode(packet, joystickIndex, state); } }; @@ -425,7 +466,7 @@ class Sidewinder::Decoder { 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) { @@ -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::decode(packet, state); + return Decoder::decode(packet, joystickIndex, state); case Model::SW_3D_PRO: - return Decoder::decode(packet, state); + return Decoder::decode(packet, joystickIndex, state); case Model::SW_PRECISION_PRO: - return Decoder::decode(packet, state); + return Decoder::decode(packet, joystickIndex, state); case Model::SW_FORCE_FEEDBACK_PRO: - return Decoder::decode(packet, state); + return Decoder::decode(packet, joystickIndex, state); case Model::SW_FORCE_FEEDBACK_WHEEL: - return Decoder::decode(packet, state); + return Decoder::decode(packet, joystickIndex, state); default: - return Decoder::decode(packet, state); + return Decoder::decode(packet, joystickIndex, state); } }