The Switch Pro controller now works via USB and we can now read the IMU data as well

This commit is contained in:
Kristian Sloth Lauszus 2021-05-03 22:34:46 +02:00
parent f19f610081
commit 2dfb767093
8 changed files with 464 additions and 122 deletions

View file

@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
# find examples -type f -name "*.ino" | rev | cut -d/ -f2- | rev | sort | sed -z 's/\n/, /g'
example: [examples/ambx, examples/acm/acm_terminal, examples/adk/adk_barcode, examples/adk/ArduinoBlinkLED, examples/adk/demokit_20, examples/adk/term_test, examples/adk/term_time, examples/Bluetooth/BTHID, examples/Bluetooth/PS3BT, examples/Bluetooth/PS3Multi, examples/Bluetooth/PS3SPP, examples/Bluetooth/PS4BT, examples/Bluetooth/PS5BT, examples/Bluetooth/SPP, examples/Bluetooth/SPPMulti, examples/Bluetooth/SwitchProBT, examples/Bluetooth/Wii, examples/Bluetooth/WiiBalanceBoard, examples/Bluetooth/WiiIRCamera, examples/Bluetooth/WiiMulti, examples/Bluetooth/WiiUProController, examples/board_qc, examples/cdc_XR21B1411/XR_terminal, examples/ftdi/USBFTDILoopback, examples/GPIO/Blink, examples/GPIO/Blink_LowLevel, examples/GPIO/Input, examples/HID/le3dp, examples/HID/scale, examples/HID/SRWS1, examples/HID/t16km, examples/HID/USBHIDBootKbd, examples/HID/USBHIDBootKbdAndMouse, examples/HID/USBHIDBootMouse, examples/HID/USBHID_desc, examples/HID/USBHIDJoystick, examples/HID/USBHIDMultimediaKbd, examples/hub_demo, examples/max_LCD, examples/pl2303/pl2303_gprs_terminal, examples/pl2303/pl2303_gps, examples/pl2303/pl2303_tinygps, examples/pl2303/pl2303_xbee_terminal, examples/PS3USB, examples/PS4USB, examples/PS5USB, examples/PSBuzz, examples/USB_desc, examples/USBH_MIDI/bidirectional_converter, examples/USBH_MIDI/eVY1_sample, examples/USBH_MIDI/USBH_MIDI_dump, examples/USBH_MIDI/USB_MIDI_converter, examples/USBH_MIDI/USB_MIDI_converter_multi, examples/Xbox/XBOXOLD, examples/Xbox/XBOXONE, examples/Xbox/XBOXONESBT, examples/Xbox/XBOXRECV, examples/Xbox/XBOXUSB]
example: [examples/ambx, examples/acm/acm_terminal, examples/adk/adk_barcode, examples/adk/ArduinoBlinkLED, examples/adk/demokit_20, examples/adk/term_test, examples/adk/term_time, examples/Bluetooth/BTHID, examples/Bluetooth/PS3BT, examples/Bluetooth/PS3Multi, examples/Bluetooth/PS3SPP, examples/Bluetooth/PS4BT, examples/Bluetooth/PS5BT, examples/Bluetooth/SPP, examples/Bluetooth/SPPMulti, examples/Bluetooth/SwitchProBT, examples/Bluetooth/Wii, examples/Bluetooth/WiiBalanceBoard, examples/Bluetooth/WiiIRCamera, examples/Bluetooth/WiiMulti, examples/Bluetooth/WiiUProController, examples/board_qc, examples/cdc_XR21B1411/XR_terminal, examples/ftdi/USBFTDILoopback, examples/GPIO/Blink, examples/GPIO/Blink_LowLevel, examples/GPIO/Input, examples/HID/le3dp, examples/HID/scale, examples/HID/SRWS1, examples/HID/t16km, examples/HID/USBHIDBootKbd, examples/HID/USBHIDBootKbdAndMouse, examples/HID/USBHIDBootMouse, examples/HID/USBHID_desc, examples/HID/USBHIDJoystick, examples/HID/USBHIDMultimediaKbd, examples/hub_demo, examples/max_LCD, examples/pl2303/pl2303_gprs_terminal, examples/pl2303/pl2303_gps, examples/pl2303/pl2303_tinygps, examples/pl2303/pl2303_xbee_terminal, examples/PS3USB, examples/PS4USB, examples/PS5USB, examples/PSBuzz, examples/SwitchProUSB, examples/USB_desc, examples/USBH_MIDI/bidirectional_converter, examples/USBH_MIDI/eVY1_sample, examples/USBH_MIDI/USBH_MIDI_dump, examples/USBH_MIDI/USB_MIDI_converter, examples/USBH_MIDI/USB_MIDI_converter_multi, examples/Xbox/XBOXOLD, examples/Xbox/XBOXONE, examples/Xbox/XBOXONESBT, examples/Xbox/XBOXRECV, examples/Xbox/XBOXUSB]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2

