├── kicad ├── fp-info-cache ├── fp-lib-table ├── sym-lib-table ├── gameport-adapter.kicad_prl ├── gameport-adapter.kicad_pro └── gameport-adapter.kicad_sch ├── .gitignore ├── photo.jpg ├── .gitmodules ├── firmware └── gameport-adapter │ ├── GenericJoystick.h │ ├── AnalogJoystick.h │ ├── AnalogAxis.h │ ├── Utilities.h │ ├── GamePort.h │ ├── CHFlightstickPro.h │ ├── ThrustMaster.h │ ├── gameport-adapter.ino │ ├── Joystick.h │ ├── CHF16CombatStick.h │ ├── Buffer.h │ ├── GrIP.h │ ├── .clang-format │ ├── DigitalPin.h │ ├── HidDevice.h │ ├── HidJoystick.h │ ├── Logitech.h │ └── Sidewinder.h ├── README.md └── LICENSE /kicad/fp-info-cache: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /kicad/*-backups 2 | /kicad/*-bak 3 | /kicad/gerber/* 4 | -------------------------------------------------------------------------------- /photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/necroware/gameport-adapter/HEAD/photo.jpg -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "kicad/lib"] 2 | path = kicad/lib 3 | url = git@github.com:necroware/kicad-library.git 4 | -------------------------------------------------------------------------------- /kicad/fp-lib-table: -------------------------------------------------------------------------------- 1 | (fp_lib_table 2 | (lib (name Necroware)(type KiCad)(uri ${KIPRJMOD}/lib/Necroware.pretty)(options "")(descr "")) 3 | ) 4 | -------------------------------------------------------------------------------- /kicad/sym-lib-table: -------------------------------------------------------------------------------- 1 | (sym_lib_table 2 | (lib (name Necroware)(type Legacy)(uri ${KIPRJMOD}/lib/Necroware.lib)(options "")(descr "")) 3 | ) 4 | -------------------------------------------------------------------------------- /kicad/gameport-adapter.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 0, 4 | "active_layer_preset": "All Layers", 5 | "auto_track_width": true, 6 | "hidden_nets": [], 7 | "high_contrast_mode": 0, 8 | "net_color_mode": 1, 9 | "opacity": { 10 | "pads": 1.0, 11 | "tracks": 1.0, 12 | "vias": 1.0, 13 | "zones": 0.6 14 | }, 15 | "ratsnest_display_mode": 0, 16 | "selection_filter": { 17 | "dimensions": true, 18 | "footprints": true, 19 | "graphics": true, 20 | "keepouts": true, 21 | "lockedItems": true, 22 | "otherItems": true, 23 | "pads": true, 24 | "text": true, 25 | "tracks": true, 26 | "vias": true, 27 | "zones": true 28 | }, 29 | "visible_items": [ 30 | 0, 31 | 1, 32 | 2, 33 | 3, 34 | 4, 35 | 5, 36 | 8, 37 | 9, 38 | 10, 39 | 11, 40 | 12, 41 | 13, 42 | 14, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36 64 | ], 65 | "visible_layers": "fffffff_ffffffff", 66 | "zone_display_mode": 0 67 | }, 68 | "meta": { 69 | "filename": "gameport-adapter.kicad_prl", 70 | "version": 3 71 | }, 72 | "project": { 73 | "files": [] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/GenericJoystick.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "Joystick.h" 20 | #include "AnalogJoystick.h" 21 | 22 | template 23 | class GenericJoystick : public Joystick { 24 | public: 25 | 26 | static_assert(Axes > 0 && Axes <= 4); 27 | 28 | bool init() override { 29 | return true; 30 | } 31 | 32 | bool update() override { 33 | for (auto i = 0u; i < Axes; i++) { 34 | m_state.axes[i] = m_joystick.getAxis(i); 35 | } 36 | m_state.buttons = m_joystick.getButtons(); 37 | return true; 38 | } 39 | 40 | const State& getState() const override { 41 | return m_state; 42 | } 43 | 44 | const Description& getDescription() const override { 45 | static const Description description { 46 | "Generic Joystick", Axes, Buttons, 0 47 | }; 48 | return description; 49 | } 50 | 51 | private: 52 | AnalogJoystick m_joystick; 53 | State m_state; 54 | }; 55 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/AnalogJoystick.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "AnalogAxis.h" 20 | #include "DigitalPin.h" 21 | #include "GamePort.h" 22 | 23 | /// A common class for all analog joysticks. 24 | class AnalogJoystick { 25 | public: 26 | /// Gets axis value. 27 | /// 28 | /// @param[in] id is the axes ID 29 | /// @returns a value between 0 and 255 30 | uint16_t getAxis(int id) { 31 | switch (id) { 32 | case 0: 33 | return m_axis1.get(); 34 | case 1: 35 | return m_axis2.get(); 36 | case 2: 37 | return m_axis3.get(); 38 | case 3: 39 | return m_axis4.get(); 40 | default: 41 | return 0u; 42 | } 43 | } 44 | 45 | /// Gets the buttons state as one byte. 46 | /// 47 | /// @returns a byte every bit represents a button 48 | byte getButtons() { 49 | return m_button1.isLow() | m_button2.isLow() << 1 | m_button3.isLow() << 2 | m_button4.isLow() << 3; 50 | } 51 | 52 | private: 53 | DigitalInput::pin> m_button1; 54 | DigitalInput::pin> m_button2; 55 | DigitalInput::pin> m_button3; 56 | DigitalInput::pin> m_button4; 57 | AnalogAxis::pin> m_axis1; 58 | AnalogAxis::pin> m_axis2; 59 | AnalogAxis::pin> m_axis3; 60 | AnalogAxis::pin> m_axis4; 61 | }; 62 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/AnalogAxis.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | /// Class to read analog axis. 22 | /// 23 | /// Usually PC joystick axes are specified to have 100 Ohm resistance, but 24 | /// many analog joysticks have old, bad, or just wrong resistors. This ends 25 | /// up in incorrect positions and is especially bad for games, which don't 26 | /// have calibration features. This class takes care of this issue and 27 | /// applies automatic calibration on every read. 28 | template 29 | class AnalogAxis { 30 | public: 31 | /// Constructor. 32 | /// 33 | /// The initial state of the joystick is considered as middle 34 | /// which is used for autocalibration. 35 | AnalogAxis() { 36 | pinMode(ID, INPUT); 37 | m_mid = analogRead(ID); 38 | m_min = m_mid - 100; 39 | m_max = m_mid + 100; 40 | } 41 | 42 | /// Gets the axis state. 43 | /// 44 | /// This function automatically recalculates the outer limits and 45 | /// readjusts the position of the joystick. 46 | /// @returns a value between 0 and 1023 47 | uint16_t get() { 48 | const auto value = analogRead(ID); 49 | if (value < m_min) { 50 | m_min = value; 51 | } else if (value > m_max) { 52 | m_max = value; 53 | } 54 | 55 | if (value < m_mid) { 56 | return map(value, m_min, m_mid, 1023, 512); 57 | } 58 | return map(value, m_mid, m_max, 511, 0); 59 | } 60 | 61 | private: 62 | int m_mid; 63 | int m_min; 64 | int m_max; 65 | }; 66 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/Utilities.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | /// Debug messages on serial port are turned off by default. Comment the following 24 | /// line to enable logging to the serial port. 25 | /// Arduino Micro seems somehow to share the serial port with the USB interface. 26 | /// If the serial port will be activated, the operating system will no longer 27 | /// recognize the USB device! 28 | #define NDEBUG 29 | 30 | #ifdef NDEBUG 31 | #define initLog() 32 | #define log(...) 33 | #else 34 | inline void initLog() { 35 | Serial.begin(9600); 36 | while(!Serial); 37 | } 38 | 39 | inline void log(const char *fmt, ...) { 40 | va_list args; 41 | va_start(args, fmt); 42 | char buffer[512]; 43 | vsnprintf(buffer, sizeof(buffer), fmt, args); 44 | va_end(args); 45 | Serial.println(buffer); 46 | } 47 | #endif // !NDEBUG 48 | 49 | /// Interrupt guard (RAII). 50 | /// 51 | /// This class is used to deactivate the interrupts in performance 52 | /// critical sections. The interrupt is reactivated as soon as this 53 | /// guard runs out of scope. 54 | struct InterruptStopper { 55 | InterruptStopper() { noInterrupts(); } 56 | ~InterruptStopper() { interrupts(); } 57 | InterruptStopper(const InterruptStopper&) = delete; 58 | InterruptStopper(InterruptStopper&&) = delete; 59 | InterruptStopper& operator=(const InterruptStopper&) = delete; 60 | InterruptStopper& operator=(InterruptStopper&&) = delete; 61 | }; 62 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/GamePort.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | // Simple GamePort pins to Arduino pins mapper. 20 | // GamePort pins <1>, <8>, <9> are already connected to "VCC" via the PCB 21 | template 22 | struct GamePort { 23 | enum { pin = -1 }; 24 | }; 25 | 26 | // Analog: Button #1 27 | // Digital (e.g. SW3D): Clock 28 | template <> 29 | struct GamePort<2> { 30 | enum { pin = 10 }; 31 | }; 32 | 33 | // Analog: Joystick1 X-Axis 34 | // Digital (e.g. SW3D): Trigger 35 | template <> 36 | struct GamePort<3> { 37 | enum { pin = A0 }; 38 | }; 39 | 40 | // Analog: Joystick1 Y-Axis 41 | template <> 42 | struct GamePort<6> { 43 | enum { pin = A7 }; 44 | }; 45 | 46 | // Analog: Button #2 47 | // Digital (e.g. SW3D): Data 0 48 | template <> 49 | struct GamePort<7> { 50 | enum { pin = 8 }; 51 | }; 52 | 53 | // Analog: Button #3 54 | // Digital (e.g. SW3D): Data 1 55 | template <> 56 | struct GamePort<10> { 57 | enum { pin = 16 }; 58 | }; 59 | 60 | // Analog: Joystick2 X-Axis 61 | template <> 62 | struct GamePort<11> { 63 | enum { pin = A1 }; 64 | }; 65 | 66 | // Analog: Midi OUT 67 | template <> 68 | struct GamePort<12> { 69 | enum { pin = 5 }; 70 | }; 71 | 72 | // Analog: Joystick2 Y-Axis 73 | template <> 74 | struct GamePort<13> { 75 | enum { pin = A6 }; 76 | }; 77 | 78 | // Analog: Button #4 79 | // Digital (e.g. SW3D): Data 2 80 | template <> 81 | struct GamePort<14> { 82 | enum { pin = 7 }; 83 | }; 84 | 85 | // Analog: Midi IN 86 | template <> 87 | struct GamePort<15> { 88 | enum { pin = 9 }; 89 | }; 90 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/CHFlightstickPro.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "AnalogJoystick.h" 20 | #include "Joystick.h" 21 | 22 | class CHFlightstickPro : public Joystick { 23 | public: 24 | const Description &getDescription() const override { 25 | static const Description description{"CH FlightStick Pro", 4, 4, 1}; 26 | return description; 27 | } 28 | 29 | const State &getState() const override { 30 | return m_state; 31 | } 32 | 33 | bool init() override { 34 | return true; 35 | } 36 | 37 | bool update() override { 38 | 39 | // Unfortunately I had no real CHFlighstickPro joystick to test, but 40 | // Sidewinder 3D Pro has an emulation for CHFlighstickPro. So, this 41 | // implementation was made using that emulation and could be wrong. 42 | // CHFlighstickPro seems to be a very interesting joystick. It doesn't 43 | // allow the user to press multiple buttons simultaneously and uses 44 | // combined buttons invocations as hat switch codes instead. So, every 45 | // time a multiple buttons seem to be pressed, means that the user is 46 | // actually using the hat switch. 47 | 48 | const auto decode = [](byte code) -> byte { 49 | static const byte table[16] = {0, 0, 0, 7, 0, 6, 0, 5, 0, 4, 0, 3, 0, 2, 0, 1}; 50 | return (code < sizeof(table)) ? table[code] : 0u; 51 | }; 52 | 53 | for (auto i = 0u; i < 4; i++) { 54 | m_state.axes[i] = m_joystick.getAxis(i); 55 | } 56 | 57 | const auto code = m_joystick.getButtons(); 58 | m_state.hat = decode(code); 59 | m_state.buttons = m_state.hat ? 0u : code; 60 | 61 | return true; 62 | } 63 | 64 | private: 65 | AnalogJoystick m_joystick; 66 | State m_state; 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/ThrustMaster.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "AnalogJoystick.h" 20 | #include "Joystick.h" 21 | 22 | class ThrustMaster : public Joystick { 23 | public: 24 | 25 | const Description &getDescription() const override { 26 | static const Description description{"ThrustMaster", 3, 4, 1}; 27 | return description; 28 | } 29 | 30 | const State &getState() const override { 31 | return m_state; 32 | } 33 | 34 | bool init() override { 35 | return true; 36 | } 37 | 38 | bool update() override { 39 | 40 | // Unfortunately I had no real ThrustMaster joystick to test, but 41 | // Sidewinder 3D Pro has an emulation for ThrustMaster. So, this 42 | // implementation was made using that emulation and could be wrong. 43 | // However the ThrustMaster seem to have a strange hat switch. It is 44 | // encoded using the 3rd axis with values between 0 and 512, where 45 | // 0-127 is up, 128-255 is right, 256-383 is down and 384-479 is left. 46 | // Everything above 480 considered to be middle. 47 | const auto hat = [](uint16_t value) -> uint8_t { 48 | if (value < 128) { 49 | return 1; 50 | } 51 | if (value < 256) { 52 | return 3; 53 | } 54 | if (value < 384) { 55 | return 5; 56 | } 57 | if (value < 480) { 58 | return 7; 59 | } 60 | return 0; 61 | }; 62 | 63 | for (auto i = 0u; i < 3; i++) { 64 | m_state.axes[i] = m_joystick.getAxis(i); 65 | } 66 | m_state.hat = hat(m_joystick.getAxis(3)); 67 | m_state.buttons = m_joystick.getButtons(); 68 | 69 | return true; 70 | } 71 | 72 | private: 73 | AnalogJoystick m_joystick; 74 | State m_state; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/gameport-adapter.ino: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "DigitalPin.h" 18 | #include "HidJoystick.h" 19 | 20 | #include "CHFlightstickPro.h" 21 | #include "CHF16CombatStick.h" 22 | #include "GenericJoystick.h" 23 | #include "GrIP.h" 24 | #include "Logitech.h" 25 | #include "Sidewinder.h" 26 | #include "ThrustMaster.h" 27 | 28 | static Joystick *createJoystick() { 29 | 30 | const auto sw1 = DigitalInput<14, true>{}; 31 | const auto sw2 = DigitalInput<15, true>{}; 32 | const auto sw3 = DigitalInput<20, true>{}; 33 | const auto sw4 = DigitalInput<21, true>{}; 34 | 35 | // Give some time to setup the input 36 | delay(1); 37 | 38 | const auto sw = !sw4 << 3 | !sw3 << 2 | !sw2 << 1 | !sw1; 39 | 40 | switch (sw) { 41 | case 0b0001: 42 | return new GenericJoystick<2,4>; 43 | case 0b0010: 44 | return new GenericJoystick<3,4>; 45 | case 0b0011: 46 | return new GenericJoystick<4,4>; 47 | case 0b0100: 48 | return new CHFlightstickPro; 49 | case 0b0101: 50 | return new ThrustMaster; 51 | case 0b0110: 52 | return new CHF16CombatStick; 53 | case 0b0111: 54 | return new Sidewinder; 55 | case 0b1000: 56 | return new GrIP; 57 | case 0b1001: 58 | return new Logitech; 59 | default: 60 | return new GenericJoystick<2,2>; 61 | } 62 | } 63 | 64 | void setup() { 65 | // DEBUG information: Debugging is turned off by default 66 | // Comment the "NDEBUG" line in "Utilities.h" to enable logging to the serial monitor 67 | initLog(); 68 | } 69 | 70 | void loop() { 71 | 72 | static auto hidJoystick = [] { 73 | HidJoystick hidJoystick; 74 | hidJoystick.init(createJoystick()); 75 | return hidJoystick; 76 | }(); 77 | 78 | hidJoystick.update(); 79 | } 80 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/Joystick.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | /// Base class for all joysticks. 22 | class Joystick { 23 | public: 24 | static const auto MAX_AXES{16u}; 25 | 26 | /// Device description. 27 | /// 28 | /// This structure is used to generate the HID description 29 | /// and USB data packages 30 | struct Description { 31 | 32 | /// Human readable name. 33 | const char* name; 34 | 35 | /// Number of supported axes. 36 | uint8_t numAxes; 37 | 38 | /// Number of supported buttons. 39 | uint8_t numButtons; 40 | 41 | /// Has HAT. 42 | bool hasHat; 43 | }; 44 | 45 | /// Joystick state. 46 | struct State { 47 | 48 | /// Axes. 49 | /// 50 | /// To keep it simple all axes are unified to 10 bits 51 | /// resolution. Every joystick has to map it's values 52 | /// to the values 0..1023 independent from the internal 53 | /// resolution of the device. 54 | uint16_t axes[MAX_AXES]{}; 55 | 56 | /// Hats. 57 | /// 58 | /// HAT is represented through values 0..8 1 is up, 2 is 59 | /// 45° clockwise, 3 is right etc. Center is represented 60 | /// with zero. 61 | uint8_t hat{}; 62 | 63 | /// Buttons. 64 | /// 65 | /// Every bit is a button. 1 is pressed, 0 is released 66 | uint16_t buttons{}; 67 | }; 68 | 69 | /// Initialize joystick. 70 | /// 71 | /// @returns True on successful initialization 72 | virtual bool init() = 0; 73 | 74 | /// Update state. 75 | /// 76 | /// This function is called every time the joystick 77 | /// state has to be updated. 78 | /// 79 | /// @returns True on successful initialization 80 | virtual bool update() = 0; 81 | 82 | /// Gets the State of the Joystick. 83 | virtual const State &getState() const = 0; 84 | 85 | /// Gets the Description of the Joystick. 86 | virtual const Description &getDescription() const = 0; 87 | 88 | Joystick() = default; 89 | virtual ~Joystick() = default; 90 | Joystick(const Joystick &) = delete; 91 | Joystick(Joystick &&) = delete; 92 | Joystick &operator=(const Joystick &) = delete; 93 | Joystick &operator=(Joystick &&) = delete; 94 | }; 95 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/CHF16CombatStick.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "AnalogJoystick.h" 20 | #include "Joystick.h" 21 | 22 | class CHF16CombatStick : public Joystick { 23 | public: 24 | const Description &getDescription() const override { 25 | // CH F16 Combat Stick from 1995 26 | static const Description description{"CH F16 Combat Stick", 3, 10, 1}; 27 | return description; 28 | } 29 | 30 | const State &getState() const override { 31 | return m_state; 32 | } 33 | 34 | bool init() override { 35 | return true; 36 | } 37 | 38 | 39 | bool update() override { 40 | const auto decodeHat = [](byte code) -> byte { 41 | // Same as CH Flight Stick Pro. But the F16 stick has 4 positions on the hat only. 42 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 43 | static constexpr byte table[16] = {0, 0, 0, 7, 0, 0, 0, 5, 0, 0, 0, 3, 0, 0, 0, 1}; 44 | return (code < sizeof(table)) ? table[code] : 0u; 45 | }; 46 | 47 | const auto decodeButtons = [](byte code) -> uint16_t { 48 | // Map to 10 buttons: 5 real ones, one hat with 4 positions. Thus we need uint16 instead of byte 49 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 50 | static constexpr uint16_t table[16] = {0, 1, 8, 0, 4, 32, 256, 0, 16, 2, 128, 0, 512, 0, 64, 0}; 51 | static constexpr auto tableSize = sizeof(table) / sizeof(table[0]); 52 | return (code < tableSize) ? table[code] : 0u; 53 | }; 54 | 55 | // The 4th axis (index 2) is ignored, because there is a big jitter 56 | // and it reacts on movement of the other axes too. So you can not 57 | // assign the axes to a function in your game. 58 | m_state.axes[0] = m_joystick.getAxis(0); 59 | m_state.axes[1] = m_joystick.getAxis(1); 60 | m_state.axes[2] = m_joystick.getAxis(3); // Throttle 61 | 62 | const auto code = m_joystick.getButtons(); 63 | 64 | m_state.hat = decodeHat(code); 65 | m_state.buttons = decodeButtons(code); 66 | 67 | log("Code %d : %d , A2 %d", code, m_state.buttons, m_state.axes[2] ); 68 | return true; 69 | } 70 | 71 | private: 72 | AnalogJoystick m_joystick; 73 | State m_state; 74 | }; 75 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/Buffer.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #pragma once 17 | 18 | #include 19 | 20 | template 21 | struct Buffer { 22 | static const uint8_t MAX_SIZE{Size}; 23 | uint8_t data[MAX_SIZE]{}; 24 | uint8_t size{}; 25 | }; 26 | 27 | template 28 | class BufferFillerImpl { 29 | public: 30 | using BufferType = Buffer; 31 | 32 | explicit BufferFillerImpl(BufferType& buffer) 33 | : m_buffer(buffer) {} 34 | 35 | ~BufferFillerImpl() = default; 36 | BufferFillerImpl(const BufferFillerImpl&) = delete; 37 | BufferFillerImpl(BufferFillerImpl&&) = default; 38 | BufferFillerImpl& operator=(const BufferFillerImpl&) = delete; 39 | BufferFillerImpl& operator=(BufferFillerImpl&&) = default; 40 | 41 | template 42 | BufferFillerImpl& push(const T& bits) { 43 | return push(uint32_t(bits), sizeof(bits) * BITS_PER_BYTE); 44 | } 45 | 46 | BufferFillerImpl& push(uint32_t bits, size_t count) { 47 | while(count) { 48 | const auto pushed = fillup(bits, count); 49 | bits >>= pushed; 50 | count -= pushed; 51 | } 52 | return *this; 53 | } 54 | 55 | BufferFillerImpl& align() { 56 | if (m_bitsUsed) { 57 | m_buffer.size++; 58 | m_bitsUsed = 0u; 59 | } 60 | return *this; 61 | } 62 | 63 | private: 64 | static constexpr auto BITS_PER_BYTE{8u}; 65 | 66 | BufferType& m_buffer; 67 | size_t m_bitsUsed{}; 68 | 69 | size_t fillup(byte bits, size_t count) { 70 | 71 | const auto freeBits = BITS_PER_BYTE - m_bitsUsed; 72 | if (freeBits == 0) { 73 | return 0; 74 | } 75 | 76 | // If the total count is above the free bits available in the current 77 | // byte, then push just a chunk to fill up the current byte 78 | if (freeBits < count) { 79 | return fillup(bits, freeBits); 80 | } 81 | 82 | if (m_bitsUsed == 0u) { 83 | m_buffer.data[m_buffer.size] = 0u; 84 | } 85 | 86 | // Obviously the total count is less, than the amount of free bits 87 | // available in the current byte, so the following block will simply 88 | // add the bits to the current byte and return 89 | const auto mask = uint32_t(1u << count) - 1u; 90 | m_buffer.data[m_buffer.size] |= byte(bits & mask) << m_bitsUsed; 91 | m_bitsUsed += count; 92 | if (m_bitsUsed == BITS_PER_BYTE) { 93 | m_buffer.size++; 94 | m_bitsUsed = 0u; 95 | } 96 | return count; 97 | } 98 | }; 99 | 100 | template 101 | BufferFillerImpl BufferFiller(Buffer& buffer) { 102 | return BufferFillerImpl(buffer); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/GrIP.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "DigitalPin.h" 20 | #include "Joystick.h" 21 | 22 | /// Class to communicate with Gravis joysticks using GrIP. 23 | /// @remark This is a green field implementation, but it was heavily 24 | /// inspired by Linux Gravis/Kensington GrIP driver 25 | /// implementation. See 26 | class GrIP : public Joystick { 27 | /// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/grip.c 28 | public: 29 | /// Resets the joystick and tries to detect the model. 30 | bool init() override { 31 | while (!readPacket()) 32 | ; 33 | return true; 34 | } 35 | 36 | /// Reads the joystick state. 37 | /// @returns the state of axis, buttons etc. 38 | /// @remark if reading the state fails, the last known state is 39 | /// returned and the joystick reset is executed. 40 | bool update() override { 41 | 42 | const auto packet = readPacket(); 43 | if (packet == 0) { 44 | return false; 45 | } 46 | 47 | const auto getBit = [&](uint8_t pos) { return uint8_t(packet >> pos) & 1; }; 48 | 49 | m_state.axes[0] = map(1 + getBit(15) - getBit(16), 0, 2, 0, 1023); 50 | m_state.axes[1] = map(1 + getBit(13) - getBit(12), 0, 2, 0, 1023); 51 | 52 | m_state.buttons = getBit(8); 53 | m_state.buttons |= getBit(3) << 1; 54 | m_state.buttons |= getBit(7) << 2; 55 | m_state.buttons |= getBit(6) << 3; 56 | m_state.buttons |= getBit(10) << 4; 57 | m_state.buttons |= getBit(11) << 5; 58 | m_state.buttons |= getBit(5) << 6; 59 | m_state.buttons |= getBit(2) << 7; 60 | m_state.buttons |= getBit(0) << 8; 61 | m_state.buttons |= getBit(1) << 9; 62 | 63 | return true; 64 | } 65 | 66 | const State &getState() const override { 67 | return m_state; 68 | } 69 | 70 | const Description &getDescription() const override { 71 | static Description desc{"Gravis GamePad Pro", 2, 10, 0}; 72 | return desc; 73 | } 74 | 75 | private: 76 | /// Supported Gravis model types. 77 | /// 78 | /// @remark currently only GamePad Pro is supported and 79 | // this enum is not used yet 80 | enum class Model { 81 | 82 | /// Unknown model 83 | GRIP_UNKNOWN, 84 | 85 | /// GamePad Pro 86 | GRIP_GAMEPAD_PRO, 87 | }; 88 | 89 | DigitalInput::pin, true> m_clock; 90 | DigitalInput::pin, true> m_data; 91 | State m_state; 92 | 93 | /// Read bits packet from the joystick. 94 | uint32_t readPacket() const { 95 | 96 | // Gravis GamePad Pro sends 24 bits long packages of data all the 97 | // time. Every package starts with a binary tag sequence 011111. 98 | 99 | static const auto length = 24u; 100 | uint32_t result = 0u; 101 | 102 | // read a package of 24 bits 103 | for (auto i = 0u; i < length; i++) { 104 | if (!m_clock.wait(Edge::falling, 100)) { 105 | return 0u; 106 | } 107 | result |= uint32_t(m_data.isHigh()) << i; 108 | } 109 | 110 | // alighn the bits to have the binary tag in front. This code 111 | // was taken almost unchanged from the linux kernel. 112 | for (auto i = 0u; i < length; i++) { 113 | result = (result >> 1) | (result & 1) << (length - 1u); 114 | if ((result & 0xfe4210) == 0x7c0000) { 115 | return result; 116 | } 117 | } 118 | return 0u; 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveBitFields: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: Right 11 | AlignOperands: Align 12 | AlignTrailingComments: true 13 | AllowAllArgumentsOnNextLine: true 14 | AllowAllConstructorInitializersOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortEnumsOnASingleLine: true 17 | AllowShortBlocksOnASingleLine: Never 18 | AllowShortCaseLabelsOnASingleLine: false 19 | AllowShortFunctionsOnASingleLine: false 20 | AllowShortLambdasOnASingleLine: All 21 | AllowShortIfStatementsOnASingleLine: Never 22 | AllowShortLoopsOnASingleLine: false 23 | AlwaysBreakAfterDefinitionReturnType: None 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: false 26 | AlwaysBreakTemplateDeclarations: true 27 | BinPackArguments: true 28 | BinPackParameters: true 29 | BraceWrapping: 30 | AfterCaseLabel: false 31 | AfterClass: false 32 | AfterControlStatement: Never 33 | AfterEnum: false 34 | AfterFunction: false 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: false 38 | AfterUnion: false 39 | AfterExternBlock: false 40 | BeforeCatch: false 41 | BeforeElse: false 42 | BeforeLambdaBody: false 43 | BeforeWhile: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeBraces: Attach 50 | BreakBeforeInheritanceComma: false 51 | BreakInheritanceList: BeforeColon 52 | BreakBeforeTernaryOperators: true 53 | BreakConstructorInitializersBeforeComma: true 54 | BreakConstructorInitializers: BeforeColon 55 | BreakAfterJavaFieldAnnotations: false 56 | BreakStringLiterals: true 57 | ColumnLimit: 120 58 | CommentPragmas: '^ IWYU pragma:' 59 | CompactNamespaces: false 60 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 61 | ConstructorInitializerIndentWidth: 0 62 | ContinuationIndentWidth: 4 63 | Cpp11BracedListStyle: true 64 | DeriveLineEnding: true 65 | DerivePointerAlignment: false 66 | DisableFormat: false 67 | ExperimentalAutoDetectBinPacking: false 68 | FixNamespaceComments: true 69 | ForEachMacros: 70 | - foreach 71 | - Q_FOREACH 72 | - BOOST_FOREACH 73 | IncludeBlocks: Preserve 74 | IncludeCategories: 75 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 76 | Priority: 2 77 | SortPriority: 0 78 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 79 | Priority: 3 80 | SortPriority: 0 81 | - Regex: '.*' 82 | Priority: 1 83 | SortPriority: 0 84 | IncludeIsMainRegex: '(Test)?$' 85 | IncludeIsMainSourceRegex: '' 86 | IndentCaseLabels: true 87 | IndentCaseBlocks: false 88 | IndentGotoLabels: true 89 | IndentPPDirectives: None 90 | IndentExternBlock: AfterExternBlock 91 | IndentWidth: 2 92 | IndentWrappedFunctionNames: false 93 | InsertTrailingCommas: None 94 | JavaScriptQuotes: Leave 95 | JavaScriptWrapImports: true 96 | KeepEmptyLinesAtTheStartOfBlocks: true 97 | MacroBlockBegin: '' 98 | MacroBlockEnd: '' 99 | MaxEmptyLinesToKeep: 1 100 | NamespaceIndentation: None 101 | ObjCBinPackProtocolList: Auto 102 | ObjCBlockIndentWidth: 2 103 | ObjCBreakBeforeNestedBlockParam: true 104 | ObjCSpaceAfterProperty: false 105 | ObjCSpaceBeforeProtocolList: true 106 | PenaltyBreakAssignment: 2 107 | PenaltyBreakBeforeFirstCallParameter: 19 108 | PenaltyBreakComment: 300 109 | PenaltyBreakFirstLessLess: 120 110 | PenaltyBreakString: 1000 111 | PenaltyBreakTemplateDeclaration: 10 112 | PenaltyExcessCharacter: 1000000 113 | PenaltyReturnTypeOnItsOwnLine: 60 114 | PointerAlignment: Right 115 | ReflowComments: true 116 | SortIncludes: true 117 | SortUsingDeclarations: true 118 | SpaceAfterCStyleCast: false 119 | SpaceAfterLogicalNot: false 120 | SpaceAfterTemplateKeyword: true 121 | SpaceBeforeAssignmentOperators: true 122 | SpaceBeforeCpp11BracedList: false 123 | SpaceBeforeCtorInitializerColon: true 124 | SpaceBeforeInheritanceColon: true 125 | SpaceBeforeParens: ControlStatements 126 | SpaceBeforeRangeBasedForLoopColon: true 127 | SpaceInEmptyBlock: false 128 | SpaceInEmptyParentheses: false 129 | SpacesBeforeTrailingComments: 1 130 | SpacesInAngles: false 131 | SpacesInConditionalStatement: false 132 | SpacesInContainerLiterals: true 133 | SpacesInCStyleCastParentheses: false 134 | SpacesInParentheses: false 135 | SpacesInSquareBrackets: false 136 | SpaceBeforeSquareBrackets: false 137 | Standard: Latest 138 | StatementMacros: 139 | - Q_UNUSED 140 | - QT_REQUIRE_VERSION 141 | TabWidth: 8 142 | UseCRLF: false 143 | UseTab: Never 144 | WhitespaceSensitiveMacros: 145 | - STRINGIZE 146 | - PP_STRINGIZE 147 | - BOOST_PP_STRINGIZE 148 | ... 149 | 150 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/DigitalPin.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | /// Digital signal edge types. 22 | enum class Edge { any, falling, rising }; 23 | 24 | /// Digital pin base constants. 25 | /// 26 | /// This implementation was born out of the need for faster digital I/O. The 27 | /// original digitalRead(...) function of Arduino suite needs about 2.7us per 28 | /// call on a 16MHz MCU. Which makes it impossible to poll 5us pulses, not 29 | /// talking about doing with the data something in between. This was a hard 30 | /// requirement for this project, so following solution is up to 50% faster 31 | /// and needs about 1.6us per call on the same hardware. 32 | 33 | template 34 | struct DigitalPin { 35 | using RegType = uint8_t; 36 | const RegType mask; 37 | const RegType port; 38 | DigitalPin() 39 | : mask(digitalPinToBitMask(Id)) 40 | , port(digitalPinToPort(Id)) { 41 | } 42 | }; 43 | 44 | /// Digital Output class. 45 | template 46 | class DigitalOutput { 47 | public: 48 | /// Constructor. 49 | DigitalOutput() 50 | : m_output(*portOutputRegister(m_pin.port)) { 51 | *portModeRegister(m_pin.port) |= m_pin.mask; 52 | } 53 | 54 | /// Sets output high. 55 | void setHigh() const { 56 | m_output |= m_pin.mask; 57 | } 58 | 59 | /// Sets output low. 60 | void setLow() const { 61 | m_output &= ~m_pin.mask; 62 | } 63 | 64 | /// Sets output to the given value. 65 | void set(bool value) const { 66 | value ? setHigh() : setLow(); 67 | } 68 | 69 | /// Toggles the output. 70 | void toggle() const { 71 | m_output ^= m_pin.mask; 72 | } 73 | 74 | /// Triggers a pulse of given duration. 75 | /// @param[in] duration is the duration in microseconds 76 | void pulse(uint16_t duration = 0) const { 77 | toggle(); 78 | if (duration) { 79 | delayMicroseconds(duration); 80 | } 81 | toggle(); 82 | } 83 | 84 | private: 85 | const DigitalPin m_pin; 86 | volatile typename DigitalPin::RegType &m_output; 87 | }; 88 | 89 | /// Digital input class. 90 | template 91 | class DigitalInput { 92 | public: 93 | /// Constructor. 94 | DigitalInput() 95 | : m_input(*portInputRegister(m_pin.port)) { 96 | *portModeRegister(m_pin.port) &= ~m_pin.mask; 97 | if (Pullup) { 98 | *portOutputRegister(m_pin.port) |= m_pin.mask; 99 | } 100 | } 101 | 102 | operator bool() const { 103 | return isHigh(); 104 | } 105 | 106 | /// Read raw bit data 107 | uint8_t read() const { 108 | return m_input & m_pin.mask; 109 | } 110 | 111 | /// Checks if the input is high. 112 | bool isHigh() const { 113 | return read(); 114 | } 115 | 116 | /// Checks if the input is low 117 | bool isLow() const { 118 | return !read(); 119 | } 120 | 121 | /// Waits for an edge with given timeout. 122 | /// @param[in] edge is the type of edge to wait for 123 | /// @param[in] timeount is the timeout in microseconds 124 | uint16_t wait(Edge edge, uint16_t timeout) const { 125 | if (edge == Edge::falling) { 126 | return waitImpl(timeout, [](uint8_t a, uint8_t) {return a;}); 127 | } 128 | if (edge == Edge::rising) { 129 | return waitImpl(timeout, [](uint8_t, uint8_t b) {return b;}); 130 | } 131 | // edge == Edge::rising 132 | return waitImpl(timeout, [](uint8_t a, uint8_t b) {return a|b;}); 133 | } 134 | 135 | /// Waits for a state with given timeout. 136 | /// @param[in] state is the state to wait for 137 | /// @param[in] timeount is the timeout in microseconds 138 | uint16_t wait(bool state, uint16_t timeout) const { 139 | for (; state != isHigh() && timeout; timeout--) 140 | ; 141 | return timeout; 142 | } 143 | 144 | private: 145 | DigitalPin m_pin; 146 | volatile typename DigitalPin::RegType &m_input; 147 | 148 | template 149 | uint16_t waitImpl(uint16_t timeout, T compare) const { 150 | auto last = read(); 151 | for (; timeout; timeout--) { 152 | const auto next = read(); 153 | if (last != next) { 154 | if (compare(last, next)) { 155 | return timeout; 156 | } 157 | last = next; 158 | } 159 | } 160 | return 0u; 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/HidDevice.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #pragma once 17 | 18 | #include 19 | 20 | class HidDevice : public PluggableUSBModule { 21 | public: 22 | 23 | explicit HidDevice() 24 | : PluggableUSBModule(1, 1, epType) { 25 | PluggableUSB().plug(this); 26 | } 27 | 28 | void AppendDescriptor(HIDSubDescriptor *node) { 29 | 30 | if (rootNode == nullptr) { 31 | rootNode = node; 32 | } else { 33 | auto current = rootNode; 34 | while (current->next) { 35 | current = current->next; 36 | } 37 | current->next = node; 38 | } 39 | descriptorSize += node->length; 40 | } 41 | 42 | int SendReport(uint8_t id, const void *data, int len) const { 43 | 44 | const auto ret = USB_Send(pluggedEndpoint, &id, 1); 45 | if (ret < 0) { 46 | return ret; 47 | } 48 | 49 | const auto ret2 = USB_Send(pluggedEndpoint | TRANSFER_RELEASE, data, len); 50 | if (ret2 < 0) { 51 | return ret2; 52 | } 53 | 54 | return ret + ret2; 55 | } 56 | 57 | protected: 58 | 59 | int getInterface(uint8_t *interfaceCount) override { 60 | *interfaceCount += 1; // uses 1 61 | HIDDescriptor hidInterface { 62 | D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), 63 | D_HIDREPORT(descriptorSize), 64 | D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01) 65 | }; 66 | return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); 67 | } 68 | 69 | int getDescriptor(USBSetup &setup) override { 70 | 71 | // Check if this is a HID Class Descriptor request 72 | if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) { 73 | return 0; 74 | } 75 | if (setup.wValueH != HID_REPORT_DESCRIPTOR_TYPE) { 76 | return 0; 77 | } 78 | 79 | // In a HID Class Descriptor wIndex contains the interface number 80 | if (setup.wIndex != pluggedInterface) { 81 | return 0; 82 | } 83 | 84 | int total = 0; 85 | for (auto node = rootNode; node; node = node->next) { 86 | const auto res = USB_SendControl(0, node->data, node->length); 87 | if (res < 0) { 88 | return -1; 89 | } 90 | total += res; 91 | } 92 | 93 | // Reset the protocol on reenumeration. Normally the host should not 94 | // assume the state of the protocol due to the USB specs, but Windows 95 | // and Linux just assumes its in report mode. 96 | protocol = HID_REPORT_PROTOCOL; 97 | 98 | return total; 99 | } 100 | 101 | uint8_t getShortName(char *name) override { 102 | name[0] = 'H'; 103 | name[1] = 'I'; 104 | name[2] = 'D'; 105 | name[3] = 'A' + (descriptorSize & 0x0F); 106 | name[4] = 'A' + ((descriptorSize >> 4) & 0x0F); 107 | return 5; 108 | } 109 | 110 | bool setup(USBSetup &setup) override { 111 | 112 | if (pluggedInterface != setup.wIndex) { 113 | return false; 114 | } 115 | 116 | const auto request = setup.bRequest; 117 | const auto requestType = setup.bmRequestType; 118 | 119 | if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE) { 120 | if (request == HID_GET_REPORT) { 121 | // TODO: HID_GetReport(); 122 | return true; 123 | } 124 | if (request == HID_GET_PROTOCOL) { 125 | // TODO: Send8(protocol); 126 | return true; 127 | } 128 | if (request == HID_GET_IDLE) { 129 | // TODO: Send8(idle); 130 | } 131 | } 132 | 133 | if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE) { 134 | if (request == HID_SET_PROTOCOL) { 135 | // The USB Host tells us if we are in boot or report mode. 136 | // This only works with a real boot compatible device. 137 | protocol = setup.wValueL; 138 | return true; 139 | } 140 | if (request == HID_SET_IDLE) { 141 | idle = setup.wValueL; 142 | return true; 143 | } 144 | if (request == HID_SET_REPORT) { 145 | // uint8_t reportID = setup.wValueL; 146 | // uint16_t length = setup.wLength; 147 | // uint8_t data[length]; 148 | // Make sure to not read more data than USB_EP_SIZE. 149 | // You can read multiple times through a loop. 150 | // The first byte (may!) contain the reportID on a multreport. 151 | // USB_RecvControl(data, length); 152 | } 153 | } 154 | 155 | return false; 156 | } 157 | 158 | private: 159 | uint8_t epType[1]{EP_TYPE_INTERRUPT_IN}; 160 | HIDSubDescriptor *rootNode{nullptr}; 161 | uint16_t descriptorSize{0}; 162 | uint8_t protocol{HID_REPORT_PROTOCOL}; 163 | uint8_t idle{1}; 164 | }; 165 | 166 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/HidJoystick.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "Buffer.h" 20 | #include "HidDevice.h" 21 | #include "Joystick.h" 22 | #include "Utilities.h" 23 | #include 24 | 25 | class HidJoystick { 26 | public: 27 | bool init(Joystick *joystick) { 28 | if (!joystick || !joystick->init()) { 29 | return false; 30 | } 31 | 32 | m_joystick = joystick; 33 | m_hidDescription = createDescription(*m_joystick); 34 | m_subDescriptor = new HIDSubDescriptor{m_hidDescription.data, m_hidDescription.size}; 35 | m_hidDevice.AppendDescriptor(m_subDescriptor); 36 | 37 | log("Detected device: %s", joystick->getDescription().name); 38 | return true; 39 | } 40 | 41 | bool update() { 42 | if (!m_joystick || !m_joystick->update()) { 43 | return false; 44 | } 45 | 46 | const auto packet = createPacket(*m_joystick); 47 | m_hidDevice.SendReport(DEVICE_ID, packet.data, packet.size); 48 | return true; 49 | } 50 | 51 | private: 52 | using BufferType = Buffer<255>; 53 | static const uint8_t DEVICE_ID{3}; 54 | 55 | static BufferType createDescription(const Joystick &joystick) { 56 | 57 | enum class ID : uint8_t { 58 | application = 0x01, 59 | button = 0x09, 60 | collection = 0xa1, 61 | end_collection = 0xc0, 62 | generic_desktop = 0x01, 63 | hat_switch = 0x39, 64 | input = 0x81, 65 | input_const = 0x03, 66 | input_data = 0x02, 67 | joystick = 0x04, 68 | logical_max = 0x26, 69 | logical_min = 0x15, 70 | report_count = 0x95, 71 | report_id = 0x85, 72 | report_size = 0x75, 73 | simulation_controls = 0x02, 74 | throttle = 0xbb, 75 | usage = 0x09, 76 | usage_max = 0x29, 77 | usage_min = 0x19, 78 | usage_page = 0x05, 79 | }; 80 | 81 | const auto &desc = joystick.getDescription(); 82 | BufferType buffer; 83 | auto filler = BufferFiller(buffer); 84 | 85 | auto pushData = [&filler](uint8_t size, uint8_t count) { 86 | filler.push(ID::report_size).push(size); 87 | filler.push(ID::report_count).push(count); 88 | filler.push(ID::input).push(ID::input_data); 89 | const auto padding = (size * count) % 8u; 90 | if (padding) { 91 | filler.push(ID::report_size).push(8u - padding); 92 | filler.push(ID::report_count).push(1); 93 | filler.push(ID::input).push(ID::input_const); 94 | } 95 | }; 96 | 97 | filler.push(ID::usage_page).push(ID::generic_desktop); 98 | filler.push(ID::usage).push(ID::joystick); 99 | filler.push(ID::collection).push(ID::application); 100 | filler.push(ID::report_id).push(DEVICE_ID); 101 | 102 | // Push axes 103 | if (desc.numAxes > 0) { 104 | filler.push(ID::usage_page).push(ID::generic_desktop); 105 | for (auto i = 0u; i < desc.numAxes; i++) { 106 | static constexpr uint8_t x_axis = 0x30; 107 | filler.push(ID::usage).push(x_axis + i); 108 | } 109 | filler.push(ID::logical_min).push(0); 110 | filler.push(ID::logical_max).push(1023); 111 | pushData(10, desc.numAxes); 112 | } 113 | 114 | // Push hat 115 | if (desc.hasHat) { 116 | filler.push(ID::usage).push(ID::hat_switch); 117 | filler.push(ID::logical_min).push(1); 118 | filler.push(ID::logical_max).push(8); 119 | pushData(4, 1); 120 | } 121 | 122 | // Push buttons 123 | if (desc.numButtons > 0) { 124 | filler.push(ID::usage_page).push(ID::button); 125 | filler.push(ID::usage_min).push(1); 126 | filler.push(ID::usage_max).push(desc.numButtons); 127 | filler.push(ID::logical_min).push(0); 128 | filler.push(ID::logical_max).push(1); 129 | pushData(1, desc.numButtons); 130 | } 131 | 132 | filler.push(ID::end_collection); 133 | return buffer; 134 | } 135 | 136 | static BufferType createPacket(const Joystick &joystick) { 137 | 138 | const auto &state = joystick.getState(); 139 | const auto &description = joystick.getDescription(); 140 | BufferType buffer; 141 | auto filler = BufferFiller(buffer); 142 | 143 | for (auto i = 0u; i < description.numAxes; i++) { 144 | filler.push(state.axes[i], 10); 145 | } 146 | filler.align(); 147 | 148 | if (description.hasHat) { 149 | filler.push(state.hat, 4); 150 | filler.align(); 151 | } 152 | 153 | if (description.numButtons) { 154 | filler.push(state.buttons, description.numButtons); 155 | filler.align(); 156 | } 157 | 158 | return buffer; 159 | } 160 | 161 | Joystick *m_joystick{}; 162 | BufferType m_hidDescription{}; 163 | HIDSubDescriptor *m_subDescriptor{}; 164 | HidDevice m_hidDevice; 165 | }; 166 | -------------------------------------------------------------------------------- /kicad/gameport-adapter.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "design_settings": { 4 | "defaults": { 5 | "board_outline_line_width": 0.049999999999999996, 6 | "copper_line_width": 0.19999999999999998, 7 | "copper_text_italic": false, 8 | "copper_text_size_h": 1.5, 9 | "copper_text_size_v": 1.5, 10 | "copper_text_thickness": 0.3, 11 | "copper_text_upright": false, 12 | "courtyard_line_width": 0.049999999999999996, 13 | "dimension_precision": 4, 14 | "dimension_units": 3, 15 | "dimensions": { 16 | "arrow_length": 1270000, 17 | "extension_offset": 500000, 18 | "keep_text_aligned": true, 19 | "suppress_zeroes": false, 20 | "text_position": 0, 21 | "units_format": 1 22 | }, 23 | "fab_line_width": 0.09999999999999999, 24 | "fab_text_italic": false, 25 | "fab_text_size_h": 1.0, 26 | "fab_text_size_v": 1.0, 27 | "fab_text_thickness": 0.15, 28 | "fab_text_upright": false, 29 | "other_line_width": 0.09999999999999999, 30 | "other_text_italic": false, 31 | "other_text_size_h": 1.0, 32 | "other_text_size_v": 1.0, 33 | "other_text_thickness": 0.15, 34 | "other_text_upright": false, 35 | "pads": { 36 | "drill": 0.762, 37 | "height": 1.524, 38 | "width": 1.524 39 | }, 40 | "silk_line_width": 0.12, 41 | "silk_text_italic": false, 42 | "silk_text_size_h": 1.0, 43 | "silk_text_size_v": 1.0, 44 | "silk_text_thickness": 0.15, 45 | "silk_text_upright": false, 46 | "zones": { 47 | "45_degree_only": false, 48 | "min_clearance": 0.254 49 | } 50 | }, 51 | "diff_pair_dimensions": [], 52 | "drc_exclusions": [], 53 | "meta": { 54 | "filename": "board_design_settings.json", 55 | "version": 2 56 | }, 57 | "rule_severities": { 58 | "annular_width": "error", 59 | "clearance": "error", 60 | "copper_edge_clearance": "error", 61 | "courtyards_overlap": "error", 62 | "diff_pair_gap_out_of_range": "error", 63 | "diff_pair_uncoupled_length_too_long": "error", 64 | "drill_out_of_range": "error", 65 | "duplicate_footprints": "warning", 66 | "extra_footprint": "warning", 67 | "footprint_type_mismatch": "error", 68 | "hole_clearance": "error", 69 | "hole_near_hole": "error", 70 | "invalid_outline": "error", 71 | "item_on_disabled_layer": "error", 72 | "items_not_allowed": "error", 73 | "length_out_of_range": "error", 74 | "malformed_courtyard": "error", 75 | "microvia_drill_out_of_range": "error", 76 | "missing_courtyard": "ignore", 77 | "missing_footprint": "warning", 78 | "net_conflict": "warning", 79 | "npth_inside_courtyard": "ignore", 80 | "padstack": "error", 81 | "pth_inside_courtyard": "ignore", 82 | "shorting_items": "error", 83 | "silk_over_copper": "warning", 84 | "silk_overlap": "warning", 85 | "skew_out_of_range": "error", 86 | "through_hole_pad_without_hole": "error", 87 | "too_many_vias": "error", 88 | "track_dangling": "warning", 89 | "track_width": "error", 90 | "tracks_crossing": "error", 91 | "unconnected_items": "error", 92 | "unresolved_variable": "error", 93 | "via_dangling": "warning", 94 | "zone_has_empty_net": "error", 95 | "zones_intersect": "error" 96 | }, 97 | "rule_severitieslegacy_courtyards_overlap": true, 98 | "rule_severitieslegacy_no_courtyard_defined": false, 99 | "rules": { 100 | "allow_blind_buried_vias": false, 101 | "allow_microvias": false, 102 | "max_error": 0.005, 103 | "min_clearance": 0.0, 104 | "min_copper_edge_clearance": 0.024999999999999998, 105 | "min_hole_clearance": 0.25, 106 | "min_hole_to_hole": 0.25, 107 | "min_microvia_diameter": 0.19999999999999998, 108 | "min_microvia_drill": 0.09999999999999999, 109 | "min_silk_clearance": 0.0, 110 | "min_through_hole_diameter": 0.3, 111 | "min_track_width": 0.19999999999999998, 112 | "min_via_annular_width": 0.049999999999999996, 113 | "min_via_diameter": 0.39999999999999997, 114 | "use_height_for_length_calcs": true 115 | }, 116 | "track_widths": [], 117 | "via_dimensions": [], 118 | "zones_allow_external_fillets": false, 119 | "zones_use_no_outline": true 120 | }, 121 | "layer_presets": [] 122 | }, 123 | "boards": [], 124 | "cvpcb": { 125 | "equivalence_files": [] 126 | }, 127 | "erc": { 128 | "erc_exclusions": [], 129 | "meta": { 130 | "version": 0 131 | }, 132 | "pin_map": [ 133 | [ 134 | 0, 135 | 0, 136 | 0, 137 | 0, 138 | 0, 139 | 0, 140 | 1, 141 | 0, 142 | 0, 143 | 0, 144 | 0, 145 | 2 146 | ], 147 | [ 148 | 0, 149 | 2, 150 | 0, 151 | 1, 152 | 0, 153 | 0, 154 | 1, 155 | 0, 156 | 2, 157 | 2, 158 | 2, 159 | 2 160 | ], 161 | [ 162 | 0, 163 | 0, 164 | 0, 165 | 0, 166 | 0, 167 | 0, 168 | 1, 169 | 0, 170 | 1, 171 | 0, 172 | 1, 173 | 2 174 | ], 175 | [ 176 | 0, 177 | 1, 178 | 0, 179 | 0, 180 | 0, 181 | 0, 182 | 1, 183 | 1, 184 | 2, 185 | 1, 186 | 1, 187 | 2 188 | ], 189 | [ 190 | 0, 191 | 0, 192 | 0, 193 | 0, 194 | 0, 195 | 0, 196 | 1, 197 | 0, 198 | 0, 199 | 0, 200 | 0, 201 | 2 202 | ], 203 | [ 204 | 0, 205 | 0, 206 | 0, 207 | 0, 208 | 0, 209 | 0, 210 | 0, 211 | 0, 212 | 0, 213 | 0, 214 | 0, 215 | 2 216 | ], 217 | [ 218 | 1, 219 | 1, 220 | 1, 221 | 1, 222 | 1, 223 | 0, 224 | 1, 225 | 1, 226 | 1, 227 | 1, 228 | 1, 229 | 2 230 | ], 231 | [ 232 | 0, 233 | 0, 234 | 0, 235 | 1, 236 | 0, 237 | 0, 238 | 1, 239 | 0, 240 | 0, 241 | 0, 242 | 0, 243 | 2 244 | ], 245 | [ 246 | 0, 247 | 2, 248 | 1, 249 | 2, 250 | 0, 251 | 0, 252 | 1, 253 | 0, 254 | 2, 255 | 2, 256 | 2, 257 | 2 258 | ], 259 | [ 260 | 0, 261 | 2, 262 | 0, 263 | 1, 264 | 0, 265 | 0, 266 | 1, 267 | 0, 268 | 2, 269 | 0, 270 | 0, 271 | 2 272 | ], 273 | [ 274 | 0, 275 | 2, 276 | 1, 277 | 1, 278 | 0, 279 | 0, 280 | 1, 281 | 0, 282 | 2, 283 | 0, 284 | 0, 285 | 2 286 | ], 287 | [ 288 | 2, 289 | 2, 290 | 2, 291 | 2, 292 | 2, 293 | 2, 294 | 2, 295 | 2, 296 | 2, 297 | 2, 298 | 2, 299 | 2 300 | ] 301 | ], 302 | "rule_severities": { 303 | "bus_definition_conflict": "error", 304 | "bus_entry_needed": "error", 305 | "bus_label_syntax": "error", 306 | "bus_to_bus_conflict": "error", 307 | "bus_to_net_conflict": "error", 308 | "different_unit_footprint": "error", 309 | "different_unit_net": "error", 310 | "duplicate_reference": "error", 311 | "duplicate_sheet_names": "error", 312 | "extra_units": "error", 313 | "global_label_dangling": "warning", 314 | "hier_label_mismatch": "error", 315 | "label_dangling": "error", 316 | "lib_symbol_issues": "warning", 317 | "multiple_net_names": "warning", 318 | "net_not_bus_member": "warning", 319 | "no_connect_connected": "warning", 320 | "no_connect_dangling": "warning", 321 | "pin_not_connected": "error", 322 | "pin_not_driven": "error", 323 | "pin_to_pin": "warning", 324 | "power_pin_not_driven": "error", 325 | "similar_labels": "warning", 326 | "unannotated": "error", 327 | "unit_value_mismatch": "error", 328 | "unresolved_variable": "error", 329 | "wire_dangling": "error" 330 | } 331 | }, 332 | "libraries": { 333 | "pinned_footprint_libs": [], 334 | "pinned_symbol_libs": [] 335 | }, 336 | "meta": { 337 | "filename": "gameport-adapter.kicad_pro", 338 | "version": 1 339 | }, 340 | "net_settings": { 341 | "classes": [ 342 | { 343 | "bus_width": 12.0, 344 | "clearance": 0.25, 345 | "diff_pair_gap": 0.25, 346 | "diff_pair_via_gap": 0.25, 347 | "diff_pair_width": 0.2, 348 | "line_style": 0, 349 | "microvia_diameter": 0.3, 350 | "microvia_drill": 0.1, 351 | "name": "Default", 352 | "pcb_color": "rgba(0, 0, 0, 0.000)", 353 | "schematic_color": "rgba(0, 0, 0, 0.000)", 354 | "track_width": 0.35, 355 | "via_diameter": 0.8, 356 | "via_drill": 0.4, 357 | "wire_width": 6.0 358 | }, 359 | { 360 | "bus_width": 12.0, 361 | "clearance": 0.35, 362 | "diff_pair_gap": 0.25, 363 | "diff_pair_via_gap": 0.25, 364 | "diff_pair_width": 0.2, 365 | "line_style": 0, 366 | "microvia_diameter": 0.3, 367 | "microvia_drill": 0.1, 368 | "name": "Power", 369 | "nets": [ 370 | "GND", 371 | "VCC" 372 | ], 373 | "pcb_color": "rgba(0, 0, 0, 0.000)", 374 | "schematic_color": "rgba(0, 0, 0, 0.000)", 375 | "track_width": 1.0, 376 | "via_diameter": 0.8, 377 | "via_drill": 0.4, 378 | "wire_width": 6.0 379 | } 380 | ], 381 | "meta": { 382 | "version": 2 383 | }, 384 | "net_colors": null 385 | }, 386 | "pcbnew": { 387 | "last_paths": { 388 | "gencad": "", 389 | "idf": "", 390 | "netlist": "", 391 | "specctra_dsn": "", 392 | "step": "", 393 | "vrml": "" 394 | }, 395 | "page_layout_descr_file": "" 396 | }, 397 | "schematic": { 398 | "annotate_start_num": 0, 399 | "drawing": { 400 | "default_line_thickness": 6.0, 401 | "default_text_size": 50.0, 402 | "field_names": [], 403 | "intersheets_ref_own_page": false, 404 | "intersheets_ref_prefix": "", 405 | "intersheets_ref_short": false, 406 | "intersheets_ref_show": false, 407 | "intersheets_ref_suffix": "", 408 | "junction_size_choice": 3, 409 | "label_size_ratio": 0.25, 410 | "pin_symbol_size": 0.0, 411 | "text_offset_ratio": 0.08 412 | }, 413 | "legacy_lib_dir": "", 414 | "legacy_lib_list": [], 415 | "meta": { 416 | "version": 1 417 | }, 418 | "net_format_name": "", 419 | "ngspice": { 420 | "fix_include_paths": true, 421 | "fix_passive_vals": false, 422 | "meta": { 423 | "version": 0 424 | }, 425 | "model_mode": 0, 426 | "workbook_filename": "" 427 | }, 428 | "page_layout_descr_file": "", 429 | "plot_directory": "", 430 | "spice_adjust_passive_values": false, 431 | "spice_external_command": "spice \"%I\"", 432 | "subpart_first_id": 65, 433 | "subpart_id_separator": 0 434 | }, 435 | "sheets": [ 436 | [ 437 | "bd3a3088-a7a8-46b0-8d32-b4cc0ac8ed31", 438 | "" 439 | ] 440 | ], 441 | "text_variables": {} 442 | } 443 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/Logitech.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "DigitalPin.h" 20 | #include "GamePort.h" 21 | #include "Joystick.h" 22 | #include "Utilities.h" 23 | 24 | class Logitech : public Joystick { 25 | public: 26 | bool init() override { 27 | enableDigitalMode(); 28 | if (!readMetaData()) { 29 | return false; 30 | } 31 | 32 | // Create joystick description 33 | m_description.name = m_metaData.deviceName; 34 | m_description.numAxes = min(Joystick::MAX_AXES, 35 | m_metaData.num10bitAxes + 36 | m_metaData.num8bitAxes + 37 | m_metaData.numSecondaryHats * 2); // Each hat is mapped to two axes 38 | m_description.numButtons = m_metaData.numPrimaryButtons + m_metaData.numSecondaryButtons; 39 | m_description.hasHat = m_metaData.hasHat; 40 | 41 | // If the device is a Logitech ThunderPad Digital, manually redefine the gamepad layout to 2 axes and 8 buttons 42 | if(m_metaData.deviceID == DEVICE_THUNDERPAD_DIGITAL){ 43 | m_description.numAxes = 2; 44 | m_description.numButtons = 8; 45 | m_description.hasHat = 0; 46 | } 47 | // If the device is a Logitech WingMan Gamepad, manually redefine the gamepad layout to 2 axes and 11 buttons 48 | else if(m_metaData.deviceID == DEVICE_WINGMAN_GAMEPAD){ 49 | m_description.numAxes = 2; 50 | m_description.numButtons = 11; 51 | m_description.hasHat = 0; 52 | } 53 | 54 | // Initialize axes centers 55 | uint8_t axis = 0u; 56 | for (auto i = 0u; i < m_metaData.num10bitAxes; i++, axis++) { 57 | m_limits[axis] = { 512 - 256, 512 + 256 }; 58 | } 59 | for (auto i = 0u; i < m_metaData.num8bitAxes; i++, axis++) { 60 | m_limits[axis] = { 128 - 64, 128 + 64 }; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | bool update() override { 67 | 68 | // === Status packet format === 69 | // 70 | // Offset Bits Description 71 | // -------------------------------------------------------- 72 | // 0 4 Low nibble of the Device ID 73 | // 4 4 High nibble of the Device ID 74 | // 8 10*N 10bit axes (N is number of axes) 75 | // ? 8*N 8bit axes (N is number of axes) 76 | // ? 1*N Buttons (N is number of buttons) 77 | // ? R*N Hats (R is resolution, N is number of hats) 78 | // ? 1*N Secondary buttons (N is number of buttons) 79 | 80 | // Cyberman 2 seems not to work properly if the packets are 81 | // read too fast. Following delay will ensure, that after the 82 | // last read at least 5ms passed to ensure, that the joystick 83 | // cooled down again. 84 | delayMicroseconds(5000); 85 | 86 | const auto packet = readPacket(); 87 | 88 | if (packet.size != m_metaData.packageSize) { 89 | return false; 90 | } 91 | 92 | const auto packetID = getBits(packet, 0, 4) | getBits(packet, 4, 4) << 4; 93 | if (packetID != m_metaData.deviceID) { 94 | return false; 95 | } 96 | 97 | State state; 98 | uint16_t offset = 8u; 99 | 100 | uint8_t axis = 0u; 101 | for (auto i = 0u; i < m_metaData.num10bitAxes; i++, axis++) { 102 | state.axes[axis] = mapAxisValue(axis, getBits(packet, offset, 10)); 103 | offset += 10; 104 | } 105 | 106 | for (auto i = 0u; i < m_metaData.num8bitAxes; i++, axis++) { 107 | state.axes[axis] = mapAxisValue(axis, getBits(packet, offset, 8)); 108 | offset += 8; 109 | } 110 | 111 | uint16_t button = 0u; 112 | for (auto i = 0u; i < m_metaData.numPrimaryButtons; i++) { 113 | state.buttons |= getBits(packet, offset++, 1) << button++; 114 | } 115 | 116 | if (m_metaData.hasHat) { 117 | 118 | const auto hatResolution = getHatResolution(); 119 | state.hat = mapHatValue(getBits(packet, offset, hatResolution)); 120 | offset += hatResolution; 121 | 122 | // Secondary hats are all shown as dual axes 123 | for (auto i = 0u; i < m_metaData.numSecondaryHats; i++, axis += 2) { 124 | const auto value = mapHatValue(getBits(packet, offset, hatResolution)); 125 | offset += hatResolution; 126 | static constexpr uint16_t dx[] = { 511, 511, 1023, 1023, 1023, 511, 0, 0, 0 }; 127 | static constexpr uint16_t dy[] = { 511, 0, 0, 511, 1023, 1023, 1023, 511, 0 }; 128 | state.axes[axis + 0] = dx[value]; 129 | state.axes[axis + 1] = dy[value]; 130 | } 131 | } 132 | 133 | for (auto i = 0u; i < m_metaData.numSecondaryButtons; i++) { 134 | state.buttons |= getBits(packet, offset++, 1) << button++; 135 | } 136 | 137 | // If the device is a Logitech ThunderPad Digital, manually remap up, down, left and right buttons to X and Y axes 138 | if(m_metaData.deviceID == DEVICE_THUNDERPAD_DIGITAL){ 139 | const auto value = getBits(packet, 12, 4); 140 | static constexpr uint16_t dx[] = { 511, 0, 511, 0, 1023, 511, 1023, 511, 511, 0, 511, 0, 1023, 511, 1023, 511 }; 141 | static constexpr uint16_t dy[] = { 511, 511, 1023, 1023, 511, 511, 1023, 1023, 0, 0, 511, 511, 0, 0, 511, 511 }; 142 | state.axes[0] = dx[value]; 143 | state.axes[1] = dy[value]; 144 | 145 | state.buttons &= 0xFF0F; 146 | state.buttons |= (state.buttons & 0x0F00) >> 4; 147 | } 148 | // If the device is a Logitech WingMan Gamepad, manually remap up, down, left and right buttons to X and Y axes 149 | else if(m_metaData.deviceID == DEVICE_WINGMAN_GAMEPAD){ 150 | const auto value = getBits(packet, 8, 4); 151 | static constexpr uint16_t dx[] = { 511, 0, 511, 0, 1023, 511, 1023, 511, 511, 0, 511, 0, 1023, 511, 1023, 511 }; 152 | static constexpr uint16_t dy[] = { 511, 511, 1023, 1023, 511, 511, 1023, 1023, 0, 0, 511, 511, 0, 0, 511, 511 }; 153 | state.axes[0] = dx[value]; 154 | state.axes[1] = dy[value]; 155 | 156 | state.buttons >>= 4; 157 | } 158 | 159 | m_state = state; 160 | return true; 161 | } 162 | 163 | const State &getState() const override { 164 | return m_state; 165 | } 166 | 167 | const Description &getDescription() const override { 168 | return m_description; 169 | } 170 | 171 | private: 172 | struct MetaData { 173 | char deviceName[32]{}; 174 | uint8_t deviceID{}; 175 | uint8_t packageSize{}; 176 | uint8_t num8bitAxes{}; 177 | uint8_t num10bitAxes{}; 178 | uint8_t numPrimaryButtons{}; 179 | uint8_t numSecondaryButtons{}; 180 | uint8_t numSecondaryHats{}; 181 | uint8_t hasHat{}; 182 | uint8_t numHatDirections{}; 183 | }; 184 | 185 | struct Limits { 186 | uint16_t min, max; 187 | }; 188 | 189 | // Logitech Device ID constants, taken from the Linux Kernel ADI driver 190 | enum LogitechDevices{ 191 | DEVICE_WINGMAN_EXTREME_DIGITAL = 0x00, 192 | DEVICE_THUNDERPAD_DIGITAL = 0x01, 193 | DEVICE_SIDECAR = 0x02, 194 | DEVICE_CYBERMAN2 = 0x03, 195 | DEVICE_WINGMAN_INTERCEPTOR = 0x04, 196 | DEVICE_WINGMAN_FORMULA = 0x05, 197 | DEVICE_WINGMAN_GAMEPAD = 0x06, 198 | DEVICE_WINGMAN_EXTREME_DIGITAL_3D = 0x07, 199 | DEVICE_WINGMAN_GAMEPAD_EXTREME = 0x08, 200 | DEVICE_WINGMAN_GAMEPAD_USB = 0x09 201 | }; 202 | 203 | uint16_t mapAxisValue(uint8_t axis, uint16_t value) { 204 | if (value < m_limits[axis].min) { 205 | m_limits[axis].min = value; 206 | } else if (value > m_limits[axis].max) { 207 | m_limits[axis].max = value; 208 | } 209 | return map(value, m_limits[axis].min, m_limits[axis].max, 0, 1023); 210 | } 211 | 212 | uint8_t mapHatValue(uint16_t value) const { 213 | return map(value, 0, m_metaData.numHatDirections, 0, 8); 214 | } 215 | 216 | uint8_t getHatResolution() const { 217 | uint8_t result = 0u; 218 | for (auto value = m_metaData.numHatDirections; value; value >>= 1) { 219 | result++; 220 | } 221 | return result; 222 | } 223 | 224 | /// Internal bit structure which is filled by reading from the joystick. 225 | using Packet = Buffer<255>; 226 | 227 | static uint16_t getBits(const Packet& packet, uint8_t offset, uint8_t count) { 228 | uint16_t result = 0u; 229 | if (offset < packet.size && count <= (packet.size - offset)) { 230 | for (auto i = 0u; i < count; i++) { 231 | result = (result << 1) | packet.data[offset + i]; 232 | } 233 | } 234 | return result; 235 | } 236 | 237 | DigitalOutput::pin> m_trigger; 238 | DigitalInput::pin, true> m_data0; 239 | DigitalInput::pin, true> m_data1; 240 | MetaData m_metaData; 241 | Description m_description; 242 | State m_state; 243 | Limits m_limits[Joystick::MAX_AXES]; 244 | 245 | void enableDigitalMode() const { 246 | static constexpr uint16_t seq[] = {4, 2, 3, 10, 6, 11, 7, 9, 11, 0}; 247 | 248 | // Some devices, as the Logitech ThunderPad Digital, require some time for its 249 | // microcontroller to initialize; otherwise the enableDigitalMode command is skipped 250 | // and the device stays in analog mode. Don't use values higher than 100ms, they could 251 | // interfere with the USB initialization 252 | delay(100); 253 | 254 | for (auto i = 0u; seq[i]; i++) { 255 | m_trigger.pulse(20u); 256 | delay(seq[i]); 257 | } 258 | } 259 | 260 | byte readData() const { 261 | const auto b0 = m_data0.read(); 262 | const auto b1 = m_data1.read(); 263 | return bool(b0) | bool(b1) << 1; 264 | } 265 | 266 | Packet readPacket() const { 267 | static constexpr auto TIMEOUT = 32u; 268 | auto timeout = TIMEOUT; 269 | auto first = true; 270 | Packet packet; 271 | const InterruptStopper noirq; 272 | auto last = readData(); 273 | m_trigger.setHigh(); 274 | while (timeout-- && packet.size < Packet::MAX_SIZE) { 275 | const auto next = readData(); 276 | const auto edge = last ^ next; 277 | if (edge) { 278 | if (first) { 279 | first = false; 280 | } else { 281 | // Normally both data bits should never flip simultaneously. 282 | // We should get either 10, when data1 has flipped, or 01, 283 | // when data0 has flipped. So if we just shift the edge to 284 | // the right once, we will get the needed 1 or 0 bit value. 285 | packet.data[packet.size++] = edge >> 1; 286 | } 287 | last = next; 288 | timeout = TIMEOUT; 289 | } 290 | } 291 | m_trigger.setLow(); 292 | return packet; 293 | } 294 | 295 | bool readMetaData() { 296 | 297 | // === Metadata packet format === 298 | // 299 | // Offset Bits Description 300 | // -------------------------------------------------------- 301 | // 0 10 Metadata package size 302 | // 10 4 Low nibble of the Device ID 303 | // 14 4 High nibble of the Device ID 304 | // 18 4 Feature flags 305 | // 22 10 Status package size 306 | // 32 4 Number of axes (0..15) 307 | // 36 6 Number of buttons (0..63) 308 | // 42 6 Number of hat directions (0..63) 309 | // 48 6 Number of secondary buttons (0..63) 310 | // 54 4 Number of secondary hats (0..15) 311 | // 58 4 Number of 8-bit axes (0..15) 312 | // 62 4 Length of the cname in bytes (0..15) 313 | // 66 8*N Characters of the cname (N is the length) 314 | 315 | // === Flags === 316 | // 317 | // Bit Description 318 | // --------------------- 319 | // 1 Reserved 320 | // 2 Reserved 321 | // 3 Has Hat 322 | // 4 Has 10-bit axes 323 | 324 | const auto packet = readPacket(); 325 | 326 | const auto metaSize = getBits(packet, 0, 10); 327 | if (metaSize != packet.size) { 328 | log("Meta data package size mismatch, expected %d but got %d", packet.size, metaSize); 329 | return false; 330 | } 331 | 332 | m_metaData.deviceID = getBits(packet, 10, 4) | getBits(packet, 14, 4) << 4; 333 | const auto flags = getBits(packet, 18, 4); 334 | m_metaData.hasHat = flags & 0x4; 335 | m_metaData.packageSize = getBits(packet, 22, 10); 336 | const auto numTotalAxes = getBits(packet, 32, 4); 337 | m_metaData.numPrimaryButtons = getBits(packet, 36, 6); 338 | m_metaData.numHatDirections = getBits(packet, 42, 6); 339 | m_metaData.numSecondaryButtons = getBits(packet, 48, 6); 340 | m_metaData.numSecondaryHats = getBits(packet, 54, 4); 341 | const auto num8bitAxes = getBits(packet, 58, 4); 342 | if (flags & 0x8) { 343 | m_metaData.num10bitAxes = numTotalAxes - num8bitAxes; 344 | m_metaData.num8bitAxes = num8bitAxes; 345 | } else { 346 | m_metaData.num8bitAxes = numTotalAxes; 347 | m_metaData.num10bitAxes = 0u; 348 | } 349 | const auto cnameLength = getBits(packet, 62, 4); 350 | m_metaData.deviceName[cnameLength] = 0; 351 | for (auto i = 0u; i < cnameLength; i++) { 352 | m_metaData.deviceName[i] = getBits(packet, 66 + 8 * i, 8); 353 | } 354 | 355 | return true; 356 | } 357 | }; 358 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Necroware's GamePort Adapter 2 | 3 | ![GamePort Adapter](./photo.jpg) 4 | 5 | Many people from the retro community still have their beloved joysticks and 6 | gamepads from the early days. These devices often live their lives somewhere in 7 | the dark corners of our basements and we don't dare to throw them away, because 8 | of nostalgic reasons. They remind us so much of our childhoods, where we played 9 | our Wing Commanders, X-Wings, Descents and many other games. These old joysticks 10 | were all made to be connected to the game port, usually on a sound card. But 11 | by the end of 90's and beginning of 2000's game ports vanished from our 12 | computers and were replaced by USB and our old joysticks disappeared into the 13 | past. Today not everybody has a full retro PC and many people are using their 14 | modern computer with DOSBox to play the old games, sometimes with a modern 15 | USB joystick. But wouldn't it be great to play the old games with the same 16 | joystick which we used back then? And this is where this adapter comes into 17 | play. It can be used to connect gameport joysticks to a USB port. 18 | 19 | GamePort Adapter Videos: 20 | * [Part 1: Introduction](https://youtu.be/tSJLgCD8jeM) 21 | * [Part 2: Updates](https://youtu.be/947DewHwbsE) 22 | 23 | ## How does it work? 24 | 25 | The adapter is built around Arduino Pro Micro, which uses the same ATmega32U4 26 | microcontroller as Leonardo. This microcontroller has built-in USB HID 27 | capabilities and can be used to build HID input devices, like joysticks. The 28 | adapter itself is super simple, the main brainwork was invested into the 29 | software. Very much simplified, it reads the joystick states and sends the data, 30 | via USB, to the computer, which thinks that it is communicating with a USB 31 | joystick. 32 | 33 | ## What is special about this one? 34 | 35 | There are already plenty of gameport to USB adapters for basic analog joysticks 36 | and, as far as I know, there were some efforts to communicate with Sidewinder 37 | joysticks by Microsoft as well. But there is no universal adapter, which would 38 | work with different types of joysticks. This adapter implements multiple drivers 39 | for various analog and digital joysticks, with an option to add more, in the 40 | future. 41 | 42 | Features overview: 43 | * Support for generic analog joysticks with 2/4 buttons and 2/4 axes 44 | * Four switches to select joystick type 45 | * Autodetection for various digital protocol joysticks 46 | * Auto-Calibration 47 | * Very low input lag 48 | 49 | ## What is the difference between analog and digital joystick? 50 | 51 | Many people call button-only joysticks or gamepads digital. This is kind of 52 | right, because a button is either pressed or not. You can't have an analog 53 | values in between. However by digital, in this case, we mean something different. 54 | A gameport contains 15 pins, 8 of which are used for joystick communication. 4 55 | pins are for buttons and carry digital values in sense of on/off and 4 pins are 56 | for analog axes, which deliver voltage somewhere between 0V and 5V. Joysticks 57 | which were made in the early days used this pinout. They could have a 58 | maximum of 4 buttons and 4 axes and were DOS compatible. Later, in the time of 59 | Windows 95/98 many joysticks were made to be plugged into a gameport as well, 60 | but they were not limited to 4 buttons and 4 axes. They had a lot more exciting 61 | features, like hat switches and throttle controls. But how did this work? Well 62 | the manufacturers implemented their drivers to communicate with the joystick via 63 | gameport using a proprietary communication protocol. For example, by using one 64 | pin of the gameport as clock and another one as data, the possibilities were 65 | almost limitless. Such joysticks are called digital as well, because they used 66 | digital protocols to communicate with the PC. And suddenly, many features were 67 | possible, but the price of these features was the lost compatibility to DOS. You 68 | couldn't just plug such a joystick into the gameport and expect it to work in 69 | old DOS games. The plug was the same, but the signaling was completely different. 70 | 71 | ## Which joysticks does this adapter support? 72 | 73 | Currently, the following drivers are implemented. To select the right driver, you 74 | have to use four switches, as shown in the table. The switches may be changed or 75 | extended in the future, so please pay attention to the updates. 76 | 77 | Joystick Model | Buttons | Axes | Hat | SW1-4 | Protocol | Comments 78 | -----------------------------|---------|-------|------|-------|------------|------------------------------------ 79 | Generic Analog | 2 | 2 | 0 | 0000 | Analogue | 80 | Generic Analog | 4 | 2 | 0 | 1000 | Analogue | 81 | Generic Analog | 4 | 3 | 0 | 0100 | Analogue | 3rd Axis is throttle 82 | Generic Analog | 4 | 4 | 0 | 1100 | Analogue | 83 | CH FlightStick | 4 | 4 | 1 | 0010 | Analogue | 84 | CH F16 Combat Stick | 10 | 3 | 1 | 0110 | Analogue | 85 | ThrustMaster | 4 | 3 | 1 | 1010 | Analogue | FCS MARK I/II & PFCS 86 | Sidewinder GamePad | 10 | 2 | 0 | 1110 | Sidewinder | 87 | Sidewinder 3D Pro | 8 | 4 | 1 | 1110 | Sidewinder | 88 | Sidewinder 3D Pro Plus | 9 | 4 | 1 | 1110 | Sidewinder | First version of Precision Pro 89 | Sidewinder Precision Pro | 9 | 4 | 1 | 1110 | Sidewinder | 90 | Sidewinder FFB Pro | 9 | 4 | 1 | 1110 | Sidewinder | FFB not yet implemented 91 | Sidewinder FFB Wheel | 8 | 3 | 0 | 1110 | Sidewinder | FFB not yet implemented 92 | Gravis GamePad Pro | 10 | 2 | 0 | 0001 | GrIP | 93 | Logitech WingMan Extreme | 6 | 3 | 1 | 1001 | ADI | 94 | Logitech CyberMan 2 | 8 | 6 | 0 | 1001 | ADI | 95 | Logitech WingMan Interceptor | 9 | 3 | 3 | 1001 | ADI | 2 hats are mapped as 4 axes 96 | Logitech ThunderPad Digital | 8 | 2 | 0 | 1001 | ADI | Directional buttons mapped as 2 axes 97 | Logitech WingMan Gamepad | 11 | 2 | 0 | 1001 | ADI | Directional buttons mapped as 2 axes 98 | Logitech WingMan Light | 2 | 2 | 0 | 0000 | Analogue | 99 | 100 | *Remarks:* 101 | 102 | - Please pay attention to how the same switches are used for different families 103 | of digital devices. This is possible due to fully digital communication. Using 104 | this method, the adapter implements autodetection as soon as it knows, that it 105 | is connected to a digital joystick. 106 | - Currently, only the drivers for the listed Sidewinder devices are implemented, 107 | since I have no other models at hand. The Precision Pro works natively on USB 108 | as well, but was still implemented, because we can. 109 | - Gravis used their GrIP protocol, which is currently implemented only for the 110 | Gravis GamePad Pro, but without daisy chaining possibility so far. 111 | - The implementation of the ADI protocol used by Logitech should work with all 112 | the devices which support that protocol. However only the listed Logitech devices 113 | were tested so far. 114 | 115 | ## Which joysticks were tested? 116 | 117 | Many joysticks in the wild are using the same digital protocol or are backwards 118 | compatible to the analog joysticks as they were used back in the days in DOS. 119 | Following list contains all the devices which were reported by others as working 120 | so far: 121 | 122 | * CH Mach I+ (analog, 2-axes, 2-buttons) 123 | * Gravis Analog Pro (analog) 124 | * Gravis GamePad Pro 125 | * Gravis PC GamePad (analog) 126 | * InterAct UltraRacer PC (analog) 127 | * Logitech CyberMan 2 128 | * Logitech WingMan Extreme Digital 129 | * Logitech WingMan Interceptor 130 | * Logitech WingMan Light 131 | * Logitech ThunderPad Digital 132 | * Logitech WingMan Gamepad 133 | * Medion Joypad MD9823 (analog, 4-axes, 4-buttons) 134 | * QuickShot QS-123E "Warrior 5" (analog) 135 | * Quickshot QS-146 "Intruder 5" (analog) 136 | * Quickshot QS-151 "Aviator" (analog) 137 | * QuickShot QS-201 "Super Warrior" (analog) 138 | * QuickShot QS-203 "Avenger" (analog) 139 | * Sidewinder 3D Pro 140 | * Sidewinder 3D Pro Plus 141 | * Sidewinder ForceFeedBack Pro 142 | * Sidewinder ForceFeedBack Wheel 143 | * Sidewinder GamePad 144 | * Sidewinder Precision Pro 145 | 146 | Sidewinder 3D Pro can be switched between analog and digital mode and in analog 147 | mode it can emulate the ThrustMaster and CH FlightStick. That's why you see them in 148 | the table above. Unfortunately I don't possess those joysticks in reality, so it may 149 | be that the implementation is not quite correct. 150 | 151 | ## What is auto calibration? 152 | 153 | Old analog joysticks have resistors inside, which are specified to be 100 kOhm. 154 | Unfortunately, these resistors are either worn out, bad quality or were wrong from 155 | the beginning. Therefore most of the generic analog joysticks had adjustment 156 | screws to correct the center point of the joystick. Also many games had 157 | calibration options in their settings to readjust the joystick. With USB and new 158 | digital solutions the calibration was not required anymore and was completely 159 | implemented in the joysticks and/or drivers. Many modern games have no option 160 | to re-calibrate the joystick anymore. If we try to play such newer games with 161 | an old analog joystick through this adapter, the joystick center point would be 162 | totally offset. That's why the adapter implements auto calibration internally and 163 | presents already corrected values to the operation system. 164 | 165 | __ATTENTION__: a hard requirement for using the analog joysticks is that during 166 | plugging into the USB port all axes must be in their middle state, because all 167 | the subsequent calibration happens based on the initial state. 168 | 169 | ## Technical insights into implementation 170 | 171 | The code is well documented, so if you are interested in the details, feel free 172 | to take a look into the driver implementation. All the analog joysticks were 173 | actually a straight forward task, but the Sidewinder digital protocol was kind 174 | of tough. There are already some implementations of Sidewinder protocol made for 175 | Arduino. However, they are mostly only for the Sidewinder GamePad and not really 176 | universally made. In the end I was heavily inspired by the Linux Sidewinder 177 | driver implementation. The code you can see in this project is a complete rewrite, 178 | I just took the Linux driver implementation as a reference to understand how it 179 | works. Also the Sidewinder patent US#5628686A helped a lot, especially with 180 | switching between digital and analog mode for Sidewinder 3D Pro. 181 | 182 | In opposition to the already mentioned Sidewinder for Arduino implementations, 183 | this one doesn't rely on interrupts. This implementation is from a similar idea 184 | to what the Linux driver does. It polls the port and makes a lot of things 185 | simpler due to synchronous process. The biggest problem was that the Sidwinder 186 | devices send the data incredibly fast, with a clock pulse of only 5us. It was 187 | not possible to use Arduino's digitalRead(...) function for that. It was too 188 | slow with about 2.7us per call on an Arduino Pro Micro of 16MHz. It simply 189 | made it impossible to poll 5us pulses, with such a slow function, not even 190 | considering doing something with the data in between. So out of that need, my 191 | own implementation came about, which is up to 50% faster and needs only about 192 | 1.6us per call, on the same hardware. The custom I/O functions made it possible 193 | to read the data with the speed which a Sidewinder joystick requires. The best 194 | part is that the code is written in pure C++. It is very simple to read and to 195 | use. There are no macros, no assembler or any dirty hacks, just a lot of 196 | optimization. 197 | 198 | ## Bill of materials (BOM) 199 | 200 | The hardware is super simple. To build an adapter you'll need the PCB from this 201 | project and following parts: 202 | 203 | Part | Qty | [LCSC](https://lcsc.com/) # | [Digikey](https://www.digikey.com/) # | [Mouser Electronics](https://www.mouser.com/) # | Comment 204 | --------|-------|---------|--------------------------|----------------------|------------------------------------------ 205 | CONN1 | 1 | C77835 | 609-5371-ND | 523-L77SDA15SA4CH4F | DB15 female connector 206 | R1..R4 | 4 | C172965 | 13-MFR-25FTE52-100KCT-ND | 603-MFR-25FTE52-100K | 100 kOhm resistors 207 | SW1 | 1 | C15781 | 2449-KG04ET-ND | 642-DS04T | DIP-4 switch 208 | U1 | 1 | C72120 | ED3051-5-ND | 649-DILB24P-223TLF | DIP24 Socket (optional) 209 | U1 | 1 | N/A | 1568-1060-ND | 474-DEV-12640 | Arduino Pro Micro (ATmega32U4 16MHz, 5V), including two 12 pin header connectors, MicroUSB version (see "Known issues") 210 | 211 | ## Known issues 212 | 213 | * *Some axes on an analog joystick are offset* 214 | 215 | Auto calibration requires all the axes to be in the center position during 216 | initialization. Please see the paragraph about auto calibration. 217 | 218 | * *Joystick doesn't work* 219 | 220 | Make sure that you are using one of supported joysticks or a joystick which can 221 | work in legacy analog mode 222 | 223 | * *MicroUSB port on the Arduino is not stable enough* 224 | 225 | Use the USB-C version of the Arduino instead. 226 | Or always keep the MicroUSB cable attached to the Arduino MicroUSB version to avoid further wear and apply plug/unplug operations only on the remote side of the cable. 227 | 228 | ## How to help the project? 229 | 230 | The best way is to implement more drivers. Since I have only the joysticks 231 | mentioned above, I can't contribute more than what is currently included. 232 | 233 | ## Special thanks 234 | 235 | I would like to give some special thanks to *Creopard* from the German 236 | dosreloaded.de community for providing me the mentioned joysticks. Without that 237 | donation this project wouldn't be possible. Especially dealing with Sidewinder 238 | 3D Pro was a very exciting task. 239 | 240 | ## Links 241 | * [Linux Sidewinder Driver](https://github.com/torvalds/linux/blob/master/drivers/input/joystick/sidewinder.c) 242 | * [Sidewinder patent](https://patents.google.com/patent/US5628686A/en) 243 | * [Creopard Retro Site](https://www.creopard.de/) 244 | 245 | -------------------------------------------------------------------------------- /firmware/gameport-adapter/Sidewinder.h: -------------------------------------------------------------------------------- 1 | // This file is part of Necroware's GamePort adapter firmware. 2 | // Copyright (C) 2021 Necroware 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #pragma once 18 | 19 | #include "Buffer.h" 20 | #include "DigitalPin.h" 21 | #include "Joystick.h" 22 | #include "Utilities.h" 23 | 24 | /// Class to for communication with Sidewinder joysticks. 25 | /// @remark This is a green field implementation, but it was heavily 26 | /// inspired by Linux Sidewinder driver implementation. See 27 | /// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/sidewinder.c 28 | class Sidewinder : public Joystick { 29 | public: 30 | /// Resets the joystick and tries to detect the model. 31 | bool init() override { 32 | log("Sidewinder init..."); 33 | m_errors = 0; 34 | 35 | m_model = guessModel(readPacket()); 36 | while (m_model == Model::SW_UNKNOWN) { 37 | // No data. 3d Pro analog mode? 38 | enableDigitalMode(); 39 | m_model = guessModel(readPacket()); 40 | } 41 | log("Detected model %d", m_model); 42 | return true; 43 | } 44 | 45 | bool update() override { 46 | const auto packet = readPacket(); 47 | State state; 48 | if (decode(packet, state)) { 49 | m_state = state; 50 | m_errors = 0; 51 | return true; 52 | } 53 | 54 | m_errors++; 55 | log("Packet decoding failed %d time(s)", m_errors); 56 | if (m_errors > 5) { 57 | return init(); 58 | } 59 | return false; 60 | } 61 | 62 | const State &getState() const override { 63 | return m_state; 64 | } 65 | 66 | const Description &getDescription() const override; 67 | 68 | private: 69 | /// Supported Sidewinder model types. 70 | enum class Model { 71 | /// Unknown model. 72 | SW_UNKNOWN, 73 | 74 | /// Sidewinder GamePad 75 | SW_GAMEPAD, 76 | 77 | /// Sidewinder 3D Pro 78 | SW_3D_PRO, 79 | 80 | /// Sidewinder Precision Pro 81 | SW_PRECISION_PRO, 82 | 83 | /// Sidewinder Force Feedback Pro 84 | SW_FORCE_FEEDBACK_PRO, 85 | 86 | /// Sidewinder Force Feedback Wheel 87 | SW_FORCE_FEEDBACK_WHEEL 88 | }; 89 | 90 | /// Internal bit structure which is filled by reading from the joystick. 91 | using Packet = Buffer<128u>; 92 | 93 | /// Model specific status decoder function. 94 | template 95 | struct Decoder { 96 | static const Description &getDescription(); 97 | static bool decode(const Packet &packet, State &state); 98 | }; 99 | 100 | /// Guesses joystick model from the size of the packet. 101 | Model guessModel(const Packet &packet) const { 102 | log("Guessing model by packet size of %d", packet.size); 103 | switch (packet.size) { 104 | case 15: 105 | return Model::SW_GAMEPAD; 106 | case 16: // 3bit mode 107 | case 48: { // 1bit mode 108 | const auto id = readID(packet.size); 109 | log("Data packet size is ambiguous. Guessing by ID %d", id); 110 | if (id == 14) { 111 | return Model::SW_FORCE_FEEDBACK_PRO; 112 | } 113 | return Model::SW_PRECISION_PRO; 114 | } 115 | case 11: // 3bit mode 116 | case 33: // 1bit mode 117 | return Model::SW_FORCE_FEEDBACK_WHEEL; 118 | case 64: 119 | return Model::SW_3D_PRO; 120 | default: 121 | return Model::SW_UNKNOWN; 122 | } 123 | } 124 | 125 | void cooldown() const { 126 | m_trigger.setLow(); 127 | delay(3); 128 | } 129 | 130 | void trigger() const { 131 | m_trigger.pulse(20); 132 | } 133 | 134 | DigitalInput::pin, true> m_clock; 135 | DigitalInput::pin, true> m_data0; 136 | DigitalInput::pin, true> m_data1; 137 | DigitalInput::pin, true> m_data2; 138 | DigitalOutput::pin> m_trigger; 139 | Model m_model{Model::SW_UNKNOWN}; 140 | State m_state{}; 141 | uint8_t m_errors{}; 142 | 143 | /// Enables digital mode for 3D Pro. 144 | // 145 | /// The 3D Pro can work as legacy analog joystick or in digital mode. 146 | /// This mode has to be activated explicitly. In this function timing 147 | /// is very important. See Patent: US#5628686 (page 19) for details. 148 | void enableDigitalMode() const { 149 | static const uint16_t magic = 150; 150 | static const uint16_t seq[] = {magic, magic + 725, magic + 300, magic, 0}; 151 | log("Trying to enable digital mode"); 152 | cooldown(); 153 | const InterruptStopper interruptStopper; 154 | for (auto i = 0u; seq[i]; i++) { 155 | trigger(); 156 | delayMicroseconds(seq[i]); 157 | } 158 | } 159 | 160 | /// Read bits packet from the joystick. 161 | /// 162 | /// This part is extremely performance and timing critical. Change only, if 163 | /// you know, what you are doing. 164 | Packet readPacket() const { 165 | 166 | // Packet instantiation is a very expensive call, which zeros the memory. 167 | // The instantiation should therefore happen outside of the interrupt stopper 168 | // and before triggering the device. Otherwise the clock will come before 169 | // the packet was zeroed/instantiated. 170 | Packet packet; 171 | 172 | // We are reading into a byte array instead of an uint64_t, because of two 173 | // reasons. First, bits packets can be larger, than 64 bits. We are actually 174 | // not interested in packets, which are larger than that, but may be in the 175 | // future we'd need to handle them as well. Second, for reading into an 176 | // uint64_t we would need to shift between the clock impulses, which is 177 | // impossible to do in time. Unfortunately this shift is extremely slow on 178 | // an Arduino and it's just faster to write into an array. One bit per byte. 179 | packet.size = readBits(Packet::MAX_SIZE, [this, &packet](uint8_t pos) { 180 | const auto b1 = m_data0.read(); 181 | const auto b2 = m_data1.read(); 182 | const auto b3 = m_data2.read(); 183 | packet.data[pos] = bool(b1) | bool(b2) << 1 | bool(b3) << 2; 184 | }); 185 | 186 | return packet; 187 | } 188 | 189 | uint8_t readID(uint8_t dataPacketSize) const { 190 | 191 | const auto rise = dataPacketSize / 2 - 1; 192 | const auto fall = rise + 2; 193 | 194 | const auto count = readBits(255u, [this, rise, fall](uint8_t pos) { 195 | if (pos == rise) { 196 | m_trigger.setHigh(); 197 | } 198 | else if (pos == fall) { 199 | m_trigger.setLow(); 200 | } 201 | }); 202 | return count < dataPacketSize ? 0 : count - dataPacketSize; 203 | } 204 | 205 | template 206 | uint8_t readBits(uint8_t maxCount, T&& extract) const { 207 | static const uint8_t wait_duration = 100; 208 | uint8_t count{}; 209 | cooldown(); 210 | // WARNING: Here starts the timing critical section 211 | const InterruptStopper interruptStopper; 212 | trigger(); 213 | if (m_clock.wait(true, wait_duration)) { 214 | while(count < maxCount && m_clock.wait(Edge::rising, wait_duration)) { 215 | extract(count++); 216 | } 217 | } 218 | return count; 219 | } 220 | 221 | /// Decodes bit packet into a state. 222 | bool decode(const Packet &packet, State &state) const; 223 | }; 224 | 225 | /// Placeholder for Unknown Device 226 | template <> 227 | class Sidewinder::Decoder { 228 | public: 229 | static const Description &getDescription() { 230 | static const Description desc{"Unknown", 0, 0, 0}; 231 | return desc; 232 | } 233 | 234 | static bool decode(const Packet &, State &) { 235 | return false; 236 | } 237 | }; 238 | 239 | /// Bit decoder for Sidewinder GamePad. 240 | template <> 241 | class Sidewinder::Decoder { 242 | public: 243 | static const Description &getDescription() { 244 | static const Description desc{"MS Sidewinder GamePad", 2, 10, 0}; 245 | return desc; 246 | } 247 | 248 | static bool decode(const Packet &packet, State &state) { 249 | 250 | const auto checksum = [&]() { 251 | byte result = 0u; 252 | for (auto i = 0u; i < packet.size; i++) { 253 | result ^= packet.data[i] & 1; 254 | } 255 | return result; 256 | }; 257 | 258 | if (packet.size != 15 || checksum() != 0) { 259 | return false; 260 | } 261 | 262 | // Bit 0-1: x-axis (10-left, 01-right, 11-middle) 263 | // Bit 2-3: y-axis (01-up, 10-down, 11-middle) 264 | // Bit 4-13: 10 buttons 265 | // Bit 14: checksum 266 | for (auto i = 0u; i < 10; i++) { 267 | state.buttons |= (~packet.data[i + 4] & 1) << i; 268 | } 269 | state.axes[0] = map(1 + packet.data[3] - packet.data[2], 0, 2, 0, 1023); 270 | state.axes[1] = map(1 + packet.data[0] - packet.data[1], 0, 2, 0, 1023); 271 | 272 | return true; 273 | } 274 | }; 275 | 276 | /// Bit decoder for Sidewinder 3D Pro. 277 | template <> 278 | class Sidewinder::Decoder { 279 | public: 280 | static const Description &getDescription() { 281 | static const Description desc{"MS Sidewinder 3D Pro", 4, 8, 1}; 282 | return desc; 283 | } 284 | 285 | static bool decode(const Packet &packet, State &state) { 286 | const auto value = [&]() { 287 | uint64_t result{0u}; 288 | for (auto i = 0u; i < packet.size; i++) { 289 | result |= uint64_t(packet.data[i] & 1) << i; 290 | } 291 | return result; 292 | }(); 293 | 294 | const auto bits = [&](uint8_t start, uint8_t length) { 295 | const auto mask = (1 << length) - 1; 296 | return (value >> start) & mask; 297 | }; 298 | 299 | if (packet.size != 64 || !checkSync(value) || checksum(value)) { 300 | return false; 301 | } 302 | 303 | // bit 38: button 8 + bit 8-15: buttons 1-7 (low active) 304 | state.buttons = ~(bits(8, 7) | (bits(38, 1) << 7)); 305 | 306 | // bit 3-5 + bit 16-22: x-axis (value 0-1023) 307 | state.axes[0] = bits(3, 3) << 7 | bits(16, 7); 308 | 309 | // bit 0-2 + bit 24-30: y-axis (value 0-1023) 310 | state.axes[1] = bits(0, 3) << 7 | bits(24, 7); 311 | 312 | // bit 35-36 + bit 40-46: z-axis (value 0-511) 313 | state.axes[2] = map(bits(35, 2) << 7 | bits(40, 7), 0, 511, 0, 1023); 314 | 315 | // bit 32-34 + bit 48-54: throttle-axis (value 0-1023) 316 | state.axes[3] = bits(32, 3) << 7 | bits(48, 7); 317 | 318 | // bit 6-7 + bit 60-62 (9 pos, 0 center, 1-8 clockwise) 319 | state.hat = bits(6, 1) << 3 | bits(60, 3); 320 | 321 | return true; 322 | } 323 | 324 | private: 325 | /// Checks sync bits. 326 | /// 327 | /// This code was taken from Linux driver as is. 328 | static bool checkSync(uint64_t value) { 329 | return !((value & 0x8080808080808080ULL) ^ 0x80); 330 | } 331 | 332 | /// Calculates checksum. 333 | /// 334 | /// This code was taken from Linux driver as is. 335 | static byte checksum(uint64_t value) { 336 | auto result = 0u; 337 | while (value) { 338 | result += value & 0xf; 339 | value >>= 4; 340 | } 341 | return result & 0xf; 342 | } 343 | }; 344 | 345 | /// Bit decoder for Sidewinder Precision Pro 346 | template <> 347 | class Sidewinder::Decoder { 348 | public: 349 | static const Description &getDescription() { 350 | static const Description desc{"MS Sidewinder Precision Pro", 4, 9, 1}; 351 | return desc; 352 | } 353 | 354 | static bool decode(const Packet &packet, State &state) { 355 | 356 | // The packet can be either in 3bit or in 1bit mode 357 | if (packet.size != 16 && packet.size != 48) { 358 | return false; 359 | } 360 | 361 | const auto value = [&packet]() { 362 | uint64_t result{0u}; 363 | const auto shift = 48 / packet.size; 364 | const auto mask = (shift == 3) ? 0b111 : 0b1; 365 | for (auto i = 0u; i < packet.size; i++) { 366 | result |= uint64_t(packet.data[i] & mask) << (i * shift); 367 | } 368 | return result; 369 | }(); 370 | 371 | // TODO shared code with 3D Pro? 372 | const auto bits = [&value](uint8_t start, uint8_t length) { 373 | const auto mask = (1 << length) - 1; 374 | return (value >> start) & mask; 375 | }; 376 | 377 | // TODO shared code with GP? 378 | const auto parity = [](uint64_t t) { 379 | uint32_t x = t ^ (t >> 32); 380 | x ^= x >> 16; 381 | x ^= x >> 8; 382 | x ^= x >> 4; 383 | x ^= x >> 2; 384 | x ^= x >> 1; 385 | return x & 1; 386 | }; 387 | 388 | if (!parity(value)) { 389 | return false; 390 | } 391 | 392 | state.axes[0] = bits(9, 10); 393 | state.axes[1] = bits(19, 10); 394 | state.axes[2] = map(bits(36, 6), 0, 63, 0, 1023); 395 | state.axes[3] = map(bits(29, 7), 0, 127, 0, 1023); 396 | state.hat = bits(42, 4); 397 | state.buttons = ~bits(0, 9); 398 | 399 | return true; 400 | } 401 | }; 402 | 403 | /// Descriptor for Sidewinder Force Feedback Pro. 404 | /// (The bit decoder is identical to the Precision Pro.) 405 | template <> 406 | class Sidewinder::Decoder { 407 | public: 408 | static const Description &getDescription() { 409 | static const Description desc{"MS Sidewinder Force Feedback Pro", 4, 9, 1}; 410 | return desc; 411 | } 412 | 413 | static bool decode(const Packet &packet, State &state) { 414 | // Decode is identical between the Force Feedback Pro and the Precision Pro. 415 | return Decoder::decode(packet, state); 416 | } 417 | }; 418 | 419 | /// Bit decoder for Sidewinder Force Feedback Wheel. 420 | template <> 421 | class Sidewinder::Decoder { 422 | public: 423 | static const Description &getDescription() { 424 | static const Description desc{"MS ForceFeedBack Wheel", 3, 8, 0}; 425 | return desc; 426 | } 427 | 428 | static bool decode(const Packet &packet, State &state) { 429 | 430 | // The packet can be either in 3bit or in 1bit mode 431 | if (packet.size != 11 && packet.size != 33) { 432 | return false; 433 | } 434 | 435 | const auto value = [&packet]() { 436 | uint64_t result{0u}; 437 | const auto shift = 33 / packet.size; 438 | const auto mask = (shift == 3) ? 0b111 : 0b1; 439 | for (auto i = 0u; i < packet.size; i++) { 440 | result |= uint64_t(packet.data[i] & mask) << (i * shift); 441 | } 442 | return result; 443 | }(); 444 | 445 | // TODO shared code with 3D Pro? 446 | const auto bits = [&value](uint8_t start, uint8_t length) { 447 | const auto mask = (1 << length) - 1; 448 | return (value >> start) & mask; 449 | }; 450 | 451 | // TODO shared code with GP? 452 | const auto parity = [](uint64_t t) { 453 | uint32_t x = t ^ (t >> 32); 454 | x ^= x >> 16; 455 | x ^= x >> 8; 456 | x ^= x >> 4; 457 | x ^= x >> 2; 458 | x ^= x >> 1; 459 | return x & 1; 460 | }; 461 | 462 | if (!parity(value)) { 463 | return false; 464 | } 465 | 466 | // bit 0-9: RX 467 | state.axes[0] = bits(0, 10); 468 | 469 | // bit 10-16: Rudder 470 | state.axes[1] = map(bits(10, 6), 0, 63, 0, 1023); 471 | 472 | // bit 16-21: Throttle 473 | state.axes[2] = map(bits(16, 6), 0, 63, 0, 1023); 474 | 475 | // bit 22-29: buttons 1-8 476 | state.buttons = ~bits(22, 8); 477 | 478 | return true; 479 | } 480 | }; 481 | 482 | inline const Joystick::Description &Sidewinder::getDescription() const { 483 | switch (m_model) { 484 | case Model::SW_GAMEPAD: 485 | return Decoder::getDescription(); 486 | case Model::SW_3D_PRO: 487 | return Decoder::getDescription(); 488 | case Model::SW_PRECISION_PRO: 489 | return Decoder::getDescription(); 490 | case Model::SW_FORCE_FEEDBACK_PRO: 491 | return Decoder::getDescription(); 492 | case Model::SW_FORCE_FEEDBACK_WHEEL: 493 | return Decoder::getDescription(); 494 | default: 495 | return Decoder::getDescription(); 496 | } 497 | } 498 | 499 | inline bool Sidewinder::decode(const Packet &packet, State &state) const { 500 | switch (m_model) { 501 | case Model::SW_GAMEPAD: 502 | return Decoder::decode(packet, state); 503 | case Model::SW_3D_PRO: 504 | return Decoder::decode(packet, state); 505 | case Model::SW_PRECISION_PRO: 506 | return Decoder::decode(packet, state); 507 | case Model::SW_FORCE_FEEDBACK_PRO: 508 | return Decoder::decode(packet, state); 509 | case Model::SW_FORCE_FEEDBACK_WHEEL: 510 | return Decoder::decode(packet, state); 511 | default: 512 | return Decoder::decode(packet, state); 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /kicad/gameport-adapter.kicad_sch: -------------------------------------------------------------------------------- 1 | (kicad_sch (version 20211123) (generator eeschema) 2 | 3 | (uuid bd3a3088-a7a8-46b0-8d32-b4cc0ac8ed31) 4 | 5 | (paper "A4") 6 | 7 | (title_block 8 | (title "GamePort Adapter") 9 | (date "2021-03-12") 10 | (rev "0.2") 11 | (company "Necroware") 12 | (comment 1 "by Scorp") 13 | ) 14 | 15 | (lib_symbols 16 | (symbol "Connector:DB15_Female_MountingHoles" (pin_names (offset 1.016) hide) (in_bom yes) (on_board yes) 17 | (property "Reference" "J" (id 0) (at 0 24.13 0) 18 | (effects (font (size 1.27 1.27))) 19 | ) 20 | (property "Value" "DB15_Female_MountingHoles" (id 1) (at 0 22.225 0) 21 | (effects (font (size 1.27 1.27))) 22 | ) 23 | (property "Footprint" "" (id 2) (at 0 0 0) 24 | (effects (font (size 1.27 1.27)) hide) 25 | ) 26 | (property "Datasheet" " ~" (id 3) (at 0 0 0) 27 | (effects (font (size 1.27 1.27)) hide) 28 | ) 29 | (property "ki_keywords" "female D-SUB connector" (id 4) (at 0 0 0) 30 | (effects (font (size 1.27 1.27)) hide) 31 | ) 32 | (property "ki_description" "15-pin female D-SUB connector (low-density/2 columns), Mounting Hole" (id 5) (at 0 0 0) 33 | (effects (font (size 1.27 1.27)) hide) 34 | ) 35 | (property "ki_fp_filters" "DSUB*Female*" (id 6) (at 0 0 0) 36 | (effects (font (size 1.27 1.27)) hide) 37 | ) 38 | (symbol "DB15_Female_MountingHoles_0_1" 39 | (circle (center -1.778 -17.78) (radius 0.762) 40 | (stroke (width 0) (type default) (color 0 0 0 0)) 41 | (fill (type none)) 42 | ) 43 | (circle (center -1.778 -12.7) (radius 0.762) 44 | (stroke (width 0) (type default) (color 0 0 0 0)) 45 | (fill (type none)) 46 | ) 47 | (circle (center -1.778 -7.62) (radius 0.762) 48 | (stroke (width 0) (type default) (color 0 0 0 0)) 49 | (fill (type none)) 50 | ) 51 | (circle (center -1.778 -2.54) (radius 0.762) 52 | (stroke (width 0) (type default) (color 0 0 0 0)) 53 | (fill (type none)) 54 | ) 55 | (circle (center -1.778 2.54) (radius 0.762) 56 | (stroke (width 0) (type default) (color 0 0 0 0)) 57 | (fill (type none)) 58 | ) 59 | (circle (center -1.778 7.62) (radius 0.762) 60 | (stroke (width 0) (type default) (color 0 0 0 0)) 61 | (fill (type none)) 62 | ) 63 | (circle (center -1.778 12.7) (radius 0.762) 64 | (stroke (width 0) (type default) (color 0 0 0 0)) 65 | (fill (type none)) 66 | ) 67 | (circle (center -1.778 17.78) (radius 0.762) 68 | (stroke (width 0) (type default) (color 0 0 0 0)) 69 | (fill (type none)) 70 | ) 71 | (polyline 72 | (pts 73 | (xy -3.81 -17.78) 74 | (xy -2.54 -17.78) 75 | ) 76 | (stroke (width 0) (type default) (color 0 0 0 0)) 77 | (fill (type none)) 78 | ) 79 | (polyline 80 | (pts 81 | (xy -3.81 -15.24) 82 | (xy 0.508 -15.24) 83 | ) 84 | (stroke (width 0) (type default) (color 0 0 0 0)) 85 | (fill (type none)) 86 | ) 87 | (polyline 88 | (pts 89 | (xy -3.81 -12.7) 90 | (xy -2.54 -12.7) 91 | ) 92 | (stroke (width 0) (type default) (color 0 0 0 0)) 93 | (fill (type none)) 94 | ) 95 | (polyline 96 | (pts 97 | (xy -3.81 -10.16) 98 | (xy 0.508 -10.16) 99 | ) 100 | (stroke (width 0) (type default) (color 0 0 0 0)) 101 | (fill (type none)) 102 | ) 103 | (polyline 104 | (pts 105 | (xy -3.81 -7.62) 106 | (xy -2.54 -7.62) 107 | ) 108 | (stroke (width 0) (type default) (color 0 0 0 0)) 109 | (fill (type none)) 110 | ) 111 | (polyline 112 | (pts 113 | (xy -3.81 -5.08) 114 | (xy 0.508 -5.08) 115 | ) 116 | (stroke (width 0) (type default) (color 0 0 0 0)) 117 | (fill (type none)) 118 | ) 119 | (polyline 120 | (pts 121 | (xy -3.81 -2.54) 122 | (xy -2.54 -2.54) 123 | ) 124 | (stroke (width 0) (type default) (color 0 0 0 0)) 125 | (fill (type none)) 126 | ) 127 | (polyline 128 | (pts 129 | (xy -3.81 0) 130 | (xy 0.508 0) 131 | ) 132 | (stroke (width 0) (type default) (color 0 0 0 0)) 133 | (fill (type none)) 134 | ) 135 | (polyline 136 | (pts 137 | (xy -3.81 2.54) 138 | (xy -2.54 2.54) 139 | ) 140 | (stroke (width 0) (type default) (color 0 0 0 0)) 141 | (fill (type none)) 142 | ) 143 | (polyline 144 | (pts 145 | (xy -3.81 5.08) 146 | (xy 0.508 5.08) 147 | ) 148 | (stroke (width 0) (type default) (color 0 0 0 0)) 149 | (fill (type none)) 150 | ) 151 | (polyline 152 | (pts 153 | (xy -3.81 7.62) 154 | (xy -2.54 7.62) 155 | ) 156 | (stroke (width 0) (type default) (color 0 0 0 0)) 157 | (fill (type none)) 158 | ) 159 | (polyline 160 | (pts 161 | (xy -3.81 10.16) 162 | (xy 0.508 10.16) 163 | ) 164 | (stroke (width 0) (type default) (color 0 0 0 0)) 165 | (fill (type none)) 166 | ) 167 | (polyline 168 | (pts 169 | (xy -3.81 12.7) 170 | (xy -2.54 12.7) 171 | ) 172 | (stroke (width 0) (type default) (color 0 0 0 0)) 173 | (fill (type none)) 174 | ) 175 | (polyline 176 | (pts 177 | (xy -3.81 15.24) 178 | (xy 0.508 15.24) 179 | ) 180 | (stroke (width 0) (type default) (color 0 0 0 0)) 181 | (fill (type none)) 182 | ) 183 | (polyline 184 | (pts 185 | (xy -3.81 17.78) 186 | (xy -2.54 17.78) 187 | ) 188 | (stroke (width 0) (type default) (color 0 0 0 0)) 189 | (fill (type none)) 190 | ) 191 | (polyline 192 | (pts 193 | (xy -3.81 20.955) 194 | (xy 3.175 17.145) 195 | (xy 3.175 -17.145) 196 | (xy -3.81 -20.955) 197 | (xy -3.81 20.955) 198 | ) 199 | (stroke (width 0.254) (type default) (color 0 0 0 0)) 200 | (fill (type background)) 201 | ) 202 | (circle (center 1.27 -15.24) (radius 0.762) 203 | (stroke (width 0) (type default) (color 0 0 0 0)) 204 | (fill (type none)) 205 | ) 206 | (circle (center 1.27 -10.16) (radius 0.762) 207 | (stroke (width 0) (type default) (color 0 0 0 0)) 208 | (fill (type none)) 209 | ) 210 | (circle (center 1.27 -5.08) (radius 0.762) 211 | (stroke (width 0) (type default) (color 0 0 0 0)) 212 | (fill (type none)) 213 | ) 214 | (circle (center 1.27 0) (radius 0.762) 215 | (stroke (width 0) (type default) (color 0 0 0 0)) 216 | (fill (type none)) 217 | ) 218 | (circle (center 1.27 5.08) (radius 0.762) 219 | (stroke (width 0) (type default) (color 0 0 0 0)) 220 | (fill (type none)) 221 | ) 222 | (circle (center 1.27 10.16) (radius 0.762) 223 | (stroke (width 0) (type default) (color 0 0 0 0)) 224 | (fill (type none)) 225 | ) 226 | (circle (center 1.27 15.24) (radius 0.762) 227 | (stroke (width 0) (type default) (color 0 0 0 0)) 228 | (fill (type none)) 229 | ) 230 | ) 231 | (symbol "DB15_Female_MountingHoles_1_1" 232 | (pin passive line (at 0 -22.86 90) (length 3.81) 233 | (name "PAD" (effects (font (size 1.27 1.27)))) 234 | (number "0" (effects (font (size 1.27 1.27)))) 235 | ) 236 | (pin passive line (at -7.62 17.78 0) (length 3.81) 237 | (name "1" (effects (font (size 1.27 1.27)))) 238 | (number "1" (effects (font (size 1.27 1.27)))) 239 | ) 240 | (pin passive line (at -7.62 10.16 0) (length 3.81) 241 | (name "P10" (effects (font (size 1.27 1.27)))) 242 | (number "10" (effects (font (size 1.27 1.27)))) 243 | ) 244 | (pin passive line (at -7.62 5.08 0) (length 3.81) 245 | (name "P111" (effects (font (size 1.27 1.27)))) 246 | (number "11" (effects (font (size 1.27 1.27)))) 247 | ) 248 | (pin passive line (at -7.62 0 0) (length 3.81) 249 | (name "P12" (effects (font (size 1.27 1.27)))) 250 | (number "12" (effects (font (size 1.27 1.27)))) 251 | ) 252 | (pin passive line (at -7.62 -5.08 0) (length 3.81) 253 | (name "P13" (effects (font (size 1.27 1.27)))) 254 | (number "13" (effects (font (size 1.27 1.27)))) 255 | ) 256 | (pin passive line (at -7.62 -10.16 0) (length 3.81) 257 | (name "P14" (effects (font (size 1.27 1.27)))) 258 | (number "14" (effects (font (size 1.27 1.27)))) 259 | ) 260 | (pin passive line (at -7.62 -15.24 0) (length 3.81) 261 | (name "P15" (effects (font (size 1.27 1.27)))) 262 | (number "15" (effects (font (size 1.27 1.27)))) 263 | ) 264 | (pin passive line (at -7.62 12.7 0) (length 3.81) 265 | (name "2" (effects (font (size 1.27 1.27)))) 266 | (number "2" (effects (font (size 1.27 1.27)))) 267 | ) 268 | (pin passive line (at -7.62 7.62 0) (length 3.81) 269 | (name "3" (effects (font (size 1.27 1.27)))) 270 | (number "3" (effects (font (size 1.27 1.27)))) 271 | ) 272 | (pin passive line (at -7.62 2.54 0) (length 3.81) 273 | (name "4" (effects (font (size 1.27 1.27)))) 274 | (number "4" (effects (font (size 1.27 1.27)))) 275 | ) 276 | (pin passive line (at -7.62 -2.54 0) (length 3.81) 277 | (name "5" (effects (font (size 1.27 1.27)))) 278 | (number "5" (effects (font (size 1.27 1.27)))) 279 | ) 280 | (pin passive line (at -7.62 -7.62 0) (length 3.81) 281 | (name "6" (effects (font (size 1.27 1.27)))) 282 | (number "6" (effects (font (size 1.27 1.27)))) 283 | ) 284 | (pin passive line (at -7.62 -12.7 0) (length 3.81) 285 | (name "7" (effects (font (size 1.27 1.27)))) 286 | (number "7" (effects (font (size 1.27 1.27)))) 287 | ) 288 | (pin passive line (at -7.62 -17.78 0) (length 3.81) 289 | (name "8" (effects (font (size 1.27 1.27)))) 290 | (number "8" (effects (font (size 1.27 1.27)))) 291 | ) 292 | (pin passive line (at -7.62 15.24 0) (length 3.81) 293 | (name "P9" (effects (font (size 1.27 1.27)))) 294 | (number "9" (effects (font (size 1.27 1.27)))) 295 | ) 296 | ) 297 | ) 298 | (symbol "Device:R" (pin_numbers hide) (pin_names (offset 0)) (in_bom yes) (on_board yes) 299 | (property "Reference" "R" (id 0) (at 2.032 0 90) 300 | (effects (font (size 1.27 1.27))) 301 | ) 302 | (property "Value" "R" (id 1) (at 0 0 90) 303 | (effects (font (size 1.27 1.27))) 304 | ) 305 | (property "Footprint" "" (id 2) (at -1.778 0 90) 306 | (effects (font (size 1.27 1.27)) hide) 307 | ) 308 | (property "Datasheet" "~" (id 3) (at 0 0 0) 309 | (effects (font (size 1.27 1.27)) hide) 310 | ) 311 | (property "ki_keywords" "R res resistor" (id 4) (at 0 0 0) 312 | (effects (font (size 1.27 1.27)) hide) 313 | ) 314 | (property "ki_description" "Resistor" (id 5) (at 0 0 0) 315 | (effects (font (size 1.27 1.27)) hide) 316 | ) 317 | (property "ki_fp_filters" "R_*" (id 6) (at 0 0 0) 318 | (effects (font (size 1.27 1.27)) hide) 319 | ) 320 | (symbol "R_0_1" 321 | (rectangle (start -1.016 -2.54) (end 1.016 2.54) 322 | (stroke (width 0.254) (type default) (color 0 0 0 0)) 323 | (fill (type none)) 324 | ) 325 | ) 326 | (symbol "R_1_1" 327 | (pin passive line (at 0 3.81 270) (length 1.27) 328 | (name "~" (effects (font (size 1.27 1.27)))) 329 | (number "1" (effects (font (size 1.27 1.27)))) 330 | ) 331 | (pin passive line (at 0 -3.81 90) (length 1.27) 332 | (name "~" (effects (font (size 1.27 1.27)))) 333 | (number "2" (effects (font (size 1.27 1.27)))) 334 | ) 335 | ) 336 | ) 337 | (symbol "Necroware:Arduino_Pro_Micro" (pin_names (offset 1.016)) (in_bom yes) (on_board yes) 338 | (property "Reference" "U" (id 0) (at 13.97 -1.27 0) 339 | (effects (font (size 1.27 1.27))) 340 | ) 341 | (property "Value" "Arduino_Pro_Micro" (id 1) (at 13.97 -17.78 90) 342 | (effects (font (size 1.27 1.27))) 343 | ) 344 | (property "Footprint" "" (id 2) (at 0 0 0) 345 | (effects (font (size 1.27 1.27)) hide) 346 | ) 347 | (property "Datasheet" "" (id 3) (at 0 0 0) 348 | (effects (font (size 1.27 1.27)) hide) 349 | ) 350 | (symbol "Arduino_Pro_Micro_0_1" 351 | (rectangle (start 2.54 -2.54) (end 25.4 -35.56) 352 | (stroke (width 0) (type default) (color 0 0 0 0)) 353 | (fill (type background)) 354 | ) 355 | ) 356 | (symbol "Arduino_Pro_Micro_1_1" 357 | (pin bidirectional line (at 0 -5.08 0) (length 2.54) 358 | (name "1/TX" (effects (font (size 1.27 1.27)))) 359 | (number "1" (effects (font (size 1.27 1.27)))) 360 | ) 361 | (pin bidirectional line (at 0 -27.94 0) (length 2.54) 362 | (name "7" (effects (font (size 1.27 1.27)))) 363 | (number "10" (effects (font (size 1.27 1.27)))) 364 | ) 365 | (pin bidirectional line (at 0 -30.48 0) (length 2.54) 366 | (name "8/A8" (effects (font (size 1.27 1.27)))) 367 | (number "11" (effects (font (size 1.27 1.27)))) 368 | ) 369 | (pin bidirectional line (at 0 -33.02 0) (length 2.54) 370 | (name "9/A9" (effects (font (size 1.27 1.27)))) 371 | (number "12" (effects (font (size 1.27 1.27)))) 372 | ) 373 | (pin bidirectional line (at 27.94 -33.02 180) (length 2.54) 374 | (name "10/A10" (effects (font (size 1.27 1.27)))) 375 | (number "13" (effects (font (size 1.27 1.27)))) 376 | ) 377 | (pin bidirectional line (at 27.94 -30.48 180) (length 2.54) 378 | (name "16/MOSI" (effects (font (size 1.27 1.27)))) 379 | (number "14" (effects (font (size 1.27 1.27)))) 380 | ) 381 | (pin bidirectional line (at 27.94 -27.94 180) (length 2.54) 382 | (name "14/MISO" (effects (font (size 1.27 1.27)))) 383 | (number "15" (effects (font (size 1.27 1.27)))) 384 | ) 385 | (pin bidirectional line (at 27.94 -25.4 180) (length 2.54) 386 | (name "15/SCLK" (effects (font (size 1.27 1.27)))) 387 | (number "16" (effects (font (size 1.27 1.27)))) 388 | ) 389 | (pin bidirectional line (at 27.94 -22.86 180) (length 2.54) 390 | (name "18/A0" (effects (font (size 1.27 1.27)))) 391 | (number "17" (effects (font (size 1.27 1.27)))) 392 | ) 393 | (pin bidirectional line (at 27.94 -20.32 180) (length 2.54) 394 | (name "19/A1" (effects (font (size 1.27 1.27)))) 395 | (number "18" (effects (font (size 1.27 1.27)))) 396 | ) 397 | (pin bidirectional line (at 27.94 -17.78 180) (length 2.54) 398 | (name "20/A2" (effects (font (size 1.27 1.27)))) 399 | (number "19" (effects (font (size 1.27 1.27)))) 400 | ) 401 | (pin bidirectional line (at 0 -7.62 0) (length 2.54) 402 | (name "0/RX" (effects (font (size 1.27 1.27)))) 403 | (number "2" (effects (font (size 1.27 1.27)))) 404 | ) 405 | (pin bidirectional line (at 27.94 -15.24 180) (length 2.54) 406 | (name "21/A3" (effects (font (size 1.27 1.27)))) 407 | (number "20" (effects (font (size 1.27 1.27)))) 408 | ) 409 | (pin bidirectional line (at 27.94 -12.7 180) (length 2.54) 410 | (name "VCC" (effects (font (size 1.27 1.27)))) 411 | (number "21" (effects (font (size 1.27 1.27)))) 412 | ) 413 | (pin bidirectional line (at 27.94 -10.16 180) (length 2.54) 414 | (name "RST" (effects (font (size 1.27 1.27)))) 415 | (number "22" (effects (font (size 1.27 1.27)))) 416 | ) 417 | (pin bidirectional line (at 27.94 -7.62 180) (length 2.54) 418 | (name "GND" (effects (font (size 1.27 1.27)))) 419 | (number "23" (effects (font (size 1.27 1.27)))) 420 | ) 421 | (pin bidirectional line (at 27.94 -5.08 180) (length 2.54) 422 | (name "VIN" (effects (font (size 1.27 1.27)))) 423 | (number "24" (effects (font (size 1.27 1.27)))) 424 | ) 425 | (pin bidirectional line (at 0 -10.16 0) (length 2.54) 426 | (name "GND" (effects (font (size 1.27 1.27)))) 427 | (number "3" (effects (font (size 1.27 1.27)))) 428 | ) 429 | (pin bidirectional line (at 0 -12.7 0) (length 2.54) 430 | (name "GND" (effects (font (size 1.27 1.27)))) 431 | (number "4" (effects (font (size 1.27 1.27)))) 432 | ) 433 | (pin bidirectional line (at 0 -15.24 0) (length 2.54) 434 | (name "2/SDA" (effects (font (size 1.27 1.27)))) 435 | (number "5" (effects (font (size 1.27 1.27)))) 436 | ) 437 | (pin bidirectional line (at 0 -17.78 0) (length 2.54) 438 | (name "3/SCL" (effects (font (size 1.27 1.27)))) 439 | (number "6" (effects (font (size 1.27 1.27)))) 440 | ) 441 | (pin bidirectional line (at 0 -20.32 0) (length 2.54) 442 | (name "4/A6" (effects (font (size 1.27 1.27)))) 443 | (number "7" (effects (font (size 1.27 1.27)))) 444 | ) 445 | (pin bidirectional line (at 0 -22.86 0) (length 2.54) 446 | (name "5" (effects (font (size 1.27 1.27)))) 447 | (number "8" (effects (font (size 1.27 1.27)))) 448 | ) 449 | (pin bidirectional line (at 0 -25.4 0) (length 2.54) 450 | (name "6/A7" (effects (font (size 1.27 1.27)))) 451 | (number "9" (effects (font (size 1.27 1.27)))) 452 | ) 453 | ) 454 | ) 455 | (symbol "Switch:SW_DIP_x04" (pin_names (offset 0) hide) (in_bom yes) (on_board yes) 456 | (property "Reference" "SW" (id 0) (at 0 8.89 0) 457 | (effects (font (size 1.27 1.27))) 458 | ) 459 | (property "Value" "SW_DIP_x04" (id 1) (at 0 -6.35 0) 460 | (effects (font (size 1.27 1.27))) 461 | ) 462 | (property "Footprint" "" (id 2) (at 0 0 0) 463 | (effects (font (size 1.27 1.27)) hide) 464 | ) 465 | (property "Datasheet" "~" (id 3) (at 0 0 0) 466 | (effects (font (size 1.27 1.27)) hide) 467 | ) 468 | (property "ki_keywords" "dip switch" (id 4) (at 0 0 0) 469 | (effects (font (size 1.27 1.27)) hide) 470 | ) 471 | (property "ki_description" "4x DIP Switch, Single Pole Single Throw (SPST) switch, small symbol" (id 5) (at 0 0 0) 472 | (effects (font (size 1.27 1.27)) hide) 473 | ) 474 | (property "ki_fp_filters" "SW?DIP?x4*" (id 6) (at 0 0 0) 475 | (effects (font (size 1.27 1.27)) hide) 476 | ) 477 | (symbol "SW_DIP_x04_0_0" 478 | (circle (center -2.032 -2.54) (radius 0.508) 479 | (stroke (width 0) (type default) (color 0 0 0 0)) 480 | (fill (type none)) 481 | ) 482 | (circle (center -2.032 0) (radius 0.508) 483 | (stroke (width 0) (type default) (color 0 0 0 0)) 484 | (fill (type none)) 485 | ) 486 | (circle (center -2.032 2.54) (radius 0.508) 487 | (stroke (width 0) (type default) (color 0 0 0 0)) 488 | (fill (type none)) 489 | ) 490 | (circle (center -2.032 5.08) (radius 0.508) 491 | (stroke (width 0) (type default) (color 0 0 0 0)) 492 | (fill (type none)) 493 | ) 494 | (polyline 495 | (pts 496 | (xy -1.524 -2.3876) 497 | (xy 2.3622 -1.3462) 498 | ) 499 | (stroke (width 0) (type default) (color 0 0 0 0)) 500 | (fill (type none)) 501 | ) 502 | (polyline 503 | (pts 504 | (xy -1.524 0.127) 505 | (xy 2.3622 1.1684) 506 | ) 507 | (stroke (width 0) (type default) (color 0 0 0 0)) 508 | (fill (type none)) 509 | ) 510 | (polyline 511 | (pts 512 | (xy -1.524 2.667) 513 | (xy 2.3622 3.7084) 514 | ) 515 | (stroke (width 0) (type default) (color 0 0 0 0)) 516 | (fill (type none)) 517 | ) 518 | (polyline 519 | (pts 520 | (xy -1.524 5.207) 521 | (xy 2.3622 6.2484) 522 | ) 523 | (stroke (width 0) (type default) (color 0 0 0 0)) 524 | (fill (type none)) 525 | ) 526 | (circle (center 2.032 -2.54) (radius 0.508) 527 | (stroke (width 0) (type default) (color 0 0 0 0)) 528 | (fill (type none)) 529 | ) 530 | (circle (center 2.032 0) (radius 0.508) 531 | (stroke (width 0) (type default) (color 0 0 0 0)) 532 | (fill (type none)) 533 | ) 534 | (circle (center 2.032 2.54) (radius 0.508) 535 | (stroke (width 0) (type default) (color 0 0 0 0)) 536 | (fill (type none)) 537 | ) 538 | (circle (center 2.032 5.08) (radius 0.508) 539 | (stroke (width 0) (type default) (color 0 0 0 0)) 540 | (fill (type none)) 541 | ) 542 | ) 543 | (symbol "SW_DIP_x04_0_1" 544 | (rectangle (start -3.81 7.62) (end 3.81 -5.08) 545 | (stroke (width 0.254) (type default) (color 0 0 0 0)) 546 | (fill (type background)) 547 | ) 548 | ) 549 | (symbol "SW_DIP_x04_1_1" 550 | (pin passive line (at -7.62 5.08 0) (length 5.08) 551 | (name "~" (effects (font (size 1.27 1.27)))) 552 | (number "1" (effects (font (size 1.27 1.27)))) 553 | ) 554 | (pin passive line (at -7.62 2.54 0) (length 5.08) 555 | (name "~" (effects (font (size 1.27 1.27)))) 556 | (number "2" (effects (font (size 1.27 1.27)))) 557 | ) 558 | (pin passive line (at -7.62 0 0) (length 5.08) 559 | (name "~" (effects (font (size 1.27 1.27)))) 560 | (number "3" (effects (font (size 1.27 1.27)))) 561 | ) 562 | (pin passive line (at -7.62 -2.54 0) (length 5.08) 563 | (name "~" (effects (font (size 1.27 1.27)))) 564 | (number "4" (effects (font (size 1.27 1.27)))) 565 | ) 566 | (pin passive line (at 7.62 -2.54 180) (length 5.08) 567 | (name "~" (effects (font (size 1.27 1.27)))) 568 | (number "5" (effects (font (size 1.27 1.27)))) 569 | ) 570 | (pin passive line (at 7.62 0 180) (length 5.08) 571 | (name "~" (effects (font (size 1.27 1.27)))) 572 | (number "6" (effects (font (size 1.27 1.27)))) 573 | ) 574 | (pin passive line (at 7.62 2.54 180) (length 5.08) 575 | (name "~" (effects (font (size 1.27 1.27)))) 576 | (number "7" (effects (font (size 1.27 1.27)))) 577 | ) 578 | (pin passive line (at 7.62 5.08 180) (length 5.08) 579 | (name "~" (effects (font (size 1.27 1.27)))) 580 | (number "8" (effects (font (size 1.27 1.27)))) 581 | ) 582 | ) 583 | ) 584 | ) 585 | 586 | 587 | (no_connect (at 78.74 43.18) (uuid 26bcbd54-4917-40d5-8086-2336123e04d4)) 588 | (no_connect (at 78.74 50.8) (uuid 318d3904-9b1d-4a80-9a05-fb45d853b467)) 589 | (no_connect (at 78.74 40.64) (uuid 403c3cb8-169f-4e94-90d0-8033fd154633)) 590 | (no_connect (at 106.68 45.72) (uuid 5e2c1593-19c9-4c68-afe7-ab3b1efcc185)) 591 | (no_connect (at 78.74 53.34) (uuid 94fdde98-89ac-4d8a-8fd1-26b643370b8b)) 592 | (no_connect (at 106.68 40.64) (uuid cdd5609b-b74d-422d-b077-b9c1ee047eae)) 593 | 594 | (global_label "SW3" (shape input) (at 85.09 85.09 180) (fields_autoplaced) 595 | (effects (font (size 1.27 1.27)) (justify right)) 596 | (uuid 07b1f9bd-febb-448a-b5d2-9d85f5dc28cd) 597 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 598 | (effects (font (size 1.27 1.27)) hide) 599 | ) 600 | ) 601 | (global_label "VCC" (shape input) (at 44.45 39.37 180) (fields_autoplaced) 602 | (effects (font (size 1.27 1.27)) (justify right)) 603 | (uuid 084fcc96-2688-4ebc-af9f-b6c878342afd) 604 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 605 | (effects (font (size 1.27 1.27)) hide) 606 | ) 607 | ) 608 | (global_label "GND" (shape input) (at 142.24 45.72 0) (fields_autoplaced) 609 | (effects (font (size 1.27 1.27)) (justify left)) 610 | (uuid 0c408ab4-515f-42e6-8eb9-71663adb1246) 611 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 612 | (effects (font (size 1.27 1.27)) hide) 613 | ) 614 | ) 615 | (global_label "AxisY_2" (shape input) (at 78.74 55.88 180) (fields_autoplaced) 616 | (effects (font (size 1.27 1.27)) (justify right)) 617 | (uuid 0e9fd3df-16d0-4fdf-ba24-bdc232076b00) 618 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 619 | (effects (font (size 1.27 1.27)) hide) 620 | ) 621 | ) 622 | (global_label "AxisX_1" (shape input) (at 134.62 45.72 180) (fields_autoplaced) 623 | (effects (font (size 1.27 1.27)) (justify right)) 624 | (uuid 102b27c6-5fa4-437f-bca2-44340d4e4929) 625 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 626 | (effects (font (size 1.27 1.27)) hide) 627 | ) 628 | ) 629 | (global_label "SW4" (shape input) (at 106.68 50.8 0) (fields_autoplaced) 630 | (effects (font (size 1.27 1.27)) (justify left)) 631 | (uuid 11a59fc9-a02c-448e-bb69-ff9ab0aa7d0a) 632 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 633 | (effects (font (size 1.27 1.27)) hide) 634 | ) 635 | ) 636 | (global_label "GND" (shape input) (at 100.33 85.09 0) (fields_autoplaced) 637 | (effects (font (size 1.27 1.27)) (justify left)) 638 | (uuid 15970fb1-ee04-49f2-b0f4-c860a0495c2b) 639 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 640 | (effects (font (size 1.27 1.27)) hide) 641 | ) 642 | ) 643 | (global_label "Button_3" (shape input) (at 106.68 66.04 0) (fields_autoplaced) 644 | (effects (font (size 1.27 1.27)) (justify left)) 645 | (uuid 1c76d310-1bb8-4bfd-890f-f2c0219d9069) 646 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 647 | (effects (font (size 1.27 1.27)) hide) 648 | ) 649 | ) 650 | (global_label "GND" (shape input) (at 78.74 45.72 180) (fields_autoplaced) 651 | (effects (font (size 1.27 1.27)) (justify right)) 652 | (uuid 23194f1b-b539-4d51-b706-0adb60186437) 653 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 654 | (effects (font (size 1.27 1.27)) hide) 655 | ) 656 | ) 657 | (global_label "AxisX_1" (shape input) (at 44.45 46.99 180) (fields_autoplaced) 658 | (effects (font (size 1.27 1.27)) (justify right)) 659 | (uuid 2769edd9-5bfc-4857-ab13-a32efa909110) 660 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 661 | (effects (font (size 1.27 1.27)) hide) 662 | ) 663 | ) 664 | (global_label "GND" (shape input) (at 100.33 80.01 0) (fields_autoplaced) 665 | (effects (font (size 1.27 1.27)) (justify left)) 666 | (uuid 2a470269-2077-4685-9b56-e8d1b4433587) 667 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 668 | (effects (font (size 1.27 1.27)) hide) 669 | ) 670 | ) 671 | (global_label "Button_1" (shape input) (at 44.45 41.91 180) (fields_autoplaced) 672 | (effects (font (size 1.27 1.27)) (justify right)) 673 | (uuid 2d0979b4-fffa-40d1-b2d6-d7cd9b54c586) 674 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 675 | (effects (font (size 1.27 1.27)) hide) 676 | ) 677 | ) 678 | (global_label "MIDI_TXD" (shape input) (at 78.74 58.42 180) (fields_autoplaced) 679 | (effects (font (size 1.27 1.27)) (justify right)) 680 | (uuid 32584d03-f24b-4d25-b769-99ee8dfd94f3) 681 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 682 | (effects (font (size 1.27 1.27)) hide) 683 | ) 684 | ) 685 | (global_label "GND" (shape input) (at 52.07 77.47 270) (fields_autoplaced) 686 | (effects (font (size 1.27 1.27)) (justify right)) 687 | (uuid 362ff863-ada6-463f-b0c6-9b9595cc9434) 688 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 689 | (effects (font (size 1.27 1.27)) hide) 690 | ) 691 | ) 692 | (global_label "AxisY_1" (shape input) (at 78.74 60.96 180) (fields_autoplaced) 693 | (effects (font (size 1.27 1.27)) (justify right)) 694 | (uuid 48b73a96-a760-46a8-9596-de355188404f) 695 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 696 | (effects (font (size 1.27 1.27)) hide) 697 | ) 698 | ) 699 | (global_label "SW4" (shape input) (at 85.09 87.63 180) (fields_autoplaced) 700 | (effects (font (size 1.27 1.27)) (justify right)) 701 | (uuid 4ca59c36-066c-4d6b-8e60-d80d4abecfbf) 702 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 703 | (effects (font (size 1.27 1.27)) hide) 704 | ) 705 | ) 706 | (global_label "AxisX_1" (shape input) (at 106.68 58.42 0) (fields_autoplaced) 707 | (effects (font (size 1.27 1.27)) (justify left)) 708 | (uuid 50ea8d66-baf0-40f8-a0d4-bb04ffb94d72) 709 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 710 | (effects (font (size 1.27 1.27)) hide) 711 | ) 712 | ) 713 | (global_label "AxisY_1" (shape input) (at 44.45 62.23 180) (fields_autoplaced) 714 | (effects (font (size 1.27 1.27)) (justify right)) 715 | (uuid 529984dd-b499-4faf-8d23-6de4a4127690) 716 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 717 | (effects (font (size 1.27 1.27)) hide) 718 | ) 719 | ) 720 | (global_label "Button_2" (shape input) (at 44.45 67.31 180) (fields_autoplaced) 721 | (effects (font (size 1.27 1.27)) (justify right)) 722 | (uuid 5511a972-c4e3-4868-a79d-74a348da4613) 723 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 724 | (effects (font (size 1.27 1.27)) hide) 725 | ) 726 | ) 727 | (global_label "AxisY_2" (shape input) (at 134.62 55.88 180) (fields_autoplaced) 728 | (effects (font (size 1.27 1.27)) (justify right)) 729 | (uuid 57227ea0-9d1c-4b9a-ad79-514e62fb857d) 730 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 731 | (effects (font (size 1.27 1.27)) hide) 732 | ) 733 | ) 734 | (global_label "SW1" (shape input) (at 106.68 63.5 0) (fields_autoplaced) 735 | (effects (font (size 1.27 1.27)) (justify left)) 736 | (uuid 5a30b611-101a-4e45-9b9e-59f19d148e7e) 737 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 738 | (effects (font (size 1.27 1.27)) hide) 739 | ) 740 | ) 741 | (global_label "SW2" (shape input) (at 106.68 60.96 0) (fields_autoplaced) 742 | (effects (font (size 1.27 1.27)) (justify left)) 743 | (uuid 5a7f8ff6-2d59-4d64-8572-59a0f3e564b4) 744 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 745 | (effects (font (size 1.27 1.27)) hide) 746 | ) 747 | ) 748 | (global_label "AxisY_1" (shape input) (at 134.62 50.8 180) (fields_autoplaced) 749 | (effects (font (size 1.27 1.27)) (justify right)) 750 | (uuid 5c241f7b-e19b-40e7-83eb-93d12d451853) 751 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 752 | (effects (font (size 1.27 1.27)) hide) 753 | ) 754 | ) 755 | (global_label "AxisY_2" (shape input) (at 44.45 59.69 180) (fields_autoplaced) 756 | (effects (font (size 1.27 1.27)) (justify right)) 757 | (uuid 5cc77296-73a0-4bf2-9d2c-6662825a8988) 758 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 759 | (effects (font (size 1.27 1.27)) hide) 760 | ) 761 | ) 762 | (global_label "VCC" (shape input) (at 44.45 72.39 180) (fields_autoplaced) 763 | (effects (font (size 1.27 1.27)) (justify right)) 764 | (uuid 6c9eca85-d165-45ea-ac73-a2597bf71089) 765 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 766 | (effects (font (size 1.27 1.27)) hide) 767 | ) 768 | ) 769 | (global_label "GND" (shape input) (at 142.24 40.64 0) (fields_autoplaced) 770 | (effects (font (size 1.27 1.27)) (justify left)) 771 | (uuid 79f1ae75-40c0-437d-8f27-14edb08f10a4) 772 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 773 | (effects (font (size 1.27 1.27)) hide) 774 | ) 775 | ) 776 | (global_label "GND" (shape input) (at 78.74 48.26 180) (fields_autoplaced) 777 | (effects (font (size 1.27 1.27)) (justify right)) 778 | (uuid 7af27a64-6d55-4415-a3c8-bcd566c8a0c5) 779 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 780 | (effects (font (size 1.27 1.27)) hide) 781 | ) 782 | ) 783 | (global_label "GND" (shape input) (at 44.45 52.07 180) (fields_autoplaced) 784 | (effects (font (size 1.27 1.27)) (justify right)) 785 | (uuid 80207010-4c44-48f0-913e-1c576715a15e) 786 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 787 | (effects (font (size 1.27 1.27)) hide) 788 | ) 789 | ) 790 | (global_label "Button_3" (shape input) (at 44.45 44.45 180) (fields_autoplaced) 791 | (effects (font (size 1.27 1.27)) (justify right)) 792 | (uuid 813a224c-843f-43f2-9770-eb1930196980) 793 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 794 | (effects (font (size 1.27 1.27)) hide) 795 | ) 796 | ) 797 | (global_label "GND" (shape input) (at 142.24 55.88 0) (fields_autoplaced) 798 | (effects (font (size 1.27 1.27)) (justify left)) 799 | (uuid 84ae0863-b8aa-41e6-9bcc-7d9c302c13ab) 800 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 801 | (effects (font (size 1.27 1.27)) hide) 802 | ) 803 | ) 804 | (global_label "VCC" (shape input) (at 44.45 36.83 180) (fields_autoplaced) 805 | (effects (font (size 1.27 1.27)) (justify right)) 806 | (uuid 898e311f-d055-4495-be6e-4c7ce065d220) 807 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 808 | (effects (font (size 1.27 1.27)) hide) 809 | ) 810 | ) 811 | (global_label "Button_1" (shape input) (at 106.68 68.58 0) (fields_autoplaced) 812 | (effects (font (size 1.27 1.27)) (justify left)) 813 | (uuid 8a42f275-769d-46d0-8c4d-9d8032a1a87d) 814 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 815 | (effects (font (size 1.27 1.27)) hide) 816 | ) 817 | ) 818 | (global_label "GND" (shape input) (at 142.24 50.8 0) (fields_autoplaced) 819 | (effects (font (size 1.27 1.27)) (justify left)) 820 | (uuid 8df1fb65-01e7-4440-8419-067ecd7e3059) 821 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 822 | (effects (font (size 1.27 1.27)) hide) 823 | ) 824 | ) 825 | (global_label "VCC" (shape input) (at 106.68 48.26 0) (fields_autoplaced) 826 | (effects (font (size 1.27 1.27)) (justify left)) 827 | (uuid b5d4ea29-08c0-4868-8ad9-5735c858d6e8) 828 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 829 | (effects (font (size 1.27 1.27)) hide) 830 | ) 831 | ) 832 | (global_label "MIDI_TXD" (shape input) (at 44.45 54.61 180) (fields_autoplaced) 833 | (effects (font (size 1.27 1.27)) (justify right)) 834 | (uuid b94d9d82-07bf-4cf6-a02a-5e8d117c96e4) 835 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 836 | (effects (font (size 1.27 1.27)) hide) 837 | ) 838 | ) 839 | (global_label "AxisX_2" (shape input) (at 44.45 49.53 180) (fields_autoplaced) 840 | (effects (font (size 1.27 1.27)) (justify right)) 841 | (uuid bc5388fc-f11a-411a-b074-698fbc66d18a) 842 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 843 | (effects (font (size 1.27 1.27)) hide) 844 | ) 845 | ) 846 | (global_label "GND" (shape input) (at 100.33 87.63 0) (fields_autoplaced) 847 | (effects (font (size 1.27 1.27)) (justify left)) 848 | (uuid c82e3477-f0b3-4df4-82e1-44703f7dd8e2) 849 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 850 | (effects (font (size 1.27 1.27)) hide) 851 | ) 852 | ) 853 | (global_label "Button_4" (shape input) (at 44.45 64.77 180) (fields_autoplaced) 854 | (effects (font (size 1.27 1.27)) (justify right)) 855 | (uuid cc94f237-4f3c-4055-adc8-e86d78dae130) 856 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 857 | (effects (font (size 1.27 1.27)) hide) 858 | ) 859 | ) 860 | (global_label "Button_4" (shape input) (at 78.74 63.5 180) (fields_autoplaced) 861 | (effects (font (size 1.27 1.27)) (justify right)) 862 | (uuid ce6c921d-987a-405e-8453-24fa6ce56ddb) 863 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 864 | (effects (font (size 1.27 1.27)) hide) 865 | ) 866 | ) 867 | (global_label "AxisX_2" (shape input) (at 106.68 55.88 0) (fields_autoplaced) 868 | (effects (font (size 1.27 1.27)) (justify left)) 869 | (uuid d0f31a14-c47a-4207-acc0-2f55ef35deb7) 870 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 871 | (effects (font (size 1.27 1.27)) hide) 872 | ) 873 | ) 874 | (global_label "SW1" (shape input) (at 85.09 80.01 180) (fields_autoplaced) 875 | (effects (font (size 1.27 1.27)) (justify right)) 876 | (uuid d189f300-2d01-4e58-910a-f106e6442ba5) 877 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 878 | (effects (font (size 1.27 1.27)) hide) 879 | ) 880 | ) 881 | (global_label "MIDI_RXD" (shape input) (at 44.45 69.85 180) (fields_autoplaced) 882 | (effects (font (size 1.27 1.27)) (justify right)) 883 | (uuid dd3b2783-66fd-472e-97fc-fb4fa3e36429) 884 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 885 | (effects (font (size 1.27 1.27)) hide) 886 | ) 887 | ) 888 | (global_label "GND" (shape input) (at 44.45 57.15 180) (fields_autoplaced) 889 | (effects (font (size 1.27 1.27)) (justify right)) 890 | (uuid dd9d5ece-3d48-44b0-8bb3-ff7af5d3c53e) 891 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 892 | (effects (font (size 1.27 1.27)) hide) 893 | ) 894 | ) 895 | (global_label "GND" (shape input) (at 100.33 82.55 0) (fields_autoplaced) 896 | (effects (font (size 1.27 1.27)) (justify left)) 897 | (uuid e3835e7a-421c-4631-86b9-c9e45b93d4c1) 898 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 899 | (effects (font (size 1.27 1.27)) hide) 900 | ) 901 | ) 902 | (global_label "SW2" (shape input) (at 85.09 82.55 180) (fields_autoplaced) 903 | (effects (font (size 1.27 1.27)) (justify right)) 904 | (uuid e4638165-2144-4e90-9020-20ecc0111461) 905 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 906 | (effects (font (size 1.27 1.27)) hide) 907 | ) 908 | ) 909 | (global_label "GND" (shape input) (at 106.68 43.18 0) (fields_autoplaced) 910 | (effects (font (size 1.27 1.27)) (justify left)) 911 | (uuid e8126722-6858-4626-81ac-9ff744c4d69c) 912 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 913 | (effects (font (size 1.27 1.27)) hide) 914 | ) 915 | ) 916 | (global_label "MIDI_RXD" (shape input) (at 78.74 68.58 180) (fields_autoplaced) 917 | (effects (font (size 1.27 1.27)) (justify right)) 918 | (uuid e93f5d69-4e56-4764-8b44-e780267a7940) 919 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 920 | (effects (font (size 1.27 1.27)) hide) 921 | ) 922 | ) 923 | (global_label "AxisX_2" (shape input) (at 134.62 40.64 180) (fields_autoplaced) 924 | (effects (font (size 1.27 1.27)) (justify right)) 925 | (uuid f6f25110-f789-4395-8ef7-fb1fffd7b6a0) 926 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 927 | (effects (font (size 1.27 1.27)) hide) 928 | ) 929 | ) 930 | (global_label "SW3" (shape input) (at 106.68 53.34 0) (fields_autoplaced) 931 | (effects (font (size 1.27 1.27)) (justify left)) 932 | (uuid f8e81aa4-3eeb-4b95-bab2-980cace0845a) 933 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 934 | (effects (font (size 1.27 1.27)) hide) 935 | ) 936 | ) 937 | (global_label "Button_2" (shape input) (at 78.74 66.04 180) (fields_autoplaced) 938 | (effects (font (size 1.27 1.27)) (justify right)) 939 | (uuid fd088acc-7d57-43aa-a4cd-14ba3bdec224) 940 | (property "Intersheet References" "${INTERSHEET_REFS}" (id 0) (at 0 0 0) 941 | (effects (font (size 1.27 1.27)) hide) 942 | ) 943 | ) 944 | 945 | (symbol (lib_id "Connector:DB15_Female_MountingHoles") (at 52.07 54.61 0) (unit 1) 946 | (in_bom yes) (on_board yes) 947 | (uuid 00000000-0000-0000-0000-00005fb67ac2) 948 | (property "Reference" "CONN1" (id 0) (at 55.9816 53.4416 0) 949 | (effects (font (size 1.27 1.27)) (justify left)) 950 | ) 951 | (property "Value" "" (id 1) (at 55.9816 55.753 0) 952 | (effects (font (size 1.27 1.27)) (justify left)) 953 | ) 954 | (property "Footprint" "" (id 2) (at 52.07 54.61 0) 955 | (effects (font (size 1.27 1.27)) hide) 956 | ) 957 | (property "Datasheet" " ~" (id 3) (at 52.07 54.61 0) 958 | (effects (font (size 1.27 1.27)) hide) 959 | ) 960 | (property "LCSC" "C77835" (id 4) (at 52.07 54.61 0) 961 | (effects (font (size 1.27 1.27)) hide) 962 | ) 963 | (pin "0" (uuid f0fdc5b7-db43-47ab-90bf-fad51b95fc26)) 964 | (pin "1" (uuid 29847295-8d2e-4057-b697-350843ddd9fd)) 965 | (pin "10" (uuid a9a6aa6e-0b6f-4506-8351-51981a94ed63)) 966 | (pin "11" (uuid da9e2e12-43f7-42d4-851f-97b2ae5baa73)) 967 | (pin "12" (uuid 49005ae8-5c75-4ee9-8f4e-677b40dc4b10)) 968 | (pin "13" (uuid f5428a46-2a40-4aa3-a7a4-250ed20c9889)) 969 | (pin "14" (uuid f6cb5d8d-ad91-4b4b-8099-085c6dfaf952)) 970 | (pin "15" (uuid 17ecd665-9f69-48c9-b065-6719bb7bc0d4)) 971 | (pin "2" (uuid 573e579d-4d14-46a7-a1d9-8502e98eb9a4)) 972 | (pin "3" (uuid bb77c1c1-3a48-43c2-93c6-b226d125436d)) 973 | (pin "4" (uuid b8b5a6d1-d8fc-45ed-85b7-f0675a7e0246)) 974 | (pin "5" (uuid 487a8083-b4dd-486f-b2c4-a01240e122d1)) 975 | (pin "6" (uuid 8c330fde-7a30-4c91-bb1d-512ba8cf3dd0)) 976 | (pin "7" (uuid a266fbce-292e-45c0-8ae8-77a28f5b148b)) 977 | (pin "8" (uuid 662db4fd-c95d-4dbe-b798-f3b0dfdd8955)) 978 | (pin "9" (uuid 8958b652-f4b7-4b87-8755-3e5b03e0ce48)) 979 | ) 980 | 981 | (symbol (lib_id "Necroware:Arduino_Pro_Micro") (at 78.74 35.56 0) (unit 1) 982 | (in_bom yes) (on_board yes) 983 | (uuid 00000000-0000-0000-0000-00005ff74419) 984 | (property "Reference" "U1" (id 0) (at 92.71 33.909 0)) 985 | (property "Value" "" (id 1) (at 92.71 36.2204 0)) 986 | (property "Footprint" "" (id 2) (at 78.74 35.56 0) 987 | (effects (font (size 1.27 1.27)) hide) 988 | ) 989 | (property "Datasheet" "" (id 3) (at 78.74 35.56 0) 990 | (effects (font (size 1.27 1.27)) hide) 991 | ) 992 | (property "LCSC" "C72120" (id 4) (at 78.74 35.56 0) 993 | (effects (font (size 1.27 1.27)) hide) 994 | ) 995 | (pin "1" (uuid 9dfcfa7c-e0c9-4370-a63b-2034c158a448)) 996 | (pin "10" (uuid f9989092-84d7-4e76-a909-706bc644b31c)) 997 | (pin "11" (uuid b4748257-9696-47b9-91ff-62996cf3cac6)) 998 | (pin "12" (uuid 018d93bf-6a65-4051-a8e7-3a6e297826bc)) 999 | (pin "13" (uuid c9edd1e9-4f50-4e48-bd42-e78470828a84)) 1000 | (pin "14" (uuid 62cc0655-3b97-4a4e-abab-bf6f7a6e68f2)) 1001 | (pin "15" (uuid 71eb3543-728d-432b-a160-876ea185cb91)) 1002 | (pin "16" (uuid f536419e-3f7e-4677-9858-ceb7088d1973)) 1003 | (pin "17" (uuid 190255df-417a-46a6-97ed-af0b14f82a2d)) 1004 | (pin "18" (uuid acdd9b7e-6ece-4483-8853-a467bb90d2c3)) 1005 | (pin "19" (uuid 85878123-9d12-40ff-846d-947cdc594669)) 1006 | (pin "2" (uuid e3a9f332-8022-4eca-9503-876c1b4f3dd8)) 1007 | (pin "20" (uuid 9d9b178b-f4bd-45d6-8649-5ce8c70235c0)) 1008 | (pin "21" (uuid 5491a099-3be3-4868-9dc9-6e93a1892c4c)) 1009 | (pin "22" (uuid 3f0d46d4-091d-430a-a3fe-b836fb30e283)) 1010 | (pin "23" (uuid 6dfa8536-6581-464a-b636-d23568b46855)) 1011 | (pin "24" (uuid 06761a52-a1a9-4e92-b221-3607f10d5cdb)) 1012 | (pin "3" (uuid a8cc6d54-10c6-4273-83b1-b60f7a606e9f)) 1013 | (pin "4" (uuid c6fb16bb-abf1-46da-b11b-d71a5bc1d795)) 1014 | (pin "5" (uuid f470e827-e5c5-43f5-b465-cdcd7436b005)) 1015 | (pin "6" (uuid 0ec8bec4-8313-4ebb-924a-7ef502b12458)) 1016 | (pin "7" (uuid 513a5e22-5a7c-45f1-a199-b2e5e8f9fc25)) 1017 | (pin "8" (uuid 582d5cd7-236c-48dd-bdce-aa521e377225)) 1018 | (pin "9" (uuid 51a508b0-07d0-4c0d-91d0-17d3cb38f52b)) 1019 | ) 1020 | 1021 | (symbol (lib_id "Switch:SW_DIP_x04") (at 92.71 85.09 0) (mirror y) (unit 1) 1022 | (in_bom yes) (on_board yes) 1023 | (uuid 00000000-0000-0000-0000-00005ff7e601) 1024 | (property "Reference" "SW1" (id 0) (at 92.71 73.2282 0)) 1025 | (property "Value" "" (id 1) (at 92.71 75.5396 0)) 1026 | (property "Footprint" "" (id 2) (at 92.71 85.09 0) 1027 | (effects (font (size 1.27 1.27)) hide) 1028 | ) 1029 | (property "Datasheet" "~" (id 3) (at 92.71 85.09 0) 1030 | (effects (font (size 1.27 1.27)) hide) 1031 | ) 1032 | (property "LCSC" "C15781" (id 4) (at 92.71 85.09 0) 1033 | (effects (font (size 1.27 1.27)) hide) 1034 | ) 1035 | (pin "1" (uuid e0652241-6721-4785-8eb0-993a241b29a3)) 1036 | (pin "2" (uuid 0e8f9dbc-5d48-4c49-8880-ae3b3ecd3117)) 1037 | (pin "3" (uuid bf6237d7-633c-4f89-9c74-1d5b755179f1)) 1038 | (pin "4" (uuid 1e862182-222c-4223-8db7-eb505d135408)) 1039 | (pin "5" (uuid cb9a1e6e-d7da-4fd8-acf8-73a1473dc09e)) 1040 | (pin "6" (uuid da2f543a-30dc-429e-b42c-7829617ab50d)) 1041 | (pin "7" (uuid 68bd699f-bdc2-4899-8dad-8697735af938)) 1042 | (pin "8" (uuid 858c8c22-9c01-437b-887c-3ab41d54b1fe)) 1043 | ) 1044 | 1045 | (symbol (lib_id "Device:R") (at 138.43 40.64 90) (unit 1) 1046 | (in_bom yes) (on_board yes) 1047 | (uuid 00000000-0000-0000-0000-0000604bcfe5) 1048 | (property "Reference" "R1" (id 0) (at 137.16 38.1 90) 1049 | (effects (font (size 1.27 1.27)) (justify right)) 1050 | ) 1051 | (property "Value" "" (id 1) (at 135.89 40.64 90) 1052 | (effects (font (size 1.27 1.27)) (justify right)) 1053 | ) 1054 | (property "Footprint" "" (id 2) (at 138.43 42.418 90) 1055 | (effects (font (size 1.27 1.27)) hide) 1056 | ) 1057 | (property "Datasheet" "~" (id 3) (at 138.43 40.64 0) 1058 | (effects (font (size 1.27 1.27)) hide) 1059 | ) 1060 | (property "LCSC" "C172965" (id 4) (at 138.43 40.64 90) 1061 | (effects (font (size 1.27 1.27)) hide) 1062 | ) 1063 | (pin "1" (uuid a7779120-8d8e-4c63-bd1d-1f2be0155c1e)) 1064 | (pin "2" (uuid c273dc66-91c0-491a-9f40-68fe20c13737)) 1065 | ) 1066 | 1067 | (symbol (lib_id "Device:R") (at 138.43 45.72 270) (unit 1) 1068 | (in_bom yes) (on_board yes) 1069 | (uuid 00000000-0000-0000-0000-0000604be4d6) 1070 | (property "Reference" "R2" (id 0) (at 139.7 43.18 90) 1071 | (effects (font (size 1.27 1.27)) (justify right)) 1072 | ) 1073 | (property "Value" "" (id 1) (at 140.97 45.72 90) 1074 | (effects (font (size 1.27 1.27)) (justify right)) 1075 | ) 1076 | (property "Footprint" "" (id 2) (at 138.43 43.942 90) 1077 | (effects (font (size 1.27 1.27)) hide) 1078 | ) 1079 | (property "Datasheet" "~" (id 3) (at 138.43 45.72 0) 1080 | (effects (font (size 1.27 1.27)) hide) 1081 | ) 1082 | (property "LCSC" "C172965" (id 4) (at 138.43 45.72 90) 1083 | (effects (font (size 1.27 1.27)) hide) 1084 | ) 1085 | (pin "1" (uuid c264aa90-f606-4976-a247-976787253e35)) 1086 | (pin "2" (uuid eec0d6af-b8d6-4ba4-8132-99d1ac369234)) 1087 | ) 1088 | 1089 | (symbol (lib_id "Device:R") (at 138.43 50.8 90) (unit 1) 1090 | (in_bom yes) (on_board yes) 1091 | (uuid 00000000-0000-0000-0000-0000604c20f3) 1092 | (property "Reference" "R3" (id 0) (at 137.16 48.26 90) 1093 | (effects (font (size 1.27 1.27)) (justify right)) 1094 | ) 1095 | (property "Value" "" (id 1) (at 135.89 50.8 90) 1096 | (effects (font (size 1.27 1.27)) (justify right)) 1097 | ) 1098 | (property "Footprint" "" (id 2) (at 138.43 52.578 90) 1099 | (effects (font (size 1.27 1.27)) hide) 1100 | ) 1101 | (property "Datasheet" "~" (id 3) (at 138.43 50.8 0) 1102 | (effects (font (size 1.27 1.27)) hide) 1103 | ) 1104 | (property "LCSC" "C172965" (id 4) (at 138.43 50.8 90) 1105 | (effects (font (size 1.27 1.27)) hide) 1106 | ) 1107 | (pin "1" (uuid 670e5ebf-5bdf-4075-bd4c-22eab5562316)) 1108 | (pin "2" (uuid 899fe8ae-c23d-4b20-a3e4-a59ccca71752)) 1109 | ) 1110 | 1111 | (symbol (lib_id "Device:R") (at 138.43 55.88 270) (unit 1) 1112 | (in_bom yes) (on_board yes) 1113 | (uuid 00000000-0000-0000-0000-0000604c282a) 1114 | (property "Reference" "R4" (id 0) (at 139.7 53.34 90) 1115 | (effects (font (size 1.27 1.27)) (justify right)) 1116 | ) 1117 | (property "Value" "" (id 1) (at 140.97 55.88 90) 1118 | (effects (font (size 1.27 1.27)) (justify right)) 1119 | ) 1120 | (property "Footprint" "" (id 2) (at 138.43 54.102 90) 1121 | (effects (font (size 1.27 1.27)) hide) 1122 | ) 1123 | (property "Datasheet" "~" (id 3) (at 138.43 55.88 0) 1124 | (effects (font (size 1.27 1.27)) hide) 1125 | ) 1126 | (property "LCSC" "C172965" (id 4) (at 138.43 55.88 90) 1127 | (effects (font (size 1.27 1.27)) hide) 1128 | ) 1129 | (pin "1" (uuid c748d2f5-60b1-4522-a318-c97a8c105861)) 1130 | (pin "2" (uuid 1fd3de6b-2b30-41ee-aca2-4b07f746cf42)) 1131 | ) 1132 | 1133 | (sheet_instances 1134 | (path "/" (page "1")) 1135 | ) 1136 | 1137 | (symbol_instances 1138 | (path "/00000000-0000-0000-0000-00005fb67ac2" 1139 | (reference "CONN1") (unit 1) (value "GamePort") (footprint "Connector_Dsub:DSUB-15_Female_Horizontal_P2.77x2.84mm_EdgePinOffset7.70mm_Housed_MountingHolesOffset9.12mm") 1140 | ) 1141 | (path "/00000000-0000-0000-0000-0000604bcfe5" 1142 | (reference "R1") (unit 1) (value "100k") (footprint "Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P7.62mm_Horizontal") 1143 | ) 1144 | (path "/00000000-0000-0000-0000-0000604be4d6" 1145 | (reference "R2") (unit 1) (value "100k") (footprint "Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P7.62mm_Horizontal") 1146 | ) 1147 | (path "/00000000-0000-0000-0000-0000604c20f3" 1148 | (reference "R3") (unit 1) (value "100k") (footprint "Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P7.62mm_Horizontal") 1149 | ) 1150 | (path "/00000000-0000-0000-0000-0000604c282a" 1151 | (reference "R4") (unit 1) (value "100k") (footprint "Resistor_THT:R_Axial_DIN0204_L3.6mm_D1.6mm_P7.62mm_Horizontal") 1152 | ) 1153 | (path "/00000000-0000-0000-0000-00005ff7e601" 1154 | (reference "SW1") (unit 1) (value "SW_DIP_x04") (footprint "Button_Switch_THT:SW_DIP_SPSTx04_Slide_9.78x12.34mm_W7.62mm_P2.54mm") 1155 | ) 1156 | (path "/00000000-0000-0000-0000-00005ff74419" 1157 | (reference "U1") (unit 1) (value "Arduino_Pro_Micro") (footprint "Package_DIP:DIP-24_W15.24mm_Socket") 1158 | ) 1159 | ) 1160 | ) 1161 | --------------------------------------------------------------------------------