Initial commit
This commit is contained in:
commit
7cf833e59f
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveBitFields: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 200
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentCaseLabels: true
|
||||
IndentCaseBlocks: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
- ParseTestProto
|
||||
- ParsePartialTestProto
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: Always
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
...
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.pio
|
||||
ignore/
|
||||
*.kate-swp
|
|
@ -0,0 +1,3 @@
|
|||
cmake_minimum_required(VERSION 3.16.0)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(OpenRetroPad)
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
find src -type f \( -iname '*.h' -o -iname '*.c' -o -iname '*.cpp' \) -print0 | xargs -0 clang-format -style=file -i -fallback-style=none
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the usual convention is to give header files names that end with `.h'.
|
||||
It is most portable to use only letters, digits, dashes, and underscores in
|
||||
header file names, and at most one dot.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into executable file.
|
||||
|
||||
The source code of each library should be placed in a an own separate directory
|
||||
("lib/your_library_name/[here are source files]").
|
||||
|
||||
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
and a contents of `src/main.c`:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
|
@ -0,0 +1,151 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
# env's are laid out like:
|
||||
# $board-$input-$output
|
||||
# supported values:
|
||||
# $board: micro, esp32
|
||||
# $input: radio, snes, debug
|
||||
# $output: radio, usb, bt, debug
|
||||
# please note not all boards are compatible with all inputs/outputs
|
||||
# for example esp32 can only do bt
|
||||
# micro can only do radio or usb
|
||||
|
||||
[common]
|
||||
# change GAMEPAD_COUNT if you want
|
||||
build_flags = -DGAMEPAD_COUNT=2
|
||||
|
||||
[env]
|
||||
monitor_speed = 115200
|
||||
framework = arduino
|
||||
|
||||
# boards
|
||||
|
||||
[esp32]
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
|
||||
[micro]
|
||||
platform = atmelavr
|
||||
board = micro
|
||||
|
||||
# outputs
|
||||
|
||||
[out-debug]
|
||||
build_flags = -DGAMEPAD_OUTPUT=0
|
||||
src_filter = +<gamepad/Debug-Gamepad>
|
||||
|
||||
[out-radio]
|
||||
build_flags = -DGAMEPAD_OUTPUT=1
|
||||
src_filter = +<gamepad/Radio-Gamepad>
|
||||
lib_deps = tmrh20/RF24@^1.3.9
|
||||
|
||||
[out-usb]
|
||||
build_flags = -DGAMEPAD_OUTPUT=2
|
||||
src_filter = +<gamepad/USB-Gamepad>
|
||||
framework = arduino
|
||||
platform = atmelavr
|
||||
|
||||
[out-bt]
|
||||
build_flags = -DGAMEPAD_OUTPUT=3
|
||||
src_filter = +<gamepad/ESP32-BLE-Gamepad>
|
||||
|
||||
# radio input
|
||||
|
||||
[in-radio]
|
||||
extends = out-radio
|
||||
src_filter = -<*> +<RadioReceiver.cpp>
|
||||
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=1
|
||||
|
||||
# this one is a bit crazy, just use esp32-$input-bt directly
|
||||
[env:esp32-radio-bt]
|
||||
extends = esp32, in-radio, out-bt
|
||||
src_filter = ${in-radio.src_filter} ${out-bt.src_filter}
|
||||
build_flags = ${in-radio.build_flags} ${out-bt.build_flags}
|
||||
|
||||
[env:esp32-radio-debug]
|
||||
extends = esp32, in-radio, out-debug
|
||||
src_filter = ${in-radio.src_filter} ${out-debug.src_filter}
|
||||
build_flags = ${in-radio.build_flags} ${out-debug.build_flags}
|
||||
|
||||
[env:micro-radio-usb]
|
||||
extends = micro, in-radio, out-usb
|
||||
src_filter = ${in-radio.src_filter} ${out-usb.src_filter}
|
||||
build_flags = ${in-radio.build_flags} ${out-usb.build_flags}
|
||||
|
||||
[env:micro-radio-debug]
|
||||
extends = micro, in-radio, out-debug
|
||||
src_filter = ${in-radio.src_filter} ${out-debug.src_filter}
|
||||
build_flags = ${in-radio.build_flags} ${out-debug.build_flags}
|
||||
|
||||
# not enough pins for env:micro-radio-radio , plus it'd be crazy
|
||||
|
||||
# snes input
|
||||
|
||||
[in-snes]
|
||||
src_filter = -<*> +<SnesNes.cpp>
|
||||
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=2
|
||||
|
||||
[env:esp32-snes-bt]
|
||||
extends = esp32, in-snes, out-bt
|
||||
src_filter = ${in-snes.src_filter} ${out-bt.src_filter}
|
||||
build_flags = ${in-snes.build_flags} ${out-bt.build_flags}
|
||||
|
||||
[env:esp32-snes-debug]
|
||||
extends = esp32, in-snes, out-debug
|
||||
src_filter = ${in-snes.src_filter} ${out-debug.src_filter}
|
||||
build_flags = ${in-snes.build_flags} ${out-debug.build_flags}
|
||||
|
||||
[env:micro-snes-usb]
|
||||
extends = micro, in-snes, out-usb
|
||||
src_filter = ${in-snes.src_filter} ${out-usb.src_filter}
|
||||
build_flags = ${in-snes.build_flags} ${out-usb.build_flags}
|
||||
|
||||
[env:micro-snes-debug]
|
||||
extends = micro, in-snes, out-debug
|
||||
src_filter = ${in-snes.src_filter} ${out-debug.src_filter}
|
||||
build_flags = ${in-snes.build_flags} ${out-debug.build_flags}
|
||||
|
||||
[env:micro-snes-radio]
|
||||
extends = micro, in-snes, out-radio
|
||||
src_filter = ${in-snes.src_filter} ${out-radio.src_filter}
|
||||
build_flags = ${in-snes.build_flags} ${out-radio.build_flags}
|
||||
|
||||
# debug input
|
||||
|
||||
[in-debug]
|
||||
src_filter = -<*> +<Debug.cpp>
|
||||
build_flags = ${common.build_flags} -DGAMEPAD_INPUT=2
|
||||
|
||||
[env:esp32-debug-bt]
|
||||
extends = esp32, in-debug, out-bt
|
||||
src_filter = ${in-debug.src_filter} ${out-bt.src_filter}
|
||||
build_flags = ${in-debug.build_flags} ${out-bt.build_flags}
|
||||
|
||||
[env:esp32-debug-debug]
|
||||
extends = esp32, in-debug, out-debug
|
||||
src_filter = ${in-debug.src_filter} ${out-debug.src_filter}
|
||||
build_flags = ${in-debug.build_flags} ${out-debug.build_flags}
|
||||
|
||||
[env:micro-debug-usb]
|
||||
extends = micro, in-debug, out-usb
|
||||
src_filter = ${in-debug.src_filter} ${out-usb.src_filter}
|
||||
build_flags = ${in-debug.build_flags} ${out-usb.build_flags}
|
||||
|
||||
[env:micro-debug-debug]
|
||||
extends = micro, in-debug, out-debug
|
||||
src_filter = ${in-debug.src_filter} ${out-debug.src_filter}
|
||||
build_flags = ${in-debug.build_flags} ${out-debug.build_flags}
|
||||
|
||||
[env:micro-debug-radio]
|
||||
extends = micro, in-debug, out-radio
|
||||
src_filter = ${in-debug.src_filter} ${out-radio.src_filter}
|
||||
build_flags = ${in-debug.build_flags} ${out-radio.build_flags}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Override some defaults so BT stack is enabled
|
||||
# and WiFi disabled by default in this example
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_ESP32_ENABLE_STACK_BT=y
|
||||
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
|
||||
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
|
||||
CONFIG_WIFI_ENABLED=n
|
||||
CONFIG_ESP32_ENABLE_STACK_WIFI=n
|
||||
CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=n
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "gamepad/Gamepad.h"
|
||||
|
||||
Gamepad gamepad;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Starting debug work!");
|
||||
gamepad.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (gamepad.isConnected()) {
|
||||
// test code to automate sending to test RadioReceiver (or any gamepad impl)
|
||||
uint8_t c = 0;
|
||||
Serial.println("Press buttons 1 and 32. Move all axes to center. Set DPAD to down right.");
|
||||
gamepad.press(c, BUTTON_1);
|
||||
gamepad.press(c, BUTTON_32);
|
||||
gamepad.setAxes(c, 0, 0, 0, 0, 0, 0, DPAD_DOWN_RIGHT);
|
||||
delay(500);
|
||||
Serial.println("Release all buttons. Move all axes to center. Set DPAD to center.");
|
||||
gamepad.buttons(c, 0);
|
||||
gamepad.setAxes(c, 0, 0, 0, 0, 0, 0, DPAD_CENTER);
|
||||
delay(500);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include <SPI.h>
|
||||
#include <nRF24L01.h>
|
||||
#include <RF24.h>
|
||||
|
||||
#define GAMEPAD_REPORT_ARRAY_ADD 1
|
||||
|
||||
#include "gamepad/Gamepad.h"
|
||||
|
||||
RF24 radio(7, 8); // CE, CSN
|
||||
|
||||
const byte address[13] = "OpenRetroPad";
|
||||
|
||||
Gamepad gamepad;
|
||||
|
||||
void setup() {
|
||||
gamepad.begin();
|
||||
radio.begin();
|
||||
radio.openReadingPipe(0, address);
|
||||
radio.setPALevel(RF24_PA_MIN);
|
||||
radio.startListening();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (radio.available() && gamepad.isConnected()) {
|
||||
radio.read(&gamepad.gamepadReport, 16);
|
||||
gamepad.sync(gamepad.gamepadReport[15]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define GAMEPAD_COUNT 2
|
||||
#define BUTTON_COUNT 12 // SNES has 12, NES only has 8
|
||||
|
||||
//individual data pin for each controller
|
||||
static const int DATA_PIN[GAMEPAD_COUNT] = {18, 19}; // grey, 20, 21 are the next logical ones
|
||||
|
||||
//shared pins between all controllers
|
||||
#if defined(CONFIG_BT_ENABLED)
|
||||
// ESP32
|
||||
static const int LATCH_PIN = 16; // brown
|
||||
static const int CLOCK_PIN = 17; // white
|
||||
#else
|
||||
// micro
|
||||
static const int LATCH_PIN = 2; // brown
|
||||
static const int CLOCK_PIN = 3; // white
|
||||
#endif
|
||||
// power red, ground black
|
||||
|
||||
#include "gamepad/Gamepad.h"
|
||||
|
||||
static const int translateToNES[12] = {1, 8, 2, 3, 4, 5, 6, 7, 0, 8, 8, 8};
|
||||
|
||||
class GameControllers {
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
NES = 0,
|
||||
SNES = 1,
|
||||
};
|
||||
|
||||
enum Button
|
||||
{
|
||||
B = 0,
|
||||
Y = 1,
|
||||
SELECT = 2,
|
||||
START = 3,
|
||||
UP = 4,
|
||||
DOWN = 5,
|
||||
LEFT = 6,
|
||||
RIGHT = 7,
|
||||
A = 8,
|
||||
X = 9,
|
||||
L = 10,
|
||||
R = 11,
|
||||
};
|
||||
|
||||
Type types[GAMEPAD_COUNT];
|
||||
int latchPin;
|
||||
int clockPin;
|
||||
int dataPins[GAMEPAD_COUNT];
|
||||
long buttons[GAMEPAD_COUNT][12];
|
||||
|
||||
int changedControllers[GAMEPAD_COUNT];
|
||||
|
||||
///This has to be initialized once for the shared pins latch and clock
|
||||
void init(int latch, int clock) {
|
||||
latchPin = latch;
|
||||
clockPin = clock;
|
||||
pinMode(latchPin, OUTPUT);
|
||||
digitalWrite(latchPin, LOW);
|
||||
pinMode(clockPin, OUTPUT);
|
||||
digitalWrite(clockPin, HIGH);
|
||||
for (int c = 0; c < GAMEPAD_COUNT; c++) {
|
||||
for (int i = 0; i < 12; i++) {
|
||||
buttons[c][i] = -1;
|
||||
}
|
||||
types[c] = NES; // todo: if SNES button is ever pressed, change type to SNES, maybe buttons to manually toggle?
|
||||
dataPins[c] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
///This sets the controller type and initializes its individual data pin
|
||||
void setController(int controller, Type type, int dataPin) {
|
||||
types[controller] = type;
|
||||
dataPins[controller] = dataPin;
|
||||
pinMode(dataPins[controller], INPUT_PULLUP);
|
||||
}
|
||||
|
||||
void poll(void (*controllerChanged)(const int controller)) {
|
||||
memset(changedControllers, 0, GAMEPAD_COUNT);
|
||||
|
||||
digitalWrite(latchPin, HIGH);
|
||||
delayMicroseconds(12);
|
||||
digitalWrite(latchPin, LOW);
|
||||
delayMicroseconds(6);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
for (int c = 0; c < GAMEPAD_COUNT; c++)
|
||||
if (dataPins[c] > -1) {
|
||||
if (digitalRead(dataPins[c])) {
|
||||
if (-1 != buttons[c][i]) {
|
||||
buttons[c][i] = -1;
|
||||
changedControllers[c] = 1;
|
||||
}
|
||||
} else {
|
||||
//++buttons[c][i];
|
||||
//changedControllers[c] = 1;
|
||||
if (0 != buttons[c][i]) {
|
||||
buttons[c][i] = 0;
|
||||
changedControllers[c] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
digitalWrite(clockPin, LOW);
|
||||
delayMicroseconds(6);
|
||||
digitalWrite(clockPin, HIGH);
|
||||
delayMicroseconds(6);
|
||||
}
|
||||
|
||||
for (int c = 0; c < GAMEPAD_COUNT; c++) {
|
||||
// have any buttons changed state?
|
||||
if (1 == changedControllers[c]) {
|
||||
controllerChanged(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int translate(int controller, Button b) const {
|
||||
if (types[controller] == SNES)
|
||||
return b;
|
||||
return translateToNES[b];
|
||||
}
|
||||
|
||||
///returns if button is currently down
|
||||
bool down(int controller, Button b) const {
|
||||
return buttons[controller][translate(controller, b)] >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
static const int translateToHid[12] = {
|
||||
BUTTON_B,
|
||||
BUTTON_Y,
|
||||
BUTTON_SELECT,
|
||||
BUTTON_START,
|
||||
DPAD_UP,
|
||||
DPAD_DOWN,
|
||||
DPAD_LEFT,
|
||||
DPAD_RIGHT,
|
||||
BUTTON_A,
|
||||
BUTTON_X,
|
||||
BUTTON_L,
|
||||
BUTTON_R,
|
||||
};
|
||||
|
||||
GameControllers controllers;
|
||||
|
||||
Gamepad gamepad;
|
||||
|
||||
void setup() {
|
||||
gamepad.begin();
|
||||
|
||||
//initialize shared pins
|
||||
controllers.init(LATCH_PIN, CLOCK_PIN);
|
||||
|
||||
//activate first controller ans set the type to SNES
|
||||
for (int c = 0; c < GAMEPAD_COUNT; c++) {
|
||||
controllers.setController(c, GameControllers::NES, DATA_PIN[c]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void controllerChangedDebug(const int c) {
|
||||
Serial.print("controllerChanged!!!!: ");
|
||||
Serial.println(c);
|
||||
Serial.print("c: ");
|
||||
Serial.print(c);
|
||||
for (uint8_t btn = 0; btn < 12; btn++) {
|
||||
Serial.print("; ");
|
||||
Serial.print(btn);
|
||||
Serial.print(", ");
|
||||
Serial.print(controllers.buttons[c][controllers.translate(c, static_cast<GameControllers::Button>(btn))]);
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
*/
|
||||
|
||||
void controllerChanged(const int c) {
|
||||
//controllerChangedDebug(c);
|
||||
|
||||
gamepad.buttons(c, 0);
|
||||
// if start and select are held at the same time, send menu and only menu
|
||||
if (controllers.down(c, GameControllers::START) && controllers.down(c, GameControllers::SELECT)) {
|
||||
gamepad.press(c, BUTTON_MENU);
|
||||
} else {
|
||||
// actually send buttons held
|
||||
for (uint8_t btn = 0; btn < 12; btn++) {
|
||||
if (btn > 3 && btn < 8)
|
||||
continue; // skip dpad
|
||||
if (controllers.down(c, static_cast<GameControllers::Button>(btn))) {
|
||||
gamepad.press(c, translateToHid[btn]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (controllers.down(c, GameControllers::DOWN)) {
|
||||
if (controllers.down(c, GameControllers::RIGHT)) {
|
||||
gamepad.setHatSync(c, DPAD_DOWN_RIGHT);
|
||||
} else if (controllers.down(c, GameControllers::LEFT)) {
|
||||
gamepad.setHatSync(c, DPAD_DOWN_LEFT);
|
||||
} else {
|
||||
gamepad.setHatSync(c, DPAD_DOWN);
|
||||
}
|
||||
} else if (controllers.down(c, GameControllers::UP)) {
|
||||
if (controllers.down(c, GameControllers::RIGHT)) {
|
||||
gamepad.setHatSync(c, DPAD_UP_RIGHT);
|
||||
} else if (controllers.down(c, GameControllers::LEFT)) {
|
||||
gamepad.setHatSync(c, DPAD_UP_LEFT);
|
||||
} else {
|
||||
gamepad.setHatSync(c, DPAD_UP);
|
||||
}
|
||||
} else if (controllers.down(c, GameControllers::RIGHT)) {
|
||||
gamepad.setHatSync(c, DPAD_RIGHT);
|
||||
} else if (controllers.down(c, GameControllers::LEFT)) {
|
||||
gamepad.setHatSync(c, DPAD_LEFT);
|
||||
} else {
|
||||
gamepad.setHatSync(c, DPAD_CENTERED);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (gamepad.isConnected()) {
|
||||
controllers.poll(controllerChanged); //read all controllers at once
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef USB_GAMEPAD_H
|
||||
#define USB_GAMEPAD_H
|
||||
|
||||
#include <WString.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
class DebugGamepad : public AbstractGamepad {
|
||||
public:
|
||||
DebugGamepad() : AbstractGamepad() {
|
||||
}
|
||||
|
||||
void begin(void) {
|
||||
Serial.begin(115200);
|
||||
Serial.println("DebugGamepad.begin");
|
||||
}
|
||||
|
||||
void setAxes(const uint8_t cIdx, int16_t x, int16_t y, int16_t z, int16_t rZ, char rX, char rY, signed char hat) {
|
||||
Serial.println("DebugGamepad.setAxes");
|
||||
AbstractGamepad::setAxes(cIdx, x, y, z, rZ, rX, rY, hat);
|
||||
}
|
||||
|
||||
void sync(const uint8_t cIdx) {
|
||||
Serial.print("DebugGamepad.sync: ");
|
||||
Serial.println(cIdx);
|
||||
}
|
||||
};
|
||||
|
||||
typedef DebugGamepad Gamepad;
|
||||
|
||||
#endif // USB_GAMEPAD_H
|
|
@ -0,0 +1,20 @@
|
|||
#include "BleConnectionStatus.h"
|
||||
|
||||
BleConnectionStatus::BleConnectionStatus(void) {
|
||||
}
|
||||
|
||||
void BleConnectionStatus::onConnect(BLEServer *pServer) {
|
||||
this->connected = true;
|
||||
for (int c = 0; c < GAMEPAD_COUNT; c++) {
|
||||
BLE2902 *desc = (BLE2902 *)this->inputGamepad[c]->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
|
||||
desc->setNotifications(true);
|
||||
}
|
||||
}
|
||||
|
||||
void BleConnectionStatus::onDisconnect(BLEServer *pServer) {
|
||||
this->connected = false;
|
||||
for (int c = 0; c < GAMEPAD_COUNT; c++) {
|
||||
BLE2902 *desc = (BLE2902 *)this->inputGamepad[c]->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
|
||||
desc->setNotifications(false);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef ESP32_BLE_CONNECTION_STATUS_H
|
||||
#define ESP32_BLE_CONNECTION_STATUS_H
|
||||
#include "sdkconfig.h"
|
||||
#if defined(CONFIG_BT_ENABLED)
|
||||
|
||||
#include "BLE2902.h"
|
||||
#include "BLECharacteristic.h"
|
||||
#include <BLEServer.h>
|
||||
|
||||
class BleConnectionStatus : public BLEServerCallbacks {
|
||||
public:
|
||||
BleConnectionStatus(void);
|
||||
bool connected = false;
|
||||
void onConnect(BLEServer *pServer);
|
||||
void onDisconnect(BLEServer *pServer);
|
||||
BLECharacteristic *inputGamepad[GAMEPAD_COUNT];
|
||||
};
|
||||
|
||||
#endif // CONFIG_BT_ENABLED
|
||||
#endif // ESP32_BLE_CONNECTION_STATUS_H
|
|
@ -0,0 +1,115 @@
|
|||
#ifndef ESP32_BLE_GAMEPAD_H
|
||||
#define ESP32_BLE_GAMEPAD_H
|
||||
#include "sdkconfig.h"
|
||||
#if defined(CONFIG_BT_ENABLED)
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#include "BLE2902.h"
|
||||
#include "BLEHIDDevice.h"
|
||||
#include "HIDKeyboardTypes.h"
|
||||
#include "HIDTypes.h"
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
#include <driver/adc.h>
|
||||
|
||||
#include "BLECharacteristic.h"
|
||||
#include "BLEHIDDevice.h"
|
||||
#include "BleConnectionStatus.h"
|
||||
|
||||
#if defined(CONFIG_ARDUHAL_ESP_LOG)
|
||||
#include "esp32-hal-log.h"
|
||||
#define LOG_TAG ""
|
||||
#else
|
||||
#include "esp_log.h"
|
||||
static const char *LOG_TAG = "BLEDevice";
|
||||
#endif
|
||||
|
||||
class BleGamepad : public AbstractGamepad {
|
||||
private:
|
||||
BleConnectionStatus *connectionStatus;
|
||||
BLEHIDDevice *hid;
|
||||
BLECharacteristic *inputGamepad[GAMEPAD_COUNT];
|
||||
void rawAction(uint8_t msg[], char msgSize);
|
||||
static void taskServer(void *pvParameter) {
|
||||
BleGamepad *BleGamepadInstance = (BleGamepad *)pvParameter; // static_cast<BleGamepad *>(pvParameter);
|
||||
BLEDevice::init(BleGamepadInstance->deviceName);
|
||||
BLEServer *pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(BleGamepadInstance->connectionStatus);
|
||||
|
||||
BleGamepadInstance->hid = new BLEHIDDevice(pServer);
|
||||
|
||||
for (int c = 0; c < GAMEPAD_COUNT; c++) {
|
||||
BleGamepadInstance->inputGamepad[c] = BleGamepadInstance->hid->inputReport(c); // <-- input REPORTID from report map
|
||||
BleGamepadInstance->connectionStatus->inputGamepad[c] = BleGamepadInstance->inputGamepad[c];
|
||||
}
|
||||
|
||||
BleGamepadInstance->hid->manufacturer()->setValue(BleGamepadInstance->deviceManufacturer);
|
||||
|
||||
// todo: is this change required for 2 inputs???
|
||||
// BleGamepadInstance->hid->pnp(0x01,0x02e5,0xabcc,0x0110);
|
||||
BleGamepadInstance->hid->pnp(0x02, 0x8282, 0x0132, 0x0106);
|
||||
|
||||
BleGamepadInstance->hid->hidInfo(0x00, 0x01);
|
||||
|
||||
BLESecurity *pSecurity = new BLESecurity();
|
||||
|
||||
pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND);
|
||||
|
||||
BleGamepadInstance->hid->reportMap((uint8_t *)_hidReportDescriptor, sizeof(_hidReportDescriptor));
|
||||
BleGamepadInstance->hid->startServices();
|
||||
|
||||
BleGamepadInstance->onStarted(pServer);
|
||||
|
||||
BLEAdvertising *pAdvertising = pServer->getAdvertising();
|
||||
pAdvertising->setAppearance(HID_GAMEPAD);
|
||||
pAdvertising->addServiceUUID(BleGamepadInstance->hid->hidService()->getUUID());
|
||||
pAdvertising->start();
|
||||
BleGamepadInstance->hid->setBatteryLevel(BleGamepadInstance->batteryLevel);
|
||||
|
||||
ESP_LOGD(LOG_TAG, "Advertising started!");
|
||||
vTaskDelay(portMAX_DELAY); // delay(portMAX_DELAY);
|
||||
}
|
||||
|
||||
public:
|
||||
uint8_t batteryLevel;
|
||||
std::string deviceManufacturer;
|
||||
std::string deviceName;
|
||||
|
||||
BleGamepad(std::string deviceName = "8BitDo SN30 Pro+", std::string deviceManufacturer = "8Bitdo SF30 Pro", uint8_t batteryLevel = 100) : AbstractGamepad(), hid(0) {
|
||||
this->deviceName = deviceName;
|
||||
this->deviceManufacturer = deviceManufacturer;
|
||||
this->batteryLevel = batteryLevel;
|
||||
this->connectionStatus = new BleConnectionStatus();
|
||||
}
|
||||
|
||||
void begin(void) {
|
||||
xTaskCreate(this->taskServer, "server", 20000, (void *)this, 5, NULL);
|
||||
}
|
||||
|
||||
void sync(const uint8_t cIdx) {
|
||||
if (this->isConnected()) {
|
||||
this->inputGamepad[cIdx]->setValue(gamepadReport, GAMEPAD_REPORT_LEN);
|
||||
this->inputGamepad[cIdx]->notify();
|
||||
}
|
||||
}
|
||||
|
||||
bool isConnected(void) {
|
||||
return this->connectionStatus->connected;
|
||||
}
|
||||
|
||||
void setBatteryLevel(uint8_t level) {
|
||||
this->batteryLevel = level;
|
||||
if (hid != 0)
|
||||
this->hid->setBatteryLevel(this->batteryLevel);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void onStarted(BLEServer *pServer){};
|
||||
};
|
||||
|
||||
typedef BleGamepad Gamepad;
|
||||
|
||||
#endif // CONFIG_BT_ENABLED
|
||||
#endif // ESP32_BLE_GAMEPAD_H
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
#if GAMEPAD_OUTPUT == 0
|
||||
|
||||
#include "Debug-Gamepad/DebugGamepad.h"
|
||||
|
||||
#elif GAMEPAD_OUTPUT == 1
|
||||
|
||||
#include "Radio-Gamepad/RadioGamepad.h"
|
||||
|
||||
#elif GAMEPAD_OUTPUT == 2
|
||||
|
||||
#include "USB-Gamepad/UsbGamepad.h"
|
||||
|
||||
#elif GAMEPAD_OUTPUT == 3
|
||||
|
||||
#ifndef CONFIG_BT_ENABLED
|
||||
#error BT output unsupported if CONFIG_BT_ENABLED is not set
|
||||
#endif // not CONFIG_BT_ENABLED
|
||||
|
||||
#include "ESP32-BLE-Gamepad/BleGamepad.h"
|
||||
|
||||
#else
|
||||
|
||||
#error Unsupported value for GAMEPAD_OUTPUT, must be 0-3
|
||||
|
||||
#endif
|
|
@ -0,0 +1,101 @@
|
|||
/* Copyright (c) 2010-2011 mbed.org, MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// adapted from HIDTypes.h from ESP32 lib, search for modified: in this file
|
||||
|
||||
#ifndef USBCLASS_HID_TYPES
|
||||
#define USBCLASS_HID_TYPES
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* */
|
||||
#define HID_VERSION_1_11 (0x0111)
|
||||
|
||||
/* HID Class */
|
||||
#define HID_CLASS (3)
|
||||
|
||||
// modified: redefined from arduino HID.h, about the same, so let's go with their version
|
||||
//#define HID_SUBCLASS_NONE (0)
|
||||
//#define HID_PROTOCOL_NONE (0)
|
||||
#define HID_SUBCLASS_NONE 0
|
||||
#define HID_PROTOCOL_NONE 0
|
||||
|
||||
/* Descriptors */
|
||||
#define HID_DESCRIPTOR (33)
|
||||
#define HID_DESCRIPTOR_LENGTH (0x09)
|
||||
#define REPORT_DESCRIPTOR (34)
|
||||
|
||||
/* Class requests */
|
||||
#define GET_REPORT (0x1)
|
||||
#define GET_IDLE (0x2)
|
||||
#define SET_REPORT (0x9)
|
||||
#define SET_IDLE (0xa)
|
||||
|
||||
/* HID Class Report Descriptor */
|
||||
/* Short items: size is 0, 1, 2 or 3 specifying 0, 1, 2 or 4 (four) bytes */
|
||||
/* of data as per HID Class standard */
|
||||
|
||||
/* Main items */
|
||||
#define HIDINPUT(size) (0x80 | size)
|
||||
#define HIDOUTPUT(size) (0x90 | size)
|
||||
|
||||
// modified: was FEATURE but conflicted with nRF24L01.h FEATURE
|
||||
//#define FEATURE(size) (0xb0 | size)
|
||||
#define HIDFEATURE(size) (0xb0 | size)
|
||||
|
||||
#define COLLECTION(size) (0xa0 | size)
|
||||
#define END_COLLECTION(size) (0xc0 | size)
|
||||
|
||||
/* Global items */
|
||||
#define USAGE_PAGE(size) (0x04 | size)
|
||||
#define LOGICAL_MINIMUM(size) (0x14 | size)
|
||||
#define LOGICAL_MAXIMUM(size) (0x24 | size)
|
||||
#define PHYSICAL_MINIMUM(size) (0x34 | size)
|
||||
#define PHYSICAL_MAXIMUM(size) (0x44 | size)
|
||||
#define UNIT_EXPONENT(size) (0x54 | size)
|
||||
#define UNIT(size) (0x64 | size)
|
||||
#define REPORT_SIZE(size) (0x74 | size) //bits
|
||||
#define REPORT_ID(size) (0x84 | size)
|
||||
#define REPORT_COUNT(size) (0x94 | size) //bytes
|
||||
#define PUSH(size) (0xa4 | size)
|
||||
#define POP(size) (0xb4 | size)
|
||||
|
||||
/* Local items */
|
||||
#define USAGE(size) (0x08 | size)
|
||||
#define USAGE_MINIMUM(size) (0x18 | size)
|
||||
#define USAGE_MAXIMUM(size) (0x28 | size)
|
||||
#define DESIGNATOR_INDEX(size) (0x38 | size)
|
||||
#define DESIGNATOR_MINIMUM(size) (0x48 | size)
|
||||
#define DESIGNATOR_MAXIMUM(size) (0x58 | size)
|
||||
#define STRING_INDEX(size) (0x78 | size)
|
||||
#define STRING_MINIMUM(size) (0x88 | size)
|
||||
#define STRING_MAXIMUM(size) (0x98 | size)
|
||||
#define DELIMITER(size) (0xa8 | size)
|
||||
|
||||
/* HID Report */
|
||||
/* Where report IDs are used the first byte of 'data' will be the */
|
||||
/* report ID and 'length' will include this report ID byte. */
|
||||
|
||||
#define MAX_HID_REPORT_SIZE (64)
|
||||
|
||||
typedef struct {
|
||||
uint32_t length;
|
||||
uint8_t data[MAX_HID_REPORT_SIZE];
|
||||
} HID_REPORT;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef RADIO_GAMEPAD_H
|
||||
#define RADIO_GAMEPAD_H
|
||||
|
||||
#include <SPI.h>
|
||||
#include <nRF24L01.h>
|
||||
#include <RF24.h>
|
||||
|
||||
#define GAMEPAD_REPORT_ARRAY_ADD 1
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
RF24 radio(7, 8); // CE, CSN
|
||||
const byte address[13] = "OpenRetroPad";
|
||||
|
||||
class RadioGamepad : public AbstractGamepad {
|
||||
public:
|
||||
RadioGamepad() : AbstractGamepad() {
|
||||
}
|
||||
|
||||
void begin(void) {
|
||||
Serial.println("RadioGamepad.begin");
|
||||
radio.begin();
|
||||
radio.openWritingPipe(address);
|
||||
radio.setPALevel(RF24_PA_MIN);
|
||||
radio.stopListening();
|
||||
}
|
||||
|
||||
void sync(const uint8_t cIdx) {
|
||||
Serial.println("RadioGamepad.sync");
|
||||
gamepadReport[15] = cIdx;
|
||||
radio.write(&gamepadReport, 16);
|
||||
}
|
||||
};
|
||||
|
||||
typedef RadioGamepad Gamepad;
|
||||
|
||||
#endif // RADIO_GAMEPAD_H
|
|
@ -0,0 +1,131 @@
|
|||
#ifndef USB_GAMEPAD_H
|
||||
#define USB_GAMEPAD_H
|
||||
|
||||
#include <WString.h>
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#include "HID.h"
|
||||
|
||||
// ATT: 20 chars max (including NULL at the end) according to Arduino source code.
|
||||
// Additionally serial number is used to differentiate arduino projects to have different button maps!
|
||||
const char* gp_serial = "SF30 Pro";
|
||||
|
||||
class Gamepad_ : public PluggableUSBModule {
|
||||
private:
|
||||
uint8_t reportId;
|
||||
|
||||
protected:
|
||||
uint8_t epType[1];
|
||||
uint8_t protocol;
|
||||
uint8_t idle;
|
||||
|
||||
/*
|
||||
int getInterface(uint8_t* interfaceCount);
|
||||
int getDescriptor(USBSetup& setup);
|
||||
uint8_t getShortName(char *name);
|
||||
bool setup(USBSetup& setup);
|
||||
*/
|
||||
|
||||
int getInterface(uint8_t* interfaceCount) {
|
||||
*interfaceCount += 1; // uses 1
|
||||
HIDDescriptor hidInterface = {D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE),
|
||||
D_HIDREPORT(sizeof(_hidReportDescriptor)),
|
||||
D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01)};
|
||||
return USB_SendControl(0, &hidInterface, sizeof(hidInterface));
|
||||
}
|
||||
|
||||
int getDescriptor(USBSetup& setup) {
|
||||
// Check if this is a HID Class Descriptor request
|
||||
if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) {
|
||||
return 0;
|
||||
}
|
||||
if (setup.wValueH != HID_REPORT_DESCRIPTOR_TYPE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// In a HID Class Descriptor wIndex cointains the interface number
|
||||
if (setup.wIndex != pluggedInterface) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reset the protocol on reenumeration. Normally the host should not assume the state of the protocol
|
||||
// due to the USB specs, but Windows and Linux just assumes its in report mode.
|
||||
protocol = HID_REPORT_PROTOCOL;
|
||||
|
||||
return USB_SendControl(TRANSFER_PGM, _hidReportDescriptor, sizeof(_hidReportDescriptor));
|
||||
}
|
||||
|
||||
bool setup(USBSetup& setup) {
|
||||
if (pluggedInterface != setup.wIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t request = setup.bRequest;
|
||||
uint8_t requestType = setup.bmRequestType;
|
||||
|
||||
if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE) {
|
||||
if (request == HID_GET_REPORT) {
|
||||
// TODO: HID_GetReport();
|
||||
return true;
|
||||
}
|
||||
if (request == HID_GET_PROTOCOL) {
|
||||
// TODO: Send8(protocol);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE) {
|
||||
if (request == HID_SET_PROTOCOL) {
|
||||
protocol = setup.wValueL;
|
||||
return true;
|
||||
}
|
||||
if (request == HID_SET_IDLE) {
|
||||
idle = setup.wValueL;
|
||||
return true;
|
||||
}
|
||||
if (request == HID_SET_REPORT) {
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t getShortName(char* name) {
|
||||
if (!next) {
|
||||
strcpy(name, gp_serial);
|
||||
return strlen(name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
Gamepad_(void) : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(1) {
|
||||
epType[0] = EP_TYPE_INTERRUPT_IN;
|
||||
PluggableUSB().plug(this);
|
||||
}
|
||||
|
||||
void send(const void* d, int len) {
|
||||
USB_Send(pluggedEndpoint | TRANSFER_RELEASE, d, len);
|
||||
}
|
||||
};
|
||||
|
||||
class UsbGamepad : public AbstractGamepad {
|
||||
public:
|
||||
String deviceManufacturer;
|
||||
String deviceName;
|
||||
Gamepad_ gamepad[GAMEPAD_COUNT];
|
||||
|
||||
UsbGamepad(String deviceName = "8BitDo SN30 Pro+", String deviceManufacturer = "8Bitdo SF30 Pro") : AbstractGamepad() {
|
||||
this->deviceName = deviceName;
|
||||
this->deviceManufacturer = deviceManufacturer;
|
||||
}
|
||||
|
||||
void sync(const uint8_t cIdx) {
|
||||
gamepad[cIdx].send(&gamepadReport, GAMEPAD_REPORT_LEN);
|
||||
}
|
||||
};
|
||||
|
||||
typedef UsbGamepad Gamepad;
|
||||
|
||||
#endif // USB_GAMEPAD_H
|
|
@ -0,0 +1,245 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define ARDUINO_ARCH_ESP32 1
|
||||
#include "HIDTypes.h"
|
||||
|
||||
#define BUTTON_A 1
|
||||
#define BUTTON_B 2
|
||||
#define BUTTON_MENU 4
|
||||
#define BUTTON_X 8
|
||||
#define BUTTON_Y 16
|
||||
#define BUTTON_TL 64
|
||||
#define BUTTON_L 64
|
||||
#define BUTTON_TR 128
|
||||
#define BUTTON_R 128
|
||||
#define BUTTON_TL2 256
|
||||
#define BUTTON_TR2 512
|
||||
#define BUTTON_SELECT 1024
|
||||
#define BUTTON_START 2048
|
||||
#define BUTTON_THUMBL 8192
|
||||
#define BUTTON_THUMBR 16384
|
||||
|
||||
#define BUTTON_1 1
|
||||
#define BUTTON_2 2
|
||||
#define BUTTON_3 4
|
||||
#define BUTTON_4 8
|
||||
#define BUTTON_5 16
|
||||
#define BUTTON_6 32
|
||||
#define BUTTON_7 64
|
||||
#define BUTTON_8 128
|
||||
#define BUTTON_9 256
|
||||
#define BUTTON_10 512
|
||||
#define BUTTON_11 1024
|
||||
#define BUTTON_12 2048
|
||||
#define BUTTON_13 4096
|
||||
#define BUTTON_14 8192
|
||||
#define BUTTON_15 16384
|
||||
#define BUTTON_16 32768
|
||||
#define BUTTON_17 65536
|
||||
#define BUTTON_18 131072
|
||||
#define BUTTON_19 262144
|
||||
#define BUTTON_20 524288
|
||||
#define BUTTON_21 1048576
|
||||
#define BUTTON_22 2097152
|
||||
#define BUTTON_23 4194304
|
||||
#define BUTTON_24 8388608
|
||||
#define BUTTON_25 16777216
|
||||
#define BUTTON_26 33554432
|
||||
#define BUTTON_27 67108864
|
||||
#define BUTTON_28 134217728
|
||||
#define BUTTON_29 268435456
|
||||
#define BUTTON_30 536870912
|
||||
#define BUTTON_31 1073741824
|
||||
#define BUTTON_32 2147483648
|
||||
|
||||
#define DPAD_CENTER 0
|
||||
#define DPAD_CENTERED 0
|
||||
#define DPAD_UP 1
|
||||
#define DPAD_UP_RIGHT 2
|
||||
#define DPAD_RIGHT 3
|
||||
#define DPAD_DOWN_RIGHT 4
|
||||
#define DPAD_DOWN 5
|
||||
#define DPAD_DOWN_LEFT 6
|
||||
#define DPAD_LEFT 7
|
||||
#define DPAD_UP_LEFT 8
|
||||
|
||||
#ifndef GAMEPAD_REPORT_ARRAY_ADD
|
||||
// this is used by radio gamepad to send additional info
|
||||
#define GAMEPAD_REPORT_ARRAY_ADD 0
|
||||
#endif
|
||||
|
||||
// this must match _hidReportDescriptor, length to actually send
|
||||
#define GAMEPAD_REPORT_LEN 15
|
||||
|
||||
static const uint8_t _hidReportDescriptor[] PROGMEM = {
|
||||
USAGE_PAGE(1),
|
||||
0x01, // USAGE_PAGE (Generic Desktop)
|
||||
USAGE(1),
|
||||
0x05, // USAGE (Gamepad)
|
||||
COLLECTION(1),
|
||||
0x01, // COLLECTION (Application)
|
||||
USAGE(1),
|
||||
0x01, // USAGE (Pointer)
|
||||
COLLECTION(1),
|
||||
0x00, // COLLECTION (Physical)
|
||||
|
||||
// ------------------------------------------------- Buttons (1 to 32)
|
||||
USAGE_PAGE(1),
|
||||
0x09, // USAGE_PAGE (Button)
|
||||
USAGE_MINIMUM(1),
|
||||
0x01, // USAGE_MINIMUM (Button 1)
|
||||
USAGE_MAXIMUM(1),
|
||||
0x20, // USAGE_MAXIMUM (Button 32)
|
||||
LOGICAL_MINIMUM(1),
|
||||
0x00, // LOGICAL_MINIMUM (0)
|
||||
LOGICAL_MAXIMUM(1),
|
||||
0x01, // LOGICAL_MAXIMUM (1)
|
||||
REPORT_SIZE(1),
|
||||
0x01, // REPORT_SIZE (1)
|
||||
REPORT_COUNT(1),
|
||||
0x20, // REPORT_COUNT (32)
|
||||
HIDINPUT(1),
|
||||
0x02, // INPUT (Data, Variable, Absolute) ;32 button bits
|
||||
// ------------------------------------------------- X/Y position, Z/rZ position
|
||||
USAGE_PAGE(1),
|
||||
0x01, // USAGE_PAGE (Generic Desktop)
|
||||
COLLECTION(1),
|
||||
0x00, // COLLECTION (Physical)
|
||||
USAGE(1),
|
||||
0x30, // USAGE (X)
|
||||
USAGE(1),
|
||||
0x31, // USAGE (Y)
|
||||
USAGE(1),
|
||||
0x32, // USAGE (Z)
|
||||
USAGE(1),
|
||||
0x35, // USAGE (rZ)
|
||||
0x16,
|
||||
0x01,
|
||||
0x80, //LOGICAL_MINIMUM (-32767)
|
||||
0x26,
|
||||
0xFF,
|
||||
0x7F, //LOGICAL_MAXIMUM (32767)
|
||||
REPORT_SIZE(1),
|
||||
0x10, // REPORT_SIZE (16)
|
||||
REPORT_COUNT(1),
|
||||
0x04, // REPORT_COUNT (4)
|
||||
HIDINPUT(1),
|
||||
0x02, // INPUT (Data,Var,Abs)
|
||||
// ------------------------------------------------- Triggers
|
||||
USAGE(1),
|
||||
0x33, // USAGE (rX) Left Trigger
|
||||
USAGE(1),
|
||||
0x34, // USAGE (rY) Right Trigger
|
||||
LOGICAL_MINIMUM(1),
|
||||
0x81, // LOGICAL_MINIMUM (-127)
|
||||
LOGICAL_MAXIMUM(1),
|
||||
0x7f, // LOGICAL_MAXIMUM (127)
|
||||
REPORT_SIZE(1),
|
||||
0x08, // REPORT_SIZE (8)
|
||||
REPORT_COUNT(1),
|
||||
0x02, // REPORT_COUNT (2)
|
||||
HIDINPUT(1),
|
||||
0x02, // INPUT (Data, Variable, Absolute) ;4 bytes (X,Y,Z,rZ)
|
||||
END_COLLECTION(0), // END_COLLECTION
|
||||
|
||||
USAGE_PAGE(1),
|
||||
0x01, // USAGE_PAGE (Generic Desktop)
|
||||
USAGE(1),
|
||||
0x39, // USAGE (Hat switch)
|
||||
USAGE(1),
|
||||
0x39, // USAGE (Hat switch)
|
||||
LOGICAL_MINIMUM(1),
|
||||
0x01, // LOGICAL_MINIMUM (1)
|
||||
LOGICAL_MAXIMUM(1),
|
||||
0x08, // LOGICAL_MAXIMUM (8)
|
||||
REPORT_SIZE(1),
|
||||
0x04, // REPORT_SIZE (4)
|
||||
REPORT_COUNT(1),
|
||||
0x02, // REPORT_COUNT (2)
|
||||
HIDINPUT(1),
|
||||
0x02, // INPUT (Data, Variable, Absolute) ;1 byte Hat1, Hat2
|
||||
|
||||
END_COLLECTION(0), // END_COLLECTION
|
||||
END_COLLECTION(0) // END_COLLECTION
|
||||
};
|
||||
|
||||
class AbstractGamepad {
|
||||
public:
|
||||
uint32_t _buttons[GAMEPAD_COUNT];
|
||||
uint8_t gamepadReport[GAMEPAD_REPORT_LEN + GAMEPAD_REPORT_ARRAY_ADD];
|
||||
|
||||
AbstractGamepad() {
|
||||
}
|
||||
|
||||
virtual void begin(void) {
|
||||
}
|
||||
|
||||
virtual void end(void) {
|
||||
}
|
||||
|
||||
virtual bool isConnected(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void setAxes(const uint8_t cIdx, int16_t x, int16_t y, int16_t z, int16_t rZ, char rX, char rY, signed char hat) {
|
||||
if (x == -32768) {
|
||||
x = -32767;
|
||||
}
|
||||
if (y == -32768) {
|
||||
y = -32767;
|
||||
}
|
||||
if (z == -32768) {
|
||||
z = -32767;
|
||||
}
|
||||
if (rZ == -32768) {
|
||||
rZ = -32767;
|
||||
}
|
||||
|
||||
gamepadReport[0] = _buttons[cIdx];
|
||||
gamepadReport[1] = (_buttons[cIdx] >> 8);
|
||||
gamepadReport[2] = (_buttons[cIdx] >> 16);
|
||||
gamepadReport[3] = (_buttons[cIdx] >> 24);
|
||||
gamepadReport[4] = x;
|
||||
gamepadReport[5] = (x >> 8);
|
||||
gamepadReport[6] = y;
|
||||
gamepadReport[7] = (y >> 8);
|
||||
gamepadReport[8] = z;
|
||||
gamepadReport[9] = (z >> 8);
|
||||
gamepadReport[10] = rZ;
|
||||
gamepadReport[11] = (rZ >> 8);
|
||||
gamepadReport[12] = (signed char)(rX - 128);
|
||||
gamepadReport[13] = (signed char)(rY - 128);
|
||||
gamepadReport[14] = hat;
|
||||
if (gamepadReport[12] == -128) {
|
||||
gamepadReport[12] = -127;
|
||||
}
|
||||
if (gamepadReport[13] == -128) {
|
||||
gamepadReport[13] = -127;
|
||||
}
|
||||
|
||||
this->sync(cIdx);
|
||||
}
|
||||
|
||||
virtual void setHatSync(const uint8_t cIdx, signed char hat) {
|
||||
setAxes(cIdx, 0, 0, 0, 0, 0, 0, hat);
|
||||
}
|
||||
|
||||
virtual void buttons(const uint8_t cIdx, uint32_t b) {
|
||||
_buttons[cIdx] = b;
|
||||
}
|
||||
|
||||
virtual void press(const uint8_t cIdx, uint32_t b) {
|
||||
buttons(cIdx, _buttons[cIdx] | b);
|
||||
}
|
||||
|
||||
virtual void release(const uint8_t cIdx, uint32_t b) {
|
||||
buttons(cIdx, _buttons[cIdx] & ~b);
|
||||
}
|
||||
|
||||
virtual bool isPressed(const uint8_t cIdx, uint32_t b) {
|
||||
return ((b & _buttons[cIdx]) > 0);
|
||||
}
|
||||
|
||||
virtual void sync(const uint8_t cIdx); // actually sends report
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
Loading…
Reference in New Issue