├── 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 | 
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 |
--------------------------------------------------------------------------------