View file

@ -23,7 +23,7 @@ For more information about the hardware see the [Hardware Manual](https://chome.
* __Alexei Glushchenko__ - <alex-gl@mail.ru>
* Developers of the USB Core, HID, FTDI, ADK, ACM, and PL2303 libraries
* __Kristian Sloth Lauszus__ - <lauszus@gmail.com>
* Developer of the [BTD](#bluetooth-libraries), [BTHID](#bthid-library), [SPP](#spp-library), [PS5](#ps5-library), [PS4](#ps4-library), [PS3](#ps3-library), [Wii](#wii-library), [Xbox](#xbox-library), and [PSBuzz](#ps-buzz-library) libraries
* Developer of the [BTD](#bluetooth-libraries), [BTHID](#bthid-library), [SPP](#spp-library), [PS5](#ps5-library), [PS4](#ps4-library), [PS3](#ps3-library), [Wii](#wii-library), [Switch Pro](#switch-pro-library), [Xbox](#xbox-library), and [PSBuzz](#ps-buzz-library) libraries
* __Andrew Kroll__ - <xxxajk@gmail.com>
* Major contributor to mass storage code
* __guruthree__
@ -55,6 +55,7 @@ For more information about the hardware see the [Hardware Manual](https://chome.
* [Xbox ONE Library](#xbox-one-library)
* [Xbox ONE S Library](#xbox-one-s-library)
* [Wii library](#wii-library)
* [Switch Pro Library](#switch-pro-library)
* [PS Buzz Library](#ps-buzz-library)
* [HID Libraries](#hid-libraries)
* [MIDI Library](#midi-library)
@ -330,6 +331,21 @@ All the information about the Wii controllers are from these sites:
* <http://wiibrew.org/wiki/Wii_Balance_Board>
* The old library created by _Tomoyuki Tanaka_: <https://github.com/moyuchin/WiiRemote_on_Arduino> also helped a lot.
### Switch Pro Library
The Switch Pro library is split up into the [SwitchProBT](SwitchProBT.h) and the [SwitchProUSB](SwitchProUSB.h) library. These allow you to use the Nintendo Switch Pro controller via Bluetooth and USB.
The [SwitchProBT.ino](examples/Bluetooth/SwitchProBT/SwitchProBT.ino) and [SwitchProUSB.ino](examples/SwitchProUSB/SwitchProUSB.ino) examples shows how to easily read the buttons, joysticks and IMU on the controller via Bluetooth and USB respectively. It is also possible to control the rumble and LEDs on the controller.
To pair with the Switch Pro controller via Bluetooth you need create the SwitchProBT instance like so: ```SwitchProBT SwitchPro(&Btd, PAIR);``` and then press the Sync button next to the USB connector to put the controller into pairing mode.
It should then automatically pair the dongle with your controller. This only have to be done once.
All the information about the controller are from these sites:
* <https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering>
* <https://github.com/Dan611/hid-procon>
### [PS Buzz Library](PSBuzz.cpp)
This library implements support for the Playstation Buzz controllers via USB.

View file

@ -17,18 +17,6 @@
#include "SwitchProParser.h"
enum DPADEnum {
DPAD_UP = 0x0,
DPAD_UP_RIGHT = 0x1,
DPAD_RIGHT = 0x2,
DPAD_RIGHT_DOWN = 0x3,
DPAD_DOWN = 0x4,
DPAD_DOWN_LEFT = 0x5,
DPAD_LEFT = 0x6,
DPAD_LEFT_UP = 0x7,
DPAD_OFF = 0x8,
};
// To enable serial debugging see "settings.h"
//#define PRINTREPORT // Uncomment to print the report send by the Switch Pro Controller
@ -38,27 +26,9 @@ int8_t SwitchProParser::getButtonIndexSwitchPro(ButtonEnum b) {
return index;
}
bool SwitchProParser::checkDpad(ButtonEnum b) {
switch (b) {
case UP:
return switchProData.btn.dpad == DPAD_LEFT_UP || switchProData.btn.dpad == DPAD_UP || switchProData.btn.dpad == DPAD_UP_RIGHT;
case RIGHT:
return switchProData.btn.dpad == DPAD_UP_RIGHT || switchProData.btn.dpad == DPAD_RIGHT || switchProData.btn.dpad == DPAD_RIGHT_DOWN;
case DOWN:
return switchProData.btn.dpad == DPAD_RIGHT_DOWN || switchProData.btn.dpad == DPAD_DOWN || switchProData.btn.dpad == DPAD_DOWN_LEFT;
case LEFT:
return switchProData.btn.dpad == DPAD_DOWN_LEFT || switchProData.btn.dpad == DPAD_LEFT || switchProData.btn.dpad == DPAD_LEFT_UP;
default:
return false;
}
}
bool SwitchProParser::getButtonPress(ButtonEnum b) {
const int8_t index = getButtonIndexSwitchPro(b); if (index < 0) return 0;
if (index <= LEFT) // Dpad
return checkDpad(b);
else
return switchProData.btn.val & (1UL << pgm_read_byte(&SWITCH_PRO_BUTTONS[index]));
return switchProData.btn.val & (1UL << pgm_read_byte(&SWITCH_PRO_BUTTONS[index]));
}
bool SwitchProParser::getButtonClick(ButtonEnum b) {
@ -70,11 +40,20 @@ bool SwitchProParser::getButtonClick(ButtonEnum b) {
}
int16_t SwitchProParser::getAnalogHat(AnalogHatEnum a) {
return switchProData.hatValue[(uint8_t)a] - 0x7FFF; // Subtract the center value
switch((uint8_t)a) {
case 0:
return switchProData.leftHatX - 2048; // Subtract the center value
case 1:
return 2048 - switchProData.leftHatY; // Invert, so it follows the same coordinate as the simple report
case 2:
return switchProData.rightHatX - 2048; // Subtract the center value
default:
return 2048 - switchProData.rightHatY; // Invert, so it follows the same coordinate as the simple report
}
}
void SwitchProParser::Parse(uint8_t len, uint8_t *buf) {
if (len > 1 && buf) {
if (len > 0 && buf) {
#ifdef PRINTREPORT
Notify(PSTR("\r\nLen: "), 0x80); Notify(len, 0x80);
Notify(PSTR(", data: "), 0x80);
@ -84,51 +63,50 @@ void SwitchProParser::Parse(uint8_t len, uint8_t *buf) {
}
#endif
if (buf[0] == 0x3F) // Simple input report
memcpy(&switchProData, buf + 1, min((uint8_t)(len - 1), MFK_CASTUINT8T sizeof(switchProData)));
else if (buf[0] == 0x21) // Subcommand reply
return;
else {
// This driver always uses the standard full report that includes the IMU data.
// The downside is that it requires more processing power, as the data is send contentiously
// while the simple input report is only send when the button state changes however the simple
// input report is not available via USB and does not include the IMU data.
if (buf[0] == 0x3F) // Simple input report via Bluetooth
switchProOutput.enableFullReportMode = true; // Switch over to the full report
else if (buf[0] == 0x30) { // Standard full mode
if (len < 4) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nReport is too short: "), 0x80);
D_PrintHex<uint8_t > (len, 0x80);
#endif
return;
}
memcpy(&switchProData, buf + 3, min((uint8_t)(len - 3), MFK_CASTUINT8T sizeof(switchProData)));
if (switchProData.btn.val != oldButtonState.val) { // Check if anything has changed
buttonClickState.val = switchProData.btn.val & ~oldButtonState.val; // Update click state variable
oldButtonState.val = switchProData.btn.val;
}
message_counter++;
} else if (buf[0] == 0x21) {
// Subcommand reply via Bluetooth
} else if (buf[0] == 0x81) {
// Subcommand reply via USB
} else {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nUnknown report id: "), 0x80);
D_PrintHex<uint8_t > (buf[0], 0x80);
Notify(PSTR(", len: "), 0x80);
D_PrintHex<uint8_t > (len, 0x80);
#endif
return;
}
// Workaround issue with the controller sending invalid joystick values when it is connected
for (uint8_t i = 0; i < sizeof(switchProData.hatValue) / sizeof(switchProData.hatValue[0]); i++) {
if (switchProData.hatValue[i] < 1000 || switchProData.hatValue[i] > 0xFFFF - 1000)
switchProData.hatValue[i] = 0x7FFF; // Center value
}
if (switchProData.btn.val != oldButtonState.val) { // Check if anything has changed
buttonClickState.val = switchProData.btn.val & ~oldButtonState.val; // Update click state variable
oldButtonState.val = switchProData.btn.val;
// The DPAD buttons does not set the different bits, but set a value corresponding to the buttons pressed, we will simply set the bits ourself
uint8_t newDpad = 0;
if (checkDpad(UP))
newDpad |= 1 << UP;
if (checkDpad(RIGHT))
newDpad |= 1 << RIGHT;
if (checkDpad(DOWN))
newDpad |= 1 << DOWN;
if (checkDpad(LEFT))
newDpad |= 1 << LEFT;
if (newDpad != oldDpad) {
buttonClickState.dpad = newDpad & ~oldDpad; // Override values
oldDpad = newDpad;
}
}
message_counter++;
}
if (switchProOutput.ledReportChanged || switchProOutput.ledHomeReportChanged)
sendLedOutputReport(); // Send output report
if (switchProOutput.sendHandshake)
sendHandshake();
else if (switchProOutput.disableTimeout)
disableTimeout();
else if (switchProOutput.ledReportChanged || switchProOutput.ledHomeReportChanged ||
switchProOutput.enableFullReportMode || switchProOutput.enableImu != -1)
sendOutputCmd();
else if (switchProOutput.leftRumbleOn || switchProOutput.rightRumbleOn) {
// We need to send the rumble report repeatedly to keep it on
uint32_t now = millis();
@ -136,11 +114,10 @@ void SwitchProParser::Parse(uint8_t len, uint8_t *buf) {
rumble_on_timer = now;
sendRumbleOutputReport();
}
} else
rumble_on_timer = 0;
}
}
void SwitchProParser::sendLedOutputReport() {
void SwitchProParser::sendOutputCmd() {
// See: https://github.com/Dan611/hid-procon
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
// https://github.com/HisashiKato/USB_Host_Shield_Library_2.0_BTXBOX/blob/master/src/SWProBTParser.h#L152-L153
@ -196,6 +173,21 @@ void SwitchProParser::sendLedOutputReport() {
buf[0x0A + 3] = (0xF /* Mini Cycle 1 LED intensity */ << 4) | 0x0 /* Mini Cycle 2 LED intensity */;
sendOutputReport(buf, 10 + 4);
} else if (switchProOutput.enableFullReportMode) {
switchProOutput.enableFullReportMode = false;
// See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x03-set-input-report-mode
buf[0x0A + 0] = 0x03; // PROCON_CMD_MODE
buf[0x0A + 1] = 0x30; // PROCON_ARG_INPUT_FULL
sendOutputReport(buf, 10 + 2);
} else if (switchProOutput.enableImu != -1) {
// See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x40-enable-imu-6-axis-sensor
buf[0x0A + 0] = 0x40; // PROCON_CMD_GYRO
buf[0x0A + 1] = switchProOutput.enableImu ? 1 : 0; // The new state is stored in the variable
switchProOutput.enableImu = -1;
sendOutputReport(buf, 12);
}
}
@ -203,7 +195,6 @@ void SwitchProParser::sendRumbleOutputReport() {
// See: https://github.com/Dan611/hid-procon
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
// https://github.com/HisashiKato/USB_Host_Shield_Library_2.0_BTXBOX/blob/master/src/SWProBTParser.h#L152-L153
uint8_t buf[10] = { 0 };
buf[0x00] = 0x10; // Report ID - PROCON_CMD_RUMBLE_ONLY
buf[0x01] = output_sequence_counter++; // Lowest 4-bit is a sequence number, which needs to be increased for every report
@ -238,15 +229,14 @@ void SwitchProParser::sendRumbleOutputReport() {
}
void SwitchProParser::Reset() {
for (uint8_t i = 0; i < sizeof(switchProData.hatValue) / sizeof(switchProData.hatValue[0]); i++)
switchProData.hatValue[i] = 0x7FFF; // Center value
// Center joysticks
switchProData.leftHatX = switchProData.leftHatY = switchProData.rightHatX = switchProData.rightHatY = 2048;
// Reset buttons variables
switchProData.btn.val = 0;
oldButtonState.val = 0;
buttonClickState.val = 0;
switchProData.btn.dpad = DPAD_OFF;
oldButtonState.dpad = DPAD_OFF;
buttonClickState.dpad = 0;
oldDpad = 0;
output_sequence_counter = 0;
rumble_on_timer = 0;
@ -256,4 +246,8 @@ void SwitchProParser::Reset() {
switchProOutput.ledHome = false;
switchProOutput.ledReportChanged = false;
switchProOutput.ledHomeReportChanged = false;
switchProOutput.enableFullReportMode = false;
switchProOutput.enableImu = -1;
switchProOutput.sendHandshake = false;
switchProOutput.disableTimeout = false;
};

View file

@ -39,73 +39,99 @@ const uint8_t SWITCH_PRO_LEDS[] PROGMEM = {
/** Buttons on the controller */
const uint8_t SWITCH_PRO_BUTTONS[] PROGMEM = {
0x10, // UP
0x11, // RIGHT
0x12, // DOWN
0x11, // UP
0x12, // RIGHT
0x10, // DOWN
0x13, // LEFT
0x0D, // Capture
0x09, // PLUS
0x0A, // L3
0x0B, // R3
0x0B, // L3
0x0A, // R3
0x08, // MINUS
0x0C, // HOME
0, 0, // Skip
0x00, // B
0x01, // A
0x03, // X
0x02, // Y
0x02, // B
0x03, // A
0x01, // X
0x00, // Y
0x04, // L
0x05, // R
0x06, // ZL
0x16, // L
0x06, // R
0x17, // ZL
0x07, // ZR
};
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_notes.md#standard-input-report-format
union SwitchProButtons {
struct {
uint8_t b : 1;
uint8_t a : 1;
uint8_t y : 1;
uint8_t x : 1;
uint8_t b : 1;
uint8_t a : 1;
uint8_t l : 1;
uint8_t dummy1 : 2;
uint8_t r : 1;
uint8_t zl : 1;
uint8_t zr : 1;
uint8_t minus : 1;
uint8_t plus : 1;
uint8_t l3 : 1;
uint8_t r3 : 1;
uint8_t l3 : 1;
uint8_t home : 1;
uint8_t capture : 1;
uint8_t dummy1 : 2;
uint8_t dummy2 : 2;
uint8_t dpad : 4;
uint8_t dummy2 : 4;
uint8_t dummy3 : 2;
uint8_t l : 1;
uint8_t zl : 1;
} __attribute__((packed));
uint32_t val : 24;
} __attribute__((packed));
struct ImuData {
int16_t accX, accY, accZ;
int16_t gyroX, gyroY, gyroZ;
} __attribute__((packed));
struct SwitchProData {
// TODO: Add byte 2 containing battery level and connection info
/* Button and joystick values */
SwitchProButtons btn; // 0-2 bytes
uint16_t hatValue[4]; // 3-10 bytes
SwitchProButtons btn; // Bytes 3-5
// Bytes 6-11
uint16_t leftHatX : 12;
uint16_t leftHatY : 12;
uint16_t rightHatX : 12;
uint16_t rightHatY : 12;
uint8_t vibratorInput; // What is this used for?
// Bytes 13-48
// Three samples of the IMU is sent in one message
// See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
ImuData imu[3];
} __attribute__((packed));
struct SwitchProOutput {
bool leftRumbleOn;
bool rightRumbleOn;
uint8_t ledMask; // Higher nibble flashes the LEDs, lower nibble set them on/off
uint8_t ledMask; // Higher nibble flashes the LEDs, lower nibble sets them on/off
bool ledHome;
// Used to only send the report when the state changes
// Used to send the reports at the same rate as the controller is sending messages
bool ledReportChanged;
bool ledHomeReportChanged;
bool enableFullReportMode;
int8_t enableImu; // -1 == Do nothing, 0 == disable IMU, 1 == enable IMU
bool sendHandshake;
bool disableTimeout;
} __attribute__((packed));
/** This class parses all the data sent by the Switch Pro controller */
@ -138,7 +164,15 @@ public:
*/
int16_t getAnalogHat(AnalogHatEnum a);
#if 0
/**
* Used to enable/disable the IMU. By default it is disabled.
* @param enable Enable/disable the IMU.
*/
void enableImu(bool enable) {
// TODO: Should we just always enable it?
switchProOutput.enableImu = enable ? 1 : 0;
}
/**
* Get the angle of the controller calculated using the accelerometer.
* @param a Either ::Pitch or ::Roll.
@ -146,9 +180,9 @@ public:
*/
float getAngle(AngleEnum a) {
if (a == Pitch)
return (atan2f(-ps5Data.accY, -ps5Data.accZ) + PI) * RAD_TO_DEG;
return (atan2f(-switchProData.imu[0].accY, -switchProData.imu[0].accZ) + PI) * RAD_TO_DEG;
else
return (atan2f(ps5Data.accX, -ps5Data.accZ) + PI) * RAD_TO_DEG;
return (atan2f(switchProData.imu[0].accX, -switchProData.imu[0].accZ) + PI) * RAD_TO_DEG;
};
/**
@ -159,22 +193,21 @@ public:
int16_t getSensor(SensorEnum s) {
switch(s) {
case gX:
return ps5Data.gyroX;
return switchProData.imu[0].gyroX;
case gY:
return ps5Data.gyroY;
return switchProData.imu[0].gyroY;
case gZ:
return ps5Data.gyroZ;
return switchProData.imu[0].gyroZ;
case aX:
return ps5Data.accX;
return switchProData.imu[0].accX;
case aY:
return ps5Data.accY;
return switchProData.imu[0].accY;
case aZ:
return ps5Data.accZ;
return switchProData.imu[0].accZ;
default:
return 0;
}
};
#endif
/** Turn both rumble and the LEDs off. */
void setAllOff() {
@ -305,17 +338,27 @@ protected:
*/
virtual void sendOutputReport(uint8_t *data, uint8_t len) = 0;
/** Used to send a handshake command via USB before disabling the timeout. */
virtual void sendHandshake() {}
/**
* Needed to disable USB timeout for the controller,
* so it sends out data without the host having to send data continuously.
*/
virtual void disableTimeout() {}
/** Allow derived classes to access the output variables. */
SwitchProOutput switchProOutput;
private:
static int8_t getButtonIndexSwitchPro(ButtonEnum b);
bool checkDpad(ButtonEnum b); // Used to check Switch Pro DPAD buttons
void sendLedOutputReport();
void sendOutputCmd();
void sendRumbleOutputReport();
SwitchProData switchProData;
SwitchProButtons oldButtonState, buttonClickState;
SwitchProOutput switchProOutput;
uint8_t oldDpad;
uint16_t message_counter = 0;
uint8_t output_sequence_counter : 4;
uint32_t rumble_on_timer = 0;

145
SwitchProUSB.h Normal file
View file

@ -0,0 +1,145 @@
/* Copyright (C) 2021 Kristian Sloth Lauszus. All rights reserved.
This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").
Contact information
-------------------
Kristian Sloth Lauszus
Web : https://lauszus.com
e-mail : lauszus@gmail.com
*/
#ifndef _switch_pro_usb_h_
#define _switch_pro_usb_h_
#include "hiduniversal.h"
#include "SwitchProParser.h"
#define SWITCH_PRO_VID 0x057E // Nintendo Corporation
#define SWITCH_PRO_PID 0x2009 // Switch Pro Controller
/**
* This class implements support for the Switch Pro controller via USB.
* It uses the HIDUniversal class for all the USB communication.
*/
class SwitchProUSB : public HIDUniversal, public SwitchProParser {
public:
/**
* Constructor for the SwitchProUSB class.
* @param p Pointer to the USB class instance.
*/
SwitchProUSB(USB *p) :
HIDUniversal(p) {
SwitchProParser::Reset();
};
/**
* Used to check if a Switch Pro controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return HIDUniversal::isReady() && HIDUniversal::VID == SWITCH_PRO_VID && HIDUniversal::PID == SWITCH_PRO_PID;
};
/**
* Used to call your own function when the device is successfully initialized.
* @param funcOnInit Function to call.
*/
void attachOnInit(void (*funcOnInit)(void)) {
pFuncOnInit = funcOnInit;
};
protected:
/** @name HIDUniversal implementation */
/**
* Used to parse USB HID data.
* @param hid Pointer to the HID class.
* @param is_rpt_id Only used for Hubs.
* @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/
virtual void ParseHIDData(USBHID *hid __attribute__((unused)), bool is_rpt_id __attribute__((unused)), uint8_t len, uint8_t *buf) {
if (HIDUniversal::VID == SWITCH_PRO_VID && HIDUniversal::PID == SWITCH_PRO_PID)
SwitchProParser::Parse(len, buf);
};
/**
* Called when a device is successfully initialized.
* Use attachOnInit(void (*funcOnInit)(void)) to call your own function.
* This is useful for instance if you want to set the LEDs in a specific way.
*/
virtual uint8_t OnInitSuccessful() {
if (HIDUniversal::VID == SWITCH_PRO_VID && HIDUniversal::PID == SWITCH_PRO_PID) {
SwitchProParser::Reset();
// We need to send a handshake and disable the timeout or the Pro controller will stop sending data via USB
// We can not send the commands quickly after each other, so we simply send out the commands at the same
// rate as the controller is sending data
switchProOutput.sendHandshake = switchProOutput.disableTimeout = true;
if (pFuncOnInit)
pFuncOnInit(); // Call the user function
else {
setLedOn(LED1); // Turn on the LED1
setLedHomeOn(); // Turn on the home LED
}
};
return 0;
};
/**@}*/
/** @name SwitchProParser implementation */
virtual void sendOutputReport(uint8_t *data, uint8_t len) {
// The first 8 bytes are always the same. The actual report follows
uint8_t buf[8 + len] = { 0x80 /* PROCON_REPORT_SEND_USB */, 0x92 /*PROCON_USB_DO_CMD */, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00 };
memcpy(buf + 8, data, len);
// Endpoint (control endpoint), Interface (0x00), Report Type (Output 0x02), Report ID (0x80), nbytes, data
SetReport(epInfo[0].epAddr, 0, 0x02, buf[0], sizeof(buf), buf);
};
virtual void sendHandshake() {
switchProOutput.sendHandshake = false;
// See: https://github.com/Dan611/hid-procon/blob/master/hid-procon.c
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/USB-HID-Notes.md
uint8_t buf[2] = { 0x80 /* PROCON_REPORT_SEND_USB */, 0x02 /* PROCON_USB_HANDSHAKE */ };
// Endpoint (control endpoint), Interface (0x00), Report Type (Output 0x02), Report ID (0x80), nbytes, data
SetReport(epInfo[0].epAddr, 0, 0x02, buf[0], sizeof(buf), buf);
};
virtual void disableTimeout() {
switchProOutput.disableTimeout = false;
// See: https://github.com/Dan611/hid-procon/blob/master/hid-procon.c
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/USB-HID-Notes.md
uint8_t buf[2] = { 0x80 /* PROCON_REPORT_SEND_USB */, 0x04 /* PROCON_USB_ENABLE */ };
// Endpoint (control endpoint), Interface (0x00), Report Type (Output 0x02), Report ID (0x80), nbytes, data
SetReport(epInfo[0].epAddr, 0, 0x02, buf[0], sizeof(buf), buf);
};
/**@}*/
/** @name USBDeviceConfig implementation */
/**
* Used by the USB core to check what this driver support.
* @param vid The device's VID.
* @param pid The device's PID.
* @return Returns true if the device's VID and PID matches this driver.
*/
virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
return (vid == SWITCH_PRO_VID && pid == SWITCH_PRO_PID);
};
/**@}*/
private:
void (*pFuncOnInit)(void); // Pointer to function called in onInit()
};
#endif

View file

@ -25,6 +25,7 @@ SwitchProBT SwitchPro(&Btd, PAIR);
// After that you can simply create the instance like so and then press a button on the device
//SwitchProBT SwitchPro(&Btd);
bool printAngle = false;
uint16_t lastMessageCounter = -1;
uint32_t capture_timer;
@ -46,10 +47,11 @@ void loop() {
if (SwitchPro.connected() && lastMessageCounter != SwitchPro.getMessageCounter()) {
lastMessageCounter = SwitchPro.getMessageCounter();
if (SwitchPro.getAnalogHat(LeftHatX) > 5000 || SwitchPro.getAnalogHat(LeftHatX) < -5000 ||
SwitchPro.getAnalogHat(LeftHatY) > 5000 || SwitchPro.getAnalogHat(LeftHatY) < -5000 ||
SwitchPro.getAnalogHat(RightHatX) > 5000 || SwitchPro.getAnalogHat(RightHatX) < -5000 ||
SwitchPro.getAnalogHat(RightHatY) > 5000 || SwitchPro.getAnalogHat(RightHatY) < -5000) {
// The joysticks values are uncalibrated
if (SwitchPro.getAnalogHat(LeftHatX) > 500 || SwitchPro.getAnalogHat(LeftHatX) < -500 ||
SwitchPro.getAnalogHat(LeftHatY) > 500 || SwitchPro.getAnalogHat(LeftHatY) < -500 ||
SwitchPro.getAnalogHat(RightHatX) > 500 || SwitchPro.getAnalogHat(RightHatX) < -500 ||
SwitchPro.getAnalogHat(RightHatY) > 500 || SwitchPro.getAnalogHat(RightHatY) < -500) {
Serial.print(F("\r\nLeftHatX: "));
Serial.print(SwitchPro.getAnalogHat(LeftHatX));
Serial.print(F("\tLeftHatY: "));
@ -97,8 +99,11 @@ void loop() {
Serial.print(F("\r\nDown"));
}
if (SwitchPro.getButtonClick(PLUS))
if (SwitchPro.getButtonClick(PLUS)) {
printAngle = !printAngle;
SwitchPro.enableImu(printAngle);
Serial.print(F("\r\nPlus"));
}
if (SwitchPro.getButtonClick(MINUS))
Serial.print(F("\r\nMinus"));
@ -131,5 +136,12 @@ void loop() {
Serial.print(F("\r\nL3"));
if (SwitchPro.getButtonClick(R3))
Serial.print(F("\r\nR3"));
if (printAngle) { // Print angle calculated using the accelerometer only
Serial.print(F("\r\nPitch: "));
Serial.print(SwitchPro.getAngle(Pitch));
Serial.print(F("\tRoll: "));
Serial.print(SwitchPro.getAngle(Roll));
}
}
}

View file

@ -0,0 +1,128 @@
/*
Example sketch for the Switch Pro USB library - developed by Kristian Sloth Lauszus
For more information visit the Github repository: github.com/felis/USB_Host_Shield_2.0 or
send me an e-mail: lauszus@gmail.com
*/
#include <SwitchProUSB.h>
// Satisfy the IDE, which needs to see the include statement in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb;
SwitchProUSB SwitchPro(&Usb);
bool printAngle = false;
uint16_t lastMessageCounter = -1;
uint32_t capture_timer;
void setup() {
Serial.begin(115200);
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
if (Usb.Init() == -1) {
Serial.print(F("\r\nOSC did not start"));
while (1); // Halt
}
Serial.print(F("\r\nSwitch Pro USB Library Started"));
}
void loop() {
Usb.Task();
if (SwitchPro.connected() && lastMessageCounter != SwitchPro.getMessageCounter()) {
lastMessageCounter = SwitchPro.getMessageCounter();
// The joysticks values are uncalibrated
if (SwitchPro.getAnalogHat(LeftHatX) > 500 || SwitchPro.getAnalogHat(LeftHatX) < -500 ||
SwitchPro.getAnalogHat(LeftHatY) > 500 || SwitchPro.getAnalogHat(LeftHatY) < -500 ||
SwitchPro.getAnalogHat(RightHatX) > 500 || SwitchPro.getAnalogHat(RightHatX) < -500 ||
SwitchPro.getAnalogHat(RightHatY) > 500 || SwitchPro.getAnalogHat(RightHatY) < -500) {
Serial.print(F("\r\nLeftHatX: "));
Serial.print(SwitchPro.getAnalogHat(LeftHatX));
Serial.print(F("\tLeftHatY: "));
Serial.print(SwitchPro.getAnalogHat(LeftHatY));
Serial.print(F("\tRightHatX: "));
Serial.print(SwitchPro.getAnalogHat(RightHatX));
Serial.print(F("\tRightHatY: "));
Serial.print(SwitchPro.getAnalogHat(RightHatY));
}
if (SwitchPro.getButtonClick(CAPTURE))
Serial.print(F("\r\nCapture"));
if (SwitchPro.getButtonClick(HOME)) {
Serial.print(F("\r\nHome"));
SwitchPro.setLedHomeToggle();
}
if (SwitchPro.getButtonClick(LEFT)) {
SwitchPro.setLedOff();
SwitchPro.setLedOn(LED1);
Serial.print(F("\r\nLeft"));
}
if (SwitchPro.getButtonClick(UP)) {
SwitchPro.setLedOff();
SwitchPro.setLedOn(LED2);
Serial.print(F("\r\nUp"));
}
if (SwitchPro.getButtonClick(RIGHT)) {
SwitchPro.setLedOff();
SwitchPro.setLedOn(LED3);
Serial.print(F("\r\nRight"));
}
if (SwitchPro.getButtonClick(DOWN)) {
SwitchPro.setLedOff();
SwitchPro.setLedOn(LED4);
Serial.print(F("\r\nDown"));
}
if (SwitchPro.getButtonClick(PLUS)) {
printAngle = !printAngle;
SwitchPro.enableImu(printAngle);
Serial.print(F("\r\nPlus"));
}
if (SwitchPro.getButtonClick(MINUS))
Serial.print(F("\r\nMinus"));
if (SwitchPro.getButtonClick(A))
Serial.print(F("\r\nA"));
if (SwitchPro.getButtonClick(B))
Serial.print(F("\r\nB"));
if (SwitchPro.getButtonClick(X))
Serial.print(F("\r\nX"));
if (SwitchPro.getButtonClick(Y))
Serial.print(F("\r\nY"));
if (SwitchPro.getButtonClick(L)) {
SwitchPro.setRumbleLeft(false);
Serial.print(F("\r\nL"));
}
if (SwitchPro.getButtonClick(R)) {
SwitchPro.setRumbleRight(false);
Serial.print(F("\r\nR"));
}
if (SwitchPro.getButtonClick(ZL)) {
SwitchPro.setRumbleLeft(true);
Serial.print(F("\r\nZL"));
}
if (SwitchPro.getButtonClick(ZR)) {
SwitchPro.setRumbleRight(true);
Serial.print(F("\r\nZR"));
}
if (SwitchPro.getButtonClick(L3))
Serial.print(F("\r\nL3"));
if (SwitchPro.getButtonClick(R3))
Serial.print(F("\r\nR3"));
if (printAngle) { // Print angle calculated using the accelerometer only
Serial.print(F("\r\nPitch: "));
Serial.print(SwitchPro.getAngle(Pitch));
Serial.print(F("\tRoll: "));
Serial.print(SwitchPro.getAngle(Roll));
}
}
}

View file

@ -39,6 +39,7 @@ PS4USB KEYWORD1
PS5BT KEYWORD1
PS5USB KEYWORD1
SwitchProBT KEYWORD1
SwitchProUSB KEYWORD1
####################################################
# Methods and Functions (KEYWORD2)
@ -62,8 +63,11 @@ getTemperature KEYWORD2
disconnect KEYWORD2
setAllOff KEYWORD2
setRumble KEYWORD2
setRumbleOff KEYWORD2
setRumbleOn KEYWORD2
setRumbleLeft KEYWORD2
setRumbleRight KEYWORD2
setLedOff KEYWORD2
setLedOn KEYWORD2
setLedToggle KEYWORD2