Merge branch 'felis:master' into master

This commit is contained in:
Adam McDaniel 2021-05-18 13:37:00 -06:00
commit caeb12805b
80 changed files with 5833 additions and 1053 deletions

71
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,71 @@
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
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/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
- name: Install PlatformIO
run: |
pip install -U pip setuptools wheel
pip install platformio adafruit-nrfutil
- name: Install MIDI library
if: contains(matrix.example, 'MIDI')
# https://platformio.org/lib/show/62/MIDI%20Library
run: pio lib -g install 62
- name: Install TinyGPS library
if: contains(matrix.example, 'tinygps')
# https://platformio.org/lib/show/416/TinyGPS
run: pio lib -g install 416
- name: Run PlatformIO
run: |
# Skip all Wii examples and the PS3SPP example on Uno, as they will not fit with debugging enabled
if [[ "${{ matrix.example }}" != *"Wii"* && "${{ matrix.example }}" != *"PS3SPP" ]]; then UNO="--board=uno"; fi
# There is a conflict with the internal Teensy MIDI library, so skip this example on Teensy 3.x and 4.x
# See: https://travis-ci.org/github/felis/USB_Host_Shield_2.0/jobs/743787235
if [[ "${{ matrix.example }}" != *"bidirectional_converter" ]]; then TEENSY35="--board=teensy35"; TEENSY36="--board=teensy36"; TEENSY40="--board=teensy40"; TEENSY41="--board=teensy41"; fi
pio ci --lib="." $UNO --board=genuino101 --board=teensylc $TEENSY40 $TEENSY41 --board=esp12e --board=nodemcu
# Teensy 3.x depends on the SPI4Teensy3 library: https://platformio.org/lib/show/417/SPI4Teensy3
pio ci --lib="." --board=teensy30 --board=teensy31 $TEENSY35 $TEENSY36 --project-option="lib_deps=SPI4Teensy3"
# Ignore "initialization from incompatible pointer type" warning in the ESP32 core
pio ci --lib="." --board=esp32dev --project-option="build_flags=-Wno-incompatible-pointer-types"
# Workaround https://github.com/arduino/ArduinoCore-sam/issues/69
pio ci --lib="." --board=due --project-option="build_flags=-Wno-misleading-indentation"
# Ignore warnings in the Arduino core
pio ci --lib="." --board=adafruit_feather_nrf52840 --project-option="build_flags=-Wno-sign-compare -Wno-unused-function -Wno-unused-variable"
env:
PLATFORMIO_CI_SRC: ${{ matrix.example }}
PLATFORMIO_BUILD_FLAGS: -DWIICAMERA -DDEBUG_USB_HOST -Wall -Werror
doc:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- name: Install dependencies
# We need GraphViz to draw figures and graphs
# Doxygen is used for generating the documentation
run: sudo apt-get -y install doxygen graphviz
- name: Generate documentation
run: |
# Fix error in the Doxygen Markdown parser and generate the documentation
sed -i 's/@YuuichiAkagawa/\\@YuuichiAkagawa/' README.md
doxygen doc/Doxyfile
touch doc/html/.nojekyll
- name: Deploy documentation
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc/html

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
*.zip *.zip
*.rar *.rar
build/ build/
venv/

View file

@ -1,106 +0,0 @@
language: python
python:
- "2.7"
# Cache PlatformIO packages using Travis CI container-based infrastructure
sudo: false
cache:
directories:
- "~/.platformio"
addons:
apt:
packages:
# We need GraphViz to draw figures and graphs
- graphviz
# Doxygen is used for generating the documentation
- doxygen
# Generated using: find examples -type f -name "*.ino" | rev | cut -d/ -f2- | rev | sed 's/^/ - PLATFORMIO_CI_SRC=/' > tmp.yml
env:
- PLATFORMIO_CI_SRC=examples/acm/acm_terminal
- PLATFORMIO_CI_SRC=examples/adk/adk_barcode
- PLATFORMIO_CI_SRC=examples/adk/ArduinoBlinkLED
- PLATFORMIO_CI_SRC=examples/adk/demokit_20
- PLATFORMIO_CI_SRC=examples/adk/term_test
- PLATFORMIO_CI_SRC=examples/adk/term_time
- PLATFORMIO_CI_SRC=examples/Bluetooth/BTHID
- PLATFORMIO_CI_SRC=examples/Bluetooth/PS3BT
- PLATFORMIO_CI_SRC=examples/Bluetooth/PS3Multi
- PLATFORMIO_CI_SRC=examples/Bluetooth/PS3SPP SKIP_UNO=true
- PLATFORMIO_CI_SRC=examples/Bluetooth/PS4BT
- PLATFORMIO_CI_SRC=examples/Bluetooth/SPP
- PLATFORMIO_CI_SRC=examples/Bluetooth/SPPMulti
- PLATFORMIO_CI_SRC=examples/Bluetooth/Wii
- PLATFORMIO_CI_SRC=examples/Bluetooth/WiiBalanceBoard SKIP_UNO=true
- PLATFORMIO_CI_SRC=examples/Bluetooth/WiiIRCamera PLATFORMIO_BUILD_FLAGS="-DWIICAMERA" SKIP_UNO=true
- PLATFORMIO_CI_SRC=examples/Bluetooth/WiiMulti SKIP_UNO=true
- PLATFORMIO_CI_SRC=examples/Bluetooth/WiiUProController SKIP_UNO=true
- PLATFORMIO_CI_SRC=examples/board_qc
- PLATFORMIO_CI_SRC=examples/cdc_XR21B1411/XR_terminal
- PLATFORMIO_CI_SRC=examples/ftdi/USBFTDILoopback
- PLATFORMIO_CI_SRC=examples/GPIO/Blink
- PLATFORMIO_CI_SRC=examples/GPIO/Blink_LowLevel
- PLATFORMIO_CI_SRC=examples/GPIO/Input
- PLATFORMIO_CI_SRC=examples/HID/le3dp
- PLATFORMIO_CI_SRC=examples/HID/scale
- PLATFORMIO_CI_SRC=examples/HID/SRWS1
- PLATFORMIO_CI_SRC=examples/HID/USBHID_desc
- PLATFORMIO_CI_SRC=examples/HID/USBHIDBootKbd
- PLATFORMIO_CI_SRC=examples/HID/USBHIDBootKbdAndMouse
- PLATFORMIO_CI_SRC=examples/HID/USBHIDBootMouse
- PLATFORMIO_CI_SRC=examples/HID/USBHIDJoystick
- PLATFORMIO_CI_SRC=examples/HID/USBHIDMultimediaKbd
- PLATFORMIO_CI_SRC=examples/hub_demo
- PLATFORMIO_CI_SRC=examples/max_LCD
- PLATFORMIO_CI_SRC=examples/pl2303/pl2303_gprs_terminal
- PLATFORMIO_CI_SRC=examples/pl2303/pl2303_gps
- PLATFORMIO_CI_SRC=examples/pl2303/pl2303_tinygps
- PLATFORMIO_CI_SRC=examples/pl2303/pl2303_xbee_terminal
- PLATFORMIO_CI_SRC=examples/PS3USB
- PLATFORMIO_CI_SRC=examples/PS4USB
- PLATFORMIO_CI_SRC=examples/PSBuzz
# - PLATFORMIO_CI_SRC=examples/testusbhostFAT
- PLATFORMIO_CI_SRC=examples/USB_desc
- PLATFORMIO_CI_SRC=examples/USBH_MIDI/bidirectional_converter
- PLATFORMIO_CI_SRC=examples/USBH_MIDI/eVY1_sample
- PLATFORMIO_CI_SRC=examples/USBH_MIDI/USB_MIDI_converter
- PLATFORMIO_CI_SRC=examples/USBH_MIDI/USB_MIDI_converter_multi
- PLATFORMIO_CI_SRC=examples/USBH_MIDI/USBH_MIDI_dump
- PLATFORMIO_CI_SRC=examples/Xbox/XBOXOLD
- PLATFORMIO_CI_SRC=examples/Xbox/XBOXONE
- PLATFORMIO_CI_SRC=examples/Xbox/XBOXRECV
- PLATFORMIO_CI_SRC=examples/Xbox/XBOXUSB
install:
- pip install -U platformio
- export PLATFORMIO_BUILD_FLAGS="$PLATFORMIO_BUILD_FLAGS -DDEBUG_USB_HOST -Wall -Werror"
#
# Libraries from PlatformIO Library Registry:
#
# http://platformio.org/lib/show/62/MIDI
# http://platformio.org/lib/show/416/TinyGPS
# http://platformio.org/lib/show/417/SPI4Teensy3
- platformio lib install 62 416 417
script:
- if [[ -z "$SKIP_UNO" ]]; then UNO="--board=uno"; fi
- platformio ci --lib="." $UNO --board=genuino101 --board=teensy30 --board=teensy31 --board=teensy35 --board=teensy36 --board=teensylc --board=esp12e --board=nodemcu --board=esp32dev
- platformio ci --lib="." --board=due --project-option="build_flags=-Wno-misleading-indentation" # Workaround https://travis-ci.org/felis/USB_Host_Shield_2.0/jobs/569237654 and https://github.com/arduino/ArduinoCore-sam/issues/69
before_deploy:
# Fix errors in the Doxygen Markdown parser and generate the docs
- sed -i 's/Circuits@/Circuits\\@/' README.md
- sed -i 's/@YuuichiAkagawa/\\@YuuichiAkagawa/' README.md
- doxygen doc/Doxyfile
- touch doc/html/.nojekyll
deploy:
provider: pages
github-token: $GITHUB_TOKEN
local_dir: doc/html
skip_cleanup: true
keep-history: true
on:
branch: master

251
AMBX.cpp Normal file
View file

@ -0,0 +1,251 @@
/* Copyright (C) 2021 Aran Vink. 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
-------------------
Aran Vink
e-mail : aranvink@gmail.com
*/
#include "AMBX.h"
// To enable serial debugging see "settings.h"
//#define EXTRADEBUG // Uncomment to get even more debugging data
AMBX::AMBX(USB *p) :
pUsb(p), // pointer to USB class instance - mandatory
bAddress(0) // device address - mandatory
{
for(uint8_t i = 0; i < AMBX_MAX_ENDPOINTS; i++) {
epInfo[i].epAddr = 0;
epInfo[i].maxPktSize = (i) ? 0 : 8;
epInfo[i].bmSndToggle = 0;
epInfo[i].bmRcvToggle = 0;
epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER;
}
if(pUsb) // register in USB subsystem
pUsb->RegisterDeviceClass(this); //set devConfig[] entry
}
uint8_t AMBX::Init(uint8_t parent, uint8_t port, bool lowspeed) {
uint8_t buf[sizeof (USB_DEVICE_DESCRIPTOR)];
USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(buf);
uint8_t rcode;
UsbDevice *p = NULL;
EpInfo *oldep_ptr = NULL;
uint16_t PID;
uint16_t VID;
// get memory address of USB device address pool
AddressPool &addrPool = pUsb->GetAddressPool();
#ifdef EXTRADEBUG
Notify(PSTR("\r\nAMBX Init"), 0x80);
#endif
// check if address has already been assigned to an instance
if(bAddress) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nAddress in use"), 0x80);
#endif
return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE;
}
// Get pointer to pseudo device with address 0 assigned
p = addrPool.GetUsbDevicePtr(0);
if(!p) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nAddress not found"), 0x80);
#endif
return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
}
if(!p->epinfo) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nepinfo is null"), 0x80);
#endif
return USB_ERROR_EPINFO_IS_NULL;
}
// Save old pointer to EP_RECORD of address 0
oldep_ptr = p->epinfo;
// Temporary assign new pointer to epInfo to p->epinfo in order to avoid toggle inconsistence
p->epinfo = epInfo;
p->lowspeed = lowspeed;
// Get device descriptor
rcode = pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), (uint8_t*)buf); // Get device descriptor - addr, ep, nbytes, data
// Restore p->epinfo
p->epinfo = oldep_ptr;
if(rcode)
goto FailGetDevDescr;
VID = udd->idVendor;
PID = udd->idProduct;
if(VID != AMBX_VID || (PID != AMBX_PID))
goto FailUnknownDevice;
// Allocate new address according to device class
bAddress = addrPool.AllocAddress(parent, false, port);
if(!bAddress)
return USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL;
// Extract Max Packet Size from device descriptor
epInfo[0].maxPktSize = udd->bMaxPacketSize0;
// Assign new address to the device
rcode = pUsb->setAddr(0, 0, bAddress);
if(rcode) {
p->lowspeed = false;
addrPool.FreeAddress(bAddress);
bAddress = 0;
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nsetAddr: "), 0x80);
D_PrintHex<uint8_t > (rcode, 0x80);
#endif
return rcode;
}
#ifdef EXTRADEBUG
Notify(PSTR("\r\nAddr: "), 0x80);
D_PrintHex<uint8_t > (bAddress, 0x80);
#endif
p->lowspeed = false;
//get pointer to assigned address record
p = addrPool.GetUsbDevicePtr(bAddress);
if(!p)
return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
p->lowspeed = lowspeed;
// Assign epInfo to epinfo pointer - only EP0 is known
rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo);
if(rcode)
goto FailSetDevTblEntry;
/* The application will work in reduced host mode, so we can save program and data
memory space. After verifying the PID and VID we will use known values for the
configuration values for device, interface, endpoints for the AMBX Controller */
/* Initialize data structures for endpoints of device */
epInfo[ AMBX_OUTPUT_PIPE ].epAddr = AMBX_ENDPOINT_OUT; // AMBX output endpoint
epInfo[ AMBX_OUTPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
epInfo[ AMBX_OUTPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
epInfo[ AMBX_OUTPUT_PIPE ].maxPktSize = AMBX_EP_MAXPKTSIZE;
epInfo[ AMBX_OUTPUT_PIPE ].bmSndToggle = 0;
epInfo[ AMBX_OUTPUT_PIPE ].bmRcvToggle = 0;
rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo);
if(rcode)
goto FailSetDevTblEntry;
delay(200); //Give time for address change
//For some reason this is need to make it work
rcode = pUsb->setConf(bAddress, epInfo[ AMBX_CONTROL_PIPE ].epAddr, 1);
if(rcode)
goto FailSetConfDescr;
if(PID == AMBX_PID || PID) {
AMBXConnected = true;
}
onInit();
Notify(PSTR("\r\n"), 0x80);
return 0; // Successful configuration
/* Diagnostic messages */
FailGetDevDescr:
#ifdef DEBUG_USB_HOST
NotifyFailGetDevDescr();
goto Fail;
#endif
FailSetDevTblEntry:
#ifdef DEBUG_USB_HOST
NotifyFailSetDevTblEntry();
goto Fail;
#endif
FailSetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailSetConfDescr();
#endif
goto Fail;
FailUnknownDevice:
#ifdef DEBUG_USB_HOST
NotifyFailUnknownDevice(VID, PID);
#endif
rcode = USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED;
Fail:
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nAMBX Init Failed, error code: "), 0x80);
NotifyFail(rcode);
#endif
Release();
return rcode;
}
/* Performs a cleanup after failed Init() attempt */
uint8_t AMBX::Release() {
AMBXConnected = false;
pUsb->GetAddressPool().FreeAddress(bAddress);
bAddress = 0;
return 0;
}
uint8_t AMBX::Poll() {
return 0;
}
void AMBX::Light_Command(uint8_t *data, uint16_t nbytes) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nLight command "), 0x80);
#endif
pUsb->outTransfer(bAddress, epInfo[ AMBX_OUTPUT_PIPE ].epAddr, nbytes, data);
}
void AMBX::setLight(uint8_t ambx_light, uint8_t r, uint8_t g, uint8_t b) {
writeBuf[0] = AMBX_PREFIX_COMMAND;
writeBuf[1] = ambx_light;
writeBuf[2] = AMBX_SET_COLOR_COMMAND;
writeBuf[3] = r;
writeBuf[4] = g;
writeBuf[5] = b;
Light_Command(writeBuf, AMBX_LIGHT_COMMAND_BUFFER_SIZE);
}
void AMBX::setLight(AmbxLightsEnum ambx_light, AmbxColorsEnum color) { // Use this to set the Light with Color using the predefined in "AMBXEnums.h"
setLight(ambx_light, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
}
void AMBX::setAllLights(AmbxColorsEnum color) { // Use this to set the Color using the predefined colors in "AMBXEnums.h"
setLight(Sidelight_left, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
setLight(Sidelight_right, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
setLight(Wallwasher_center, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
setLight(Wallwasher_left, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
setLight(Wallwasher_right, (uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
}
void AMBX::onInit() {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nOnInit execute "), 0x80);
#endif
if(pFuncOnInit)
pFuncOnInit(); // Call the user function
}

159
AMBX.h Normal file
View file

@ -0,0 +1,159 @@
/* Copyright (C) 2021 Aran Vink. 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
-------------------
Aran Vink
e-mail : aranvink@gmail.com
*/
#ifndef _ambxusb_h_
#define _ambxusb_h_
#include "Usb.h"
#include "AMBXEnums.h"
/* AMBX data taken from descriptors */
#define AMBX_EP_MAXPKTSIZE 40 // max size for data via USB
/* Names we give to the 3 AMBX but note only one is actually used (output) */
#define AMBX_CONTROL_PIPE 0
#define AMBX_OUTPUT_PIPE 1
#define AMBX_INPUT_PIPE 2
/* PID and VID of the different devices */
#define AMBX_VID 0x0471 // Philips
#define AMBX_PID 0x083F // AMBX Controller
/* Endpoint addresses */
#define AMBX_ENDPOINT_IN 0x81
#define AMBX_ENDPOINT_OUT 0x02
#define AMBX_ENDPOINT_PNP 0x83
/* Output payload constants */
#define AMBX_PREFIX_COMMAND 0xA1
#define AMBX_SET_COLOR_COMMAND 0x03
/* LEFT/RIGHT lights. Normally placed adjecent to your screen. */
#define AMBX_LIGHT_LEFT 0x0B
#define AMBX_LIGHT_RIGHT 0x1B
/* Wallwasher lights. Normally placed behind your screen. */
#define AMBX_LIGHT_WW_LEFT 0x2B
#define AMBX_LIGHT_WW_CENTER 0x3B
#define AMBX_LIGHT_WW_RIGHT 0x4B
#define AMBX_LIGHT_COMMAND_BUFFER_SIZE 6
#define AMBX_MAX_ENDPOINTS 3
/**
* This class implements support for AMBX
* One can only set the color of the bulbs, no other accesories like rumble pad, fans, etc. are supported
*
*/
class AMBX : public USBDeviceConfig {
public:
/**
* Constructor for the AMBX class.
* @param pUsb Pointer to USB class instance.
*/
AMBX(USB *pUsb);
/** @name USBDeviceConfig implementation */
/**
* Initialize the AMBX Controller.
* @param parent Hub number.
* @param port Port number on the hub.
* @param lowspeed Speed of the device.
* @return 0 on success.
*/
uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed);
/**
* Release the USB device.
* @return 0 on success.
*/
uint8_t Release();
/**
* Poll the USB Input endpoins and run the state machines.
* @return 0 on success.
*/
uint8_t Poll();
/**
* Get the device address.
* @return The device address.
*/
virtual uint8_t GetAddress() {
return bAddress;
};
/**
* 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 == AMBX_VID && (pid == AMBX_PID));
};
/**@}*/
/**
* Use this to set the Color using RGB values.
* @param r,g,b RGB value.
*/
void setLight(uint8_t ambx_light, uint8_t r, uint8_t g, uint8_t b);
/**
* Use this to set the color using the predefined colors in ::ColorsEnum.
* @param color The desired color.
*/
void setLight(AmbxLightsEnum ambx_light, AmbxColorsEnum color);
/**
* Use this to set the color using the predefined colors in ::ColorsEnum.
* @param color The desired color.
*/
void setAllLights(AmbxColorsEnum color);
/**
* Used to call your own function when the controller is successfully initialized.
* @param funcOnInit Function to call.
*/
void attachOnInit(void (*funcOnInit)(void)) {
pFuncOnInit = funcOnInit;
};
/**@}*/
bool AMBXConnected;
protected:
/** Pointer to USB class instance. */
USB *pUsb;
/** Device address. */
uint8_t bAddress;
/** Endpoint info structure. */
EpInfo epInfo[AMBX_MAX_ENDPOINTS];
private:
/**
* Called when the AMBX controller is successfully initialized.
*/
void onInit();
void (*pFuncOnInit)(void); // Pointer to function called in onInit()
uint8_t writeBuf[AMBX_EP_MAXPKTSIZE]; // General purpose buffer for output data
/* Private commands */
void Light_Command(uint8_t *data, uint16_t nbytes);
};
#endif

38
AMBXEnums.h Normal file
View file

@ -0,0 +1,38 @@
/* Copyright (C) 2021 Aran Vink. 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
-------------------
Aran Vink
e-mail : aranvink@gmail.com
*/
#ifndef _ambxenums_h
#define _ambxenums_h
/** Used to set the colors of the AMBX lights. This is just a limited predefined set, the lights allow ANY value between 0x00 and 0xFF */
enum AmbxColorsEnum {
Red = 0xFF0000,
Green = 0x00FF00,
Blue = 0x0000FF,
White = 0xFFFFFF,
Off = 0x000000,
};
/** Used to select light in the AMBX system */
enum AmbxLightsEnum {
Sidelight_left = 0x0B,
Sidelight_right = 0x1B,
Wallwasher_left = 0x2B,
Wallwasher_center = 0x3B,
Wallwasher_right = 0x4B,
};
#endif

268
BTD.cpp
View file

@ -29,11 +29,13 @@ connectToWii(false),
pairWithWii(false), pairWithWii(false),
connectToHIDDevice(false), connectToHIDDevice(false),
pairWithHIDDevice(false), pairWithHIDDevice(false),
useSimplePairing(false),
pUsb(p), // Pointer to USB class instance - mandatory pUsb(p), // Pointer to USB class instance - mandatory
bAddress(0), // Device address - mandatory bAddress(0), // Device address - mandatory
bNumEP(1), // If config descriptor needs to be parsed bNumEP(1), // If config descriptor needs to be parsed
qNextPollTime(0), // Reset NextPollTime qNextPollTime(0), // Reset NextPollTime
pollInterval(0), pollInterval(0),
simple_pairing_supported(false),
bPollEnable(false) // Don't start polling before dongle is connected bPollEnable(false) // Don't start polling before dongle is connected
{ {
for(uint8_t i = 0; i < BTD_NUM_SERVICES; i++) for(uint8_t i = 0; i < BTD_NUM_SERVICES; i++)
@ -315,12 +317,13 @@ void BTD::Initialize() {
incomingWii = false; incomingWii = false;
connectToHIDDevice = false; connectToHIDDevice = false;
incomingHIDDevice = false; incomingHIDDevice = false;
incomingPS4 = false; incomingPSController = false;
bAddress = 0; // Clear device address bAddress = 0; // Clear device address
bNumEP = 1; // Must have to be reset to 1 bNumEP = 1; // Must have to be reset to 1
qNextPollTime = 0; // Reset next poll time qNextPollTime = 0; // Reset next poll time
pollInterval = 0; pollInterval = 0;
bPollEnable = false; // Don't start polling before dongle is connected bPollEnable = false; // Don't start polling before dongle is connected
simple_pairing_supported = false;
} }
/* Extracts interrupt-IN, bulk-IN, bulk-OUT endpoint information from config descriptor */ /* Extracts interrupt-IN, bulk-IN, bulk-OUT endpoint information from config descriptor */
@ -408,7 +411,57 @@ void BTD::HCI_event_task() {
hci_set_flag(HCI_FLAG_CMD_COMPLETE); // Set command complete flag hci_set_flag(HCI_FLAG_CMD_COMPLETE); // Set command complete flag
if((hcibuf[3] == 0x01) && (hcibuf[4] == 0x10)) { // Parameters from read local version information if((hcibuf[3] == 0x01) && (hcibuf[4] == 0x10)) { // Parameters from read local version information
hci_version = hcibuf[6]; // Used to check if it supports 2.0+EDR - see http://www.bluetooth.org/Technical/AssignedNumbers/hci.htm hci_version = hcibuf[6]; // Used to check if it supports 2.0+EDR - see http://www.bluetooth.org/Technical/AssignedNumbers/hci.htm
#ifdef EXTRADEBUG
if(!hci_check_flag(HCI_FLAG_READ_VERSION)) {
Notify(PSTR("\r\nHCI version: "), 0x80);
D_PrintHex<uint8_t > (hci_version, 0x80);
}
#endif
hci_set_flag(HCI_FLAG_READ_VERSION); hci_set_flag(HCI_FLAG_READ_VERSION);
} else if((hcibuf[3] == 0x04) && (hcibuf[4] == 0x10)) { // Parameters from read local extended features
if(!hci_check_flag(HCI_FLAG_LOCAL_EXTENDED_FEATURES)) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nPage number: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[6], 0x80);
Notify(PSTR("\r\nMaximum page number: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[7], 0x80);
Notify(PSTR("\r\nExtended LMP features:"), 0x80);
for(uint8_t i = 0; i < 8; i++) {
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (hcibuf[8 + i], 0x80);
}
#endif
if(hcibuf[6] == 0) { // Page 0
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nDongle "), 0x80);
#endif
if(hcibuf[8 + 6] & (1U << 3)) {
simple_pairing_supported = true;
#ifdef DEBUG_USB_HOST
Notify(PSTR("supports"), 0x80);
#endif
} else {
simple_pairing_supported = false;
#ifdef DEBUG_USB_HOST
Notify(PSTR("does NOT support"), 0x80);
#endif
}
#ifdef DEBUG_USB_HOST
Notify(PSTR(" secure simple pairing (controller support)"), 0x80);
#endif
} else if(hcibuf[6] == 1) { // Page 1
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nDongle "), 0x80);
if(hcibuf[8 + 0] & (1U << 0))
Notify(PSTR("supports"), 0x80);
else
Notify(PSTR("does NOT support"), 0x80);
Notify(PSTR(" secure simple pairing (host support)"), 0x80);
#endif
}
}
hci_set_flag(HCI_FLAG_LOCAL_EXTENDED_FEATURES);
} else if((hcibuf[3] == 0x09) && (hcibuf[4] == 0x10)) { // Parameters from read local bluetooth address } else if((hcibuf[3] == 0x09) && (hcibuf[4] == 0x10)) { // Parameters from read local bluetooth address
for(uint8_t i = 0; i < 6; i++) for(uint8_t i = 0; i < 6; i++)
my_bdaddr[i] = hcibuf[6 + i]; my_bdaddr[i] = hcibuf[6 + i];
@ -422,6 +475,12 @@ void BTD::HCI_event_task() {
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nHCI Command Failed: "), 0x80); Notify(PSTR("\r\nHCI Command Failed: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[2], 0x80); D_PrintHex<uint8_t > (hcibuf[2], 0x80);
Notify(PSTR("\r\nNum HCI Command Packets: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[3], 0x80);
Notify(PSTR("\r\nCommand Opcode: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[4], 0x80);
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (hcibuf[5], 0x80);
#endif #endif
} }
break; break;
@ -465,7 +524,7 @@ void BTD::HCI_event_task() {
D_PrintHex<uint8_t > (classOfDevice[0], 0x80); D_PrintHex<uint8_t > (classOfDevice[0], 0x80);
#endif #endif
if(pairWithWii && classOfDevice[2] == 0x00 && (classOfDevice[1] & 0x05) && (classOfDevice[0] & 0x0C)) { // See http://wiibrew.org/wiki/Wiimote#SDP_information if(pairWithWii && classOfDevice[2] == 0x00 && (classOfDevice[1] == 0x05) && (classOfDevice[0] & 0x0C)) { // See http://wiibrew.org/wiki/Wiimote#SDP_information
checkRemoteName = true; // Check remote name to distinguish between the different controllers checkRemoteName = true; // Check remote name to distinguish between the different controllers
for(uint8_t j = 0; j < 6; j++) for(uint8_t j = 0; j < 6; j++)
@ -473,8 +532,10 @@ void BTD::HCI_event_task() {
hci_set_flag(HCI_FLAG_DEVICE_FOUND); hci_set_flag(HCI_FLAG_DEVICE_FOUND);
break; break;
} else if(pairWithHIDDevice && (classOfDevice[1] & 0x05) && (classOfDevice[0] & 0xC8)) { // Check if it is a mouse, keyboard or a gamepad - see: http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html } else if(pairWithHIDDevice && (classOfDevice[1] & 0x0F) == 0x05 && (classOfDevice[0] & 0xC8)) { // Check if it is a mouse, keyboard or a gamepad - see: http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
checkRemoteName = true; // Used to print name in the serial monitor if serial debugging is enabled
if(classOfDevice[0] & 0x80) if(classOfDevice[0] & 0x80)
Notify(PSTR("\r\nMouse found"), 0x80); Notify(PSTR("\r\nMouse found"), 0x80);
if(classOfDevice[0] & 0x40) if(classOfDevice[0] & 0x40)
@ -482,7 +543,6 @@ void BTD::HCI_event_task() {
if(classOfDevice[0] & 0x08) if(classOfDevice[0] & 0x08)
Notify(PSTR("\r\nGamepad found"), 0x80); Notify(PSTR("\r\nGamepad found"), 0x80);
#endif #endif
for(uint8_t j = 0; j < 6; j++) for(uint8_t j = 0; j < 6; j++)
disc_bdaddr[j] = hcibuf[j + 3 + 6 * i]; disc_bdaddr[j] = hcibuf[j + 3 + 6 * i];
@ -524,7 +584,7 @@ void BTD::HCI_event_task() {
if(remote_name[i] == '\0') // End of string if(remote_name[i] == '\0') // End of string
break; break;
} }
// TODO: Altid sæt '\0' i remote name! // TODO: Always set '\0' in remote name!
hci_set_flag(HCI_FLAG_REMOTE_NAME_COMPLETE); hci_set_flag(HCI_FLAG_REMOTE_NAME_COMPLETE);
} }
break; break;
@ -536,7 +596,7 @@ void BTD::HCI_event_task() {
for(uint8_t i = 0; i < 3; i++) for(uint8_t i = 0; i < 3; i++)
classOfDevice[i] = hcibuf[i + 8]; classOfDevice[i] = hcibuf[i + 8];
if((classOfDevice[1] & 0x05) && (classOfDevice[0] & 0xC8)) { // Check if it is a mouse, keyboard or a gamepad if((classOfDevice[1] & 0x0F) == 0x05 && (classOfDevice[0] & 0xC8)) { // Check if it is a mouse, keyboard or a gamepad
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
if(classOfDevice[0] & 0x80) if(classOfDevice[0] & 0x80)
Notify(PSTR("\r\nMouse is connecting"), 0x80); Notify(PSTR("\r\nMouse is connecting"), 0x80);
@ -598,6 +658,10 @@ void BTD::HCI_event_task() {
Notify(PSTR("\r\nPairing successful with HID device"), 0x80); Notify(PSTR("\r\nPairing successful with HID device"), 0x80);
#endif #endif
connectToHIDDevice = true; // Used to indicate to the BTHID service, that it should connect to this device connectToHIDDevice = true; // Used to indicate to the BTHID service, that it should connect to this device
} else {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nPairing was successful"), 0x80);
#endif
} }
} else { } else {
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
@ -608,24 +672,82 @@ void BTD::HCI_event_task() {
hci_state = HCI_DISCONNECT_STATE; hci_state = HCI_DISCONNECT_STATE;
} }
break; break;
case EV_IO_CAPABILITY_REQUEST:
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nReceived IO Capability Request"), 0x80);
#endif
hci_io_capability_request_reply();
break;
case EV_IO_CAPABILITY_RESPONSE:
#ifdef EXTRADEBUG
Notify(PSTR("\r\nReceived IO Capability Response: "), 0x80);
Notify(PSTR("\r\nIO capability: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[8], 0x80);
Notify(PSTR("\r\nOOB data present: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[9], 0x80);
Notify(PSTR("\r\nAuthentication request: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[10], 0x80);
#endif
break;
case EV_USER_CONFIRMATION_REQUEST:
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nUser confirmation Request"), 0x80);
#ifdef EXTRADEBUG
Notify(PSTR(": \r\nNumeric value: "), 0x80);
for(uint8_t i = 0; i < 4; i++) {
Notify(PSTR(" "), 0x80);
D_PrintHex<uint8_t > (hcibuf[8 + i], 0x80);
}
#endif
#endif
// Simply confirm the connection, as the host has no "NoInputNoOutput" capabilities
hci_user_confirmation_request_reply();
break;
case EV_SIMPLE_PAIRING_COMPLETE:
#ifdef EXTRADEBUG
if(!hcibuf[2]) { // Check if connected OK
Notify(PSTR("\r\nSimple Pairing succeeded"), 0x80);
} else {
Notify(PSTR("\r\nSimple Pairing failed: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[2], 0x80);
}
#endif
break;
/* We will just ignore the following events */ /* We will just ignore the following events */
case EV_MAX_SLOTS_CHANGE:
case EV_NUM_COMPLETE_PKT: case EV_NUM_COMPLETE_PKT:
break;
case EV_ROLE_CHANGED: case EV_ROLE_CHANGED:
case EV_PAGE_SCAN_REP_MODE: case EV_PAGE_SCAN_REP_MODE:
case EV_LOOPBACK_COMMAND: case EV_LOOPBACK_COMMAND:
case EV_DATA_BUFFER_OVERFLOW: case EV_DATA_BUFFER_OVERFLOW:
case EV_CHANGE_CONNECTION_LINK: case EV_CHANGE_CONNECTION_LINK:
case EV_MAX_SLOTS_CHANGE:
case EV_QOS_SETUP_COMPLETE: case EV_QOS_SETUP_COMPLETE:
case EV_LINK_KEY_NOTIFICATION: case EV_LINK_KEY_NOTIFICATION:
case EV_ENCRYPTION_CHANGE: case EV_ENCRYPTION_CHANGE:
case EV_READ_REMOTE_VERSION_INFORMATION_COMPLETE: case EV_READ_REMOTE_VERSION_INFORMATION_COMPLETE:
#ifdef EXTRADEBUG
if(hcibuf[0] != 0x00) {
Notify(PSTR("\r\nIgnore HCI Event: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[0], 0x80);
}
#endif
break; break;
#ifdef EXTRADEBUG #ifdef EXTRADEBUG
default: default:
if(hcibuf[0] != 0x00) { if(hcibuf[0] != 0x00) {
Notify(PSTR("\r\nUnmanaged HCI Event: "), 0x80); Notify(PSTR("\r\nUnmanaged HCI Event: "), 0x80);
D_PrintHex<uint8_t > (hcibuf[0], 0x80); D_PrintHex<uint8_t > (hcibuf[0], 0x80);
Notify(PSTR(", data: "), 0x80);
for(uint16_t i = 0; i < hcibuf[1]; i++) {
D_PrintHex<uint8_t > (hcibuf[2 + i], 0x80);
Notify(PSTR(" "), 0x80);
}
} }
break; break;
#endif #endif
@ -700,18 +822,56 @@ void BTD::HCI_task() {
case HCI_LOCAL_VERSION_STATE: // The local version is used by the PS3BT class case HCI_LOCAL_VERSION_STATE: // The local version is used by the PS3BT class
if(hci_check_flag(HCI_FLAG_READ_VERSION)) { if(hci_check_flag(HCI_FLAG_READ_VERSION)) {
if(btdName != NULL) { if(btdName != NULL) {
hci_set_local_name(btdName); hci_write_local_name(btdName);
hci_state = HCI_SET_NAME_STATE; hci_state = HCI_WRITE_NAME_STATE;
} else if(useSimplePairing) {
hci_read_local_extended_features(0); // "Requests the normal LMP features as returned by Read_Local_Supported_Features"
//hci_read_local_extended_features(1); // Read page 1
hci_state = HCI_LOCAL_EXTENDED_FEATURES_STATE;
} else } else
hci_state = HCI_CHECK_DEVICE_SERVICE; hci_state = HCI_CHECK_DEVICE_SERVICE;
} }
break; break;
case HCI_SET_NAME_STATE: case HCI_WRITE_NAME_STATE:
if(hci_check_flag(HCI_FLAG_CMD_COMPLETE)) { if(hci_check_flag(HCI_FLAG_CMD_COMPLETE)) {
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nThe name is set to: "), 0x80); Notify(PSTR("\r\nThe name was set to: "), 0x80);
NotifyStr(btdName, 0x80); NotifyStr(btdName, 0x80);
#endif
if(useSimplePairing) {
hci_read_local_extended_features(0); // "Requests the normal LMP features as returned by Read_Local_Supported_Features"
//hci_read_local_extended_features(1); // Read page 1
hci_state = HCI_LOCAL_EXTENDED_FEATURES_STATE;
} else
hci_state = HCI_CHECK_DEVICE_SERVICE;
}
break;
case HCI_LOCAL_EXTENDED_FEATURES_STATE:
if(hci_check_flag(HCI_FLAG_LOCAL_EXTENDED_FEATURES)) {
if(simple_pairing_supported) {
hci_write_simple_pairing_mode(true);
hci_state = HCI_WRITE_SIMPLE_PAIRING_STATE;
} else
hci_state = HCI_CHECK_DEVICE_SERVICE;
}
break;
case HCI_WRITE_SIMPLE_PAIRING_STATE:
if(hci_check_flag(HCI_FLAG_CMD_COMPLETE)) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nSimple pairing was enabled"), 0x80);
#endif
hci_set_event_mask();
hci_state = HCI_SET_EVENT_MASK_STATE;
}
break;
case HCI_SET_EVENT_MASK_STATE:
if(hci_check_flag(HCI_FLAG_CMD_COMPLETE)) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nSet event mask completed"), 0x80);
#endif #endif
hci_state = HCI_CHECK_DEVICE_SERVICE; hci_state = HCI_CHECK_DEVICE_SERVICE;
} }
@ -783,7 +943,7 @@ void BTD::HCI_task() {
else else
Notify(PSTR("\r\nConnected to HID device"), 0x80); Notify(PSTR("\r\nConnected to HID device"), 0x80);
#endif #endif
hci_authentication_request(); // This will start the pairing with the Wiimote hci_authentication_request(); // This will start the pairing with the device
hci_state = HCI_SCANNING_STATE; hci_state = HCI_SCANNING_STATE;
} else { } else {
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
@ -851,11 +1011,11 @@ void BTD::HCI_task() {
} }
if(classOfDevice[2] == 0 && classOfDevice[1] == 0x25 && classOfDevice[0] == 0x08 && strncmp((const char*)remote_name, "Wireless Controller", 19) == 0) { if(classOfDevice[2] == 0 && classOfDevice[1] == 0x25 && classOfDevice[0] == 0x08 && strncmp((const char*)remote_name, "Wireless Controller", 19) == 0) {
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nPS4 controller is connecting"), 0x80); Notify(PSTR("\r\nPS4/PS5 controller is connecting"), 0x80);
#endif #endif
incomingPS4 = true; incomingPSController = true;
} }
if(pairWithWii && checkRemoteName) if((pairWithWii || pairWithHIDDevice) && checkRemoteName)
hci_state = HCI_CONNECT_DEVICE_STATE; hci_state = HCI_CONNECT_DEVICE_STATE;
else { else {
hci_accept_connection(); hci_accept_connection();
@ -874,8 +1034,8 @@ void BTD::HCI_task() {
} }
D_PrintHex<uint8_t > (disc_bdaddr[0], 0x80); D_PrintHex<uint8_t > (disc_bdaddr[0], 0x80);
#endif #endif
if(incomingPS4) if(incomingPSController)
connectToHIDDevice = true; // We should always connect to the PS4 controller connectToHIDDevice = true; // We should always connect to the PS4/PS5 controller
// Clear these flags for a new connection // Clear these flags for a new connection
l2capConnectionClaimed = false; l2capConnectionClaimed = false;
@ -908,7 +1068,7 @@ void BTD::HCI_task() {
connectToWii = incomingWii = pairWithWii = false; connectToWii = incomingWii = pairWithWii = false;
connectToHIDDevice = incomingHIDDevice = pairWithHIDDevice = checkRemoteName = false; connectToHIDDevice = incomingHIDDevice = pairWithHIDDevice = checkRemoteName = false;
incomingPS4 = false; incomingPSController = false;
hci_state = HCI_SCANNING_STATE; hci_state = HCI_SCANNING_STATE;
} }
@ -999,6 +1159,16 @@ void BTD::hci_read_local_version_information() {
HCI_Command(hcibuf, 3); HCI_Command(hcibuf, 3);
} }
void BTD::hci_read_local_extended_features(uint8_t page_number) {
hci_clear_flag(HCI_FLAG_LOCAL_EXTENDED_FEATURES);
hcibuf[0] = 0x04; // HCI OCF = 4
hcibuf[1] = 0x04 << 2; // HCI OGF = 4
hcibuf[2] = 0x01; // parameter length = 1
hcibuf[3] = page_number;
HCI_Command(hcibuf, 4);
}
void BTD::hci_accept_connection() { void BTD::hci_accept_connection() {
hci_clear_flag(HCI_FLAG_CONNECT_COMPLETE); hci_clear_flag(HCI_FLAG_CONNECT_COMPLETE);
hcibuf[0] = 0x09; // HCI OCF = 9 hcibuf[0] = 0x09; // HCI OCF = 9
@ -1034,7 +1204,7 @@ void BTD::hci_remote_name() {
HCI_Command(hcibuf, 13); HCI_Command(hcibuf, 13);
} }
void BTD::hci_set_local_name(const char* name) { void BTD::hci_write_local_name(const char* name) {
hcibuf[0] = 0x13; // HCI OCF = 13 hcibuf[0] = 0x13; // HCI OCF = 13
hcibuf[1] = 0x03 << 2; // HCI OGF = 3 hcibuf[1] = 0x03 << 2; // HCI OGF = 3
hcibuf[2] = strlen(name) + 1; // parameter length = the length of the string + end byte hcibuf[2] = strlen(name) + 1; // parameter length = the length of the string + end byte
@ -1046,6 +1216,33 @@ void BTD::hci_set_local_name(const char* name) {
HCI_Command(hcibuf, 4 + strlen(name)); HCI_Command(hcibuf, 4 + strlen(name));
} }
void BTD::hci_set_event_mask() {
hcibuf[0] = 0x01; // HCI OCF = 01
hcibuf[1] = 0x03 << 2; // HCI OGF = 3
hcibuf[2] = 0x08;
// The first 6 bytes are the default of 1FFF FFFF FFFF
// However we need to set bits 48-55 for simple pairing to work
hcibuf[3] = 0xFF;
hcibuf[4] = 0xFF;
hcibuf[5] = 0xFF;
hcibuf[6] = 0xFF;
hcibuf[7] = 0xFF;
hcibuf[8] = 0x1F;
hcibuf[9] = 0xFF; // Enable bits 48-55 used for simple pairing
hcibuf[10] = 0x00;
HCI_Command(hcibuf, 11);
}
void BTD::hci_write_simple_pairing_mode(bool enable) {
hcibuf[0] = 0x56; // HCI OCF = 56
hcibuf[1] = 0x03 << 2; // HCI OGF = 3
hcibuf[2] = 1; // parameter length = 1
hcibuf[3] = enable ? 1 : 0;
HCI_Command(hcibuf, 4);
}
void BTD::hci_inquiry() { void BTD::hci_inquiry() {
hci_clear_flag(HCI_FLAG_DEVICE_FOUND); hci_clear_flag(HCI_FLAG_DEVICE_FOUND);
hcibuf[0] = 0x01; hcibuf[0] = 0x01;
@ -1074,7 +1271,7 @@ void BTD::hci_connect() {
void BTD::hci_connect(uint8_t *bdaddr) { void BTD::hci_connect(uint8_t *bdaddr) {
hci_clear_flag(HCI_FLAG_CONNECT_COMPLETE | HCI_FLAG_CONNECT_EVENT); hci_clear_flag(HCI_FLAG_CONNECT_COMPLETE | HCI_FLAG_CONNECT_EVENT);
hcibuf[0] = 0x05; hcibuf[0] = 0x05; // HCI OCF = 5
hcibuf[1] = 0x01 << 2; // HCI OGF = 1 hcibuf[1] = 0x01 << 2; // HCI OGF = 1
hcibuf[2] = 0x0D; // parameter Total Length = 13 hcibuf[2] = 0x0D; // parameter Total Length = 13
hcibuf[3] = bdaddr[0]; // 6 octet bdaddr (LSB) hcibuf[3] = bdaddr[0]; // 6 octet bdaddr (LSB)
@ -1158,6 +1355,37 @@ void BTD::hci_link_key_request_negative_reply() {
HCI_Command(hcibuf, 9); HCI_Command(hcibuf, 9);
} }
void BTD::hci_io_capability_request_reply() {
hcibuf[0] = 0x2B; // HCI OCF = 2B
hcibuf[1] = 0x01 << 2; // HCI OGF = 1
hcibuf[2] = 0x09;
hcibuf[3] = disc_bdaddr[0]; // 6 octet bdaddr
hcibuf[4] = disc_bdaddr[1];
hcibuf[5] = disc_bdaddr[2];
hcibuf[6] = disc_bdaddr[3];
hcibuf[7] = disc_bdaddr[4];
hcibuf[8] = disc_bdaddr[5];
hcibuf[9] = 0x03; // NoInputNoOutput
hcibuf[10] = 0x00; // OOB authentication data not present
hcibuf[11] = 0x00; // MITM Protection Not Required No Bonding. Numeric comparison with automatic accept allowed
HCI_Command(hcibuf, 12);
}
void BTD::hci_user_confirmation_request_reply() {
hcibuf[0] = 0x2C; // HCI OCF = 2C
hcibuf[1] = 0x01 << 2; // HCI OGF = 1
hcibuf[2] = 0x06; // parameter length 6
hcibuf[3] = disc_bdaddr[0]; // 6 octet bdaddr
hcibuf[4] = disc_bdaddr[1];
hcibuf[5] = disc_bdaddr[2];
hcibuf[6] = disc_bdaddr[3];
hcibuf[7] = disc_bdaddr[4];
hcibuf[8] = disc_bdaddr[5];
HCI_Command(hcibuf, 9);
}
void BTD::hci_authentication_request() { void BTD::hci_authentication_request() {
hcibuf[0] = 0x11; // HCI OCF = 11 hcibuf[0] = 0x11; // HCI OCF = 11
hcibuf[1] = 0x01 << 2; // HCI OGF = 1 hcibuf[1] = 0x01 << 2; // HCI OGF = 1

48
BTD.h
View file

@ -45,7 +45,7 @@
#define HCI_CLASS_STATE 2 #define HCI_CLASS_STATE 2
#define HCI_BDADDR_STATE 3 #define HCI_BDADDR_STATE 3
#define HCI_LOCAL_VERSION_STATE 4 #define HCI_LOCAL_VERSION_STATE 4
#define HCI_SET_NAME_STATE 5 #define HCI_WRITE_NAME_STATE 5
#define HCI_CHECK_DEVICE_SERVICE 6 #define HCI_CHECK_DEVICE_SERVICE 6
#define HCI_INQUIRY_STATE 7 // These three states are only used if it should pair and connect to a device #define HCI_INQUIRY_STATE 7 // These three states are only used if it should pair and connect to a device
@ -59,6 +59,9 @@
#define HCI_DISABLE_SCAN_STATE 14 #define HCI_DISABLE_SCAN_STATE 14
#define HCI_DONE_STATE 15 #define HCI_DONE_STATE 15
#define HCI_DISCONNECT_STATE 16 #define HCI_DISCONNECT_STATE 16
#define HCI_LOCAL_EXTENDED_FEATURES_STATE 17
#define HCI_WRITE_SIMPLE_PAIRING_STATE 18
#define HCI_SET_EVENT_MASK_STATE 19
/* HCI event flags*/ /* HCI event flags*/
#define HCI_FLAG_CMD_COMPLETE (1UL << 0) #define HCI_FLAG_CMD_COMPLETE (1UL << 0)
@ -70,6 +73,7 @@
#define HCI_FLAG_READ_VERSION (1UL << 6) #define HCI_FLAG_READ_VERSION (1UL << 6)
#define HCI_FLAG_DEVICE_FOUND (1UL << 7) #define HCI_FLAG_DEVICE_FOUND (1UL << 7)
#define HCI_FLAG_CONNECT_EVENT (1UL << 8) #define HCI_FLAG_CONNECT_EVENT (1UL << 8)
#define HCI_FLAG_LOCAL_EXTENDED_FEATURES (1UL << 9)
/* Macros for HCI event flag tests */ /* Macros for HCI event flag tests */
#define hci_check_flag(flag) (hci_event_flag & (flag)) #define hci_check_flag(flag) (hci_event_flag & (flag))
@ -86,6 +90,10 @@
#define EV_REMOTE_NAME_COMPLETE 0x07 #define EV_REMOTE_NAME_COMPLETE 0x07
#define EV_ENCRYPTION_CHANGE 0x08 #define EV_ENCRYPTION_CHANGE 0x08
#define EV_CHANGE_CONNECTION_LINK 0x09 #define EV_CHANGE_CONNECTION_LINK 0x09
#define EV_READ_REMOTE_VERSION_INFORMATION_COMPLETE 0x0C
#define EV_QOS_SETUP_COMPLETE 0x0D
#define EV_COMMAND_COMPLETE 0x0E
#define EV_COMMAND_STATUS 0x0F
#define EV_ROLE_CHANGED 0x12 #define EV_ROLE_CHANGED 0x12
#define EV_NUM_COMPLETE_PKT 0x13 #define EV_NUM_COMPLETE_PKT 0x13
#define EV_PIN_CODE_REQUEST 0x16 #define EV_PIN_CODE_REQUEST 0x16
@ -93,12 +101,13 @@
#define EV_LINK_KEY_NOTIFICATION 0x18 #define EV_LINK_KEY_NOTIFICATION 0x18
#define EV_DATA_BUFFER_OVERFLOW 0x1A #define EV_DATA_BUFFER_OVERFLOW 0x1A
#define EV_MAX_SLOTS_CHANGE 0x1B #define EV_MAX_SLOTS_CHANGE 0x1B
#define EV_READ_REMOTE_VERSION_INFORMATION_COMPLETE 0x0C
#define EV_QOS_SETUP_COMPLETE 0x0D
#define EV_COMMAND_COMPLETE 0x0E
#define EV_COMMAND_STATUS 0x0F
#define EV_LOOPBACK_COMMAND 0x19 #define EV_LOOPBACK_COMMAND 0x19
#define EV_PAGE_SCAN_REP_MODE 0x20 #define EV_PAGE_SCAN_REP_MODE 0x20
#define EV_READ_REMOTE_EXTENDED_FEATURES_COMPLETE 0x23
#define EV_IO_CAPABILITY_REQUEST 0x31
#define EV_IO_CAPABILITY_RESPONSE 0x32
#define EV_USER_CONFIRMATION_REQUEST 0x33
#define EV_SIMPLE_PAIRING_COMPLETE 0x36
/* Bluetooth states for the different Bluetooth drivers */ /* Bluetooth states for the different Bluetooth drivers */
#define L2CAP_WAIT 0 #define L2CAP_WAIT 0
@ -183,6 +192,17 @@
#define HID_CTRL_PSM 0x11 // HID_Control PSM Value #define HID_CTRL_PSM 0x11 // HID_Control PSM Value
#define HID_INTR_PSM 0x13 // HID_Interrupt PSM Value #define HID_INTR_PSM 0x13 // HID_Interrupt PSM Value
/* Used for SDP */
#define SDP_SERVICE_SEARCH_REQUEST 0x02
#define SDP_SERVICE_SEARCH_RESPONSE 0x03
#define SDP_SERVICE_ATTRIBUTE_REQUEST 0x04
#define SDP_SERVICE_ATTRIBUTE_RESPONSE 0x05
#define SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST 0x06 // See the RFCOMM specs
#define SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE 0x07 // See the RFCOMM specs
#define PNP_INFORMATION_UUID 0x1200
#define SERIALPORT_UUID 0x1101 // See http://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm
#define L2CAP_UUID 0x0100
// Used to determine if it is a Bluetooth dongle // Used to determine if it is a Bluetooth dongle
#define WI_SUBCLASS_RF 0x01 // RF Controller #define WI_SUBCLASS_RF 0x01 // RF Controller
#define WI_PROTOCOL_BT 0x01 // Bluetooth Programming Interface #define WI_PROTOCOL_BT 0x01 // Bluetooth Programming Interface
@ -320,11 +340,17 @@ public:
void hci_read_bdaddr(); void hci_read_bdaddr();
/** Read the HCI Version of the Bluetooth dongle. */ /** Read the HCI Version of the Bluetooth dongle. */
void hci_read_local_version_information(); void hci_read_local_version_information();
/** Used to check if the dongle supports simple paring */
void hci_read_local_extended_features(uint8_t page_number);
/** /**
* Set the local name of the Bluetooth dongle. * Set the local name of the Bluetooth dongle.
* @param name Desired name. * @param name Desired name.
*/ */
void hci_set_local_name(const char* name); void hci_write_local_name(const char* name);
/** Used to enable simply paring if the dongle supports it */
void hci_write_simple_pairing_mode(bool enable);
/** Used to enable events related to simple paring */
void hci_set_event_mask();
/** Enable visibility to other Bluetooth devices. */ /** Enable visibility to other Bluetooth devices. */
void hci_write_scan_enable(); void hci_write_scan_enable();
/** Disable visibility to other Bluetooth devices. */ /** Disable visibility to other Bluetooth devices. */
@ -351,6 +377,8 @@ public:
* if the Host does not have a stored Link Key for the connection. * if the Host does not have a stored Link Key for the connection.
*/ */
void hci_link_key_request_negative_reply(); void hci_link_key_request_negative_reply();
/** Used to during simple paring to confirm that the we want to connect */
void hci_user_confirmation_request_reply();
/** Used to try to authenticate with the remote device. */ /** Used to try to authenticate with the remote device. */
void hci_authentication_request(); void hci_authentication_request();
/** Start a HCI inquiry. */ /** Start a HCI inquiry. */
@ -359,6 +387,8 @@ public:
void hci_inquiry_cancel(); void hci_inquiry_cancel();
/** Connect to last device communicated with. */ /** Connect to last device communicated with. */
void hci_connect(); void hci_connect();
/** Used during simple paring to reply to a IO capability request */
void hci_io_capability_request_reply();
/** /**
* Connect to device. * Connect to device.
* @param bdaddr Bluetooth address of the device. * @param bdaddr Bluetooth address of the device.
@ -500,6 +530,9 @@ public:
return pollInterval; return pollInterval;
}; };
/** Used by the drivers to enable simple pairing */
bool useSimplePairing;
protected: protected:
/** Pointer to USB class instance. */ /** Pointer to USB class instance. */
USB *pUsb; USB *pUsb;
@ -537,11 +570,12 @@ private:
uint16_t PID, VID; // PID and VID of device connected uint16_t PID, VID; // PID and VID of device connected
uint8_t pollInterval; uint8_t pollInterval;
bool simple_pairing_supported;
bool bPollEnable; bool bPollEnable;
bool pairWiiUsingSync; // True if pairing was done using the Wii SYNC button. bool pairWiiUsingSync; // True if pairing was done using the Wii SYNC button.
bool checkRemoteName; // Used to check remote device's name before connecting. bool checkRemoteName; // Used to check remote device's name before connecting.
bool incomingPS4; // True if a PS4 controller is connecting bool incomingPSController; // True if a PS4/PS5 controller is connecting
uint8_t classOfDevice[3]; // Class of device of last device uint8_t classOfDevice[3]; // Class of device of last device
/* Variables used by high level HCI task */ /* Variables used by high level HCI task */

273
BTHID.cpp
View file

@ -30,6 +30,8 @@ protocolMode(USB_HID_BOOT_PROTOCOL) {
pBtd->btdPin = pin; pBtd->btdPin = pin;
/* Set device cid for the control and intterrupt channelse - LSB */ /* Set device cid for the control and intterrupt channelse - LSB */
sdp_dcid[0] = 0x50; // 0x0050
sdp_dcid[1] = 0x00;
control_dcid[0] = 0x70; // 0x0070 control_dcid[0] = 0x70; // 0x0070
control_dcid[1] = 0x00; control_dcid[1] = 0x00;
interrupt_dcid[0] = 0x71; // 0x0071 interrupt_dcid[0] = 0x71; // 0x0071
@ -41,19 +43,34 @@ protocolMode(USB_HID_BOOT_PROTOCOL) {
void BTHID::Reset() { void BTHID::Reset() {
connected = false; connected = false;
activeConnection = false; activeConnection = false;
SDPConnected = false;
l2cap_event_flag = 0; // Reset flags l2cap_event_flag = 0; // Reset flags
l2cap_sdp_state = L2CAP_SDP_WAIT;
l2cap_state = L2CAP_WAIT; l2cap_state = L2CAP_WAIT;
ResetBTHID(); ResetBTHID();
} }
void BTHID::disconnect() { // Use this void to disconnect the device void BTHID::disconnect() { // Use this void to disconnect the device
if(SDPConnected)
pBtd->l2cap_disconnection_request(hci_handle, ++identifier, sdp_scid, sdp_dcid);
// First the HID interrupt channel has to be disconnected, then the HID control channel and finally the HCI connection // First the HID interrupt channel has to be disconnected, then the HID control channel and finally the HCI connection
pBtd->l2cap_disconnection_request(hci_handle, ++identifier, interrupt_scid, interrupt_dcid); pBtd->l2cap_disconnection_request(hci_handle, ++identifier, interrupt_scid, interrupt_dcid);
Reset(); Reset();
l2cap_sdp_state = L2CAP_DISCONNECT_RESPONSE;
l2cap_state = L2CAP_INTERRUPT_DISCONNECT; l2cap_state = L2CAP_INTERRUPT_DISCONNECT;
} }
void BTHID::ACLData(uint8_t* l2capinbuf) { void BTHID::ACLData(uint8_t* l2capinbuf) {
if(!connected) {
if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_REQUEST) {
if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == SDP_PSM && !pBtd->sdpConnectionClaimed) {
pBtd->sdpConnectionClaimed = true;
hci_handle = pBtd->hci_handle; // Store the HCI Handle for the connection
l2cap_sdp_state = L2CAP_SDP_WAIT; // Reset state
}
}
}
if(!pBtd->l2capConnectionClaimed && pBtd->incomingHIDDevice && !connected && !activeConnection) { if(!pBtd->l2capConnectionClaimed && pBtd->incomingHIDDevice && !connected && !activeConnection) {
if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_REQUEST) { if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_REQUEST) {
if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == HID_CTRL_PSM) { if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == HID_CTRL_PSM) {
@ -85,14 +102,30 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
#endif #endif
} else if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_RESPONSE) { } else if(l2capinbuf[8] == L2CAP_CMD_CONNECTION_RESPONSE) {
if(((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) && ((l2capinbuf[18] | (l2capinbuf[19] << 8)) == SUCCESSFUL)) { // Success if(((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) && ((l2capinbuf[18] | (l2capinbuf[19] << 8)) == SUCCESSFUL)) { // Success
if(l2capinbuf[14] == control_dcid[0] && l2capinbuf[15] == control_dcid[1]) { if(l2capinbuf[14] == sdp_dcid[0] && l2capinbuf[15] == sdp_dcid[1]) {
//Notify(PSTR("\r\nHID Control Connection Complete"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nSDP Connection Complete"), 0x80);
#endif
identifier = l2capinbuf[9];
sdp_scid[0] = l2capinbuf[12];
sdp_scid[1] = l2capinbuf[13];
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nSend SDP Config Request"), 0x80);
#endif
identifier++;
pBtd->l2cap_config_request(hci_handle, identifier, sdp_scid);
} else if(l2capinbuf[14] == control_dcid[0] && l2capinbuf[15] == control_dcid[1]) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nHID Control Connection Complete"), 0x80);
#endif
identifier = l2capinbuf[9]; identifier = l2capinbuf[9];
control_scid[0] = l2capinbuf[12]; control_scid[0] = l2capinbuf[12];
control_scid[1] = l2capinbuf[13]; control_scid[1] = l2capinbuf[13];
l2cap_set_flag(L2CAP_FLAG_CONTROL_CONNECTED); l2cap_set_flag(L2CAP_FLAG_CONTROL_CONNECTED);
} else if(l2capinbuf[14] == interrupt_dcid[0] && l2capinbuf[15] == interrupt_dcid[1]) { } else if(l2capinbuf[14] == interrupt_dcid[0] && l2capinbuf[15] == interrupt_dcid[1]) {
//Notify(PSTR("\r\nHID Interrupt Connection Complete"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nHID Interrupt Connection Complete"), 0x80);
#endif
identifier = l2capinbuf[9]; identifier = l2capinbuf[9];
interrupt_scid[0] = l2capinbuf[12]; interrupt_scid[0] = l2capinbuf[12];
interrupt_scid[1] = l2capinbuf[13]; interrupt_scid[1] = l2capinbuf[13];
@ -112,7 +145,12 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
Notify(PSTR(" Identifier: "), 0x80); Notify(PSTR(" Identifier: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[9], 0x80); D_PrintHex<uint8_t > (l2capinbuf[9], 0x80);
#endif #endif
if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == HID_CTRL_PSM) { if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == SDP_PSM) {
identifier = l2capinbuf[9];
sdp_scid[0] = l2capinbuf[14];
sdp_scid[1] = l2capinbuf[15];
l2cap_set_flag(L2CAP_FLAG_CONNECTION_SDP_REQUEST);
} else if((l2capinbuf[12] | (l2capinbuf[13] << 8)) == HID_CTRL_PSM) {
identifier = l2capinbuf[9]; identifier = l2capinbuf[9];
control_scid[0] = l2capinbuf[14]; control_scid[0] = l2capinbuf[14];
control_scid[1] = l2capinbuf[15]; control_scid[1] = l2capinbuf[15];
@ -125,26 +163,51 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
} }
} else if(l2capinbuf[8] == L2CAP_CMD_CONFIG_RESPONSE) { } else if(l2capinbuf[8] == L2CAP_CMD_CONFIG_RESPONSE) {
if((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) { // Success if((l2capinbuf[16] | (l2capinbuf[17] << 8)) == 0x0000) { // Success
if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { if(l2capinbuf[12] == sdp_dcid[0] && l2capinbuf[13] == sdp_dcid[1]) {
//Notify(PSTR("\r\nHID Control Configuration Complete"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nSDP Configuration Complete"), 0x80);
#endif
identifier = l2capinbuf[9];
l2cap_set_flag(L2CAP_FLAG_CONFIG_SDP_SUCCESS);
} else if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nHID Control Configuration Complete"), 0x80);
#endif
identifier = l2capinbuf[9]; identifier = l2capinbuf[9];
l2cap_set_flag(L2CAP_FLAG_CONFIG_CONTROL_SUCCESS); l2cap_set_flag(L2CAP_FLAG_CONFIG_CONTROL_SUCCESS);
} else if(l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) { } else if(l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) {
//Notify(PSTR("\r\nHID Interrupt Configuration Complete"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nHID Interrupt Configuration Complete"), 0x80);
#endif
identifier = l2capinbuf[9]; identifier = l2capinbuf[9];
l2cap_set_flag(L2CAP_FLAG_CONFIG_INTERRUPT_SUCCESS); l2cap_set_flag(L2CAP_FLAG_CONFIG_INTERRUPT_SUCCESS);
} }
} }
} else if(l2capinbuf[8] == L2CAP_CMD_CONFIG_REQUEST) { } else if(l2capinbuf[8] == L2CAP_CMD_CONFIG_REQUEST) {
if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { if(l2capinbuf[12] == sdp_dcid[0] && l2capinbuf[13] == sdp_dcid[1]) {
//Notify(PSTR("\r\nHID Control Configuration Request"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nSDP Configuration Request"), 0x80);
#endif
pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], sdp_scid);
} else if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nHID Control Configuration Request"), 0x80);
#endif
pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], control_scid); pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], control_scid);
} else if(l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) { } else if(l2capinbuf[12] == interrupt_dcid[0] && l2capinbuf[13] == interrupt_dcid[1]) {
//Notify(PSTR("\r\nHID Interrupt Configuration Request"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nHID Interrupt Configuration Request"), 0x80);
#endif
pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], interrupt_scid); pBtd->l2cap_config_response(hci_handle, l2capinbuf[9], interrupt_scid);
} }
} else if(l2capinbuf[8] == L2CAP_CMD_DISCONNECT_REQUEST) { } else if(l2capinbuf[8] == L2CAP_CMD_DISCONNECT_REQUEST) {
if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) { if(l2capinbuf[12] == sdp_dcid[0] && l2capinbuf[13] == sdp_dcid[1]) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nDisconnect Request: SDP Channel"), 0x80);
#endif
identifier = l2capinbuf[9];
l2cap_set_flag(L2CAP_FLAG_DISCONNECT_SDP_REQUEST);
} else if(l2capinbuf[12] == control_dcid[0] && l2capinbuf[13] == control_dcid[1]) {
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nDisconnect Request: Control Channel"), 0x80); Notify(PSTR("\r\nDisconnect Request: Control Channel"), 0x80);
#endif #endif
@ -160,15 +223,31 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
Reset(); Reset();
} }
} else if(l2capinbuf[8] == L2CAP_CMD_DISCONNECT_RESPONSE) { } else if(l2capinbuf[8] == L2CAP_CMD_DISCONNECT_RESPONSE) {
if(l2capinbuf[12] == control_scid[0] && l2capinbuf[13] == control_scid[1]) { if(l2capinbuf[12] == sdp_scid[0] && l2capinbuf[13] == sdp_scid[1]) {
//Notify(PSTR("\r\nDisconnect Response: Control Channel"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nDisconnect Response: SDP Channel"), 0x80);
#endif
identifier = l2capinbuf[9];
l2cap_set_flag(L2CAP_FLAG_DISCONNECT_RESPONSE);
} else if(l2capinbuf[12] == control_scid[0] && l2capinbuf[13] == control_scid[1]) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nDisconnect Response: Control Channel"), 0x80);
#endif
identifier = l2capinbuf[9]; identifier = l2capinbuf[9];
l2cap_set_flag(L2CAP_FLAG_DISCONNECT_CONTROL_RESPONSE); l2cap_set_flag(L2CAP_FLAG_DISCONNECT_CONTROL_RESPONSE);
} else if(l2capinbuf[12] == interrupt_scid[0] && l2capinbuf[13] == interrupt_scid[1]) { } else if(l2capinbuf[12] == interrupt_scid[0] && l2capinbuf[13] == interrupt_scid[1]) {
//Notify(PSTR("\r\nDisconnect Response: Interrupt Channel"), 0x80); #ifdef EXTRADEBUG
Notify(PSTR("\r\nDisconnect Response: Interrupt Channel"), 0x80);
#endif
identifier = l2capinbuf[9]; identifier = l2capinbuf[9];
l2cap_set_flag(L2CAP_FLAG_DISCONNECT_INTERRUPT_RESPONSE); l2cap_set_flag(L2CAP_FLAG_DISCONNECT_INTERRUPT_RESPONSE);
} }
} else if(l2capinbuf[8] == L2CAP_CMD_INFORMATION_REQUEST) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nInformation request"), 0x80);
#endif
identifier = l2capinbuf[9];
pBtd->l2cap_information_response(hci_handle, identifier, l2capinbuf[12], l2capinbuf[13]);
} }
#ifdef EXTRADEBUG #ifdef EXTRADEBUG
else { else {
@ -176,6 +255,78 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
Notify(PSTR("\r\nL2CAP Unknown Signaling Command: "), 0x80); Notify(PSTR("\r\nL2CAP Unknown Signaling Command: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[8], 0x80); D_PrintHex<uint8_t > (l2capinbuf[8], 0x80);
} }
#endif
} else if(l2capinbuf[6] == sdp_dcid[0] && l2capinbuf[7] == sdp_dcid[1]) { // SDP
if(l2capinbuf[8] == SDP_SERVICE_SEARCH_REQUEST) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nSDP_SERVICE_SEARCH_REQUEST"), 0x80);
#endif
// Send response
l2capoutbuf[0] = SDP_SERVICE_SEARCH_RESPONSE;
l2capoutbuf[1] = l2capinbuf[9];//transactionIDHigh;
l2capoutbuf[2] = l2capinbuf[10];//transactionIDLow;
l2capoutbuf[3] = 0x00; // MSB Parameter Length
l2capoutbuf[4] = 0x05; // LSB Parameter Length = 5
l2capoutbuf[5] = 0x00; // MSB TotalServiceRecordCount
l2capoutbuf[6] = 0x00; // LSB TotalServiceRecordCount = 0
l2capoutbuf[7] = 0x00; // MSB CurrentServiceRecordCount
l2capoutbuf[8] = 0x00; // LSB CurrentServiceRecordCount = 0
l2capoutbuf[9] = 0x00; // No continuation state
SDP_Command(l2capoutbuf, 10);
} else if(l2capinbuf[8] == SDP_SERVICE_ATTRIBUTE_REQUEST) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nSDP_SERVICE_ATTRIBUTE_REQUEST"), 0x80);
#endif
// Send response
l2capoutbuf[0] = SDP_SERVICE_ATTRIBUTE_RESPONSE;
l2capoutbuf[1] = l2capinbuf[9];//transactionIDHigh;
l2capoutbuf[2] = l2capinbuf[10];//transactionIDLow;
l2capoutbuf[3] = 0x00; // MSB Parameter Length
l2capoutbuf[4] = 0x05; // LSB Parameter Length = 5
l2capoutbuf[5] = 0x00; // MSB AttributeListByteCount
l2capoutbuf[6] = 0x02; // LSB AttributeListByteCount = 2
// TODO: What to send?
l2capoutbuf[7] = 0x35; // Data element sequence - length in next byte
l2capoutbuf[8] = 0x00; // Length = 0
l2capoutbuf[9] = 0x00; // No continuation state
SDP_Command(l2capoutbuf, 10);
} else if(l2capinbuf[8] == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nSDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST"), 0x80);
Notify(PSTR("\r\nUUID: "), 0x80);
uint16_t uuid;
if((l2capinbuf[16] << 8 | l2capinbuf[17]) == 0x0000) // Check if it's sending the UUID as a 128-bit UUID
uuid = (l2capinbuf[18] << 8 | l2capinbuf[19]);
else // Short UUID
uuid = (l2capinbuf[16] << 8 | l2capinbuf[17]);
D_PrintHex<uint16_t > (uuid, 0x80);
Notify(PSTR("\r\nLength: "), 0x80);
uint16_t length = l2capinbuf[11] << 8 | l2capinbuf[12];
D_PrintHex<uint16_t > (length, 0x80);
Notify(PSTR("\r\nData: "), 0x80);
for(uint8_t i = 0; i < length; i++) {
D_PrintHex<uint8_t > (l2capinbuf[13 + i], 0x80);
Notify(PSTR(" "), 0x80);
}
#endif
serviceNotSupported(l2capinbuf[9], l2capinbuf[10]); // The service is not supported
}
#ifdef EXTRADEBUG
else {
Notify(PSTR("\r\nUnknown PDU: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[8], 0x80);
}
#endif #endif
} else if(l2capinbuf[6] == interrupt_dcid[0] && l2capinbuf[7] == interrupt_dcid[1]) { // l2cap_interrupt } else if(l2capinbuf[6] == interrupt_dcid[0] && l2capinbuf[7] == interrupt_dcid[1]) { // l2cap_interrupt
#ifdef PRINTREPORT #ifdef PRINTREPORT
@ -185,11 +336,13 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
Notify(PSTR(" "), 0x80); Notify(PSTR(" "), 0x80);
} }
#endif #endif
if(l2capinbuf[8] == 0xA1) { // HID_THDR_DATA_INPUT if(l2capinbuf[8] == 0xA1) { // HID BT DATA (0xA0) | Report Type (Input 0x01)
uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]); lastBtDataInputIntMillis = (uint32_t)millis(); // Store the timestamp of the report
ParseBTHIDData((uint8_t)(length - 1), &l2capinbuf[9]);
switch(l2capinbuf[9]) { uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]);
ParseBTHIDData((uint8_t)(length - 1), &l2capinbuf[9]); // First byte will be the report ID
switch(l2capinbuf[9]) { // Report ID
case 0x01: // Keyboard or Joystick events case 0x01: // Keyboard or Joystick events
if(pRptParser[KEYBOARD_PARSER_ID]) if(pRptParser[KEYBOARD_PARSER_ID])
pRptParser[KEYBOARD_PARSER_ID]->Parse(reinterpret_cast<USBHID *>(this), 0, (uint8_t)(length - 2), &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance pRptParser[KEYBOARD_PARSER_ID]->Parse(reinterpret_cast<USBHID *>(this), 0, (uint8_t)(length - 2), &l2capinbuf[10]); // Use reinterpret_cast again to extract the instance
@ -206,6 +359,11 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
break; break;
#endif #endif
} }
} else {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nUnhandled L2CAP interrupt report: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[8], 0x80);
#endif
} }
} else if(l2capinbuf[6] == control_dcid[0] && l2capinbuf[7] == control_dcid[1]) { // l2cap_control } else if(l2capinbuf[6] == control_dcid[0] && l2capinbuf[7] == control_dcid[1]) { // l2cap_control
#ifdef PRINTREPORT #ifdef PRINTREPORT
@ -215,6 +373,15 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
Notify(PSTR(" "), 0x80); Notify(PSTR(" "), 0x80);
} }
#endif #endif
if(l2capinbuf[8] == 0xA3) { // HID BT DATA (0xA0) | Report Type (Feature 0x03)
uint16_t length = ((uint16_t)l2capinbuf[5] << 8 | l2capinbuf[4]);
ParseBTHIDControlData((uint8_t)(length - 1), &l2capinbuf[9]); // First byte will be the report ID
} else {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nUnhandled L2CAP control report: "), 0x80);
D_PrintHex<uint8_t > (l2capinbuf[8], 0x80);
#endif
}
} }
#ifdef EXTRADEBUG #ifdef EXTRADEBUG
else { else {
@ -231,10 +398,59 @@ void BTHID::ACLData(uint8_t* l2capinbuf) {
} }
} }
#endif #endif
SDP_task();
L2CAP_task(); L2CAP_task();
} }
} }
void BTHID::SDP_task() {
switch(l2cap_sdp_state) {
case L2CAP_SDP_WAIT:
if(l2cap_check_flag(L2CAP_FLAG_CONNECTION_SDP_REQUEST)) {
l2cap_clear_flag(L2CAP_FLAG_CONNECTION_SDP_REQUEST); // Clear flag
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nSDP Incoming Connection Request"), 0x80);
#endif
pBtd->l2cap_connection_response(hci_handle, identifier, sdp_dcid, sdp_scid, PENDING);
delay(1);
pBtd->l2cap_connection_response(hci_handle, identifier, sdp_dcid, sdp_scid, SUCCESSFUL);
identifier++;
delay(1);
pBtd->l2cap_config_request(hci_handle, identifier, sdp_scid);
l2cap_sdp_state = L2CAP_SDP_SUCCESS;
} else if(l2cap_check_flag(L2CAP_FLAG_DISCONNECT_SDP_REQUEST)) {
l2cap_clear_flag(L2CAP_FLAG_DISCONNECT_SDP_REQUEST); // Clear flag
SDPConnected = false;
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nDisconnected SDP Channel"), 0x80);
#endif
pBtd->l2cap_disconnection_response(hci_handle, identifier, sdp_dcid, sdp_scid);
}
break;
case L2CAP_SDP_SUCCESS:
if(l2cap_check_flag(L2CAP_FLAG_CONFIG_SDP_SUCCESS)) {
l2cap_clear_flag(L2CAP_FLAG_CONFIG_SDP_SUCCESS); // Clear flag
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nSDP Successfully Configured"), 0x80);
#endif
SDPConnected = true;
l2cap_sdp_state = L2CAP_SDP_WAIT;
}
break;
case L2CAP_DISCONNECT_RESPONSE: // This is for both disconnection response from the RFCOMM and SDP channel if they were connected
if(l2cap_check_flag(L2CAP_FLAG_DISCONNECT_RESPONSE)) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nDisconnected L2CAP Connection"), 0x80);
#endif
pBtd->hci_disconnect(hci_handle);
hci_handle = -1; // Reset handle
Reset();
}
break;
}
}
void BTHID::L2CAP_task() { void BTHID::L2CAP_task() {
switch(l2cap_state) { switch(l2cap_state) {
/* These states are used if the HID device is the host */ /* These states are used if the HID device is the host */
@ -371,6 +587,27 @@ void BTHID::Run() {
} }
} }
void BTHID::SDP_Command(uint8_t* data, uint8_t nbytes) { // See page 223 in the Bluetooth specs
pBtd->L2CAP_Command(hci_handle, data, nbytes, sdp_scid[0], sdp_scid[1]);
}
void BTHID::serviceNotSupported(uint8_t transactionIDHigh, uint8_t transactionIDLow) { // See page 235 in the Bluetooth specs
l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE;
l2capoutbuf[1] = transactionIDHigh;
l2capoutbuf[2] = transactionIDLow;
l2capoutbuf[3] = 0x00; // MSB Parameter Length
l2capoutbuf[4] = 0x05; // LSB Parameter Length = 5
l2capoutbuf[5] = 0x00; // MSB AttributeListsByteCount
l2capoutbuf[6] = 0x02; // LSB AttributeListsByteCount = 2
/* Attribute ID/Value Sequence: */
l2capoutbuf[7] = 0x35; // Data element sequence - length in next byte
l2capoutbuf[8] = 0x00; // Length = 0
l2capoutbuf[9] = 0x00; // No continuation state
SDP_Command(l2capoutbuf, 10);
}
/************************************************************/ /************************************************************/
/* HID Commands */ /* HID Commands */

28
BTHID.h
View file

@ -93,6 +93,15 @@ public:
pBtd->pairWithHID(); pBtd->pairWithHID();
}; };
/**
* Used to get the millis() of the last Bluetooth DATA input report received on the interrupt channel.
* This can be used detect if the connection to a Bluetooth device is lost fx if the battery runs out or if it gets out of range.
* @return Timestamp in milliseconds of the last Bluetooth DATA input report received on the interrupt channel.
*/
uint32_t getLastMessageTime() {
return lastBtDataInputIntMillis;
};
protected: protected:
/** @name BluetoothService implementation */ /** @name BluetoothService implementation */
/** /**
@ -125,6 +134,13 @@ protected:
virtual void ParseBTHIDData(uint8_t len __attribute__((unused)), uint8_t *buf __attribute__((unused))) { virtual void ParseBTHIDData(uint8_t len __attribute__((unused)), uint8_t *buf __attribute__((unused))) {
return; return;
}; };
/**
* Same as ParseBTHIDData for reports that are sent through the
* interrupt pipe (in response to a GET_REPORT).
*/
virtual void ParseBTHIDControlData(uint8_t len __attribute__((unused)), uint8_t *buf __attribute__((unused))) {
return;
}
/** Called when a device is connected */ /** Called when a device is connected */
virtual void OnInitBTHID() { virtual void OnInitBTHID() {
return; return;
@ -141,20 +157,32 @@ protected:
/** L2CAP source CID for HID_Interrupt */ /** L2CAP source CID for HID_Interrupt */
uint8_t interrupt_scid[2]; uint8_t interrupt_scid[2];
uint8_t l2cap_sdp_state;
uint8_t sdp_scid[2]; // L2CAP source CID for SDP
private: private:
HIDReportParser *pRptParser[NUM_PARSERS]; // Pointer to HIDReportParsers. HIDReportParser *pRptParser[NUM_PARSERS]; // Pointer to HIDReportParsers.
uint8_t l2capoutbuf[BULK_MAXPKTSIZE]; // General purpose buffer for l2cap out data
void SDP_Command(uint8_t* data, uint8_t nbytes);
void serviceNotSupported(uint8_t transactionIDHigh, uint8_t transactionIDLow);
/** Set report protocol. */ /** Set report protocol. */
void setProtocol(); void setProtocol();
uint8_t protocolMode; uint8_t protocolMode;
void SDP_task();
void L2CAP_task(); // L2CAP state machine void L2CAP_task(); // L2CAP state machine
bool activeConnection; // Used to indicate if it already has established a connection bool activeConnection; // Used to indicate if it already has established a connection
bool SDPConnected;
/* Variables used for L2CAP communication */ /* Variables used for L2CAP communication */
uint8_t control_dcid[2]; // L2CAP device CID for HID_Control - Always 0x0070 uint8_t control_dcid[2]; // L2CAP device CID for HID_Control - Always 0x0070
uint8_t interrupt_dcid[2]; // L2CAP device CID for HID_Interrupt - Always 0x0071 uint8_t interrupt_dcid[2]; // L2CAP device CID for HID_Interrupt - Always 0x0071
uint8_t sdp_dcid[2];
uint8_t l2cap_state; uint8_t l2cap_state;
uint32_t lastBtDataInputIntMillis; // Variable used to store the millis value of the last Bluetooth DATA input report received on the interrupt channel
}; };
#endif #endif

111
MiniDSP.cpp Normal file
View file

@ -0,0 +1,111 @@
/* Copyright (C) 2021 Kristian Sloth Lauszus and Dennis Frett. 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
Dennis Frett
GitHub : https://github.com/dennisfrett
e-mail : dennis.frett@live.com
*/
#include "MiniDSP.h"
void MiniDSP::ParseHIDData(USBHID *hid __attribute__ ((unused)), bool is_rpt_id __attribute__ ((unused)), uint8_t len, uint8_t *buf) {
constexpr uint8_t StatusInputCommand[] = {0x05, 0xFF, 0xDA};
// Only care about valid data for the MiniDSP 2x4HD.
if(HIDUniversal::VID != MINIDSP_VID || HIDUniversal::PID != MINIDSP_PID || len <= 4 || buf == nullptr)
return;
// Check if this is a status update.
// First byte is the length, we ignore that for now.
if(memcmp(buf + 1, StatusInputCommand, sizeof (StatusInputCommand)) == 0) {
// Parse data.
// Response is of format [ length ] [ 0x05 0xFF 0xDA ] [ volume ] [ muted ].
const auto newVolume = buf[sizeof (StatusInputCommand) + 1];
const auto newIsMuted = (bool)buf[sizeof (StatusInputCommand) + 2];
const auto volumeChanged = newVolume != volume;
const auto mutedChanged = newIsMuted != muted;
// Update status.
volume = newVolume;
muted = newIsMuted;
// Call callbacks.
if(pFuncOnVolumeChange != nullptr && volumeChanged)
pFuncOnVolumeChange(volume);
if(pFuncOnMutedChange != nullptr && mutedChanged)
pFuncOnMutedChange(muted);
}
};
uint8_t MiniDSP::OnInitSuccessful() {
// Verify we're actually connected to the MiniDSP 2x4HD.
if(HIDUniversal::VID != MINIDSP_VID || HIDUniversal::PID != MINIDSP_PID)
return 0;
// Request current status so we can initialize the values.
RequestStatus();
if(pFuncOnInit != nullptr)
pFuncOnInit();
return 0;
};
uint8_t MiniDSP::Checksum(const uint8_t *data, uint8_t data_length) const {
uint16_t sum = 0;
for(uint8_t i = 0; i < data_length; i++)
sum += data[i];
return sum & 0xFF;
}
void MiniDSP::SendCommand(uint8_t *command, uint8_t command_length) const {
// Sanity check on command length.
if(command_length > 63)
return;
// Message is padded to 64 bytes with 0xFF and is of format:
// [ length (command + checksum byte) ] [ command ] [ checksum ] [ OxFF... ]
// MiniDSP expects 64 byte messages.
uint8_t buf[64];
// Set length, including checksum byte.
buf[0] = command_length + 1;
// Copy actual command.
memcpy(&buf[1], command, command_length);
const auto checksumOffset = command_length + 1;
// Set checksum byte.
buf[checksumOffset] = Checksum(buf, command_length + 1);
// Pad the rest.
memset(&buf[checksumOffset + 1], 0xFF, sizeof (buf) - checksumOffset - 1);
pUsb->outTransfer(bAddress, epInfo[epInterruptOutIndex].epAddr, sizeof (buf), buf);
}
void MiniDSP::RequestStatus() const {
uint8_t RequestStatusOutputCommand[] = {0x05, 0xFF, 0xDA, 0x02};
SendCommand(RequestStatusOutputCommand, sizeof (RequestStatusOutputCommand));
}

191
MiniDSP.h Normal file
View file

@ -0,0 +1,191 @@
/* Copyright (C) 2021 Kristian Sloth Lauszus and Dennis Frett. 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
Dennis Frett
GitHub : https://github.com/dennisfrett
e-mail : dennis.frett@live.com
*/
#pragma once
#include "hiduniversal.h"
#define MINIDSP_VID 0x2752 // MiniDSP
#define MINIDSP_PID 0x0011 // MiniDSP 2x4HD
/**
* Arduino MiniDSP 2x4HD USB Host Driver by Dennis Frett.
*
* This class implements support for the MiniDSP 2x4HD via USB.
* Based on NodeJS implementation by Mathieu Rene:
* https://github.com/mrene/node-minidsp and the Python implementation by Mark
* Kubiak: https://github.com/markubiak/python3-minidsp.
*
* It uses the HIDUniversal class for all the USB communication.
*/
class MiniDSP : public HIDUniversal {
public:
/**
* Constructor for the MiniDSP class.
* @param p Pointer to the USB class instance.
*/
MiniDSP(USB *p) : HIDUniversal(p) {
};
/**
* Used to check if a MiniDSP 2x4HD is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return HIDUniversal::isReady() && HIDUniversal::VID == MINIDSP_VID && HIDUniversal::PID == MINIDSP_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;
};
/**
* Used to call your own function when the volume has changed.
* The volume is passed as an unsigned integer that represents twice the
* -dB value. Example: 19 represents -9.5dB.
* @param funcOnVolumeChange Function to call.
*/
void attachOnVolumeChange(void (*funcOnVolumeChange)(uint8_t)) {
pFuncOnVolumeChange = funcOnVolumeChange;
}
/**
* Used to call your own function when the muted status has changed.
* The muted status is passed as a boolean. True means muted, false
* means unmuted.
* @param funcOnMutedChange Function to call.
*/
void attachOnMutedChange(void (*funcOnMutedChange)(bool)) {
pFuncOnMutedChange = funcOnMutedChange;
}
/**
* Retrieve the current volume of the MiniDSP.
* The volume is passed as an unsigned integer that represents twice the
* -dB value. Example: 19 represents -9.5dB.
* @return Current volume.
*/
int getVolume() const {
return volume;
}
/**
* Retrieve the current volume of the MiniDSP in -dB.
* @return Current volume.
*/
float getVolumeDB() const {
return volume / -2.0;
}
/**
* Retrieve the current muted status of the MiniDSP
* @return `true` if the device is muted, `false` otherwise.
*/
bool isMuted() const {
return muted;
}
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.
*/
void ParseHIDData(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *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.
*/
uint8_t OnInitSuccessful();
/**@}*/
/** @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 == MINIDSP_VID && pid == MINIDSP_PID;
};
/**@}*/
private:
/**
* Calculate checksum for given buffer.
* Checksum is given by summing up all bytes in `data` and returning the first byte.
* @param data Buffer to calculate checksum for.
* @param data_length Length of the buffer.
*/
uint8_t Checksum(const uint8_t *data, uint8_t data_length) const;
/**
* Send the "Request status" command to the MiniDSP. The response
* includes the current volume and the muted status.
*/
void
RequestStatus() const;
/**
* Send the given MiniDSP command. This function will create a buffer
* with the expected header and checksum and send it to the MiniDSP.
* Responses will come in throug `ParseHIDData`.
* @param command Buffer of the command to send.
* @param command_length Length of the buffer.
*/
void SendCommand(uint8_t *command, uint8_t command_length) const;
// Callbacks
// Pointer to function called in onInit().
void (*pFuncOnInit)(void) = nullptr;
// Pointer to function called when volume changes.
void (*pFuncOnVolumeChange)(uint8_t) = nullptr;
// Pointer to function called when muted status changes.
void (*pFuncOnMutedChange)(bool) = nullptr;
// -----------------------------------------------------------------------------
// MiniDSP state. Currently only volume and muted status are
// implemented, but others can be added easily if needed.
// The volume is stored as an unsigned integer that represents twice the
// -dB value. Example: 19 represents -9.5dB.
uint8_t volume = 0;
bool muted = false;
};

View file

@ -47,18 +47,21 @@ BluetoothService(p) // Pointer to USB class instance - mandatory
} }
bool PS3BT::getButtonPress(ButtonEnum b) { bool PS3BT::getButtonPress(ButtonEnum b) {
return (ButtonState & pgm_read_dword(&PS3_BUTTONS[(uint8_t)b])); const int8_t index = getButtonIndexPS3(b); if (index < 0) return 0;
return (ButtonState & pgm_read_dword(&PS3_BUTTONS[index]));
} }
bool PS3BT::getButtonClick(ButtonEnum b) { bool PS3BT::getButtonClick(ButtonEnum b) {
uint32_t button = pgm_read_dword(&PS3_BUTTONS[(uint8_t)b]); const int8_t index = getButtonIndexPS3(b); if (index < 0) return 0;
uint32_t button = pgm_read_dword(&PS3_BUTTONS[index]);
bool click = (ButtonClickState & button); bool click = (ButtonClickState & button);
ButtonClickState &= ~button; // Clear "click" event ButtonClickState &= ~button; // Clear "click" event
return click; return click;
} }
uint8_t PS3BT::getAnalogButton(ButtonEnum a) { uint8_t PS3BT::getAnalogButton(ButtonEnum a) {
return (uint8_t)(l2capinbuf[pgm_read_byte(&PS3_ANALOG_BUTTONS[(uint8_t)a])]); const int8_t index = getButtonIndexPS3(a); if (index < 0) return 0;
return (uint8_t)(l2capinbuf[pgm_read_byte(&PS3_ANALOG_BUTTONS[index])]);
} }
uint8_t PS3BT::getAnalogHat(AnalogHatEnum a) { uint8_t PS3BT::getAnalogHat(AnalogHatEnum a) {

View file

@ -138,4 +138,10 @@ enum StatusEnum {
Bluetooth = (40 << 8) | 0x16, // Operating by Bluetooth and rumble is turned off Bluetooth = (40 << 8) | 0x16, // Operating by Bluetooth and rumble is turned off
}; };
inline int8_t getButtonIndexPS3(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(PS3_BUTTONS) / sizeof(PS3_BUTTONS[0]))) return -1;
return index;
}
#endif #endif

View file

@ -314,18 +314,21 @@ void PS3USB::printReport() { // Uncomment "#define PRINTREPORT" to print the rep
} }
bool PS3USB::getButtonPress(ButtonEnum b) { bool PS3USB::getButtonPress(ButtonEnum b) {
return (ButtonState & pgm_read_dword(&PS3_BUTTONS[(uint8_t)b])); const int8_t index = getButtonIndexPS3(b); if (index < 0) return 0;
return (ButtonState & pgm_read_dword(&PS3_BUTTONS[index]));
} }
bool PS3USB::getButtonClick(ButtonEnum b) { bool PS3USB::getButtonClick(ButtonEnum b) {
uint32_t button = pgm_read_dword(&PS3_BUTTONS[(uint8_t)b]); const int8_t index = getButtonIndexPS3(b); if (index < 0) return 0;
uint32_t button = pgm_read_dword(&PS3_BUTTONS[index]);
bool click = (ButtonClickState & button); bool click = (ButtonClickState & button);
ButtonClickState &= ~button; // Clear "click" event ButtonClickState &= ~button; // Clear "click" event
return click; return click;
} }
uint8_t PS3USB::getAnalogButton(ButtonEnum a) { uint8_t PS3USB::getAnalogButton(ButtonEnum a) {
return (uint8_t)(readBuf[(pgm_read_byte(&PS3_ANALOG_BUTTONS[(uint8_t)a])) - 9]); const int8_t index = getButtonIndexPS3(a); if (index < 0) return 0;
return (uint8_t)(readBuf[(pgm_read_byte(&PS3_ANALOG_BUTTONS[index])) - 9]);
} }
uint8_t PS3USB::getAnalogHat(AnalogHatEnum a) { uint8_t PS3USB::getAnalogHat(AnalogHatEnum a) {

View file

@ -65,10 +65,8 @@ protected:
virtual void OnInitBTHID() { virtual void OnInitBTHID() {
PS4Parser::Reset(); PS4Parser::Reset();
enable_sixaxis(); // Make the controller send out the entire output report enable_sixaxis(); // Make the controller send out the entire output report
if (pFuncOnInit) if (!pFuncOnInit)
pFuncOnInit(); // Call the user function setLed(Blue); // Only call this is a user function has not been set
else
setLed(Blue);
}; };
/** Used to reset the different buffers to there default values */ /** Used to reset the different buffers to there default values */

View file

@ -32,6 +32,12 @@ enum DPADEnum {
// To enable serial debugging see "settings.h" // To enable serial debugging see "settings.h"
//#define PRINTREPORT // Uncomment to print the report send by the PS4 Controller //#define PRINTREPORT // Uncomment to print the report send by the PS4 Controller
int8_t PS4Parser::getButtonIndexPS4(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(PS4_BUTTONS) / sizeof(PS4_BUTTONS[0]))) return -1;
return index;
}
bool PS4Parser::checkDpad(ButtonEnum b) { bool PS4Parser::checkDpad(ButtonEnum b) {
switch (b) { switch (b) {
case UP: case UP:
@ -48,23 +54,26 @@ bool PS4Parser::checkDpad(ButtonEnum b) {
} }
bool PS4Parser::getButtonPress(ButtonEnum b) { bool PS4Parser::getButtonPress(ButtonEnum b) {
if (b <= LEFT) // Dpad const int8_t index = getButtonIndexPS4(b); if (index < 0) return 0;
if (index <= LEFT) // Dpad
return checkDpad(b); return checkDpad(b);
else else
return ps4Data.btn.val & (1UL << pgm_read_byte(&PS4_BUTTONS[(uint8_t)b])); return ps4Data.btn.val & (1UL << pgm_read_byte(&PS4_BUTTONS[index]));
} }
bool PS4Parser::getButtonClick(ButtonEnum b) { bool PS4Parser::getButtonClick(ButtonEnum b) {
uint32_t mask = 1UL << pgm_read_byte(&PS4_BUTTONS[(uint8_t)b]); const int8_t index = getButtonIndexPS4(b); if (index < 0) return 0;
uint32_t mask = 1UL << pgm_read_byte(&PS4_BUTTONS[index]);
bool click = buttonClickState.val & mask; bool click = buttonClickState.val & mask;
buttonClickState.val &= ~mask; // Clear "click" event buttonClickState.val &= ~mask; // Clear "click" event
return click; return click;
} }
uint8_t PS4Parser::getAnalogButton(ButtonEnum b) { uint8_t PS4Parser::getAnalogButton(ButtonEnum b) {
if (b == L2) // These are the only analog buttons on the controller const int8_t index = getButtonIndexPS4(b); if (index < 0) return 0;
if (index == ButtonIndex(L2)) // These are the only analog buttons on the controller
return ps4Data.trigger[0]; return ps4Data.trigger[0];
else if (b == R2) else if (index == ButtonIndex(R2))
return ps4Data.trigger[1]; return ps4Data.trigger[1];
return 0; return 0;
} }

View file

@ -362,6 +362,7 @@ protected:
virtual void sendOutputReport(PS4Output *output) = 0; virtual void sendOutputReport(PS4Output *output) = 0;
private: private:
static int8_t getButtonIndexPS4(ButtonEnum b);
bool checkDpad(ButtonEnum b); // Used to check PS4 DPAD buttons bool checkDpad(ButtonEnum b); // Used to check PS4 DPAD buttons
PS4Data ps4Data; PS4Data ps4Data;

View file

@ -65,7 +65,7 @@ protected:
* @param len The length of the incoming data. * @param len The length of the incoming data.
* @param buf Pointer to the data buffer. * @param buf Pointer to the data buffer.
*/ */
virtual void ParseHIDData(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) { virtual void ParseHIDData(USBHID *hid __attribute__((unused)), bool is_rpt_id __attribute__((unused)), uint8_t len, uint8_t *buf) {
if (HIDUniversal::VID == PS4_VID && (HIDUniversal::PID == PS4_PID || HIDUniversal::PID == PS4_PID_SLIM)) if (HIDUniversal::VID == PS4_VID && (HIDUniversal::PID == PS4_PID || HIDUniversal::PID == PS4_PID_SLIM))
PS4Parser::Parse(len, buf); PS4Parser::Parse(len, buf);
}; };

233
PS5BT.h Normal file
View file

@ -0,0 +1,233 @@
/* 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 _ps5bt_h_
#define _ps5bt_h_
#include "BTHID.h"
#include "PS5Parser.h"
/**
* Generated from the standard Ethernet CRC-32 polynomial:
* x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0
* Source: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c
*/
const uint32_t crc32_table[] PROGMEM = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
/*
* There are multiple 16-bit CRC polynomials in common use, but this is
* *the* standard CRC-32 polynomial, first popularized by Ethernet.
* x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0
*/
#define CRC32_POLY_LE 0xedb88320
static inline uint32_t crc32_le_generic(uint32_t crc, uint8_t const *p, size_t len, uint32_t polynomial) {
// Source: https://github.com/torvalds/linux/blob/c4cf498dc0241fa2d758dba177634268446afb06/lib/crc32.c
int i;
while (len--) {
crc ^= *p++;
for (i = 0; i < 8; i++)
crc = (crc >> 1) ^ ((crc & 1) ? polynomial : 0);
}
return crc;
}
static inline uint32_t crc32(uint32_t crc, const void *buf, size_t size) {
#if 1 // Use a table, as it's faster, but takes up more space
// Inspired by: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/libkern/crc32.c
const uint8_t *p = (const uint8_t*)buf;
while (size--)
crc = pgm_read_dword(&crc32_table[*p++ ^ (crc & 0xFF)]) ^ (crc >> 8);
return crc;
#else // Can be used to save flash, but is slower
return crc32_le_generic(crc, (uint8_t const*)buf, size, CRC32_POLY_LE);
#endif
};
/**
* This class implements support for the PS5 controller via Bluetooth.
* It uses the BTHID class for all the Bluetooth communication.
*/
class PS5BT : public BTHID, public PS5Parser {
public:
/**
* Constructor for the PS5BT class.
* @param p Pointer to the BTD class instance.
* @param pair Set this to true in order to pair with the device. If the argument is omitted then it will not pair with it. One can use ::PAIR to set it to true.
* @param pin Write the pin to BTD#btdPin. If argument is omitted, then "0000" will be used.
*/
PS5BT(BTD *p, bool pair = false, const char *pin = "0000") :
BTHID(p, pair, pin), output_sequence_counter(0) {
PS5Parser::Reset();
};
/**
* Used to check if a PS5 controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return BTHID::connected;
};
protected:
/** @name BTHID implementation */
/**
* Used to parse Bluetooth HID data.
* @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/
virtual void ParseBTHIDData(uint8_t len, uint8_t *buf) {
PS5Parser::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 void OnInitBTHID() {
PS5Parser::Reset();
enable_sixaxis(); // Make the controller send out the entire output report
// Only call this is a user function has not been set
if (!pFuncOnInit)
setLed(Red); // Set the LED to red, as the PS5 controller turns Bluetooth when searching for a device
};
/** Used to reset the different buffers to there default values */
virtual void ResetBTHID() {
PS5Parser::Reset();
};
/**@}*/
/** @name PS5Parser implementation */
virtual void sendOutputReport(PS5Output *output) {
// See the series of patches here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
uint8_t buf[1 /* BT DATA Output Report */ + 1 /* report id */ + 1 /* seq_tag */ + 1 /* tag */ + 47 /* common */ + 24 /* reserved */ + 4 /* crc32 */];
memset(buf, 0, sizeof(buf));
// Send as a Bluetooth HID DATA output report on the interrupt channel
buf[0] = 0xA2; // HID BT DATA (0xA0) | Report Type (Output 0x02)
buf[0x01] = 0x31; // Report ID
buf[0x02] = (output_sequence_counter << 4) | 0x0; // Highest 4-bit is a sequence number, which needs to be increased every report. Lowest 4-bit is tag and can be zero for now.
if(++output_sequence_counter == 15)
output_sequence_counter = 0;
buf[0x03] = 0x10; // Magic number must be set to 0x10
buf[0x01 + 3] = 0xFF; // feature flags 1
buf[0x02 + 3]= 0xF7; // feature flags 2
buf[0x03 + 3] = output->smallRumble; // Small Rumble
buf[0x04 + 3] = output->bigRumble; // Big rumble
// 5-7 headphone, speaker, mic volume, audio flags
buf[0x09 + 3] = (uint8_t)output->microphoneLed;
// 0x0A mute flags
// Adaptive Triggers: 0x0B-0x14 right, 0x15 unknown, 0x16-0x1F left
rightTrigger.processTrigger(&buf[0x0B + 3]); // right
leftTrigger.processTrigger(&buf[0x16 + 3]); // left
// 0x20-0x24 unknown
// 0x25 trigger motor effect strengths
// 0x26 speaker volume
// player LEDs
buf[0x27 + 3] = 0x03; // led brightness, pulse
buf[0x2A + 3] = output->disableLeds ? 0x01 : 0x2; // led pulse option
// buf[0x2B] LED brightness, 0 = full, 1= medium, 2 = low
buf[0x2C + 3] = output->playerLeds; // 5 white player LEDs
// lightbar
buf[0x2D + 3] = output->r; // Red
buf[0x2E + 3] = output->g; // Green
buf[0x2F + 3] = output->b; // Blue
uint32_t crc = ~crc32(0xFFFFFFFF, buf, sizeof(buf) - 4 /* Do not include the crc32 */); // Note how the report type is also included in the output report
buf[75] = crc & 0xFF;
buf[76] = (crc >> 8) & 0xFF;
buf[77] = (crc >> 16);
buf[78] = (crc >> 24);
output->reportChanged = false;
// Send the Bluetooth DATA output report on the interrupt channel
pBtd->L2CAP_Command(hci_handle, buf, sizeof(buf), interrupt_scid[0], interrupt_scid[1]);
};
/**@}*/
private:
void enable_sixaxis() { // Command used to make the PS5 controller send out the entire output report
// Request the paring info. This makes the controller send out the full report - see: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
uint8_t buf[2];
buf[0] = 0x43; // HID BT Get_report (0x40) | Report Type (Feature 0x03)
buf[1] = 0x09; // Report ID for paring info
// Send the Bluetooth Get_report Feature report on the control channel
pBtd->L2CAP_Command(hci_handle, buf, 2, control_scid[0], control_scid[1]);
};
uint8_t output_sequence_counter;
};
#endif

174
PS5Parser.cpp Normal file
View file

@ -0,0 +1,174 @@
/* 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
Thanks to Joseph Duchesne for the initial code.
Based on Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows PS5 port
and the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
*/
#include "PS5Parser.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 PS5 Controller
int8_t PS5Parser::getButtonIndexPS5(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(PS5_BUTTONS) / sizeof(PS5_BUTTONS[0]))) return -1;
return index;
}
bool PS5Parser::checkDpad(ButtonEnum b) {
switch (b) {
case UP:
return ps5Data.btn.dpad == DPAD_LEFT_UP || ps5Data.btn.dpad == DPAD_UP || ps5Data.btn.dpad == DPAD_UP_RIGHT;
case RIGHT:
return ps5Data.btn.dpad == DPAD_UP_RIGHT || ps5Data.btn.dpad == DPAD_RIGHT || ps5Data.btn.dpad == DPAD_RIGHT_DOWN;
case DOWN:
return ps5Data.btn.dpad == DPAD_RIGHT_DOWN || ps5Data.btn.dpad == DPAD_DOWN || ps5Data.btn.dpad == DPAD_DOWN_LEFT;
case LEFT:
return ps5Data.btn.dpad == DPAD_DOWN_LEFT || ps5Data.btn.dpad == DPAD_LEFT || ps5Data.btn.dpad == DPAD_LEFT_UP;
default:
return false;
}
}
bool PS5Parser::getButtonPress(ButtonEnum b) {
const int8_t index = getButtonIndexPS5(b); if (index < 0) return 0;
if (index <= LEFT) // Dpad
return checkDpad(b);
else
return ps5Data.btn.val & (1UL << pgm_read_byte(&PS5_BUTTONS[index]));
}
bool PS5Parser::getButtonClick(ButtonEnum b) {
const int8_t index = getButtonIndexPS5(b); if (index < 0) return 0;
uint32_t mask = 1UL << pgm_read_byte(&PS5_BUTTONS[index]);
bool click = buttonClickState.val & mask;
buttonClickState.val &= ~mask; // Clear "click" event
return click;
}
uint8_t PS5Parser::getAnalogButton(ButtonEnum b) {
const int8_t index = getButtonIndexPS5(b); if (index < 0) return 0;
if (index == ButtonIndex(L2)) // These are the only analog buttons on the controller
return ps5Data.trigger[0];
else if (index == ButtonIndex(R2))
return ps5Data.trigger[1];
return 0;
}
uint8_t PS5Parser::getAnalogHat(AnalogHatEnum a) {
return ps5Data.hatValue[(uint8_t)a];
}
void PS5Parser::Parse(uint8_t len, uint8_t *buf) {
if (len > 1 && buf) {
#ifdef PRINTREPORT
Notify(PSTR("\r\nLen: "), 0x80); Notify(len, 0x80);
Notify(PSTR(", data: "), 0x80);
for (uint8_t i = 0; i < len; i++) {
D_PrintHex<uint8_t > (buf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
#endif
if (buf[0] == 0x01) // Check report ID
memcpy(&ps5Data, buf + 1, min((uint8_t)(len - 1), MFK_CASTUINT8T sizeof(ps5Data)));
else if (buf[0] == 0x31) { // This report is send via Bluetooth, it has an offset of 1 compared to the USB data
if (len < 3) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nReport is too short: "), 0x80);
D_PrintHex<uint8_t > (len, 0x80);
#endif
return;
}
memcpy(&ps5Data, buf + 2, min((uint8_t)(len - 2), MFK_CASTUINT8T sizeof(ps5Data)));
} 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;
}
if (ps5Data.btn.val != oldButtonState.val) { // Check if anything has changed
buttonClickState.val = ps5Data.btn.val & ~oldButtonState.val; // Update click state variable
oldButtonState.val = ps5Data.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 (ps5Output.reportChanged || leftTrigger.reportChanged || rightTrigger.reportChanged)
sendOutputReport(&ps5Output); // Send output report
}
void PS5Parser::Reset() {
uint8_t i;
for (i = 0; i < sizeof(ps5Data.hatValue); i++)
ps5Data.hatValue[i] = 127; // Center value
ps5Data.btn.val = 0;
oldButtonState.val = 0;
for (i = 0; i < sizeof(ps5Data.trigger); i++)
ps5Data.trigger[i] = 0;
for (i = 0; i < sizeof(ps5Data.xy.finger)/sizeof(ps5Data.xy.finger[0]); i++)
ps5Data.xy.finger[i].touching = 1; // The bit is cleared if the finger is touching the touchpad
ps5Data.btn.dpad = DPAD_OFF;
oldButtonState.dpad = DPAD_OFF;
buttonClickState.dpad = 0;
oldDpad = 0;
leftTrigger.Reset();
rightTrigger.Reset();
ps5Output.bigRumble = ps5Output.smallRumble = 0;
ps5Output.microphoneLed = 0;
ps5Output.disableLeds = 0;
ps5Output.playerLeds = 0;
ps5Output.r = ps5Output.g = ps5Output.b = 0;
ps5Output.reportChanged = false;
};

415
PS5Parser.h Normal file
View file

@ -0,0 +1,415 @@
/* 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
Thanks to Joseph Duchesne for the initial code.
Based on Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows PS5 port
and the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
*/
#ifndef _ps5parser_h_
#define _ps5parser_h_
#include "Usb.h"
#include "controllerEnums.h"
#include "PS5Trigger.h"
/** Buttons on the controller */
const uint8_t PS5_BUTTONS[] PROGMEM = {
UP, // UP
RIGHT, // RIGHT
DOWN, // DOWN
LEFT, // LEFT
0x0C, // CREATE
0x0D, // OPTIONS
0x0E, // L3
0x0F, // R3
0x0A, // L2
0x0B, // R2
0x08, // L1
0x09, // R1
0x07, // TRIANGLE
0x06, // CIRCLE
0x05, // CROSS
0x04, // SQUARE
0x10, // PS
0x11, // TOUCHPAD
0x12, // MICROPHONE
};
union PS5Buttons {
struct {
uint8_t dpad : 4;
uint8_t square : 1;
uint8_t cross : 1;
uint8_t circle : 1;
uint8_t triangle : 1;
uint8_t l1 : 1;
uint8_t r1 : 1;
uint8_t l2 : 1;
uint8_t r2 : 1;
uint8_t create : 1;
uint8_t menu : 1;
uint8_t l3 : 1;
uint8_t r3 : 1;
uint8_t ps : 1;
uint8_t touchpad : 1;
uint8_t mic : 1;
uint8_t dummy : 5;
} __attribute__((packed));
uint32_t val : 24;
} __attribute__((packed));
struct ps5TouchpadXY {
struct {
uint8_t counter : 7; // Increments every time a finger is touching the touchpad
uint8_t touching : 1; // The top bit is cleared if the finger is touching the touchpad
uint16_t x : 12;
uint16_t y : 12;
} __attribute__((packed)) finger[2]; // 0 = first finger, 1 = second finger
} __attribute__((packed));
union PS5Status {
struct {
// first byte
uint8_t headphone : 1;
uint8_t dummy : 2; // Seems to change when a jack is plugged in. First bit stays on when a mic is plugged in.
uint8_t usb : 1; // charging
uint8_t dummy2: 4;
// second byte
uint8_t mic : 1;
uint8_t dummy3 : 3;
} __attribute__((packed));
uint16_t val;
} __attribute__((packed));
struct PS5Data {
/* Button and joystick values */
uint8_t hatValue[4]; // 0-3 bytes
uint8_t trigger[2]; // 4-5
uint8_t sequence_number; // 6
PS5Buttons btn; // 7-9
uint8_t reserved[5]; // 0xA-0xD
/* Gyro and accelerometer values */
int16_t gyroX, gyroZ, gyroY; // 0x0F - 0x14
int16_t accX, accZ, accY; // 0x15-0x1A
int32_t sensor_timestamp;
uint8_t reserved2;
// 0x20 - 0x23 touchpad point 1
// 0x24 - 0x27 touchpad point 2
ps5TouchpadXY xy;
#if 0 // The status byte depends on if it's sent via USB or Bluetooth, so is not parsed for now
uint8_t reserved3; // 0x28
uint8_t rightTriggerFeedback; // 0x29
uint8_t leftTriggerFeedback; // 0x2A
uint8_t reserved4[10]; // 0x2B - 0x34
// status bytes 0x35-0x36
PS5Status status;
#endif
} __attribute__((packed));
struct PS5Output {
uint8_t bigRumble, smallRumble; // Rumble
uint8_t microphoneLed;
uint8_t disableLeds;
uint8_t playerLeds;
uint8_t r, g, b; // RGB for lightbar
bool reportChanged; // The data is send when data is received from the controller
} __attribute__((packed));
/** This class parses all the data sent by the PS5 controller */
class PS5Parser {
public:
/** Constructor for the PS5Parser class. */
PS5Parser() : leftTrigger(), rightTrigger() {
Reset();
};
/** Used these to manipulate the haptic triggers */
PS5Trigger leftTrigger, rightTrigger;
/** @name PS5 Controller functions */
/**
* getButtonPress(ButtonEnum b) will return true as long as the button is held down.
*
* While getButtonClick(ButtonEnum b) will only return it once.
*
* So you instance if you need to increase a variable once you would use getButtonClick(ButtonEnum b),
* but if you need to drive a robot forward you would use getButtonPress(ButtonEnum b).
* @param b ::ButtonEnum to read.
* @return getButtonPress(ButtonEnum b) will return a true as long as a button is held down, while getButtonClick(ButtonEnum b) will return true once for each button press.
*/
bool getButtonPress(ButtonEnum b);
bool getButtonClick(ButtonEnum b);
/**@}*/
/** @name PS5 Controller functions */
/**
* Used to get the analog value from button presses.
* @param b The ::ButtonEnum to read.
* The supported buttons are:
* ::L2 and ::R2.
* @return Analog value in the range of 0-255.
*/
uint8_t getAnalogButton(ButtonEnum b);
/**
* Used to read the analog joystick.
* @param a ::LeftHatX, ::LeftHatY, ::RightHatX, and ::RightHatY.
* @return Return the analog value in the range of 0-255.
*/
uint8_t getAnalogHat(AnalogHatEnum a);
/**
* Get the x-coordinate of the touchpad. Position 0 is in the top left.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
*/
uint16_t getX(uint8_t finger = 0) {
return ps5Data.xy.finger[finger].x;
};
/**
* Get the y-coordinate of the touchpad. Position 0 is in the top left.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
* @return Returns the y-coordinate of the finger.
*/
uint16_t getY(uint8_t finger = 0) {
return ps5Data.xy.finger[finger].y;
};
/**
* Returns whenever the user is toucing the touchpad.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
* @return Returns true if the specific finger is touching the touchpad.
*/
bool isTouching(uint8_t finger = 0) {
return !(ps5Data.xy.finger[finger].touching); // The bit is cleared when a finger is touching the touchpad
};
/**
* This counter increments every time a finger touches the touchpad.
* @param finger 0 = first finger, 1 = second finger. If omitted, then 0 will be used.
* @return Return the value of the counter, note that it is only a 7-bit value.
*/
uint8_t getTouchCounter(uint8_t finger = 0) {
return ps5Data.xy.finger[finger].counter;
};
/**
* Get the angle of the controller calculated using the accelerometer.
* @param a Either ::Pitch or ::Roll.
* @return Return the angle in the range of 0-360.
*/
float getAngle(AngleEnum a) {
if (a == Pitch)
return (atan2f(-ps5Data.accY, -ps5Data.accZ) + PI) * RAD_TO_DEG;
else
return (atan2f(ps5Data.accX, -ps5Data.accZ) + PI) * RAD_TO_DEG;
};
/**
* Used to get the raw values from the 3-axis gyroscope and 3-axis accelerometer inside the PS5 controller.
* @param s The sensor to read.
* @return Returns the raw sensor reading.
*/
int16_t getSensor(SensorEnum s) {
switch(s) {
case gX:
return ps5Data.gyroX;
case gY:
return ps5Data.gyroY;
case gZ:
return ps5Data.gyroZ;
case aX:
return ps5Data.accX;
case aY:
return ps5Data.accY;
case aZ:
return ps5Data.accZ;
default:
return 0;
}
};
#if 0 // Seems to only be available via Bluetooth, so have been disabled for now
/**
* Return the battery level of the PS5 controller.
* @return The battery level in the range 0-15.
*/
uint8_t getBatteryLevel() {
return ps5Data.status.battery;
};
#endif
#if 0 // These are only valid via USB, so have been commented out for now
/**
* Use this to check if an USB cable is connected to the PS5 controller.
* @return Returns true if an USB cable is connected.
*/
bool getUsbStatus() {
return ps5Data.status.usb;
};
/**
* Use this to check if an audio jack cable is connected to the PS5 controller.
* @return Returns true if an audio jack cable is connected.
*/
bool getAudioStatus() {
return ps5Data.status.headphone;
};
/**
* Use this to check if a microphone is connected to the PS5 controller.
* @return Returns true if a microphone is connected.
*/
bool getMicStatus() {
return ps5Data.status.mic;
};
#endif
/** Turn both rumble and the LEDs off. */
void setAllOff() {
setRumbleOff();
setLedOff();
};
/** Set rumble off. */
void setRumbleOff() {
setRumbleOn(0, 0);
};
/**
* Turn on rumble.
* @param mode Either ::RumbleHigh or ::RumbleLow.
*/
void setRumbleOn(RumbleEnum mode) {
if (mode == RumbleLow)
setRumbleOn(0x00, 0xFF);
else
setRumbleOn(0xFF, 0x00);
};
/**
* Turn on rumble.
* @param bigRumble Value for big motor.
* @param smallRumble Value for small motor.
*/
void setRumbleOn(uint8_t bigRumble, uint8_t smallRumble) {
ps5Output.bigRumble = bigRumble;
ps5Output.smallRumble = smallRumble;
ps5Output.reportChanged = true;
};
/** Turn all LEDs off. */
void setLedOff() {
setLed(0, 0, 0);
};
/**
* Use this to set the color using RGB values.
* @param r,g,b RGB value.
*/
void setLed(uint8_t r, uint8_t g, uint8_t b) {
ps5Output.r = r;
ps5Output.g = g;
ps5Output.b = b;
ps5Output.reportChanged = true;
};
/**
* Use this to set the color using the predefined colors in ::ColorsEnum.
* @param color The desired color.
*/
void setLed(ColorsEnum color) {
setLed((uint8_t)(color >> 16), (uint8_t)(color >> 8), (uint8_t)(color));
};
/** Turn all player LEDs off. */
void setPlayerLedOff() {
setPlayerLed(0);
}
/**
* Use this to set five player LEDs.
* @param mask Bit mask to set the five player LEDs. The first 5 bits represent a LED each.
*/
void setPlayerLed(uint8_t mask) {
ps5Output.playerLeds = mask;
ps5Output.reportChanged = true;
}
/** Use to turn the microphone LED off. */
void setMicLedOff() {
setMicLed(0);
}
/**
* Use this to turn the microphone LED on/off.
* @param on Turn the microphone LED on/off.
*/
void setMicLed(bool on) {
ps5Output.microphoneLed = on ? 1 : 0;
ps5Output.reportChanged = true;
}
/** Get the incoming message count. */
uint16_t getMessageCounter(){
return message_counter;
}
protected:
/**
* Used to parse data sent from the PS5 controller.
* @param len Length of the data.
* @param buf Pointer to the data buffer.
*/
void Parse(uint8_t len, uint8_t *buf);
/** Used to reset the different buffers to their default values */
void Reset();
/**
* Send the output to the PS5 controller. This is implemented in PS5BT.h and PS5USB.h.
* @param output Pointer to PS5Output buffer;
*/
virtual void sendOutputReport(PS5Output *output) = 0;
private:
static int8_t getButtonIndexPS5(ButtonEnum b);
bool checkDpad(ButtonEnum b); // Used to check PS5 DPAD buttons
PS5Data ps5Data;
PS5Buttons oldButtonState, buttonClickState;
PS5Output ps5Output;
uint8_t oldDpad;
uint16_t message_counter = 0;
};
#endif

94
PS5Trigger.cpp Normal file
View file

@ -0,0 +1,94 @@
/**
* @file PS5Trigger.cpp
* @author Ludwig Füchsl, adapted for USB_Host_Library SAMD by Joseph Duchesne
* @brief Based on Ludwig Füchsl's DualSense Windows driver https://github.com/Ohjurot/DualSense-Windows
* @date 2020-11-25
*
* @copyright Copyright (c) 2020 Ludwig Füchsl
*
* 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.
*
* Minor updates by Kristian Sloth Lauszus.
*/
#include "PS5Trigger.h"
void PS5Trigger::processTrigger(uint8_t* buffer) {
// Switch on effect
switch (data.effectType) {
// Continious
case EffectType::ContinuousResitance:
// Mode
buffer[0x00] = 0x01;
// Parameters
buffer[0x01] = data.Continuous.startPosition;
buffer[0x02] = data.Continuous.force;
break;
// Section
case EffectType::SectionResitance:
// Mode
buffer[0x00] = 0x02;
// Parameters
buffer[0x01] = data.Section.startPosition;
buffer[0x02] = data.Section.endPosition;
break;
// EffectEx
case EffectType::EffectEx:
// Mode
buffer[0x00] = 0x02 | 0x20 | 0x04;
// Parameters
buffer[0x01] = 0xFF - data.EffectEx.startPosition;
// Keep flag
if (data.EffectEx.keepEffect)
buffer[0x02] = 0x02;
// Forces
buffer[0x04] = data.EffectEx.beginForce;
buffer[0x05] = data.EffectEx.middleForce;
buffer[0x06] = data.EffectEx.endForce;
// Frequency
buffer[0x09] = data.EffectEx.frequency / 2;
if(buffer[0x09] < 1) buffer[0x09] = 1; // minimum frequency
break;
// Calibrate
case EffectType::Calibrate:
// Mode
buffer[0x00] = 0xFC;
break;
// No resistance / default
case EffectType::NoResitance:
default:
// All zero
buffer[0x00] = 0x00;
buffer[0x01] = 0x00;
buffer[0x02] = 0x00;
break;
}
reportChanged = false;
}

168
PS5Trigger.h Normal file
View file

@ -0,0 +1,168 @@
/**
* @file PS5Trigger.h
* @author Ludwig Füchsl, adapted for USB_Host_Library SAMD by Joseph Duchesne
* @brief Based on Ludwig Füchsl's DualSense Windows driver https://github.com/Ohjurot/DualSense-Windows
* @version 0.1
* @date 2020-11-25
*
* @copyright Copyright (c) 2020 Ludwig Füchsl
*
* 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.
*
* Minor updates by Kristian Sloth Lauszus.
*/
#ifndef _ps5trigger_h_
#define _ps5trigger_h_
#include <inttypes.h>
class PS5Trigger {
private:
// Type of trigger effect
typedef enum _EffectType : uint8_t {
NoResitance = 0x00, // No resistance is applied
ContinuousResitance = 0x01, // Continuous Resitance is applied
SectionResitance = 0x02, // Seciton resistance is appleyed
EffectEx = 0x26, // Extended trigger effect
Calibrate = 0xFC, // Calibrate triggers
} EffectType;
// Trigger effect
typedef struct _EffectData {
// Trigger effect type
EffectType effectType;
// Union for effect parameters
union {
// Union one raw data
uint8_t _u1_raw[6];
// For type == ContinuousResitance
struct {
uint8_t startPosition; // Start position of resistance
uint8_t force; // Force of resistance
uint8_t _pad[4]; // PAD / UNUSED
} __attribute__((packed)) Continuous;
// For type == SectionResitance
struct {
uint8_t startPosition; // Start position of resistance
uint8_t endPosition; // End position of resistance (>= start)
uint8_t _pad[4]; // PAD / UNUSED
} __attribute__((packed)) Section;
// For type == EffectEx
struct {
uint8_t startPosition; // Position at witch the effect starts
bool keepEffect; // Wher the effect should keep playing when trigger goes beyond 255
uint8_t beginForce; // Force applied when trigger >= (255 / 2)
uint8_t middleForce; // Force applied when trigger <= (255 / 2)
uint8_t endForce; // Force applied when trigger is beyond 255
uint8_t frequency; // Vibration frequency of the trigger
} __attribute__((packed)) EffectEx;
} __attribute__((packed));
} __attribute__((packed)) EffectData;
EffectData data;
public:
bool reportChanged = false;
/**
* @brief Apply the trigger data to a PS5 update buffer
*
* @param buffer The buffer at the start offset for this trigger data
*/
void processTrigger(uint8_t* buffer);
/**
* Clear force feedback on trigger without report changed
*/
void Reset() {
data.effectType = EffectType::NoResitance;
reportChanged = false;
};
/**
* Clear force feedback on trigger
*/
void clearTriggerForce() {
data.effectType = EffectType::NoResitance;
reportChanged = true;
};
/**
* Set continuous force feedback on trigger
* @param start 0-255 trigger pull to start resisting
* @param force The force amount
*/
void setTriggerForce(uint8_t start, uint8_t force) {
if (force == 0)
data.effectType = EffectType::NoResitance;
else {
data.effectType = EffectType::ContinuousResitance;
data.Continuous.startPosition = start;
data.Continuous.force = force;
}
reportChanged = true;
};
/**
* Set section force feedback on trigger
* @param start trigger pull to start resisting
* @param end trigger pull to stop resisting
*/
void setTriggerForceSection(uint8_t start, uint8_t end) {
data.effectType = EffectType::SectionResitance;
data.Section.startPosition = start;
data.Section.endPosition = end;
reportChanged = true;
};
/**
* Set effect force feedback on trigger
* @param start trigger pull to start resisting
* @param keep Keep effect active after max trigger pull
* @param begin_force 0-255 force at start position
* @param mid_force 0-255 force half way between start and max pull
* @param end_force 0-255 force at max pull
* @param frequency Vibration frequency of the trigger
*/
void setTriggerForceEffect(uint8_t start, bool keep, uint8_t begin_force, uint8_t mid_force, uint8_t end_force, uint8_t frequency) {
data.effectType = EffectType::SectionResitance;
data.EffectEx.startPosition = start;
data.EffectEx.keepEffect = keep;
data.EffectEx.beginForce = begin_force;
data.EffectEx.middleForce = mid_force;
data.EffectEx.endForce = end_force;
data.EffectEx.frequency = frequency;
reportChanged = true;
};
};
#endif

157
PS5USB.h Normal file
View file

@ -0,0 +1,157 @@
/* 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
Thanks to Joseph Duchesne for the initial port. Data structure mapping partially based
on values from Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows
*/
#ifndef _ps5usb_h_
#define _ps5usb_h_
#include "hiduniversal.h"
#include "PS5Parser.h"
#define PS5_VID 0x054C // Sony Corporation
#define PS5_PID 0x0CE6 // PS5 Controller
/**
* This class implements support for the PS5 controller via USB.
* It uses the HIDUniversal class for all the USB communication.
*/
class PS5USB : public HIDUniversal, public PS5Parser {
public:
/**
* Constructor for the PS5USB class.
* @param p Pointer to the USB class instance.
*/
PS5USB(USB *p) :
HIDUniversal(p) {
PS5Parser::Reset();
};
/**
* Used to check if a PS5 controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return HIDUniversal::isReady() && HIDUniversal::VID == PS5_VID && HIDUniversal::PID == PS5_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 == PS5_VID && HIDUniversal::PID == PS5_PID)
PS5Parser::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 == PS5_VID && HIDUniversal::PID == PS5_PID) {
PS5Parser::Reset();
if (pFuncOnInit)
pFuncOnInit(); // Call the user function
else
setLed(Red); // Set the LED to red, so it is consistent with the PS5BT driver
};
return 0;
};
/**@}*/
/** @name PS5Parser implementation */
virtual void sendOutputReport(PS5Output *output) { // Source: https://github.com/chrippa/ds4drv
// PS4 Source: https://github.com/chrippa/ds4drv
// PS5 values from https://www.reddit.com/r/gamedev/comments/jumvi5/dualsense_haptics_leds_and_more_hid_output_report/,
// Ludwig Füchsl's https://github.com/Ohjurot/DualSense-Windows and
// the series of patches found here: https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/
uint8_t buf[1 /* report id */ + 47 /* common */];
memset(buf, 0, sizeof(buf));
buf[0x00] = 0x02; // Report ID
buf[0x01] = 0xFF; // feature flags 1
buf[0x02]= 0xF7; // feature flags 2
buf[0x03] = output->smallRumble; // Small Rumble
buf[0x04] = output->bigRumble; // Big rumble
// 5-7 headphone, speaker, mic volume, audio flags
buf[0x09] = (uint8_t)output->microphoneLed;
// 0x0A mute flags
// Adaptive Triggers: 0x0B-0x14 right, 0x15 unknown, 0x16-0x1F left
rightTrigger.processTrigger(&buf[0x0B]); // right
leftTrigger.processTrigger(&buf[0x16]); // left
// 0x20-0x24 unknown
// 0x25 trigger motor effect strengths
// 0x26 speaker volume
// player LEDs
buf[0x27] = 0x03; // led brightness, pulse
buf[0x2A] = output->disableLeds ? 0x01 : 0x2; // led pulse option
// buf[0x2B] LED brightness, 0 = full, 1= medium, 2 = low
buf[0x2C] = output->playerLeds; // 5 white player LEDs
// lightbar
buf[0x2D] = output->r; // Red
buf[0x2E] = output->g; // Green
buf[0x2F] = output->b; // Blue
output->reportChanged = false;
// There is no need to calculate a crc32 when the controller is connected via USB
pUsb->outTransfer(bAddress, epInfo[ hidInterfaces[0].epIndex[epInterruptOutIndex] ].epAddr, 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 == PS5_VID && pid == PS5_PID);
};
/**@}*/
private:
void (*pFuncOnInit)(void); // Pointer to function called in onInit()
};
#endif

View file

@ -49,12 +49,20 @@ uint8_t PSBuzz::OnInitSuccessful() {
return 0; return 0;
}; };
int8_t PSBuzz::getButtonIndexBuzz(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if (index > 4) return -1; // 5 buttons, 0-4 inclusive
return index;
}
bool PSBuzz::getButtonPress(ButtonEnum b, uint8_t controller) { bool PSBuzz::getButtonPress(ButtonEnum b, uint8_t controller) {
return psbuzzButtons.val & (1UL << (b + 5 * controller)); // Each controller uses 5 bits, so the value is shifted 5 for each controller const int8_t index = getButtonIndexBuzz(b); if (index < 0) return 0;
return psbuzzButtons.val & (1UL << (index + 5 * controller)); // Each controller uses 5 bits, so the value is shifted 5 for each controller
}; };
bool PSBuzz::getButtonClick(ButtonEnum b, uint8_t controller) { bool PSBuzz::getButtonClick(ButtonEnum b, uint8_t controller) {
uint32_t mask = (1UL << (b + 5 * controller)); // Each controller uses 5 bits, so the value is shifted 5 for each controller const int8_t index = getButtonIndexBuzz(b); if (index < 0) return 0;
uint32_t mask = (1UL << (index + 5 * controller)); // Each controller uses 5 bits, so the value is shifted 5 for each controller
bool click = buttonClickState.val & mask; bool click = buttonClickState.val & mask;
buttonClickState.val &= ~mask; // Clear "click" event buttonClickState.val &= ~mask; // Clear "click" event
return click; return click;

View file

@ -175,6 +175,8 @@ protected:
/**@}*/ /**@}*/
private: private:
static int8_t getButtonIndexBuzz(ButtonEnum b);
void (*pFuncOnInit)(void); // Pointer to function called in onInit() void (*pFuncOnInit)(void); // Pointer to function called in onInit()
void PSBuzz_Command(uint8_t *data, uint16_t nbytes); void PSBuzz_Command(uint8_t *data, uint16_t nbytes);

View file

@ -2,7 +2,7 @@
The code is released under the GNU General Public License. The code is released under the GNU General Public License.
__________ __________
[![Build Status](https://travis-ci.org/felis/USB_Host_Shield_2.0.svg?branch=master)](https://travis-ci.org/felis/USB_Host_Shield_2.0) [![](https://github.com/felis/USB_Host_Shield_2.0/workflows/CI/badge.svg)](https://github.com/felis/USB_Host_Shield_2.0/actions?query=branch%3Amaster)
# Summary # Summary
This is Revision 2.0 of MAX3421E-based USB Host Shield Library for AVR's. This is Revision 2.0 of MAX3421E-based USB Host Shield Library for AVR's.
@ -19,17 +19,19 @@ For more information about the hardware see the [Hardware Manual](https://chome.
# Developed By # Developed By
* __Oleg Mazurov - <mazurov@gmail.com> * __Oleg Mazurov__ - <mazurov@gmail.com>
* __Alexei Glushchenko, Circuits@Home__ - <alex-gl@mail.ru> * __Alexei Glushchenko__ - <alex-gl@mail.ru>
* Developers of the USB Core, HID, FTDI, ADK, ACM, and PL2303 libraries * Developers of the USB Core, HID, FTDI, ADK, ACM, and PL2303 libraries
* __Kristian Lauszus, TKJ Electronics__ - <kristianl@tkjelectronics.com> * __Kristian Sloth Lauszus__ - <lauszus@gmail.com>
* Developer of the [BTD](#bluetooth-libraries), [BTHID](#bthid-library), [SPP](#spp-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> * __Andrew Kroll__ - <xxxajk@gmail.com>
* Major contributor to mass storage code * Major contributor to mass storage code
* __guruthree__ * __guruthree__
* [Xbox ONE](#xbox-one-library) controller support * [Xbox ONE](#xbox-one-library) controller support
* __Yuuichi Akagawa__ - [@YuuichiAkagawa](https://twitter.com/yuuichiakagawa) * __Yuuichi Akagawa__ - [@YuuichiAkagawa](https://twitter.com/yuuichiakagawa)
* Developer of the [MIDI](#midi-library) library * Developer of the [MIDI](#midi-library) library
* __Aran Vink__ - <aranvink@gmail.com>
* Developer of the [amBX](#amBX-library) library
# Table of Contents # Table of Contents
@ -44,16 +46,20 @@ For more information about the hardware see the [Hardware Manual](https://chome.
* [Bluetooth libraries](#bluetooth-libraries) * [Bluetooth libraries](#bluetooth-libraries)
* [BTHID library](#bthid-library) * [BTHID library](#bthid-library)
* [SPP library](#spp-library) * [SPP library](#spp-library)
* [PS5 Library](#ps5-library)
* [PS4 Library](#ps4-library) * [PS4 Library](#ps4-library)
* [PS3 Library](#ps3-library) * [PS3 Library](#ps3-library)
* [Xbox Libraries](#xbox-libraries) * [Xbox Libraries](#xbox-libraries)
* [Xbox library](#xbox-library) * [Xbox library](#xbox-library)
* [Xbox 360 Library](#xbox-360-library) * [Xbox 360 Library](#xbox-360-library)
* [Xbox ONE Library](#xbox-one-library) * [Xbox ONE Library](#xbox-one-library)
* [Xbox ONE S Library](#xbox-one-s-library)
* [Wii library](#wii-library) * [Wii library](#wii-library)
* [Switch Pro Library](#switch-pro-library)
* [PS Buzz Library](#ps-buzz-library) * [PS Buzz Library](#ps-buzz-library)
* [HID Libraries](#hid-libraries) * [HID Libraries](#hid-libraries)
* [MIDI Library](#midi-library) * [MIDI Library](#midi-library)
* [amBX Library](#amBX-library)
* [Interface modifications](#interface-modifications) * [Interface modifications](#interface-modifications)
* [FAQ](#faq) * [FAQ](#faq)
@ -108,12 +114,13 @@ Currently the following boards are supported by the library:
* Arduino Due, Intel Galileo, Intel Galileo 2, and Intel Edison * Arduino Due, Intel Galileo, Intel Galileo 2, and Intel Edison
* Note that the Intel Galileo uses pin 2 and 3 as INT and SS pin respectively by default, so some modifications to the shield are needed. See the "Interface modifications" section in the [hardware manual](https://chome.nerpa.tech/usb-host-shield-hardware-manual) for more information. * Note that the Intel Galileo uses pin 2 and 3 as INT and SS pin respectively by default, so some modifications to the shield are needed. See the "Interface modifications" section in the [hardware manual](https://chome.nerpa.tech/usb-host-shield-hardware-manual) for more information.
* Note native USB host is not supported on any of these platforms. You will have to use the shield for now. * Note native USB host is not supported on any of these platforms. You will have to use the shield for now.
* Teensy (Teensy++ 1.0, Teensy 2.0, Teensy++ 2.0, Teensy 3.x, and Teensy LC) * Teensy (Teensy++ 1.0, Teensy 2.0, Teensy++ 2.0, Teensy 3.x, Teensy LC and Teensy 4.x)
* Note if you are using the Teensy 3.x you should download this SPI library as well: <https://github.com/xxxajk/spi4teensy3>. You should then add ```#include <spi4teensy3.h>``` to your .ino file. * Note if you are using the Teensy 3.x you should download this SPI library as well: <https://github.com/xxxajk/spi4teensy3>. You should then add ```#include <spi4teensy3.h>``` to your .ino file.
* Balanduino * Balanduino
* Sanguino * Sanguino
* Black Widdow * Black Widdow
* RedBearLab nRF51822 * RedBearLab nRF51822
* Adafruit Feather nRF52840 Express
* Digilent chipKIT * Digilent chipKIT
* Please see: <https://chome.nerpa.tech/mcu/usb/running-usb-host-code-on-digilent-chipkit-board>. * Please see: <https://chome.nerpa.tech/mcu/usb/running-usb-host-code-on-digilent-chipkit-board>.
* STM32F4 * STM32F4
@ -170,9 +177,25 @@ More information can be found at these blog posts:
To implement the SPP protocol I used a Bluetooth sniffing tool called [PacketLogger](http://www.tkjelectronics.com/uploads/PacketLogger.zip) developed by Apple. To implement the SPP protocol I used a Bluetooth sniffing tool called [PacketLogger](http://www.tkjelectronics.com/uploads/PacketLogger.zip) developed by Apple.
It enables me to see the Bluetooth communication between my Mac and any device. It enables me to see the Bluetooth communication between my Mac and any device.
### PS5 Library
The PS5 library is split up into the [PS5BT](PS5BT.h) and the [PS5USB](PS5USB.h) library. These allow you to use the Sony PS5 controller via Bluetooth and USB.
The [PS5BT.ino](examples/Bluetooth/PS5BT/PS5BT.ino) and [PS5USB.ino](examples/PS5USB/PS5USB.ino) examples shows how to easily read the buttons, joysticks, touchpad and IMU on the controller via Bluetooth and USB respectively. It is also possible to control the rumble, lightbar, microphone LED and player LEDs on the controller. Furthermore the new haptic trigger effects are also supported.
To pair with the PS5 controller via Bluetooth you need create the PS5BT instance like so: ```PS5BT PS5(&Btd, PAIR);``` and then hold down the Create button and then hold down the PS without releasing the Create button. The PS5 controller will then start to blink blue indicating that it is in pairing mode.
It should then automatically pair the dongle with your controller. This only have to be done once.
Thanks to Joseph Duchesne for the initial USB code.
The driver is based on the official Sony driver for Linux: <https://patchwork.kernel.org/project/linux-input/cover/20201219062336.72568-1-roderick@gaikai.com/>.
Also thanks to Ludwig Füchsl's <https://github.com/Ohjurot/DualSense-Windows> for his work on the haptic triggers.
### PS4 Library ### PS4 Library
The PS4BT library is split up into the [PS4BT](PS4BT.h) and the [PS4USB](PS4USB.h) library. These allow you to use the Sony PS4 controller via Bluetooth and USB. The PS4 library is split up into the [PS4BT](PS4BT.h) and the [PS4USB](PS4USB.h) library. These allow you to use the Sony PS4 controller via Bluetooth and USB.
The [PS4BT.ino](examples/Bluetooth/PS4BT/PS4BT.ino) and [PS4USB.ino](examples/PS4USB/PS4USB.ino) examples shows how to easily read the buttons, joysticks, touchpad and IMU on the controller via Bluetooth and USB respectively. It is also possible to control the rumble and light on the controller and get the battery level. The [PS4BT.ino](examples/Bluetooth/PS4BT/PS4BT.ino) and [PS4USB.ino](examples/PS4USB/PS4USB.ino) examples shows how to easily read the buttons, joysticks, touchpad and IMU on the controller via Bluetooth and USB respectively. It is also possible to control the rumble and light on the controller and get the battery level.
@ -186,7 +209,7 @@ For information see the following blog post: <http://blog.tkjelectronics.dk/2014
Also check out this excellent Wiki by Frank Zhao about the PS4 controller: <http://eleccelerator.com/wiki/index.php?title=DualShock_4> and this Linux driver: <https://github.com/chrippa/ds4drv>. Also check out this excellent Wiki by Frank Zhao about the PS4 controller: <http://eleccelerator.com/wiki/index.php?title=DualShock_4> and this Linux driver: <https://github.com/chrippa/ds4drv>.
Several guides on how to use the PS4 library has been written by Dr. James E. Barger and are available at the following link: <https://sites.google.com/view/vbatc-engineeringtechnology2/control-system-tutorials/ps4-tutorials>. Several guides on how to use the PS4 library has been written by Dr. James E. Barger and are available at the following link: <https://sites.google.com/view/crosswaystation/ps4-tutorials>.
### PS3 Library ### PS3 Library
@ -257,12 +280,18 @@ All the information regarding the Xbox 360 controller protocol are form these si
#### Xbox ONE Library #### Xbox ONE Library
An Xbox ONE controller is supported via USB in the [XBOXONE](XBOXONE.cpp) class. It is heavily based on the 360 library above. In addition to cross referencing the above, information on the protocol was found at: A Xbox ONE controller is supported via USB in the [XBOXONE](XBOXONE.cpp) class. It is heavily based on the 360 library above. In addition to cross referencing the above, information on the protocol was found at:
* <https://github.com/quantus/xbox-one-controller-protocol> * <https://github.com/quantus/xbox-one-controller-protocol>
* <https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c> * <https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c>
* <https://github.com/kylelemons/xbox/blob/master/xbox.go> * <https://github.com/kylelemons/xbox/blob/master/xbox.go>
#### Xbox ONE S Library
A Xbox ONE controller is supported via Bluetooth in the [XBOXONESBT](XBOXONESBT.cpp) class.
Special thanks to [HisashiKato](https://github.com/HisashiKato) for his help: <https://github.com/felis/USB_Host_Shield_2.0/issues/252#issuecomment-716912362>.
### [Wii library](Wii.cpp) ### [Wii library](Wii.cpp)
The [Wii](Wii.cpp) library support the Wiimote, but also the Nunchuch and Motion Plus extensions via Bluetooth. The Wii U Pro Controller and Wii Balance Board are also supported via Bluetooth. The [Wii](Wii.cpp) library support the Wiimote, but also the Nunchuch and Motion Plus extensions via Bluetooth. The Wii U Pro Controller and Wii Balance Board are also supported via Bluetooth.
@ -302,6 +331,21 @@ All the information about the Wii controllers are from these sites:
* <http://wiibrew.org/wiki/Wii_Balance_Board> * <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. * 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) ### [PS Buzz Library](PSBuzz.cpp)
This library implements support for the Playstation Buzz controllers via USB. This library implements support for the Playstation Buzz controllers via USB.
@ -327,7 +371,14 @@ You can convert USB MIDI keyboard to legacy serial MIDI.
* [USB_MIDI_converter.ino](examples/USBH_MIDI/USB_MIDI_converter/USB_MIDI_converter.ino) * [USB_MIDI_converter.ino](examples/USBH_MIDI/USB_MIDI_converter/USB_MIDI_converter.ino)
* [USB_MIDI_converter_multi.ino](examples/USBH_MIDI/USB_MIDI_converter_multi/USB_MIDI_converter_multi.ino) * [USB_MIDI_converter_multi.ino](examples/USBH_MIDI/USB_MIDI_converter_multi/USB_MIDI_converter_multi.ino)
For information see the following page: <http://yuuichiakagawa.github.io/USBH_MIDI/>. For more information see : <https://github.com/YuuichiAkagawa/USBH_MIDI>.
### [amBX Library](AMBX.cpp)
This library support Philips amBX lights.
You can set the colors of the lights individually or all at once. The rumble pad and fans are not supported.
* [AMBX.ino](examples/ambx/AMBX.ino)
# Interface modifications # Interface modifications

View file

@ -189,7 +189,7 @@ void SPP::ACLData(uint8_t* l2capinbuf) {
} }
#endif #endif
} else if(l2capinbuf[6] == sdp_dcid[0] && l2capinbuf[7] == sdp_dcid[1]) { // SDP } else if(l2capinbuf[6] == sdp_dcid[0] && l2capinbuf[7] == sdp_dcid[1]) { // SDP
if(l2capinbuf[8] == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST_PDU) { if(l2capinbuf[8] == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST) {
if(((l2capinbuf[16] << 8 | l2capinbuf[17]) == SERIALPORT_UUID) || ((l2capinbuf[16] << 8 | l2capinbuf[17]) == 0x0000 && (l2capinbuf[18] << 8 | l2capinbuf[19]) == SERIALPORT_UUID)) { // Check if it's sending the full UUID, see: https://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm, we will just check the first four bytes if(((l2capinbuf[16] << 8 | l2capinbuf[17]) == SERIALPORT_UUID) || ((l2capinbuf[16] << 8 | l2capinbuf[17]) == 0x0000 && (l2capinbuf[18] << 8 | l2capinbuf[19]) == SERIALPORT_UUID)) { // Check if it's sending the full UUID, see: https://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm, we will just check the first four bytes
if(firstMessage) { if(firstMessage) {
serialPortResponse1(l2capinbuf[9], l2capinbuf[10]); serialPortResponse1(l2capinbuf[9], l2capinbuf[10]);
@ -536,7 +536,7 @@ void SPP::SDP_Command(uint8_t* data, uint8_t nbytes) { // See page 223 in the Bl
} }
void SPP::serviceNotSupported(uint8_t transactionIDHigh, uint8_t transactionIDLow) { // See page 235 in the Bluetooth specs void SPP::serviceNotSupported(uint8_t transactionIDHigh, uint8_t transactionIDLow) { // See page 235 in the Bluetooth specs
l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE_PDU; l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE;
l2capoutbuf[1] = transactionIDHigh; l2capoutbuf[1] = transactionIDHigh;
l2capoutbuf[2] = transactionIDLow; l2capoutbuf[2] = transactionIDLow;
l2capoutbuf[3] = 0x00; // MSB Parameter Length l2capoutbuf[3] = 0x00; // MSB Parameter Length
@ -553,7 +553,7 @@ void SPP::serviceNotSupported(uint8_t transactionIDHigh, uint8_t transactionIDLo
} }
void SPP::serialPortResponse1(uint8_t transactionIDHigh, uint8_t transactionIDLow) { void SPP::serialPortResponse1(uint8_t transactionIDHigh, uint8_t transactionIDLow) {
l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE_PDU; l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE;
l2capoutbuf[1] = transactionIDHigh; l2capoutbuf[1] = transactionIDHigh;
l2capoutbuf[2] = transactionIDLow; l2capoutbuf[2] = transactionIDLow;
l2capoutbuf[3] = 0x00; // MSB Parameter Length l2capoutbuf[3] = 0x00; // MSB Parameter Length
@ -615,7 +615,7 @@ void SPP::serialPortResponse1(uint8_t transactionIDHigh, uint8_t transactionIDLo
} }
void SPP::serialPortResponse2(uint8_t transactionIDHigh, uint8_t transactionIDLow) { void SPP::serialPortResponse2(uint8_t transactionIDHigh, uint8_t transactionIDLow) {
l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE_PDU; l2capoutbuf[0] = SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE;
l2capoutbuf[1] = transactionIDHigh; l2capoutbuf[1] = transactionIDHigh;
l2capoutbuf[2] = transactionIDLow; l2capoutbuf[2] = transactionIDLow;
l2capoutbuf[3] = 0x00; // MSB Parameter Length l2capoutbuf[3] = 0x00; // MSB Parameter Length

8
SPP.h
View file

@ -20,12 +20,6 @@
#include "BTD.h" #include "BTD.h"
/* Used for SDP */
#define SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST_PDU 0x06 // See the RFCOMM specs
#define SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE_PDU 0x07 // See the RFCOMM specs
#define SERIALPORT_UUID 0x1101 // See http://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm
#define L2CAP_UUID 0x0100
/* Used for RFCOMM */ /* Used for RFCOMM */
#define RFCOMM_SABM 0x2F #define RFCOMM_SABM 0x2F
#define RFCOMM_UA 0x63 #define RFCOMM_UA 0x63
@ -120,7 +114,7 @@ public:
*/ */
size_t write(const uint8_t* data, size_t size); size_t write(const uint8_t* data, size_t size);
/** Pull in write(const char *str) from Print */ /** Pull in write(const char *str) from Print */
#if !defined(RBL_NRF51822) #if !defined(RBL_NRF51822) && !defined(NRF52_SERIES)
using Print::write; using Print::write;
#endif #endif
#else #else

94
SwitchProBT.h Normal file
View file

@ -0,0 +1,94 @@
/* 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_bt_h_
#define _switch_pro_bt_h_
#include "BTHID.h"
#include "SwitchProParser.h"
/**
* This class implements support for the Switch Pro controller via Bluetooth.
* It uses the BTHID class for all the Bluetooth communication.
*/
class SwitchProBT : public BTHID, public SwitchProParser {
public:
/**
* Constructor for the SwitchProBT class.
* @param p Pointer to the BTD class instance.
* @param pair Set this to true in order to pair with the device. If the argument is omitted then it will not pair with it. One can use ::PAIR to set it to true.
* @param pin Write the pin to BTD#btdPin. If argument is omitted, then "0000" will be used.
*/
SwitchProBT(BTD *p, bool pair = false, const char *pin = "0000") :
BTHID(p, pair, pin) {
SwitchProParser::Reset();
};
/**
* Used to check if a Switch Pro controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return BTHID::connected;
};
protected:
/** @name BTHID implementation */
/**
* Used to parse Bluetooth HID data.
* @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/
virtual void ParseBTHIDData(uint8_t len, uint8_t *buf) {
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 void OnInitBTHID() {
SwitchProParser::Reset();
// Only call this is a user function has not been set
if (!pFuncOnInit) {
setLedOn(LED1); // Turn on the LED1
setLedHomeOn(); // Turn on the home LED
}
};
/** Used to reset the different buffers to there default values */
virtual void ResetBTHID() {
SwitchProParser::Reset();
};
/**@}*/
/** @name SwitchProParser implementation */
virtual void sendOutputReport(uint8_t *data, uint8_t len) {
uint8_t buf[1 /* BT DATA Output Report */ + len];
// Send as a Bluetooth HID DATA output report on the interrupt channel
buf[0] = 0xA2; // HID BT DATA (0xA0) | Report Type (Output 0x02)
memcpy(&buf[1], data, len);
// Send the Bluetooth DATA output report on the interrupt channel
pBtd->L2CAP_Command(hci_handle, buf, sizeof(buf), interrupt_scid[0], interrupt_scid[1]);
};
/**@}*/
};
#endif

253
SwitchProParser.cpp Normal file
View file

@ -0,0 +1,253 @@
/* 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
*/
#include "SwitchProParser.h"
// To enable serial debugging see "settings.h"
//#define PRINTREPORT // Uncomment to print the report send by the Switch Pro Controller
int8_t SwitchProParser::getButtonIndexSwitchPro(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(SWITCH_PRO_BUTTONS) / sizeof(SWITCH_PRO_BUTTONS[0]))) return -1;
return index;
}
bool SwitchProParser::getButtonPress(ButtonEnum b) {
const int8_t index = getButtonIndexSwitchPro(b); if (index < 0) return 0;
return switchProData.btn.val & (1UL << pgm_read_byte(&SWITCH_PRO_BUTTONS[index]));
}
bool SwitchProParser::getButtonClick(ButtonEnum b) {
const int8_t index = getButtonIndexSwitchPro(b); if (index < 0) return 0;
uint32_t mask = 1UL << pgm_read_byte(&SWITCH_PRO_BUTTONS[index]);
bool click = buttonClickState.val & mask;
buttonClickState.val &= ~mask; // Clear "click" event
return click;
}
int16_t SwitchProParser::getAnalogHat(AnalogHatEnum a) {
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 > 0 && buf) {
#ifdef PRINTREPORT
Notify(PSTR("\r\nLen: "), 0x80); Notify(len, 0x80);
Notify(PSTR(", data: "), 0x80);
for (uint8_t i = 0; i < len; i++) {
D_PrintHex<uint8_t > (buf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
#endif
// 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 < 3) {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nReport is too short: "), 0x80);
D_PrintHex<uint8_t > (len, 0x80);
#endif
return;
}
memcpy(&switchProData, buf + 2, min((uint8_t)(len - 2), 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
}
}
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();
if (now - rumble_on_timer > 1000) {
rumble_on_timer = now;
sendRumbleOutputReport();
}
}
}
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
uint8_t buf[14] = { 0 };
buf[0x00] = 0x01; // Report ID - PROCON_CMD_AND_RUMBLE
buf[0x01] = output_sequence_counter++; // Lowest 4-bit is a sequence number, which needs to be increased for every report
// Left rumble data
if (switchProOutput.leftRumbleOn) {
buf[0x02 + 0] = 0x28;
buf[0x02 + 1] = 0x88;
buf[0x02 + 2] = 0x60;
buf[0x02 + 3] = 0x61;
} else {
buf[0x02 + 0] = 0x00;
buf[0x02 + 1] = 0x01;
buf[0x02 + 2] = 0x40;
buf[0x02 + 3] = 0x40;
}
// Right rumble data
if (switchProOutput.rightRumbleOn) {
buf[0x02 + 4] = 0x28;
buf[0x02 + 5] = 0x88;
buf[0x02 + 6] = 0x60;
buf[0x02 + 7] = 0x61;
} else {
buf[0x02 + 4] = 0x00;
buf[0x02 + 5] = 0x01;
buf[0x02 + 6] = 0x40;
buf[0x02 + 7] = 0x40;
}
// Sub commands
if (switchProOutput.ledReportChanged) {
switchProOutput.ledReportChanged = false;
// See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x30-set-player-lights
buf[0x0A + 0] = 0x30; // PROCON_CMD_LED
buf[0x0A + 1] = switchProOutput.ledMask; // Lower 4-bits sets the LEDs constantly on, the higher 4-bits can be used to flash the LEDs
sendOutputReport(buf, 10 + 2);
} else if (switchProOutput.ledHomeReportChanged) {
switchProOutput.ledHomeReportChanged = false;
// It is possible set up to 15 mini cycles, but we simply just set the LED constantly on/off
// See: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_subcommands_notes.md#subcommand-0x38-set-home-light
buf[0x0A + 0] = 0x38; // PROCON_CMD_LED_HOME
buf[0x0A + 1] = (0 /* Number of cycles */ << 4) | (switchProOutput.ledHome ? 0xF : 0) /* Global mini cycle duration */;
buf[0x0A + 2] = (0xF /* LED start intensity */ << 4) | 0x0 /* Number of full cycles */;
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);
}
}
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
// Left rumble data
if (switchProOutput.leftRumbleOn) {
buf[0x02 + 0] = 0x28;
buf[0x02 + 1] = 0x88;
buf[0x02 + 2] = 0x60;
buf[0x02 + 3] = 0x61;
} else {
buf[0x02 + 0] = 0x00;
buf[0x02 + 1] = 0x01;
buf[0x02 + 2] = 0x40;
buf[0x02 + 3] = 0x40;
}
// Right rumble data
if (switchProOutput.rightRumbleOn) {
buf[0x02 + 4] = 0x28;
buf[0x02 + 5] = 0x88;
buf[0x02 + 6] = 0x60;
buf[0x02 + 7] = 0x61;
} else {
buf[0x02 + 4] = 0x00;
buf[0x02 + 5] = 0x01;
buf[0x02 + 6] = 0x40;
buf[0x02 + 7] = 0x40;
}
sendOutputReport(buf, 10);
}
void SwitchProParser::Reset() {
// Center joysticks
switchProData.leftHatX = switchProData.leftHatY = switchProData.rightHatX = switchProData.rightHatY = 2048;
// Reset buttons variables
switchProData.btn.val = 0;
oldButtonState.val = 0;
buttonClickState.val = 0;
output_sequence_counter = 0;
rumble_on_timer = 0;
switchProOutput.leftRumbleOn = false;
switchProOutput.rightRumbleOn = false;
switchProOutput.ledMask = 0;
switchProOutput.ledHome = false;
switchProOutput.ledReportChanged = false;
switchProOutput.ledHomeReportChanged = false;
switchProOutput.enableFullReportMode = false;
switchProOutput.enableImu = -1;
switchProOutput.sendHandshake = false;
switchProOutput.disableTimeout = false;
}

385
SwitchProParser.h Normal file
View file

@ -0,0 +1,385 @@
/* 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_parser_h_
#define _switch_pro_parser_h_
#include "Usb.h"
#include "controllerEnums.h"
/** Used to set the LEDs on the controller */
const uint8_t SWITCH_PRO_LEDS[] PROGMEM = {
0x00, // OFF
0x01, // LED1
0x02, // LED2
0x04, // LED3
0x08, // LED4
0x09, // LED5
0x0A, // LED6
0x0C, // LED7
0x0D, // LED8
0x0E, // LED9
0x0F, // LED10
};
/** Buttons on the controller */
const uint8_t SWITCH_PRO_BUTTONS[] PROGMEM = {
0x11, // UP
0x12, // RIGHT
0x10, // DOWN
0x13, // LEFT
0x0D, // Capture
0x09, // PLUS
0x0B, // L3
0x0A, // R3
0x08, // MINUS
0x0C, // HOME
0, 0, // Skip
0x02, // B
0x03, // A
0x01, // X
0x00, // Y
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 y : 1;
uint8_t x : 1;
uint8_t b : 1;
uint8_t a : 1;
uint8_t dummy1 : 2;
uint8_t r : 1;
uint8_t zr : 1;
uint8_t minus : 1;
uint8_t plus : 1;
uint8_t r3 : 1;
uint8_t l3 : 1;
uint8_t home : 1;
uint8_t capture : 1;
uint8_t dummy2 : 2;
uint8_t dpad : 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 {
struct {
uint8_t connection_info : 4;
uint8_t battery_level : 4;
} __attribute__((packed));
/* Button and joystick values */
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 sets them on/off
bool ledHome;
// 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 */
class SwitchProParser {
public:
/** Constructor for the SwitchProParser class. */
SwitchProParser() : output_sequence_counter(0) {
Reset();
};
/** @name Switch Pro Controller functions */
/**
* getButtonPress(ButtonEnum b) will return true as long as the button is held down.
*
* While getButtonClick(ButtonEnum b) will only return it once.
*
* So you instance if you need to increase a variable once you would use getButtonClick(ButtonEnum b),
* but if you need to drive a robot forward you would use getButtonPress(ButtonEnum b).
* @param b ::ButtonEnum to read.
* @return getButtonPress(ButtonEnum b) will return a true as long as a button is held down, while getButtonClick(ButtonEnum b) will return true once for each button press.
*/
bool getButtonPress(ButtonEnum b);
bool getButtonClick(ButtonEnum b);
/**@}*/
/** @name Switch Pro Controller functions */
/**
* Used to read the analog joystick.
* @param a ::LeftHatX, ::LeftHatY, ::RightHatX, and ::RightHatY.
* @return Return the analog value as a signed 16-bit value.
*/
int16_t getAnalogHat(AnalogHatEnum a);
/**
* 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.
* @return Return the angle in the range of 0-360.
*/
float getAngle(AngleEnum a) {
if (a == Pitch)
return (atan2f(-switchProData.imu[0].accY, -switchProData.imu[0].accZ) + PI) * RAD_TO_DEG;
else
return (atan2f(switchProData.imu[0].accX, -switchProData.imu[0].accZ) + PI) * RAD_TO_DEG;
};
/**
* Used to get the raw values from the 3-axis gyroscope and 3-axis accelerometer inside the PS5 controller.
* @param s The sensor to read.
* @return Returns the raw sensor reading.
*/
int16_t getSensor(SensorEnum s) {
switch(s) {
case gX:
return switchProData.imu[0].gyroX;
case gY:
return switchProData.imu[0].gyroY;
case gZ:
return switchProData.imu[0].gyroZ;
case aX:
return switchProData.imu[0].accX;
case aY:
return switchProData.imu[0].accY;
case aZ:
return switchProData.imu[0].accZ;
default:
return 0;
}
};
/** Turn both rumble and the LEDs off. */
void setAllOff() {
setRumbleOff();
setLedOff();
setLedHomeOff();
};
/** Set rumble off. */
void setRumbleOff() {
setRumble(false, false);
}
/** Toggle rumble. */
void setRumbleToggle() {
setRumble(!switchProOutput.leftRumbleOn, !switchProOutput.rightRumbleOn);
}
/**
* Turn on/off rumble.
* @param leftRumbleOn Turn on/off left rumble motor.
* @param rightRumbleOn Turn on/off right rumble motor.
*/
void setRumble(bool leftRumbleOn, bool rightRumbleOn) {
switchProOutput.leftRumbleOn = leftRumbleOn;
switchProOutput.rightRumbleOn = rightRumbleOn;
switchProOutput.ledReportChanged = true; // Set this, so the rumble effect gets changed immediately
}
/**
* Turn on/off the left rumble.
* @param on Turn on/off left rumble motor.
*/
void setRumbleLeft(bool on) {
switchProOutput.leftRumbleOn = on;
switchProOutput.ledReportChanged = true; // Set this, so the rumble effect gets changed immediately
}
/**
* Turn on/off the right rumble.
* @param on Turn on/off right rumble motor.
*/
void setRumbleRight(bool on) {
switchProOutput.rightRumbleOn = on;
switchProOutput.ledReportChanged = true; // Set this, so the rumble effect gets changed immediately
}
/**
* Set LED value without using the ::LEDEnum.
* This can also be used to flash the LEDs by setting the high 4-bits of the mask.
* @param value See: ::LEDEnum.
*/
void setLedRaw(uint8_t mask) {
switchProOutput.ledMask = mask;
switchProOutput.ledReportChanged = true;
}
/** Turn all LEDs off. */
void setLedOff() {
setLedRaw(0);
}
/**
* Turn the specific ::LEDEnum off.
* @param a The ::LEDEnum to turn off.
*/
void setLedOff(LEDEnum a) {
switchProOutput.ledMask &= ~((uint8_t)(pgm_read_byte(&SWITCH_PRO_LEDS[(uint8_t)a]) & 0x0f));
switchProOutput.ledReportChanged = true;
}
/**
* Turn the specific ::LEDEnum on.
* @param a The ::LEDEnum to turn on.
*/
void setLedOn(LEDEnum a) {
switchProOutput.ledMask |= (uint8_t)(pgm_read_byte(&SWITCH_PRO_LEDS[(uint8_t)a]) & 0x0f);
switchProOutput.ledReportChanged = true;
}
/**
* Toggle the specific ::LEDEnum.
* @param a The ::LEDEnum to toggle.
*/
void setLedToggle(LEDEnum a) {
switchProOutput.ledMask ^= (uint8_t)(pgm_read_byte(&SWITCH_PRO_LEDS[(uint8_t)a]) & 0x0f);
switchProOutput.ledReportChanged = true;
}
/** Turn home LED off. */
void setLedHomeOff() {
switchProOutput.ledHome = false;
switchProOutput.ledHomeReportChanged = true;
}
/** Turn home LED on. */
void setLedHomeOn() {
switchProOutput.ledHome = true;
switchProOutput.ledHomeReportChanged = true;
}
/** Toggle home LED. */
void setLedHomeToggle() {
switchProOutput.ledHome = !switchProOutput.ledHome;
switchProOutput.ledHomeReportChanged = true;
}
/** Get the incoming message count. */
uint16_t getMessageCounter() {
return message_counter;
}
/**
* Return the battery level of the Switch Pro Controller.
* @return The battery level as a bit mask according to ::SwitchProBatteryLevel:
* 4=full, 3=medium, 2=low, 1=critical, 0=empty.
*/
uint8_t getBatteryLevel() {
return switchProData.battery_level >> 1;
}
/**
* Returns whenever the controller is plugged in and charging.
* @return Returns True if the controller is charging.
*/
bool isCharging() {
return switchProData.battery_level & 0x01;
}
protected:
/**
* Used to parse data sent from the Switch Pro controller.
* @param len Length of the data.
* @param buf Pointer to the data buffer.
*/
void Parse(uint8_t len, uint8_t *buf);
/** Used to reset the different buffers to their default values */
void Reset();
/**
* Send the output to the Switch Pro controller. This is implemented in SwitchProBT.h and SwitchProUSB.h.
* @param data Pointer to buffer to send by the derived class.
* @param len Length of buffer.
*/
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);
void sendOutputCmd();
void sendRumbleOutputReport();
SwitchProData switchProData;
SwitchProButtons oldButtonState, buttonClickState;
uint16_t message_counter = 0;
uint8_t output_sequence_counter : 4;
uint32_t rumble_on_timer = 0;
};
#endif

156
SwitchProUSB.h Normal file
View file

@ -0,0 +1,156 @@
/* 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) {
// Based on: https://github.com/Dan611/hid-procon
// The first 8 bytes are always the same. The actual report follows
uint8_t buf[8 + len];
buf[0] = 0x80; // PROCON_REPORT_SEND_USB
buf[1] = 0x92; // PROCON_USB_DO_CMD
buf[2] = 0x00;
buf[3] = 0x31;
buf[4] = 0x00;
buf[5] = 0x00;
buf[6] = 0x00;
buf[7] = 0x00;
// Cope over the report
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

@ -388,6 +388,11 @@ uint8_t USB::OutTransfer(EpInfo *pep, uint16_t nak_limit, uint16_t nbytes, uint8
data_p += bytes_tosend; data_p += bytes_tosend;
}//while( bytes_left... }//while( bytes_left...
breakout: breakout:
/* If rcode(=rHRSL) is non-zero, untransmitted data remains in the SNDFIFO. */
if(rcode != 0) {
//Switch the FIFO containing the OUT data back under microcontroller control and reset pointer.
regWr(rSNDBC, 0);
}
pep->bmSndToggle = (regRd(rHRSL) & bmSNDTOGRD) ? 1 : 0; //bmSNDTOG1 : bmSNDTOG0; //update toggle pep->bmSndToggle = (regRd(rHRSL) & bmSNDTOGRD) ? 1 : 0; //bmSNDTOG1 : bmSNDTOG0; //update toggle
return ( rcode); //should be 0 in all cases return ( rcode); //should be 0 in all cases

36
Wii.cpp
View file

@ -1094,19 +1094,39 @@ void WII::readWiiBalanceBoardCalibration() {
/* WII Commands */ /* WII Commands */
/************************************************************/ /************************************************************/
int8_t WII::getButtonIndexWii(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(WII_BUTTONS) / sizeof(WII_BUTTONS[0]))) return -1;
return index;
}
int8_t WII::getButtonIndexWiiPro(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(WII_PROCONTROLLER_BUTTONS) / sizeof(WII_PROCONTROLLER_BUTTONS[0]))) return -1;
return index;
}
bool WII::getButtonPress(ButtonEnum b) { // Return true when a button is pressed bool WII::getButtonPress(ButtonEnum b) { // Return true when a button is pressed
if(wiiUProControllerConnected) if (wiiUProControllerConnected) {
return (ButtonState & pgm_read_dword(&WII_PROCONTROLLER_BUTTONS[(uint8_t)b])); const int8_t index = getButtonIndexWiiPro(b); if (index < 0) return 0;
else return (ButtonState & pgm_read_dword(&WII_PROCONTROLLER_BUTTONS[index]));
return (ButtonState & pgm_read_dword(&WII_BUTTONS[(uint8_t)b])); }
else {
const int8_t index = getButtonIndexWii(b); if (index < 0) return 0;
return (ButtonState & pgm_read_dword(&WII_BUTTONS[index]));
}
} }
bool WII::getButtonClick(ButtonEnum b) { // Only return true when a button is clicked bool WII::getButtonClick(ButtonEnum b) { // Only return true when a button is clicked
uint32_t button; uint32_t button;
if(wiiUProControllerConnected) if (wiiUProControllerConnected) {
button = pgm_read_dword(&WII_PROCONTROLLER_BUTTONS[(uint8_t)b]); const int8_t index = getButtonIndexWiiPro(b); if (index < 0) return 0;
else button = pgm_read_dword(&WII_PROCONTROLLER_BUTTONS[index]);
button = pgm_read_dword(&WII_BUTTONS[(uint8_t)b]); }
else {
const int8_t index = getButtonIndexWii(b); if (index < 0) return 0;
button = pgm_read_dword(&WII_BUTTONS[index]);
}
bool click = (ButtonClickState & button); bool click = (ButtonClickState & button);
ButtonClickState &= ~button; // clear "click" event ButtonClickState &= ~button; // clear "click" event
return click; return click;

2
Wii.h
View file

@ -431,6 +431,8 @@ protected:
/**@}*/ /**@}*/
private: private:
static int8_t getButtonIndexWii(ButtonEnum b);
static int8_t getButtonIndexWiiPro(ButtonEnum b);
void L2CAP_task(); // L2CAP state machine void L2CAP_task(); // L2CAP state machine

View file

@ -292,27 +292,79 @@ void XBOXOLD::printReport(uint16_t length __attribute__((unused))) { //Uncomment
#endif #endif
} }
int8_t XBOXOLD::getAnalogIndex(ButtonEnum b) {
// A, B, X, Y, BLACK, WHITE, L1, and R1 are analog buttons
const int8_t index = ButtonIndex(b);
switch (index) {
case ButtonIndex(A):
case ButtonIndex(B):
case ButtonIndex(X):
case ButtonIndex(Y):
case ButtonIndex(BLACK):
case ButtonIndex(WHITE):
case ButtonIndex(L1):
case ButtonIndex(R1):
return index;
default: break;
}
return -1;
}
int8_t XBOXOLD::getDigitalIndex(ButtonEnum b) {
// UP, DOWN, LEFT, RIGHT, START, BACK, L3, and R3 are digital buttons
const int8_t index = ButtonIndex(b);
switch (index) {
case ButtonIndex(UP):
case ButtonIndex(DOWN):
case ButtonIndex(LEFT):
case ButtonIndex(RIGHT):
case ButtonIndex(START):
case ButtonIndex(BACK):
case ButtonIndex(L3):
case ButtonIndex(R3):
return index;
default: break;
}
return -1;
}
uint8_t XBOXOLD::getButtonPress(ButtonEnum b) { uint8_t XBOXOLD::getButtonPress(ButtonEnum b) {
uint8_t button = pgm_read_byte(&XBOXOLD_BUTTONS[(uint8_t)b]); const int8_t analogIndex = getAnalogIndex(b);
if(b == A || b == B || b == X || b == Y || b == BLACK || b == WHITE || b == L1 || b == R1) // A, B, X, Y, BLACK, WHITE, L1, and R1 are analog buttons if (analogIndex >= 0) {
return buttonValues[button]; // Analog buttons const uint8_t buttonIndex = pgm_read_byte(&XBOXOLD_BUTTONS[analogIndex]);
return (ButtonState & button); // Digital buttons return buttonValues[buttonIndex];
}
const int8_t digitalIndex = getDigitalIndex(b);
if (digitalIndex >= 0) {
const uint8_t buttonMask = pgm_read_byte(&XBOXOLD_BUTTONS[digitalIndex]);
return (ButtonState & buttonMask);
}
return 0;
} }
bool XBOXOLD::getButtonClick(ButtonEnum b) { bool XBOXOLD::getButtonClick(ButtonEnum b) {
uint8_t button = pgm_read_byte(&XBOXOLD_BUTTONS[(uint8_t)b]); const int8_t analogIndex = getAnalogIndex(b);
if(b == A || b == B || b == X || b == Y || b == BLACK || b == WHITE || b == L1 || b == R1) { // A, B, X, Y, BLACK, WHITE, L1, and R1 are analog buttons if (analogIndex >= 0) {
if(buttonClicked[button]) { const uint8_t buttonIndex = pgm_read_byte(&XBOXOLD_BUTTONS[analogIndex]);
buttonClicked[button] = false; if (buttonClicked[buttonIndex]) {
buttonClicked[buttonIndex] = false;
return true; return true;
} }
return false; return false;
} }
const int8_t digitalIndex = getDigitalIndex(b);
bool click = (ButtonClickState & button); if (digitalIndex >= 0) {
ButtonClickState &= ~button; // clear "click" event const uint8_t mask = pgm_read_byte(&XBOXOLD_BUTTONS[digitalIndex]);
const bool click = (ButtonClickState & mask);
ButtonClickState &= ~mask;
return click; return click;
} }
return 0;
}
int16_t XBOXOLD::getAnalogHat(AnalogHatEnum a) { int16_t XBOXOLD::getAnalogHat(AnalogHatEnum a) {
return hatValue[a]; return hatValue[a];

View file

@ -153,6 +153,9 @@ protected:
EpInfo epInfo[XBOX_MAX_ENDPOINTS]; EpInfo epInfo[XBOX_MAX_ENDPOINTS];
private: private:
static int8_t getAnalogIndex(ButtonEnum b);
static int8_t getDigitalIndex(ButtonEnum b);
/** /**
* Called when the controller is successfully initialized. * Called when the controller is successfully initialized.
* Use attachOnInit(void (*funcOnInit)(void)) to call your own function. * Use attachOnInit(void (*funcOnInit)(void)) to call your own function.

View file

@ -331,9 +331,9 @@ void XBOXONE::readReport() {
if(readBuf[0] == 0x07) { if(readBuf[0] == 0x07) {
// The XBOX button has a separate message // The XBOX button has a separate message
if(readBuf[4] == 1) if(readBuf[4] == 1)
ButtonState |= pgm_read_word(&XBOX_BUTTONS[XBOX]); ButtonState |= pgm_read_word(&XBOX_BUTTONS[ButtonIndex(XBOX)]);
else else
ButtonState &= ~pgm_read_word(&XBOX_BUTTONS[XBOX]); ButtonState &= ~pgm_read_word(&XBOX_BUTTONS[ButtonIndex(XBOX)]);
if(ButtonState != OldButtonState) { if(ButtonState != OldButtonState) {
ButtonClickState = ButtonState & ~OldButtonState; // Update click state variable ButtonClickState = ButtonState & ~OldButtonState; // Update click state variable
@ -348,7 +348,7 @@ void XBOXONE::readReport() {
return; return;
} }
uint16_t xbox = ButtonState & pgm_read_word(&XBOX_BUTTONS[XBOX]); // Since the XBOX button is separate, save it and add it back in uint16_t xbox = ButtonState & pgm_read_word(&XBOX_BUTTONS[ButtonIndex(XBOX)]); // Since the XBOX button is separate, save it and add it back in
// xbox button from before, dpad, abxy, start/back, sync, stick click, shoulder buttons // xbox button from before, dpad, abxy, start/back, sync, stick click, shoulder buttons
ButtonState = xbox | (((uint16_t)readBuf[5] & 0xF) << 8) | (readBuf[4] & 0xF0) | (((uint16_t)readBuf[4] & 0x0C) << 10) | ((readBuf[4] & 0x01) << 3) | (((uint16_t)readBuf[5] & 0xC0) << 8) | ((readBuf[5] & 0x30) >> 4); ButtonState = xbox | (((uint16_t)readBuf[5] & 0xF) << 8) | (readBuf[4] & 0xF0) | (((uint16_t)readBuf[4] & 0x0C) << 10) | ((readBuf[4] & 0x01) << 3) | (((uint16_t)readBuf[5] & 0xC0) << 8) | ((readBuf[5] & 0x30) >> 4);
@ -378,28 +378,30 @@ void XBOXONE::readReport() {
} }
uint16_t XBOXONE::getButtonPress(ButtonEnum b) { uint16_t XBOXONE::getButtonPress(ButtonEnum b) {
if(b == L2) // These are analog buttons const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
if(index == ButtonIndex(L2)) // These are analog buttons
return triggerValue[0]; return triggerValue[0];
else if(b == R2) else if(index == ButtonIndex(R2))
return triggerValue[1]; return triggerValue[1];
return (bool)(ButtonState & ((uint16_t)pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]))); return (bool)(ButtonState & ((uint16_t)pgm_read_word(&XBOX_BUTTONS[index])));
} }
bool XBOXONE::getButtonClick(ButtonEnum b) { bool XBOXONE::getButtonClick(ButtonEnum b) {
if(b == L2) { const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
if(index == ButtonIndex(L2)) {
if(L2Clicked) { if(L2Clicked) {
L2Clicked = false; L2Clicked = false;
return true; return true;
} }
return false; return false;
} else if(b == R2) { } else if(index == ButtonIndex(R2)) {
if(R2Clicked) { if(R2Clicked) {
R2Clicked = false; R2Clicked = false;
return true; return true;
} }
return false; return false;
} }
uint16_t button = pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]); uint16_t button = pgm_read_word(&XBOX_BUTTONS[index]);
bool click = (ButtonClickState & button); bool click = (ButtonClickState & button);
ButtonClickState &= ~button; // Clear "click" event ButtonClickState &= ~button; // Clear "click" event
return click; return click;
@ -414,8 +416,10 @@ uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) {
data[2] = cmdCounter++; // Increment the output command counter data[2] = cmdCounter++; // Increment the output command counter
uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_ONE_OUTPUT_PIPE ].epAddr, nbytes, data); uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_ONE_OUTPUT_PIPE ].epAddr, nbytes, data);
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nXboxCommand, Return: "), 0x80); if(rcode) {
Notify(PSTR("\r\nXboxCommand failed. Return: "), 0x80);
D_PrintHex<uint8_t > (rcode, 0x80); D_PrintHex<uint8_t > (rcode, 0x80);
}
#endif #endif
return rcode; return rcode;
} }

View file

@ -45,6 +45,7 @@
#define XBOX_ONE_PID3 0x02E3 // Microsoft X-Box One Elite pad #define XBOX_ONE_PID3 0x02E3 // Microsoft X-Box One Elite pad
#define XBOX_ONE_PID4 0x02EA // Microsoft X-Box One S pad #define XBOX_ONE_PID4 0x02EA // Microsoft X-Box One S pad
#define XBOX_ONE_PID13 0x0B0A // Microsoft X-Box One Adaptive Controller #define XBOX_ONE_PID13 0x0B0A // Microsoft X-Box One Adaptive Controller
#define XBOX_ONE_PID14 0x0B12 // Microsoft X-Box Core Controller
// Unofficial controllers // Unofficial controllers
#define XBOX_VID2 0x0738 // Mad Catz #define XBOX_VID2 0x0738 // Mad Catz
@ -125,7 +126,8 @@ public:
return ((vid == XBOX_VID1 || vid == XBOX_VID2 || vid == XBOX_VID3 || vid == XBOX_VID4 || vid == XBOX_VID5 || vid == XBOX_VID6) && return ((vid == XBOX_VID1 || vid == XBOX_VID2 || vid == XBOX_VID3 || vid == XBOX_VID4 || vid == XBOX_VID5 || vid == XBOX_VID6) &&
(pid == XBOX_ONE_PID1 || pid == XBOX_ONE_PID2 || pid == XBOX_ONE_PID3 || pid == XBOX_ONE_PID4 || (pid == XBOX_ONE_PID1 || pid == XBOX_ONE_PID2 || pid == XBOX_ONE_PID3 || pid == XBOX_ONE_PID4 ||
pid == XBOX_ONE_PID5 || pid == XBOX_ONE_PID6 || pid == XBOX_ONE_PID7 || pid == XBOX_ONE_PID8 || pid == XBOX_ONE_PID5 || pid == XBOX_ONE_PID6 || pid == XBOX_ONE_PID7 || pid == XBOX_ONE_PID8 ||
pid == XBOX_ONE_PID9 || pid == XBOX_ONE_PID10 || pid == XBOX_ONE_PID11 || pid == XBOX_ONE_PID12 || pid == XBOX_ONE_PID13)); pid == XBOX_ONE_PID9 || pid == XBOX_ONE_PID10 || pid == XBOX_ONE_PID11 || pid == XBOX_ONE_PID12 ||
pid == XBOX_ONE_PID13 || pid == XBOX_ONE_PID14));
}; };
/**@}*/ /**@}*/

88
XBOXONESBT.h Normal file
View file

@ -0,0 +1,88 @@
/* Copyright (C) 2020 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 _xboxonesbt_h_
#define _xboxonesbt_h_
#include "BTHID.h"
#include "XBOXONESParser.h"
/**
* This class implements support for the Xbox One S controller via Bluetooth.
* It uses the BTHID class for all the Bluetooth communication.
*/
class XBOXONESBT : public BTHID, public XBOXONESParser {
public:
/**
* Constructor for the XBOXONESBT class.
* @param p Pointer to the BTD class instance.
* @param pair Set this to true in order to pair with the device. If the argument is omitted then it will not pair with it. One can use ::PAIR to set it to true.
*/
XBOXONESBT(BTD *p, bool pair = false) :
BTHID(p, pair) {
XBOXONESParser::Reset();
pBtd->useSimplePairing = true; // The Xbox One S controller only works via simple pairing
};
/**
* Used to check if a Xbox One S controller is connected.
* @return Returns true if it is connected.
*/
bool connected() {
return BTHID::connected;
};
protected:
/** @name BTHID implementation */
/**
* Used to parse Bluetooth HID data.
* @param len The length of the incoming data.
* @param buf Pointer to the data buffer.
*/
virtual void ParseBTHIDData(uint8_t len, uint8_t *buf) {
XBOXONESParser::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 void OnInitBTHID() {
XBOXONESParser::Reset();
};
/** Used to reset the different buffers to there default values */
virtual void ResetBTHID() {
XBOXONESParser::Reset();
};
/**@}*/
/** @name XBOXONESParser implementation */
virtual void sendOutputReport(uint8_t *data, uint8_t nbytes) {
// See: https://lore.kernel.org/patchwork/patch/973394/
uint8_t buf[nbytes + 2];
buf[0] = 0xA2; // HID BT DATA (0xA0) | Report Type (Output 0x02)
buf[1] = 0x03; // Report ID
memcpy(buf + 2, data, nbytes);
// Send the Bluetooth DATA output report on the interrupt channel
pBtd->L2CAP_Command(hci_handle, buf, sizeof(buf), interrupt_scid[0], interrupt_scid[1]);
};
/**@}*/
};
#endif

221
XBOXONESParser.cpp Normal file
View file

@ -0,0 +1,221 @@
/* Copyright (C) 2020 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
*/
#include "XBOXONESParser.h"
// To enable serial debugging see "settings.h"
//#define PRINTREPORT // Uncomment to print the report send by the Xbox One S Controller
/** Buttons on the controller */
const uint8_t XBOX_ONE_S_BUTTONS[] PROGMEM = {
UP, // UP
RIGHT, // RIGHT
DOWN, // DOWN
LEFT, // LEFT
0x0E, // VIEW
0x0F, // MENU
0x10, // L3
0x11, // R3
0, 0, // Skip L2 and R2 as these are analog buttons
0x0C, // L1
0x0D, // R1
0x09, // B
0x08, // A
0x0A, // X
0x0B, // Y
0, // XBOX - this is sent in another report
};
enum DPADEnum {
DPAD_OFF = 0x0,
DPAD_UP = 0x1,
DPAD_UP_RIGHT = 0x2,
DPAD_RIGHT = 0x3,
DPAD_RIGHT_DOWN = 0x4,
DPAD_DOWN = 0x5,
DPAD_DOWN_LEFT = 0x6,
DPAD_LEFT = 0x7,
DPAD_LEFT_UP = 0x8,
};
int8_t XBOXONESParser::getButtonIndexXboxOneS(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(XBOX_ONE_S_BUTTONS) / sizeof(XBOX_ONE_S_BUTTONS[0]))) return -1;
return index;
}
bool XBOXONESParser::checkDpad(ButtonEnum b) {
switch (b) {
case UP:
return xboxOneSData.btn.dpad == DPAD_LEFT_UP || xboxOneSData.btn.dpad == DPAD_UP || xboxOneSData.btn.dpad == DPAD_UP_RIGHT;
case RIGHT:
return xboxOneSData.btn.dpad == DPAD_UP_RIGHT || xboxOneSData.btn.dpad == DPAD_RIGHT || xboxOneSData.btn.dpad == DPAD_RIGHT_DOWN;
case DOWN:
return xboxOneSData.btn.dpad == DPAD_RIGHT_DOWN || xboxOneSData.btn.dpad == DPAD_DOWN || xboxOneSData.btn.dpad == DPAD_DOWN_LEFT;
case LEFT:
return xboxOneSData.btn.dpad == DPAD_DOWN_LEFT || xboxOneSData.btn.dpad == DPAD_LEFT || xboxOneSData.btn.dpad == DPAD_LEFT_UP;
default:
return false;
}
}
uint16_t XBOXONESParser::getButtonPress(ButtonEnum b) {
const int8_t index = getButtonIndexXboxOneS(b); if (index < 0) return 0;
if (index == ButtonIndex(L2))
return xboxOneSData.trigger[0];
else if (index == ButtonIndex(R2))
return xboxOneSData.trigger[1];
else if (index <= LEFT) // Dpad
return checkDpad(b);
else if (index == ButtonIndex(XBOX))
return xboxButtonState;
return xboxOneSData.btn.val & (1UL << pgm_read_byte(&XBOX_ONE_S_BUTTONS[index]));
}
bool XBOXONESParser::getButtonClick(ButtonEnum b) {
const int8_t index = getButtonIndexXboxOneS(b); if (index < 0) return 0;
if(index == ButtonIndex(L2)) {
if(L2Clicked) {
L2Clicked = false;
return true;
}
return false;
} else if(index == ButtonIndex(R2)) {
if(R2Clicked) {
R2Clicked = false;
return true;
}
return false;
} else if (index == ButtonIndex(XBOX)) {
bool click = xboxbuttonClickState;
xboxbuttonClickState = 0; // Clear "click" event
return click;
}
uint32_t mask = 1UL << pgm_read_byte(&XBOX_ONE_S_BUTTONS[index]);
bool click = buttonClickState.val & mask;
buttonClickState.val &= ~mask; // Clear "click" event
return click;
}
int16_t XBOXONESParser::getAnalogHat(AnalogHatEnum a) {
return xboxOneSData.hatValue[(uint8_t)a] - 32768; // Convert to signed integer
}
void XBOXONESParser::Parse(uint8_t len, uint8_t *buf) {
if (len > 1 && buf) {
#ifdef PRINTREPORT
Notify(PSTR("\r\n"), 0x80);
for (uint8_t i = 0; i < len; i++) {
D_PrintHex<uint8_t > (buf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
#endif
if (buf[0] == 0x01) // Check report ID
memcpy(&xboxOneSData, buf + 1, min((uint8_t)(len - 1), MFK_CASTUINT8T sizeof(xboxOneSData)));
else if (buf[0] == 0x02) { // This report contains the Xbox button
xboxButtonState = buf[1];
if(xboxButtonState != xboxOldButtonState) {
xboxbuttonClickState = xboxButtonState & ~xboxOldButtonState; // Update click state variable
xboxOldButtonState = xboxButtonState;
}
return;
} else if (buf[0] == 0x04) // Heartbeat
return;
else {
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nUnknown report id: "), 0x80);
D_PrintHex<uint8_t > (buf[0], 0x80);
#endif
return;
}
if (xboxOneSData.btn.val != oldButtonState.val) { // Check if anything has changed
buttonClickState.val = xboxOneSData.btn.val & ~oldButtonState.val; // Update click state variable
oldButtonState.val = xboxOneSData.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;
}
}
// Handle click detection for triggers
if(xboxOneSData.trigger[0] != 0 && triggerOld[0] == 0)
L2Clicked = true;
triggerOld[0] = xboxOneSData.trigger[0];
if(xboxOneSData.trigger[1] != 0 && triggerOld[1] == 0)
R2Clicked = true;
triggerOld[1] = xboxOneSData.trigger[1];
}
}
void XBOXONESParser::Reset() {
uint8_t i;
for (i = 0; i < sizeof(xboxOneSData.hatValue) / sizeof(xboxOneSData.hatValue[0]); i++)
xboxOneSData.hatValue[i] = 32768; // Center value
xboxOneSData.btn.val = 0;
oldButtonState.val = 0;
for (i = 0; i < sizeof(xboxOneSData.trigger) / sizeof(xboxOneSData.trigger[0]); i++)
xboxOneSData.trigger[i] = 0;
xboxOneSData.btn.dpad = DPAD_OFF;
oldButtonState.dpad = DPAD_OFF;
buttonClickState.dpad = 0;
oldDpad = 0;
};
void XBOXONESParser::setRumbleOff() {
// See: https://lore.kernel.org/patchwork/patch/973394/
uint8_t buf[8];
buf[0] = 0x0F; // Disable all rumble motors
buf[1] = 0;
buf[2] = 0;
buf[3] = 0;
buf[4] = 0;
buf[5] = 0; // Duration of effect in 10 ms
buf[6] = 0; // Start delay in 10 ms
buf[7] = 0; // Loop count
sendOutputReport(buf, sizeof(buf));
}
void XBOXONESParser::setRumbleOn(uint8_t leftTrigger, uint8_t rightTrigger, uint8_t leftMotor, uint8_t rightMotor) {
// See: https://lore.kernel.org/patchwork/patch/973394/
uint8_t buf[8];
buf[0] = 1 << 3 /* Left trigger */ | 1 << 2 /* Right trigger */ | 1 << 1 /* Left motor */ | 1 << 0 /* Right motor */;
buf[1] = leftTrigger;
buf[2] = rightTrigger;
buf[3] = leftMotor;
buf[4] = rightMotor;
buf[5] = 255; // Duration of effect in 10 ms
buf[6] = 0; // Start delay in 10 ms
buf[7] = 255; // Loop count
sendOutputReport(buf, sizeof(buf));
}

129
XBOXONESParser.h Normal file
View file

@ -0,0 +1,129 @@
/* Copyright (C) 2020 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 _xboxonesparser_h_
#define _xboxonesparser_h_
#include "Usb.h"
#include "controllerEnums.h"
union XboxOneSButtons {
struct {
uint8_t dpad : 4;
uint8_t reserved : 4;
uint8_t a : 1;
uint8_t b : 1;
uint8_t x : 1;
uint8_t y : 1;
uint8_t l1 : 1;
uint8_t r1 : 1;
uint8_t view : 1;
uint8_t menu : 1;
uint8_t l3 : 1;
uint8_t r3 : 1;
uint8_t reserved2 : 6;
} __attribute__((packed));
uint32_t val : 24;
} __attribute__((packed));
struct XboxOneSData {
/* Button and joystick values */
uint16_t hatValue[4];
uint16_t trigger[2];
XboxOneSButtons btn;
} __attribute__((packed));
/** This class parses all the data sent by the Xbox One S controller */
class XBOXONESParser {
public:
/** Constructor for the XBOXONESParser class. */
XBOXONESParser() {
Reset();
};
/** @name Xbox One S Controller functions */
/**
* getButtonPress(ButtonEnum b) will return true as long as the button is held down.
*
* While getButtonClick(ButtonEnum b) will only return it once.
*
* So you instance if you need to increase a variable once you would use getButtonClick(ButtonEnum b),
* but if you need to drive a robot forward you would use getButtonPress(ButtonEnum b).
* @param b ::ButtonEnum to read.
* @return getButtonPress(ButtonEnum b) will return a true as long as a button is held down, while getButtonClick(ButtonEnum b) will return true once for each button press.
*/
uint16_t getButtonPress(ButtonEnum b);
bool getButtonClick(ButtonEnum b);
/**@}*/
/**
* Used to read the analog joystick.
* @param a ::LeftHatX, ::LeftHatY, ::RightHatX, and ::RightHatY.
* @return Return the analog value as a 16-bit signed integer.
*/
int16_t getAnalogHat(AnalogHatEnum a);
/** Used to set the rumble off. */
void setRumbleOff();
/**
* Used to turn on rumble continuously.
* @param leftTrigger Left trigger force.
* @param rightTrigger Right trigger force.
* @param leftMotor Left motor force.
* @param rightMotor Right motor force.
*/
void setRumbleOn(uint8_t leftTrigger, uint8_t rightTrigger, uint8_t leftMotor, uint8_t rightMotor);
protected:
/**
* Used to parse data sent from the Xbox One S controller.
* @param len Length of the data.
* @param buf Pointer to the data buffer.
*/
void Parse(uint8_t len, uint8_t *buf);
/** Used to reset the different buffers to their default values */
void Reset();
/**
* Send the output to the Xbox One S controller. This is implemented in XBOXONESBT.h.
* @param output Pointer to data buffer.
* @param nbytes Bytes to send.
*/
virtual void sendOutputReport(uint8_t *data, uint8_t nbytes) = 0;
private:
static int8_t getButtonIndexXboxOneS(ButtonEnum b);
bool checkDpad(ButtonEnum b); // Used to check Xbox One S DPAD buttons
XboxOneSData xboxOneSData;
XboxOneSButtons oldButtonState, buttonClickState;
uint8_t oldDpad;
// The Xbox button is sent in a separate report
uint8_t xboxButtonState, xboxOldButtonState, xboxbuttonClickState;
uint16_t triggerOld[2];
bool L2Clicked; // These buttons are analog, so we use we use these bools to check if they where clicked or not
bool R2Clicked;
};
#endif

View file

@ -89,7 +89,7 @@ uint8_t XBOXRECV::ConfigureDevice(uint8_t parent, uint8_t port, bool lowspeed) {
VID = udd->idVendor; VID = udd->idVendor;
PID = udd->idProduct; PID = udd->idProduct;
if((VID != XBOX_VID && VID != MADCATZ_VID && VID != JOYTECH_VID) || (PID != XBOX_WIRELESS_RECEIVER_PID && PID != XBOX_WIRELESS_RECEIVER_THIRD_PARTY_PID)) { // Check if it's a Xbox receiver using the Vendor ID and Product ID if((VID != XBOX_VID && VID != MADCATZ_VID && VID != JOYTECH_VID) || (PID != XBOX_WIRELESS_RECEIVER_PID_1 && PID != XBOX_WIRELESS_RECEIVER_PID_2 && PID != XBOX_WIRELESS_RECEIVER_THIRD_PARTY_PID)) { // Check if it's a Xbox receiver using the Vendor ID and Product ID
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nYou'll need a wireless receiver for this libary to work"), 0x80); Notify(PSTR("\r\nYou'll need a wireless receiver for this libary to work"), 0x80);
#endif #endif
@ -408,28 +408,30 @@ void XBOXRECV::printReport(uint8_t controller __attribute__((unused)), uint8_t n
} }
uint8_t XBOXRECV::getButtonPress(ButtonEnum b, uint8_t controller) { uint8_t XBOXRECV::getButtonPress(ButtonEnum b, uint8_t controller) {
if(b == L2) // These are analog buttons const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
if(index == ButtonIndex(L2)) // These are analog buttons
return (uint8_t)(ButtonState[controller] >> 8); return (uint8_t)(ButtonState[controller] >> 8);
else if(b == R2) else if(index == ButtonIndex(R2))
return (uint8_t)ButtonState[controller]; return (uint8_t)ButtonState[controller];
return (bool)(ButtonState[controller] & ((uint32_t)pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]) << 16)); return (bool)(ButtonState[controller] & ((uint32_t)pgm_read_word(&XBOX_BUTTONS[index]) << 16));
} }
bool XBOXRECV::getButtonClick(ButtonEnum b, uint8_t controller) { bool XBOXRECV::getButtonClick(ButtonEnum b, uint8_t controller) {
if(b == L2) { const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
if(index == ButtonIndex(L2)) {
if(L2Clicked[controller]) { if(L2Clicked[controller]) {
L2Clicked[controller] = false; L2Clicked[controller] = false;
return true; return true;
} }
return false; return false;
} else if(b == R2) { } else if(index == ButtonIndex(R2)) {
if(R2Clicked[controller]) { if(R2Clicked[controller]) {
R2Clicked[controller] = false; R2Clicked[controller] = false;
return true; return true;
} }
return false; return false;
} }
uint16_t button = pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]); uint16_t button = pgm_read_word(&XBOX_BUTTONS[index]);
bool click = (ButtonClickState[controller] & button); bool click = (ButtonClickState[controller] & button);
ButtonClickState[controller] &= ~button; // clear "click" event ButtonClickState[controller] &= ~button; // clear "click" event
return click; return click;

View file

@ -42,7 +42,8 @@
#define MADCATZ_VID 0x1BAD // For unofficial Mad Catz receivers #define MADCATZ_VID 0x1BAD // For unofficial Mad Catz receivers
#define JOYTECH_VID 0x162E // For unofficial Joytech controllers #define JOYTECH_VID 0x162E // For unofficial Joytech controllers
#define XBOX_WIRELESS_RECEIVER_PID 0x0719 // Microsoft Wireless Gaming Receiver #define XBOX_WIRELESS_RECEIVER_PID_1 0x0719 // Microsoft Wireless Gaming Receiver
#define XBOX_WIRELESS_RECEIVER_PID_2 0x02A9 // Microsoft Wireless Gaming Receiver
#define XBOX_WIRELESS_RECEIVER_THIRD_PARTY_PID 0x0291 // Third party Wireless Gaming Receiver #define XBOX_WIRELESS_RECEIVER_THIRD_PARTY_PID 0x0291 // Third party Wireless Gaming Receiver
#define XBOX_MAX_ENDPOINTS 9 #define XBOX_MAX_ENDPOINTS 9
@ -111,7 +112,7 @@ public:
* @return Returns true if the device's VID and PID matches this driver. * @return Returns true if the device's VID and PID matches this driver.
*/ */
virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) { virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
return ((vid == XBOX_VID || vid == MADCATZ_VID || vid == JOYTECH_VID) && (pid == XBOX_WIRELESS_RECEIVER_PID || pid == XBOX_WIRELESS_RECEIVER_THIRD_PARTY_PID)); return ((vid == XBOX_VID || vid == MADCATZ_VID || vid == JOYTECH_VID) && (pid == XBOX_WIRELESS_RECEIVER_PID_1 || pid == XBOX_WIRELESS_RECEIVER_PID_2 || pid == XBOX_WIRELESS_RECEIVER_THIRD_PARTY_PID));
}; };
/**@}*/ /**@}*/

View file

@ -281,28 +281,30 @@ void XBOXUSB::printReport() { //Uncomment "#define PRINTREPORT" to print the rep
} }
uint8_t XBOXUSB::getButtonPress(ButtonEnum b) { uint8_t XBOXUSB::getButtonPress(ButtonEnum b) {
if(b == L2) // These are analog buttons const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
if(index == ButtonIndex(L2)) // These are analog buttons
return (uint8_t)(ButtonState >> 8); return (uint8_t)(ButtonState >> 8);
else if(b == R2) else if(index == ButtonIndex(R2))
return (uint8_t)ButtonState; return (uint8_t)ButtonState;
return (bool)(ButtonState & ((uint32_t)pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]) << 16)); return (bool)(ButtonState & ((uint32_t)pgm_read_word(&XBOX_BUTTONS[index]) << 16));
} }
bool XBOXUSB::getButtonClick(ButtonEnum b) { bool XBOXUSB::getButtonClick(ButtonEnum b) {
if(b == L2) { const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
if(index == ButtonIndex(L2)) {
if(L2Clicked) { if(L2Clicked) {
L2Clicked = false; L2Clicked = false;
return true; return true;
} }
return false; return false;
} else if(b == R2) { } else if(index == ButtonIndex(R2)) {
if(R2Clicked) { if(R2Clicked) {
R2Clicked = false; R2Clicked = false;
return true; return true;
} }
return false; return false;
} }
uint16_t button = pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]); uint16_t button = pgm_read_word(&XBOX_BUTTONS[index]);
bool click = (ButtonClickState & button); bool click = (ButtonClickState & button);
ButtonClickState &= ~button; // clear "click" event ButtonClickState &= ~button; // clear "click" event
return click; return click;

149
avrpins.h
View file

@ -814,6 +814,7 @@ public:
#define pgm_read_pointer(p) pgm_read_dword(p) #define pgm_read_pointer(p) pgm_read_dword(p)
#if defined(CORE_TEENSY) && (defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)) #if defined(CORE_TEENSY) && (defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__))
// Teensy 3.x
#include "core_pins.h" #include "core_pins.h"
#include "avr_emulation.h" #include "avr_emulation.h"
@ -913,6 +914,7 @@ MAKE_PIN(P63, CORE_PIN63_PORTREG, CORE_PIN63_BIT, CORE_PIN63_CONFIG);
#undef MAKE_PIN #undef MAKE_PIN
#elif defined(CORE_TEENSY) && (defined(__MKL26Z64__)) #elif defined(CORE_TEENSY) && (defined(__MKL26Z64__))
// Teensy-LC
// we could get lower level by making these macros work properly. // we could get lower level by making these macros work properly.
// for now just use the semi optimised version, it costs a lookup in the pin pgm table per op // for now just use the semi optimised version, it costs a lookup in the pin pgm table per op
@ -976,6 +978,91 @@ MAKE_PIN(P26, CORE_PIN26_PORTREG, 26, CORE_PIN26_CONFIG);
#undef MAKE_PIN #undef MAKE_PIN
#elif defined(__IMXRT1062__) && (defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41))
// Teensy 4.x
#include "core_pins.h"
#define MAKE_PIN(className, pin) \
class className { \
public: \
static void Set() { \
digitalWriteFast(pin, HIGH);\
} \
static void Clear() { \
digitalWriteFast(pin, LOW); \
} \
static void SetDirRead() { \
pinMode(pin, INPUT); \
} \
static void SetDirWrite() { \
pinMode(pin, OUTPUT); \
} \
static uint8_t IsSet() { \
return digitalReadFast(pin); \
} \
};
MAKE_PIN(P0, 0);
MAKE_PIN(P1, 1);
MAKE_PIN(P2, 2);
MAKE_PIN(P3, 3);
MAKE_PIN(P4, 4);
MAKE_PIN(P5, 5);
MAKE_PIN(P6, 6);
MAKE_PIN(P7, 7);
MAKE_PIN(P8, 8);
MAKE_PIN(P9, 9);
MAKE_PIN(P10, 10);
MAKE_PIN(P11, 11);
MAKE_PIN(P12, 12);
MAKE_PIN(P13, 13);
MAKE_PIN(P14, 14);
MAKE_PIN(P15, 15);
MAKE_PIN(P16, 16);
MAKE_PIN(P17, 17);
MAKE_PIN(P18, 18);
MAKE_PIN(P19, 19);
MAKE_PIN(P20, 20);
MAKE_PIN(P21, 21);
MAKE_PIN(P22, 22);
MAKE_PIN(P23, 23);
MAKE_PIN(P24, 24);
MAKE_PIN(P25, 25);
MAKE_PIN(P26, 26);
MAKE_PIN(P27, 27);
MAKE_PIN(P28, 28);
MAKE_PIN(P29, 29);
MAKE_PIN(P30, 30);
MAKE_PIN(P31, 31);
MAKE_PIN(P32, 35);
MAKE_PIN(P33, 33);
MAKE_PIN(P34, 34);
MAKE_PIN(P35, 35);
MAKE_PIN(P36, 36);
MAKE_PIN(P37, 37);
MAKE_PIN(P38, 38);
MAKE_PIN(P39, 39);
#ifdef ARDUINO_TEENSY41
MAKE_PIN(P40, 40);
MAKE_PIN(P41, 41);
MAKE_PIN(P42, 42);
MAKE_PIN(P43, 43);
MAKE_PIN(P44, 44);
MAKE_PIN(P45, 45);
MAKE_PIN(P46, 46);
MAKE_PIN(P47, 47);
MAKE_PIN(P48, 48);
MAKE_PIN(P49, 49);
MAKE_PIN(P50, 50);
MAKE_PIN(P51, 51);
MAKE_PIN(P52, 52);
MAKE_PIN(P53, 53);
MAKE_PIN(P54, 54);
#endif
#undef MAKE_PIN
#elif defined(ARDUINO_SAM_DUE) && defined(__SAM3X8E__) #elif defined(ARDUINO_SAM_DUE) && defined(__SAM3X8E__)
// SetDirRead: // SetDirRead:
@ -1207,6 +1294,68 @@ MAKE_PIN(P19, GPIOC, GPIO_PIN_0); // A5
#undef MAKE_PIN #undef MAKE_PIN
#elif defined(ARDUINO_NRF52840_FEATHER)
#define MAKE_PIN(className, pin) \
class className { \
public: \
static void Set() { \
nrf_gpio_pin_set(pin); \
} \
static void Clear() { \
nrf_gpio_pin_clear(pin); \
} \
static void SetDirRead() { \
nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL); \
} \
static void SetDirWrite() { \
nrf_gpio_cfg_output(pin); \
} \
static uint8_t IsSet() { \
return (uint8_t)nrf_gpio_pin_read(pin); \
} \
};
// Based on variants/feather_nrf52840_express/variant.cpp
// g_ADigitalPinMap could be used directly, but it would be slower
MAKE_PIN(P0, (25));
MAKE_PIN(P1, (24));
MAKE_PIN(P2, (10));
MAKE_PIN(P3, (47));
MAKE_PIN(P4, (42));
MAKE_PIN(P5, (40));
MAKE_PIN(P6, (7));
MAKE_PIN(P7, (34));
MAKE_PIN(P8, (16));
MAKE_PIN(P9, (26));
MAKE_PIN(P10, (27));
MAKE_PIN(P11, (6));
MAKE_PIN(P12, (8));
MAKE_PIN(P13, (41));
MAKE_PIN(P14, (4));
MAKE_PIN(P15, (5));
MAKE_PIN(P17, (30));
MAKE_PIN(P18, (28));
MAKE_PIN(P16, (2));
MAKE_PIN(P19, (3));
MAKE_PIN(P20, (29));
MAKE_PIN(P21, (31));
MAKE_PIN(P22, (12));
MAKE_PIN(P23, (11));
MAKE_PIN(P24, (15));
MAKE_PIN(P25, (13));
MAKE_PIN(P26, (14));
MAKE_PIN(P27, (19));
MAKE_PIN(P28, (20));
MAKE_PIN(P29, (17));
MAKE_PIN(P30, (22));
MAKE_PIN(P31, (23));
MAKE_PIN(P32, (21));
MAKE_PIN(P33, (9));
#undef MAKE_PIN
#else #else
#error "Please define board in avrpins.h" #error "Please define board in avrpins.h"

View file

@ -108,12 +108,14 @@ bool ConfigDescParser<CLASS_ID, SUBCLASS_ID, PROTOCOL_ID, MASK>::ParseDescriptor
theBuffer.valueSize = 2; theBuffer.valueSize = 2;
valParser.Initialize(&theBuffer); valParser.Initialize(&theBuffer);
stateParseDescr = 1; stateParseDescr = 1;
// fall through
case 1: case 1:
if(!valParser.Parse(pp, pcntdn)) if(!valParser.Parse(pp, pcntdn))
return false; return false;
dscrLen = *((uint8_t*)theBuffer.pValue); dscrLen = *((uint8_t*)theBuffer.pValue);
dscrType = *((uint8_t*)theBuffer.pValue + 1); dscrType = *((uint8_t*)theBuffer.pValue + 1);
stateParseDescr = 2; stateParseDescr = 2;
// fall through
case 2: case 2:
// This is a sort of hack. Assuming that two bytes are all ready in the buffer // This is a sort of hack. Assuming that two bytes are all ready in the buffer
// the pointer is positioned two bytes ahead in order for the rest of descriptor // the pointer is positioned two bytes ahead in order for the rest of descriptor
@ -122,6 +124,7 @@ bool ConfigDescParser<CLASS_ID, SUBCLASS_ID, PROTOCOL_ID, MASK>::ParseDescriptor
// in the buffer. // in the buffer.
theBuffer.pValue = varBuffer + 2; theBuffer.pValue = varBuffer + 2;
stateParseDescr = 3; stateParseDescr = 3;
// fall through
case 3: case 3:
switch(dscrType) { switch(dscrType) {
case USB_DESCRIPTOR_INTERFACE: case USB_DESCRIPTOR_INTERFACE:
@ -135,6 +138,7 @@ bool ConfigDescParser<CLASS_ID, SUBCLASS_ID, PROTOCOL_ID, MASK>::ParseDescriptor
theBuffer.valueSize = dscrLen - 2; theBuffer.valueSize = dscrLen - 2;
valParser.Initialize(&theBuffer); valParser.Initialize(&theBuffer);
stateParseDescr = 4; stateParseDescr = 4;
// fall through
case 4: case 4:
switch(dscrType) { switch(dscrType) {
case USB_DESCRIPTOR_CONFIGURATION: case USB_DESCRIPTOR_CONFIGURATION:

View file

@ -77,7 +77,7 @@ enum RumbleEnum {
/** This enum is used to read all the different buttons on the different controllers */ /** This enum is used to read all the different buttons on the different controllers */
enum ButtonEnum { enum ButtonEnum {
/**@{*/ /**@{*/
/** These buttons are available on all the the controllers */ /** Directional Pad Buttons - available on most controllers */
UP = 0, UP = 0,
RIGHT = 1, RIGHT = 1,
DOWN = 2, DOWN = 2,
@ -85,74 +85,135 @@ enum ButtonEnum {
/**@}*/ /**@}*/
/**@{*/ /**@{*/
/** Wii buttons */ /** Playstation buttons */
PLUS = 5, TRIANGLE,
TWO = 6, CIRCLE,
ONE = 7, CROSS,
MINUS = 8, SQUARE,
HOME = 9,
Z = 10, SELECT,
C = 11, START,
B = 12,
A = 13, L3,
R3,
L1,
R1,
L2,
R2,
PS,
/**@}*/ /**@}*/
/**@{*/ /**@{*/
/** These are only available on the Wii U Pro Controller */ /** PS3 Move Controller */
L = 16, MOVE, // Covers 12 bits - we only need to read the top 8
R = 17, T, // Covers 12 bits - we only need to read the top 8
ZL = 18,
ZR = 19,
/**@}*/ /**@}*/
/**@{*/ /**@{*/
/** PS3 controllers buttons */ /** PS Buzz controllers */
SELECT = 4, RED,
START = 5, YELLOW,
L3 = 6, GREEN,
R3 = 7, ORANGE,
BLUE,
L2 = 8,
R2 = 9,
L1 = 10,
R1 = 11,
TRIANGLE = 12,
CIRCLE = 13,
CROSS = 14,
SQUARE = 15,
PS = 16,
MOVE = 17, // Covers 12 bits - we only need to read the top 8
T = 18, // Covers 12 bits - we only need to read the top 8
/**@}*/ /**@}*/
/** PS4 controllers buttons - SHARE and OPTIONS are present instead of SELECT and START */ /**@{*/
SHARE = 4, /** PS4 buttons - SHARE and OPTIONS are present instead of SELECT and START */
OPTIONS = 5, SHARE,
TOUCHPAD = 17, OPTIONS,
TOUCHPAD,
/**@}*/
/**@{*/
/** PS5 buttons */
CREATE,
MICROPHONE,
/**@}*/ /**@}*/
/**@{*/ /**@{*/
/** Xbox buttons */ /** Xbox buttons */
BACK = 4, A,
X = 14, B,
Y = 15, X,
XBOX = 16, Y,
SYNC = 17,
BLACK = 8, // Available on the original Xbox controller BACK,
WHITE = 9, // Available on the original Xbox controller // START, // listed under Playstation buttons
// L1, // listed under Playstation buttons
// R1, // listed under Playstation buttons
// L2, // listed under Playstation buttons
// R2, // listed under Playstation buttons
XBOX,
SYNC,
BLACK, // Available on the original Xbox controller
WHITE, // Available on the original Xbox controller
/**@}*/ /**@}*/
/** PS Buzz controllers */ /**@{*/
RED = 0, /** Xbox One S buttons */
YELLOW = 1, VIEW,
GREEN = 2, MENU,
ORANGE = 3, /**@}*/
BLUE = 4,
/**@{*/
/** Wii buttons */
PLUS,
TWO,
ONE,
MINUS,
HOME,
Z,
C,
// B, // listed under Xbox buttons
// A, // listed under Xbox buttons
/**@}*/
/**@{*/
/** Wii U Pro Controller */
L,
R,
ZL,
ZR,
/**@}*/
/**@{*/
/** Switch Pro Controller */
CAPTURE,
/**@}*/ /**@}*/
}; };
inline constexpr int8_t ButtonIndex(ButtonEnum key) {
// using a chained ternary in place of a switch for constexpr on older compilers
return
(key == UP || key == RED) ? 0 :
(key == RIGHT || key == YELLOW) ? 1 :
(key == DOWN || key == GREEN) ? 2 :
(key == LEFT || key == ORANGE) ? 3 :
(key == SELECT || key == SHARE || key == BACK || key == VIEW || key == BLUE || key == CREATE || key == CAPTURE) ? 4 :
(key == START || key == OPTIONS || key == MENU || key == PLUS) ? 5 :
(key == L3 || key == TWO) ? 6 :
(key == R3 || key == ONE) ? 7 :
(key == L2 || key == MINUS || key == BLACK) ? 8 :
(key == R2 || key == HOME || key == WHITE) ? 9 :
(key == L1 || key == Z) ? 10 :
(key == R1 || key == C) ? 11 :
(key == TRIANGLE || key == B) ? 12 :
(key == CIRCLE || key == A) ? 13 :
(key == CROSS || key == X) ? 14 :
(key == SQUARE || key == Y) ? 15 :
(key == L || key == PS || key == XBOX) ? 16 :
(key == R || key == MOVE || key == TOUCHPAD || key == SYNC) ? 17 :
(key == ZL || key == T || key == MICROPHONE) ? 18 :
(key == ZR) ? 19 :
-1; // not a match
}
/** Joysticks on the PS3 and Xbox controllers. */ /** Joysticks on the PS3 and Xbox controllers. */
enum AnalogHatEnum { enum AnalogHatEnum {
/** Left joystick x-axis */ /** Left joystick x-axis */

View file

@ -25,7 +25,7 @@ BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so
BTHID bthid(&Btd, PAIR, "0000"); BTHID bthid(&Btd, PAIR, "0000");
// After that you can simply create the instance like so and then press any button on the device // After that you can simply create the instance like so and then press any button on the device
//BTHID hid(&Btd); //BTHID bthid(&Btd);
KbdRptParser keyboardPrs; KbdRptParser keyboardPrs;
MouseRptParser mousePrs; MouseRptParser mousePrs;

View file

@ -0,0 +1,170 @@
/*
Example sketch for the PS5 Bluetooth 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 <PS5BT.h>
#include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb;
//USBHub Hub1(&Usb); // Some dongles have a hub inside
BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so
/* You can create the instance of the PS5BT class in two ways */
// This will start an inquiry and then pair with the PS5 controller - you only have to do this once
// You will need to hold down the PS and Share button at the same time, the PS5 controller will then start to blink rapidly indicating that it is in pairing mode
PS5BT PS5(&Btd, PAIR);
// After that you can simply create the instance like so and then press the PS button on the device
//PS5BT PS5(&Btd);
bool printAngle = false, printTouch = false;
uint16_t lastMessageCounter = -1;
uint8_t player_led_mask = 0;
bool microphone_led = false;
uint32_t ps_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\nPS5 Bluetooth Library Started"));
}
void loop() {
Usb.Task();
if (PS5.connected() && lastMessageCounter != PS5.getMessageCounter()) {
lastMessageCounter = PS5.getMessageCounter();
if (PS5.getAnalogHat(LeftHatX) > 137 || PS5.getAnalogHat(LeftHatX) < 117 || PS5.getAnalogHat(LeftHatY) > 137 || PS5.getAnalogHat(LeftHatY) < 117 || PS5.getAnalogHat(RightHatX) > 137 || PS5.getAnalogHat(RightHatX) < 117 || PS5.getAnalogHat(RightHatY) > 137 || PS5.getAnalogHat(RightHatY) < 117) {
Serial.print(F("\r\nLeftHatX: "));
Serial.print(PS5.getAnalogHat(LeftHatX));
Serial.print(F("\tLeftHatY: "));
Serial.print(PS5.getAnalogHat(LeftHatY));
Serial.print(F("\tRightHatX: "));
Serial.print(PS5.getAnalogHat(RightHatX));
Serial.print(F("\tRightHatY: "));
Serial.print(PS5.getAnalogHat(RightHatY));
}
if (PS5.getAnalogButton(L2) || PS5.getAnalogButton(R2)) { // These are the only analog buttons on the PS5 controller
Serial.print(F("\r\nL2: "));
Serial.print(PS5.getAnalogButton(L2));
Serial.print(F("\tR2: "));
Serial.print(PS5.getAnalogButton(R2));
}
// Set the left trigger to resist at the right trigger's level
static uint8_t oldR2Value = 0xFF;
if (PS5.getAnalogButton(R2) != oldR2Value) {
oldR2Value = PS5.getAnalogButton(R2);
PS5.leftTrigger.setTriggerForce(oldR2Value, 255);
}
// Hold the PS button for 1 second to disconnect the controller
// This prevents the controller from disconnecting when it is reconnected,
// as the PS button is sent when it reconnects
if (PS5.getButtonPress(PS)) {
if (millis() - ps_timer > 1000)
PS5.disconnect();
} else
ps_timer = millis();
if (PS5.getButtonClick(PS))
Serial.print(F("\r\nPS"));
if (PS5.getButtonClick(TRIANGLE)) {
Serial.print(F("\r\nTriangle"));
PS5.setRumbleOn(RumbleLow);
}
if (PS5.getButtonClick(CIRCLE)) {
Serial.print(F("\r\nCircle"));
PS5.setRumbleOn(RumbleHigh);
}
if (PS5.getButtonClick(CROSS)) {
Serial.print(F("\r\nCross"));
// Set the player LEDs
player_led_mask = (player_led_mask << 1) | 1;
if (player_led_mask > 0x1F)
player_led_mask = 0;
PS5.setPlayerLed(player_led_mask); // The bottom 5 bits set player LEDs
}
if (PS5.getButtonClick(SQUARE)) {
Serial.print(F("\r\nSquare"));
PS5.setRumbleOff();
}
if (PS5.getButtonClick(UP)) {
Serial.print(F("\r\nUp"));
PS5.setLed(Red);
} if (PS5.getButtonClick(RIGHT)) {
Serial.print(F("\r\nRight"));
PS5.setLed(Blue);
} if (PS5.getButtonClick(DOWN)) {
Serial.print(F("\r\nDown"));
PS5.setLed(Yellow);
} if (PS5.getButtonClick(LEFT)) {
Serial.print(F("\r\nLeft"));
PS5.setLed(Green);
}
if (PS5.getButtonClick(L1))
Serial.print(F("\r\nL1"));
if (PS5.getButtonClick(L3))
Serial.print(F("\r\nL3"));
if (PS5.getButtonClick(R1))
Serial.print(F("\r\nR1"));
if (PS5.getButtonClick(R3))
Serial.print(F("\r\nR3"));
if (PS5.getButtonClick(CREATE))
Serial.print(F("\r\nCreate"));
if (PS5.getButtonClick(OPTIONS)) {
Serial.print(F("\r\nOptions"));
printAngle = !printAngle;
}
if (PS5.getButtonClick(TOUCHPAD)) {
Serial.print(F("\r\nTouchpad"));
printTouch = !printTouch;
}
if (PS5.getButtonClick(MICROPHONE)) {
Serial.print(F("\r\nMicrophone"));
microphone_led = !microphone_led;
PS5.setMicLed(microphone_led);
}
if (printAngle) { // Print angle calculated using the accelerometer only
Serial.print(F("\r\nPitch: "));
Serial.print(PS5.getAngle(Pitch));
Serial.print(F("\tRoll: "));
Serial.print(PS5.getAngle(Roll));
}
if (printTouch) { // Print the x, y coordinates of the touchpad
if (PS5.isTouching(0) || PS5.isTouching(1)) // Print newline and carriage return if any of the fingers are touching the touchpad
Serial.print(F("\r\n"));
for (uint8_t i = 0; i < 2; i++) { // The touchpad track two fingers
if (PS5.isTouching(i)) { // Print the position of the finger if it is touching the touchpad
Serial.print(F("X")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getX(i));
Serial.print(F("\tY")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getY(i));
Serial.print(F("\t"));
}
}
}
}
}

View file

@ -0,0 +1,152 @@
/*
Example sketch for the Switch Pro Bluetooth 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 <SwitchProBT.h>
#include <usbhub.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;
//USBHub Hub1(&Usb); // Some dongles have a hub inside
BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so
/* You can create the instance of the SwitchProBT class in two ways */
// This will start an inquiry and then pair with the Switch Pro controller - you only have to do this once
// You will need to press the Sync button next to the USB connector to put the controller into pairing mode.
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;
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 Bluetooth 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));
}
// Hold the CAPTURE button for 1 second to disconnect the controller
// This prevents the controller from disconnecting when it is reconnected,
// as the CAPTURE button is sent when it reconnects
if (SwitchPro.getButtonPress(CAPTURE)) {
if (millis() - capture_timer > 1000)
SwitchPro.disconnect();
} else
capture_timer = millis();
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"));
Serial.print(F("\r\nBattery level: "));
Serial.print(SwitchPro.getBatteryLevel());
Serial.print(F(", charging: "));
Serial.print(SwitchPro.isCharging());
}
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

@ -1,7 +1,8 @@
/* Parser for standard HID scale (usage page 0x8d) data input report (ID 3) */ #if defined(ARDUINO_SAM_DUE) || defined(ARDUINO_NRF52840_FEATHER)
#ifdef ARDUINO_SAM_DUE
#include <avr/dtostrf.h> #include <avr/dtostrf.h>
#endif #endif
/* Parser for standard HID scale (usage page 0x8d) data input report (ID 3) */
#include "scale_rptparser.h" #include "scale_rptparser.h"
const char* UNITS[13] = { const char* UNITS[13] = {

View file

@ -0,0 +1,47 @@
/*
Example sketch for the MiniDSP 2x4HD library - developed by Dennis Frett
*/
#include <MiniDSP.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb;
MiniDSP MiniDSP(&Usb);
void OnMiniDSPConnected() {
Serial.println("MiniDSP connected");
}
void OnVolumeChange(uint8_t volume) {
Serial.println("Volume is: " + String(volume));
}
void OnMutedChange(bool isMuted) {
Serial.println("Muted status: " + String(isMuted ? "muted" : "unmuted"));
}
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.println(F("\r\nMiniDSP 2x4HD Library Started"));
// Register callbacks.
MiniDSP.attachOnInit(&OnMiniDSPConnected);
MiniDSP.attachOnVolumeChange(&OnVolumeChange);
MiniDSP.attachOnMutedChange(&OnMutedChange);
}
void loop() {
Usb.Task();
}

150
examples/PS5USB/PS5USB.ino Normal file
View file

@ -0,0 +1,150 @@
/*
Example sketch for the PS5 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 <PS5USB.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb;
PS5USB PS5(&Usb);
bool printAngle = false, printTouch = false;
uint16_t lastMessageCounter = -1;
uint8_t player_led_mask = 0;
bool microphone_led = false;
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\nPS5 USB Library Started"));
}
void loop() {
Usb.Task();
if (PS5.connected() && lastMessageCounter != PS5.getMessageCounter()) {
lastMessageCounter = PS5.getMessageCounter();
if (PS5.getAnalogHat(LeftHatX) > 137 || PS5.getAnalogHat(LeftHatX) < 117 || PS5.getAnalogHat(LeftHatY) > 137 || PS5.getAnalogHat(LeftHatY) < 117 || PS5.getAnalogHat(RightHatX) > 137 || PS5.getAnalogHat(RightHatX) < 117 || PS5.getAnalogHat(RightHatY) > 137 || PS5.getAnalogHat(RightHatY) < 117) {
Serial.print(F("\r\nLeftHatX: "));
Serial.print(PS5.getAnalogHat(LeftHatX));
Serial.print(F("\tLeftHatY: "));
Serial.print(PS5.getAnalogHat(LeftHatY));
Serial.print(F("\tRightHatX: "));
Serial.print(PS5.getAnalogHat(RightHatX));
Serial.print(F("\tRightHatY: "));
Serial.print(PS5.getAnalogHat(RightHatY));
}
if (PS5.getAnalogButton(L2) || PS5.getAnalogButton(R2)) { // These are the only analog buttons on the PS5 controller
Serial.print(F("\r\nL2: "));
Serial.print(PS5.getAnalogButton(L2));
Serial.print(F("\tR2: "));
Serial.print(PS5.getAnalogButton(R2));
}
// Set the left trigger to resist at the right trigger's level
static uint8_t oldR2Value = 0xFF;
if (PS5.getAnalogButton(R2) != oldR2Value) {
oldR2Value = PS5.getAnalogButton(R2);
PS5.leftTrigger.setTriggerForce(oldR2Value, 255);
}
if (PS5.getButtonClick(PS))
Serial.print(F("\r\nPS"));
if (PS5.getButtonClick(TRIANGLE)) {
Serial.print(F("\r\nTriangle"));
PS5.setRumbleOn(RumbleLow);
}
if (PS5.getButtonClick(CIRCLE)) {
Serial.print(F("\r\nCircle"));
PS5.setRumbleOn(RumbleHigh);
}
if (PS5.getButtonClick(CROSS)) {
Serial.print(F("\r\nCross"));
// Set the player LEDs
player_led_mask = (player_led_mask << 1) | 1;
if (player_led_mask > 0x1F)
player_led_mask = 0;
PS5.setPlayerLed(player_led_mask); // The bottom 5 bits set player LEDs
}
if (PS5.getButtonClick(SQUARE)) {
Serial.print(F("\r\nSquare"));
PS5.setRumbleOff();
}
if (PS5.getButtonClick(UP)) {
Serial.print(F("\r\nUp"));
PS5.setLed(Red);
} if (PS5.getButtonClick(RIGHT)) {
Serial.print(F("\r\nRight"));
PS5.setLed(Blue);
} if (PS5.getButtonClick(DOWN)) {
Serial.print(F("\r\nDown"));
PS5.setLed(Yellow);
} if (PS5.getButtonClick(LEFT)) {
Serial.print(F("\r\nLeft"));
PS5.setLed(Green);
}
if (PS5.getButtonClick(L1))
Serial.print(F("\r\nL1"));
if (PS5.getButtonClick(L3))
Serial.print(F("\r\nL3"));
if (PS5.getButtonClick(R1))
Serial.print(F("\r\nR1"));
if (PS5.getButtonClick(R3))
Serial.print(F("\r\nR3"));
if (PS5.getButtonClick(CREATE))
Serial.print(F("\r\nCreate"));
if (PS5.getButtonClick(OPTIONS)) {
Serial.print(F("\r\nOptions"));
printAngle = !printAngle;
}
if (PS5.getButtonClick(TOUCHPAD)) {
Serial.print(F("\r\nTouchpad"));
printTouch = !printTouch;
}
if (PS5.getButtonClick(MICROPHONE)) {
Serial.print(F("\r\nMicrophone"));
microphone_led = !microphone_led;
PS5.setMicLed(microphone_led);
}
if (printAngle) { // Print angle calculated using the accelerometer only
Serial.print(F("\r\nPitch: "));
Serial.print(PS5.getAngle(Pitch));
Serial.print(F("\tRoll: "));
Serial.print(PS5.getAngle(Roll));
}
if (printTouch) { // Print the x, y coordinates of the touchpad
if (PS5.isTouching(0) || PS5.isTouching(1)) // Print newline and carriage return if any of the fingers are touching the touchpad
Serial.print(F("\r\n"));
for (uint8_t i = 0; i < 2; i++) { // The touchpad track two fingers
if (PS5.isTouching(i)) { // Print the position of the finger if it is touching the touchpad
Serial.print(F("X")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getX(i));
Serial.print(F("\tY")); Serial.print(i + 1); Serial.print(F(": "));
Serial.print(PS5.getY(i));
Serial.print(F("\t"));
}
}
}
}
}

View file

@ -0,0 +1,133 @@
/*
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"));
Serial.print(F("\r\nBattery level: "));
Serial.print(SwitchPro.getBatteryLevel());
Serial.print(F(", charging: "));
Serial.print(SwitchPro.isCharging());
}
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

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* USB-MIDI dump utility * USB-MIDI dump utility
* Copyright (C) 2013-2017 Yuuichi Akagawa * Copyright (C) 2013-2021 Yuuichi Akagawa
* *
* for use with USB Host Shield 2.0 from Circuitsathome.com * for use with USB Host Shield 2.0 from Circuitsathome.com
* https://github.com/felis/USB_Host_Shield_2.0 * https://github.com/felis/USB_Host_Shield_2.0
@ -13,35 +13,37 @@
#include <usbh_midi.h> #include <usbh_midi.h>
#include <usbhub.h> #include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb; USB Usb;
//USBHub Hub(&Usb); USBHub Hub(&Usb);
USBH_MIDI Midi(&Usb); USBH_MIDI Midi(&Usb);
void MIDI_poll(); void MIDI_poll();
uint16_t pid, vid; void onInit()
{
char buf[20];
uint16_t vid = Midi.idVendor();
uint16_t pid = Midi.idProduct();
sprintf(buf, "VID:%04X, PID:%04X", vid, pid);
Serial.println(buf);
}
void setup() void setup()
{ {
vid = pid = 0;
Serial.begin(115200); Serial.begin(115200);
if (Usb.Init() == -1) { if (Usb.Init() == -1) {
while (1); //halt while (1); //halt
}//if (Usb.Init() == -1... }//if (Usb.Init() == -1...
delay( 200 ); delay( 200 );
// Register onInit() function
Midi.attachOnInit(onInit);
} }
void loop() void loop()
{ {
Usb.Task(); Usb.Task();
//uint32_t t1 = (uint32_t)micros();
if ( Midi ) { if ( Midi ) {
MIDI_poll(); MIDI_poll();
} }
@ -50,23 +52,16 @@ void loop()
// Poll USB MIDI Controler and send to serial MIDI // Poll USB MIDI Controler and send to serial MIDI
void MIDI_poll() void MIDI_poll()
{ {
char buf[20]; char buf[16];
uint8_t bufMidi[64]; uint8_t bufMidi[MIDI_EVENT_PACKET_SIZE];
uint16_t rcvd; uint16_t rcvd;
if (Midi.idVendor() != vid || Midi.idProduct() != pid) {
vid = Midi.idVendor();
pid = Midi.idProduct();
sprintf(buf, "VID:%04X, PID:%04X", vid, pid);
Serial.println(buf);
}
if (Midi.RecvData( &rcvd, bufMidi) == 0 ) { if (Midi.RecvData( &rcvd, bufMidi) == 0 ) {
uint32_t time = (uint32_t)millis(); uint32_t time = (uint32_t)millis();
sprintf(buf, "%04X%04X: ", (uint16_t)(time >> 16), (uint16_t)(time & 0xFFFF)); // Split variable to prevent warnings on the ESP8266 platform sprintf(buf, "%04X%04X:%3d:", (uint16_t)(time >> 16), (uint16_t)(time & 0xFFFF), rcvd); // Split variable to prevent warnings on the ESP8266 platform
Serial.print(buf); Serial.print(buf);
Serial.print(rcvd);
Serial.print(':'); for (int i = 0; i < MIDI_EVENT_PACKET_SIZE; i++) {
for (int i = 0; i < 64; i++) {
sprintf(buf, " %02X", bufMidi[i]); sprintf(buf, " %02X", bufMidi[i]);
Serial.print(buf); Serial.print(buf);
} }

View file

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* USB-MIDI to Legacy Serial MIDI converter * USB-MIDI to Legacy Serial MIDI converter
* Copyright (C) 2012-2017 Yuuichi Akagawa * Copyright (C) 2012-2021 Yuuichi Akagawa
* *
* Idea from LPK25 USB-MIDI to Serial MIDI converter * Idea from LPK25 USB-MIDI to Serial MIDI converter
* by Collin Cunningham - makezine.com, narbotic.com * by Collin Cunningham - makezine.com, narbotic.com
@ -13,17 +13,16 @@
#include <usbh_midi.h> #include <usbh_midi.h>
#include <usbhub.h> #include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
#ifdef USBCON #ifdef USBCON
#define _MIDI_SERIAL_PORT Serial1 #define _MIDI_SERIAL_PORT Serial1
#else #else
#define _MIDI_SERIAL_PORT Serial #define _MIDI_SERIAL_PORT Serial
#endif #endif
// Set to 1 if you want to wait for the Serial MIDI transmission to complete.
// For more information, see https://github.com/felis/USB_Host_Shield_2.0/issues/570
#define ENABLE_MIDI_SERIAL_FLUSH 0
////////////////////////// //////////////////////////
// MIDI Pin assign // MIDI Pin assign
// 2 : GND // 2 : GND
@ -35,7 +34,6 @@ USB Usb;
USBH_MIDI Midi(&Usb); USBH_MIDI Midi(&Usb);
void MIDI_poll(); void MIDI_poll();
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime);
void setup() void setup()
{ {
@ -50,12 +48,12 @@ void setup()
void loop() void loop()
{ {
Usb.Task(); Usb.Task();
uint32_t t1 = (uint32_t)micros();
if ( Midi ) { if ( Midi ) {
MIDI_poll(); MIDI_poll();
} }
//delay(1ms) //delay(1ms) if you want
doDelay(t1, (uint32_t)micros(), 1000); //delayMicroseconds(1000);
} }
// Poll USB MIDI Controler and send to serial MIDI // Poll USB MIDI Controler and send to serial MIDI
@ -68,17 +66,9 @@ void MIDI_poll()
if ( (size = Midi.RecvData(outBuf)) > 0 ) { if ( (size = Midi.RecvData(outBuf)) > 0 ) {
//MIDI Output //MIDI Output
_MIDI_SERIAL_PORT.write(outBuf, size); _MIDI_SERIAL_PORT.write(outBuf, size);
#if ENABLE_MIDI_SERIAL_FLUSH
_MIDI_SERIAL_PORT.flush();
#endif
} }
} while (size > 0); } while (size > 0);
} }
// Delay time (max 16383 us)
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime)
{
uint32_t t3;
t3 = t2 - t1;
if ( t3 < delayTime ) {
delayMicroseconds(delayTime - t3);
}
}

View file

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* USB-MIDI to Legacy Serial MIDI converter * USB-MIDI to Legacy Serial MIDI converter
* Copyright (C) 2012-2017 Yuuichi Akagawa * Copyright (C) 2012-2021 Yuuichi Akagawa
* *
* Idea from LPK25 USB-MIDI to Serial MIDI converter * Idea from LPK25 USB-MIDI to Serial MIDI converter
* by Collin Cunningham - makezine.com, narbotic.com * by Collin Cunningham - makezine.com, narbotic.com
@ -13,17 +13,16 @@
#include <usbh_midi.h> #include <usbh_midi.h>
#include <usbhub.h> #include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
#ifdef USBCON #ifdef USBCON
#define _MIDI_SERIAL_PORT Serial1 #define _MIDI_SERIAL_PORT Serial1
#else #else
#define _MIDI_SERIAL_PORT Serial #define _MIDI_SERIAL_PORT Serial
#endif #endif
// Set to 1 if you want to wait for the Serial MIDI transmission to complete.
// For more information, see https://github.com/felis/USB_Host_Shield_2.0/issues/570
#define ENABLE_MIDI_SERIAL_FLUSH 0
////////////////////////// //////////////////////////
// MIDI Pin assign // MIDI Pin assign
// 2 : GND // 2 : GND
@ -36,8 +35,7 @@ USBHub Hub1(&Usb);
USBH_MIDI Midi1(&Usb); USBH_MIDI Midi1(&Usb);
USBH_MIDI Midi2(&Usb); USBH_MIDI Midi2(&Usb);
void MIDI_poll(); void MIDI_poll(USBH_MIDI &Midi);
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime);
void setup() void setup()
{ {
@ -52,15 +50,15 @@ void setup()
void loop() void loop()
{ {
Usb.Task(); Usb.Task();
uint32_t t1 = (uint32_t)micros();
if ( Midi1 ) { if ( Midi1 ) {
MIDI_poll(Midi1); MIDI_poll(Midi1);
} }
if ( Midi2 ) { if ( Midi2 ) {
MIDI_poll(Midi2); MIDI_poll(Midi2);
} }
//delay(1ms) //delay(1ms) if you want
doDelay(t1, (uint32_t)micros(), 1000); //delayMicroseconds(1000);
} }
// Poll USB MIDI Controler and send to serial MIDI // Poll USB MIDI Controler and send to serial MIDI
@ -73,6 +71,9 @@ void MIDI_poll(USBH_MIDI &Midi)
if ( (size = Midi.RecvData(outBuf)) > 0 ) { if ( (size = Midi.RecvData(outBuf)) > 0 ) {
//MIDI Output //MIDI Output
_MIDI_SERIAL_PORT.write(outBuf, size); _MIDI_SERIAL_PORT.write(outBuf, size);
#if ENABLE_MIDI_SERIAL_FLUSH
_MIDI_SERIAL_PORT.flush();
#endif
} }
} while (size > 0); } while (size > 0);
} }

View file

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* Legacy Serial MIDI and USB Host bidirectional converter * Legacy Serial MIDI and USB Host bidirectional converter
* Copyright (C) 2013-2017 Yuuichi Akagawa * Copyright (C) 2013-2021 Yuuichi Akagawa
* *
* for use with Arduino MIDI library * for use with Arduino MIDI library
* https://github.com/FortySevenEffects/arduino_midi_library/ * https://github.com/FortySevenEffects/arduino_midi_library/
@ -16,12 +16,6 @@
#include <usbh_midi.h> #include <usbh_midi.h>
#include <usbhub.h> #include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
//Arduino MIDI library v4.2 compatibility //Arduino MIDI library v4.2 compatibility
#ifdef MIDI_CREATE_DEFAULT_INSTANCE #ifdef MIDI_CREATE_DEFAULT_INSTANCE
MIDI_CREATE_DEFAULT_INSTANCE(); MIDI_CREATE_DEFAULT_INSTANCE();
@ -32,6 +26,10 @@ MIDI_CREATE_DEFAULT_INSTANCE();
#define _MIDI_SERIAL_PORT Serial #define _MIDI_SERIAL_PORT Serial
#endif #endif
// Set to 1 if you want to wait for the Serial MIDI transmission to complete.
// For more information, see https://github.com/felis/USB_Host_Shield_2.0/issues/570
#define ENABLE_MIDI_SERIAL_FLUSH 0
////////////////////////// //////////////////////////
// MIDI Pin assign // MIDI Pin assign
// 2 : GND // 2 : GND
@ -83,6 +81,11 @@ void loop()
//SysEx is handled by event. //SysEx is handled by event.
break; break;
default : default :
// If this is a channel messages, set the channel number.
if( msg[0] < 0xf0 ){
// The getchannel() returns 1-16, but the MIDI status byte starts at 0.
msg[0] |= MIDI.getChannel() - 1;
}
msg[1] = MIDI.getData1(); msg[1] = MIDI.getData1();
msg[2] = MIDI.getData2(); msg[2] = MIDI.getData2();
Midi.SendData(msg, 0); Midi.SendData(msg, 0);
@ -125,6 +128,9 @@ void MIDI_poll()
_MIDI_SERIAL_PORT.write(outbuf, rc); _MIDI_SERIAL_PORT.write(outbuf, rc);
p += 4; p += 4;
} }
#if ENABLE_MIDI_SERIAL_FLUSH
_MIDI_SERIAL_PORT.flush();
#endif
readPtr += 4; readPtr += 4;
} }
#else #else
@ -133,6 +139,9 @@ void MIDI_poll()
if ( (size = Midi.RecvData(outBuf)) > 0 ) { if ( (size = Midi.RecvData(outBuf)) > 0 ) {
//MIDI Output //MIDI Output
_MIDI_SERIAL_PORT.write(outBuf, size); _MIDI_SERIAL_PORT.write(outBuf, size);
#if ENABLE_MIDI_SERIAL_FLUSH
_MIDI_SERIAL_PORT.flush();
#endif
} }
} while (size > 0); } while (size > 0);
#endif #endif

View file

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* eVY1 Shield sample - Say 'Konnichiwa' * eVY1 Shield sample - Say 'Konnichiwa'
* Copyright (C) 2014-2016 Yuuichi Akagawa * Copyright (C) 2014-2021 Yuuichi Akagawa
* *
* This is sample program. Do not expect perfect behavior. * This is sample program. Do not expect perfect behavior.
******************************************************************************* *******************************************************************************
@ -9,12 +9,6 @@
#include <usbh_midi.h> #include <usbh_midi.h>
#include <usbhub.h> #include <usbhub.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb; USB Usb;
//USBHub Hub(&Usb); //USBHub Hub(&Usb);
USBH_MIDI Midi(&Usb); USBH_MIDI Midi(&Usb);
@ -23,7 +17,6 @@ void MIDI_poll();
void noteOn(uint8_t note); void noteOn(uint8_t note);
void noteOff(uint8_t note); void noteOff(uint8_t note);
uint16_t pid, vid;
uint8_t exdata[] = { uint8_t exdata[] = {
0xf0, 0x43, 0x79, 0x09, 0x00, 0x50, 0x10, 0xf0, 0x43, 0x79, 0x09, 0x00, 0x50, 0x10,
'k', ' ', 'o', ',', //Ko 'k', ' ', 'o', ',', //Ko
@ -34,15 +27,22 @@ uint8_t exdata[] = {
0x00, 0xf7 0x00, 0xf7
}; };
void onInit()
{
// Send Phonetic symbols via SysEx
Midi.SendSysEx(exdata, sizeof(exdata));
delay(500);
}
void setup() void setup()
{ {
vid = pid = 0;
Serial.begin(115200);
if (Usb.Init() == -1) { if (Usb.Init() == -1) {
while (1); //halt while (1); //halt
}//if (Usb.Init() == -1... }//if (Usb.Init() == -1...
delay( 200 ); delay( 200 );
// Register onInit() function
Midi.attachOnInit(onInit);
} }
void loop() void loop()
@ -61,13 +61,6 @@ void loop()
void MIDI_poll() void MIDI_poll()
{ {
uint8_t inBuf[ 3 ]; uint8_t inBuf[ 3 ];
//first call?
if (Midi.idVendor() != vid || Midi.idProduct() != pid) {
vid = Midi.idVendor(); pid = Midi.idProduct();
Midi.SendSysEx(exdata, sizeof(exdata));
delay(500);
}
Midi.RecvData(inBuf); Midi.RecvData(inBuf);
} }

View file

@ -0,0 +1,133 @@
/*
Example sketch for the Xbox One S Bluetooth 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 <XBOXONESBT.h>
#include <usbhub.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;
//USBHub Hub1(&Usb); // Some dongles have a hub inside
BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so
/* You can create the instance of the XBOXONESBT class in two ways */
// This will start an inquiry and then pair with the Xbox One S controller - you only have to do this once
// You will need to hold down the Sync and Xbox button at the same time, the Xbox One S controller will then start to blink rapidly indicating that it is in pairing mode
XBOXONESBT Xbox(&Btd, PAIR);
// After that you can simply create the instance like so and then press the Xbox button on the device
//XBOXONESBT Xbox(&Btd);
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\nXbox One S Bluetooth Library Started"));
}
void loop() {
Usb.Task();
if (Xbox.connected()) {
if (Xbox.getAnalogHat(LeftHatX) > 7500 || Xbox.getAnalogHat(LeftHatX) < -7500 || Xbox.getAnalogHat(LeftHatY) > 7500 || Xbox.getAnalogHat(LeftHatY) < -7500 || Xbox.getAnalogHat(RightHatX) > 7500 || Xbox.getAnalogHat(RightHatX) < -7500 || Xbox.getAnalogHat(RightHatY) > 7500 || Xbox.getAnalogHat(RightHatY) < -7500) {
if (Xbox.getAnalogHat(LeftHatX) > 7500 || Xbox.getAnalogHat(LeftHatX) < -7500) {
Serial.print(F("LeftHatX: "));
Serial.print(Xbox.getAnalogHat(LeftHatX));
Serial.print("\t");
}
if (Xbox.getAnalogHat(LeftHatY) > 7500 || Xbox.getAnalogHat(LeftHatY) < -7500) {
Serial.print(F("LeftHatY: "));
Serial.print(Xbox.getAnalogHat(LeftHatY));
Serial.print("\t");
}
if (Xbox.getAnalogHat(RightHatX) > 7500 || Xbox.getAnalogHat(RightHatX) < -7500) {
Serial.print(F("RightHatX: "));
Serial.print(Xbox.getAnalogHat(RightHatX));
Serial.print("\t");
}
if (Xbox.getAnalogHat(RightHatY) > 7500 || Xbox.getAnalogHat(RightHatY) < -7500) {
Serial.print(F("RightHatY: "));
Serial.print(Xbox.getAnalogHat(RightHatY));
}
Serial.println();
}
if (Xbox.getButtonPress(L2) > 0 || Xbox.getButtonPress(R2) > 0) {
if (Xbox.getButtonPress(L2) > 0) {
Serial.print(F("L2: "));
Serial.print(Xbox.getButtonPress(L2));
Serial.print("\t");
}
if (Xbox.getButtonPress(R2) > 0) {
Serial.print(F("R2: "));
Serial.print(Xbox.getButtonPress(R2));
Serial.print("\t");
}
Serial.println();
}
// Set rumble effect
static uint16_t oldL2Value, oldR2Value;
if (Xbox.getButtonPress(L2) != oldL2Value || Xbox.getButtonPress(R2) != oldR2Value) {
oldL2Value = Xbox.getButtonPress(L2);
oldR2Value = Xbox.getButtonPress(R2);
uint8_t leftRumble = map(oldL2Value, 0, 1023, 0, 255); // Map the trigger values into a byte
uint8_t rightRumble = map(oldR2Value, 0, 1023, 0, 255);
if (leftRumble > 0 || rightRumble > 0)
Xbox.setRumbleOn(leftRumble, rightRumble, leftRumble, rightRumble);
else
Xbox.setRumbleOff();
}
if (Xbox.getButtonClick(UP))
Serial.println(F("Up"));
if (Xbox.getButtonClick(DOWN))
Serial.println(F("Down"));
if (Xbox.getButtonClick(LEFT))
Serial.println(F("Left"));
if (Xbox.getButtonClick(RIGHT))
Serial.println(F("Right"));
if (Xbox.getButtonClick(VIEW))
Serial.println(F("View"));
if (Xbox.getButtonClick(MENU))
Serial.println(F("Menu"));
if (Xbox.getButtonClick(XBOX)) {
Serial.println(F("Xbox"));
Xbox.disconnect();
}
if (Xbox.getButtonClick(L1))
Serial.println(F("L1"));
if (Xbox.getButtonClick(R1))
Serial.println(F("R1"));
if (Xbox.getButtonClick(L2))
Serial.println(F("L2"));
if (Xbox.getButtonClick(R2))
Serial.println(F("R2"));
if (Xbox.getButtonClick(L3))
Serial.println(F("L3"));
if (Xbox.getButtonClick(R3))
Serial.println(F("R3"));
if (Xbox.getButtonClick(A))
Serial.println(F("A"));
if (Xbox.getButtonClick(B))
Serial.println(F("B"));
if (Xbox.getButtonClick(X))
Serial.println(F("X"));
if (Xbox.getButtonClick(Y))
Serial.println(F("Y"));
}
}

61
examples/ambx/AMBX.ino Normal file
View file

@ -0,0 +1,61 @@
/*
Example sketch for the AMBX library - developed by Aran Vink <aranvink@gmail.com>
*/
#include <AMBX.h>
// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
USB Usb;
AMBX AMBX(&Usb); // This will just create the instance
uint8_t state = 0;
const long interval = 1000;
unsigned long previousMillis = 0;
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\nAMBX USB Library Started"));
}
void loop() {
Usb.Task();
if (AMBX.AMBXConnected) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (state > 4) {
state = 0;
}
if (state == 0) {
Serial.print(F("\r\nRed"));
AMBX.setAllLights(Red);
} else if (state == 1) {
Serial.print(F("\r\nGreen"));
AMBX.setAllLights(Green);
} else if (state == 2) {
Serial.print(F("\r\nBlue"));
AMBX.setAllLights(Blue);
} else if (state == 3) {
Serial.print(F("\r\nWhite"));
AMBX.setAllLights(White);
}
state++;
}
}
//Example using single light:
//AMBX.setLight(Wallwasher_center, White);
}

View file

@ -306,6 +306,13 @@ void HIDComposite::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint
// Fill in interface structure in case of new interface // Fill in interface structure in case of new interface
if(!piface) { if(!piface) {
if(bNumIface >= maxHidInterfaces) {
// don't overflow hidInterfaces[]
Notify(PSTR("\r\n EndpointXtract(): Not adding HID interface because we already have "), 0x80);
Notify(bNumIface, 0x80);
Notify(PSTR(" interfaces and can't hold more. "), 0x80);
return;
}
piface = hidInterfaces + bNumIface; piface = hidInterfaces + bNumIface;
piface->bmInterface = iface; piface->bmInterface = iface;
piface->bmAltSet = alt; piface->bmAltSet = alt;
@ -320,6 +327,13 @@ void HIDComposite::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint
index = 0; index = 0;
if(index) { if(index) {
if(bNumEP >= totalEndpoints) {
// don't overflow epInfo[] either
Notify(PSTR("\r\n EndpointXtract(): Not adding endpoint info because we already have "), 0x80);
Notify(bNumEP, 0x80);
Notify(PSTR(" endpoints and can't hold more. "), 0x80);
return;
}
// Fill in the endpoint info structure // Fill in the endpoint info structure
epInfo[bNumEP].epAddr = (pep->bEndpointAddress & 0x0F); epInfo[bNumEP].epAddr = (pep->bEndpointAddress & 0x0F);
epInfo[bNumEP].maxPktSize = (uint8_t)pep->wMaxPacketSize; epInfo[bNumEP].maxPktSize = (uint8_t)pep->wMaxPacketSize;

View file

@ -23,6 +23,8 @@ e-mail : support@circuitsathome.com
class HIDComposite : public USBHID { class HIDComposite : public USBHID {
protected:
struct ReportParser { struct ReportParser {
uint8_t rptId; uint8_t rptId;
HIDReportParser *rptParser; HIDReportParser *rptParser;
@ -40,7 +42,7 @@ class HIDComposite : public USBHID {
uint8_t bmAltSet : 3; uint8_t bmAltSet : 3;
uint8_t bmProtocol : 2; uint8_t bmProtocol : 2;
}; };
uint8_t epIndex[maxEpPerInterface]; uint8_t epIndex[maxEpPerInterface + 1]; // We need to make room for the control endpoint as well
}; };
uint8_t bConfNum; // configuration number uint8_t bConfNum; // configuration number
@ -57,10 +59,13 @@ class HIDComposite : public USBHID {
void ZeroMemory(uint8_t len, uint8_t *buf); void ZeroMemory(uint8_t len, uint8_t *buf);
protected:
EpInfo epInfo[totalEndpoints]; EpInfo epInfo[totalEndpoints];
HIDInterface hidInterfaces[maxHidInterfaces]; HIDInterface hidInterfaces[maxHidInterfaces];
// FIXME: bHasReportId is never set (except to false in constructor)
// should probably be in EpInfo, /maybe/ in HIDInterface
// but setting it isn't that easy (requires parsing report descriptors)
bool bHasReportId; bool bHasReportId;
uint16_t PID, VID; // PID and VID of connected device uint16_t PID, VID; // PID and VID of connected device

View file

@ -1113,16 +1113,19 @@ uint8_t ReportDescParserBase::ParseItem(uint8_t **pp, uint16_t *pcntdn) {
if(!pcntdn) if(!pcntdn)
return enErrorIncomplete; return enErrorIncomplete;
// fall through
case 1: case 1:
//USBTRACE2("\r\niSz:",itemSize); //USBTRACE2("\r\niSz:",itemSize);
theBuffer.valueSize = itemSize; theBuffer.valueSize = itemSize;
valParser.Initialize(&theBuffer); valParser.Initialize(&theBuffer);
itemParseState = 2; itemParseState = 2;
// fall through
case 2: case 2:
if(!valParser.Parse(pp, pcntdn)) if(!valParser.Parse(pp, pcntdn))
return enErrorIncomplete; return enErrorIncomplete;
itemParseState = 3; itemParseState = 3;
// fall through
case 3: case 3:
{ {
uint8_t data = *((uint8_t*)varBuffer); uint8_t data = *((uint8_t*)varBuffer);
@ -1448,14 +1451,17 @@ uint8_t ReportDescParser2::ParseItem(uint8_t **pp, uint16_t *pcntdn) {
if(!pcntdn) if(!pcntdn)
return enErrorIncomplete; return enErrorIncomplete;
// fall through
case 1: case 1:
theBuffer.valueSize = itemSize; theBuffer.valueSize = itemSize;
valParser.Initialize(&theBuffer); valParser.Initialize(&theBuffer);
itemParseState = 2; itemParseState = 2;
// fall through
case 2: case 2:
if(!valParser.Parse(pp, pcntdn)) if(!valParser.Parse(pp, pcntdn))
return enErrorIncomplete; return enErrorIncomplete;
itemParseState = 3; itemParseState = 3;
// fall through
case 3: case 3:
{ {
uint8_t data = *((uint8_t*)varBuffer); uint8_t data = *((uint8_t*)varBuffer);

View file

@ -17,353 +17,6 @@ e-mail : support@circuitsathome.com
#include "hiduniversal.h" #include "hiduniversal.h"
HIDUniversal::HIDUniversal(USB *p) :
USBHID(p),
qNextPollTime(0),
pollInterval(0),
bPollEnable(false),
bHasReportId(false) {
Initialize();
if(pUsb)
pUsb->RegisterDeviceClass(this);
}
uint16_t HIDUniversal::GetHidClassDescrLen(uint8_t type, uint8_t num) {
for(uint8_t i = 0, n = 0; i < HID_MAX_HID_CLASS_DESCRIPTORS; i++) {
if(descrInfo[i].bDescrType == type) {
if(n == num)
return descrInfo[i].wDescriptorLength;
n++;
}
}
return 0;
}
void HIDUniversal::Initialize() {
for(uint8_t i = 0; i < MAX_REPORT_PARSERS; i++) {
rptParsers[i].rptId = 0;
rptParsers[i].rptParser = NULL;
}
for(uint8_t i = 0; i < HID_MAX_HID_CLASS_DESCRIPTORS; i++) {
descrInfo[i].bDescrType = 0;
descrInfo[i].wDescriptorLength = 0;
}
for(uint8_t i = 0; i < maxHidInterfaces; i++) {
hidInterfaces[i].bmInterface = 0;
hidInterfaces[i].bmProtocol = 0;
for(uint8_t j = 0; j < maxEpPerInterface; j++)
hidInterfaces[i].epIndex[j] = 0;
}
for(uint8_t i = 0; i < totalEndpoints; i++) {
epInfo[i].epAddr = 0;
epInfo[i].maxPktSize = (i) ? 0 : 8;
epInfo[i].bmSndToggle = 0;
epInfo[i].bmRcvToggle = 0;
epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER;
}
bNumEP = 1;
bNumIface = 0;
bConfNum = 0;
pollInterval = 0;
ZeroMemory(constBuffLen, prevBuf);
}
bool HIDUniversal::SetReportParser(uint8_t id, HIDReportParser *prs) {
for(uint8_t i = 0; i < MAX_REPORT_PARSERS; i++) {
if(rptParsers[i].rptId == 0 && rptParsers[i].rptParser == NULL) {
rptParsers[i].rptId = id;
rptParsers[i].rptParser = prs;
return true;
}
}
return false;
}
HIDReportParser* HIDUniversal::GetReportParser(uint8_t id) {
if(!bHasReportId)
return ((rptParsers[0].rptParser) ? rptParsers[0].rptParser : NULL);
for(uint8_t i = 0; i < MAX_REPORT_PARSERS; i++) {
if(rptParsers[i].rptId == id)
return rptParsers[i].rptParser;
}
return NULL;
}
uint8_t HIDUniversal::Init(uint8_t parent, uint8_t port, bool lowspeed) {
const uint8_t constBufSize = sizeof (USB_DEVICE_DESCRIPTOR);
uint8_t buf[constBufSize];
USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(buf);
uint8_t rcode;
UsbDevice *p = NULL;
EpInfo *oldep_ptr = NULL;
uint8_t len = 0;
uint8_t num_of_conf; // number of configurations
//uint8_t num_of_intf; // number of interfaces
AddressPool &addrPool = pUsb->GetAddressPool();
USBTRACE("HU Init\r\n");
if(bAddress)
return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE;
// Get pointer to pseudo device with address 0 assigned
p = addrPool.GetUsbDevicePtr(0);
if(!p)
return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
if(!p->epinfo) {
USBTRACE("epinfo\r\n");
return USB_ERROR_EPINFO_IS_NULL;
}
// Save old pointer to EP_RECORD of address 0
oldep_ptr = p->epinfo;
// Temporary assign new pointer to epInfo to p->epinfo in order to avoid toggle inconsistence
p->epinfo = epInfo;
p->lowspeed = lowspeed;
// Get device descriptor
rcode = pUsb->getDevDescr(0, 0, 8, (uint8_t*)buf);
if(!rcode)
len = (buf[0] > constBufSize) ? constBufSize : buf[0];
if(rcode) {
// Restore p->epinfo
p->epinfo = oldep_ptr;
goto FailGetDevDescr;
}
// Restore p->epinfo
p->epinfo = oldep_ptr;
// Allocate new address according to device class
bAddress = addrPool.AllocAddress(parent, false, port);
if(!bAddress)
return USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL;
// Extract Max Packet Size from the device descriptor
epInfo[0].maxPktSize = udd->bMaxPacketSize0;
// Assign new address to the device
rcode = pUsb->setAddr(0, 0, bAddress);
if(rcode) {
p->lowspeed = false;
addrPool.FreeAddress(bAddress);
bAddress = 0;
USBTRACE2("setAddr:", rcode);
return rcode;
}
//delay(2); //per USB 2.0 sect.9.2.6.3
USBTRACE2("Addr:", bAddress);
p->lowspeed = false;
p = addrPool.GetUsbDevicePtr(bAddress);
if(!p)
return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
p->lowspeed = lowspeed;
if(len)
rcode = pUsb->getDevDescr(bAddress, 0, len, (uint8_t*)buf);
if(rcode)
goto FailGetDevDescr;
VID = udd->idVendor; // Can be used by classes that inherits this class to check the VID and PID of the connected device
PID = udd->idProduct;
num_of_conf = udd->bNumConfigurations;
// Assign epInfo to epinfo pointer
rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo);
if(rcode)
goto FailSetDevTblEntry;
USBTRACE2("NC:", num_of_conf);
for(uint8_t i = 0; i < num_of_conf; i++) {
//HexDumper<USBReadParser, uint16_t, uint16_t> HexDump;
ConfigDescParser<USB_CLASS_HID, 0, 0,
CP_MASK_COMPARE_CLASS> confDescrParser(this);
//rcode = pUsb->getConfDescr(bAddress, 0, i, &HexDump);
rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);
if(rcode)
goto FailGetConfDescr;
if(bNumEP > 1)
break;
} // for
if(bNumEP < 2)
return USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED;
// Assign epInfo to epinfo pointer
rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo);
USBTRACE2("Cnf:", bConfNum);
// Set Configuration Value
rcode = pUsb->setConf(bAddress, 0, bConfNum);
if(rcode)
goto FailSetConfDescr;
for(uint8_t i = 0; i < bNumIface; i++) {
if(hidInterfaces[i].epIndex[epInterruptInIndex] == 0)
continue;
rcode = SetIdle(hidInterfaces[i].bmInterface, 0, 0);
if(rcode && rcode != hrSTALL)
goto FailSetIdle;
}
USBTRACE("HU configured\r\n");
OnInitSuccessful();
bPollEnable = true;
return 0;
FailGetDevDescr:
#ifdef DEBUG_USB_HOST
NotifyFailGetDevDescr();
goto Fail;
#endif
FailSetDevTblEntry:
#ifdef DEBUG_USB_HOST
NotifyFailSetDevTblEntry();
goto Fail;
#endif
FailGetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailGetConfDescr();
goto Fail;
#endif
FailSetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailSetConfDescr();
goto Fail;
#endif
FailSetIdle:
#ifdef DEBUG_USB_HOST
USBTRACE("SetIdle:");
#endif
#ifdef DEBUG_USB_HOST
Fail:
NotifyFail(rcode);
#endif
Release();
return rcode;
}
HIDUniversal::HIDInterface* HIDUniversal::FindInterface(uint8_t iface, uint8_t alt, uint8_t proto) {
for(uint8_t i = 0; i < bNumIface && i < maxHidInterfaces; i++)
if(hidInterfaces[i].bmInterface == iface && hidInterfaces[i].bmAltSet == alt
&& hidInterfaces[i].bmProtocol == proto)
return hidInterfaces + i;
return NULL;
}
void HIDUniversal::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *pep) {
// If the first configuration satisfies, the others are not concidered.
if(bNumEP > 1 && conf != bConfNum)
return;
//ErrorMessage<uint8_t>(PSTR("\r\nConf.Val"), conf);
//ErrorMessage<uint8_t>(PSTR("Iface Num"), iface);
//ErrorMessage<uint8_t>(PSTR("Alt.Set"), alt);
bConfNum = conf;
uint8_t index = 0;
HIDInterface *piface = FindInterface(iface, alt, proto);
// Fill in interface structure in case of new interface
if(!piface) {
piface = hidInterfaces + bNumIface;
piface->bmInterface = iface;
piface->bmAltSet = alt;
piface->bmProtocol = proto;
bNumIface++;
}
if((pep->bmAttributes & bmUSB_TRANSFER_TYPE) == USB_TRANSFER_TYPE_INTERRUPT)
index = (pep->bEndpointAddress & 0x80) == 0x80 ? epInterruptInIndex : epInterruptOutIndex;
if(index) {
// Fill in the endpoint info structure
epInfo[bNumEP].epAddr = (pep->bEndpointAddress & 0x0F);
epInfo[bNumEP].maxPktSize = (uint8_t)pep->wMaxPacketSize;
epInfo[bNumEP].bmSndToggle = 0;
epInfo[bNumEP].bmRcvToggle = 0;
epInfo[bNumEP].bmNakPower = USB_NAK_NOWAIT;
// Fill in the endpoint index list
piface->epIndex[index] = bNumEP; //(pep->bEndpointAddress & 0x0F);
if(pollInterval < pep->bInterval) // Set the polling interval as the largest polling interval obtained from endpoints
pollInterval = pep->bInterval;
bNumEP++;
}
//PrintEndpointDescriptor(pep);
}
uint8_t HIDUniversal::Release() {
pUsb->GetAddressPool().FreeAddress(bAddress);
bNumEP = 1;
bAddress = 0;
qNextPollTime = 0;
bPollEnable = false;
return 0;
}
bool HIDUniversal::BuffersIdentical(uint8_t len, uint8_t *buf1, uint8_t *buf2) {
for(uint8_t i = 0; i < len; i++)
if(buf1[i] != buf2[i])
return false;
return true;
}
void HIDUniversal::ZeroMemory(uint8_t len, uint8_t *buf) {
for(uint8_t i = 0; i < len; i++)
buf[i] = 0;
}
void HIDUniversal::SaveBuffer(uint8_t len, uint8_t *src, uint8_t *dest) {
for(uint8_t i = 0; i < len; i++)
dest[i] = src[i];
}
uint8_t HIDUniversal::Poll() { uint8_t HIDUniversal::Poll() {
uint8_t rcode = 0; uint8_t rcode = 0;
@ -392,12 +45,11 @@ uint8_t HIDUniversal::Poll() {
if(read > constBuffLen) if(read > constBuffLen)
read = constBuffLen; read = constBuffLen;
bool identical = BuffersIdentical(read, buf, prevBuf); // TODO: handle read == 0 ? continue like HIDComposite,
// return early like in the error case above?
// Either way passing an empty buffer to the functions below
// probably makes no sense?
SaveBuffer(read, buf, prevBuf);
if(identical)
return 0;
#if 0 #if 0
Notify(PSTR("\r\nBuf: "), 0x80); Notify(PSTR("\r\nBuf: "), 0x80);
@ -418,8 +70,3 @@ uint8_t HIDUniversal::Poll() {
} }
return rcode; return rcode;
} }
// Send a report to interrupt out endpoint. This is NOT SetReport() request!
uint8_t HIDUniversal::SndRpt(uint16_t nbytes, uint8_t *dataptr) {
return pUsb->outTransfer(bAddress, epInfo[epInterruptOutIndex].epAddr, nbytes, dataptr);
}

View file

@ -18,91 +18,43 @@ e-mail : support@circuitsathome.com
#if !defined(__HIDUNIVERSAL_H__) #if !defined(__HIDUNIVERSAL_H__)
#define __HIDUNIVERSAL_H__ #define __HIDUNIVERSAL_H__
#include "usbhid.h" #include "hidcomposite.h"
//#include "hidescriptorparser.h"
class HIDUniversal : public USBHID { class HIDUniversal : public HIDComposite {
struct ReportParser { bool SelectInterface(uint8_t iface __attribute__((unused)), uint8_t proto __attribute__((unused))) final {
uint8_t rptId; // the original HIDUniversal didn't have this at all so make it a no-op
HIDReportParser *rptParser; // (and made it final so users don't override this - if they want to use
} rptParsers[MAX_REPORT_PARSERS]; // SelectInterface() they should be deriving from HIDComposite directly)
return true;
}
// HID class specific descriptor type and length info obtained from HID descriptor void ParseHIDData(USBHID *hid, uint8_t ep __attribute__((unused)), bool is_rpt_id, uint8_t len, uint8_t *buf) final {
HID_CLASS_DESCRIPTOR_LEN_AND_TYPE descrInfo[HID_MAX_HID_CLASS_DESCRIPTORS]; // override the HIDComposite version of this method to call the HIDUniversal version
// (which doesn't use the endpoint), made it final to make sure users
// Returns HID class specific descriptor length by its type and order number // of HIDUniversal override the right version of ParseHIDData() (the other one, below)
uint16_t GetHidClassDescrLen(uint8_t type, uint8_t num); ParseHIDData(hid, is_rpt_id, len, buf);
}
struct HIDInterface {
struct {
uint8_t bmInterface : 3;
uint8_t bmAltSet : 3;
uint8_t bmProtocol : 2;
};
uint8_t epIndex[maxEpPerInterface + 1]; // We need to make room for the control endpoint as well
};
uint8_t bConfNum; // configuration number
uint8_t bNumIface; // number of interfaces in the configuration
uint8_t bNumEP; // total number of EP in the configuration
uint32_t qNextPollTime; // next poll time
uint8_t pollInterval;
bool bPollEnable; // poll enable flag
static const uint16_t constBuffLen = 64; // event buffer length
uint8_t prevBuf[constBuffLen]; // previous event buffer
void Initialize();
HIDInterface* FindInterface(uint8_t iface, uint8_t alt, uint8_t proto);
void ZeroMemory(uint8_t len, uint8_t *buf);
bool BuffersIdentical(uint8_t len, uint8_t *buf1, uint8_t *buf2);
void SaveBuffer(uint8_t len, uint8_t *src, uint8_t *dest);
protected: protected:
EpInfo epInfo[totalEndpoints];
HIDInterface hidInterfaces[maxHidInterfaces];
bool bHasReportId;
uint16_t PID, VID; // PID and VID of connected device
// HID implementation
HIDReportParser* GetReportParser(uint8_t id);
virtual uint8_t OnInitSuccessful() {
return 0;
};
virtual void ParseHIDData(USBHID *hid __attribute__((unused)), bool is_rpt_id __attribute__((unused)), uint8_t len __attribute__((unused)), uint8_t *buf __attribute__((unused))) { virtual void ParseHIDData(USBHID *hid __attribute__((unused)), bool is_rpt_id __attribute__((unused)), uint8_t len __attribute__((unused)), uint8_t *buf __attribute__((unused))) {
return; return;
}; }
public: public:
HIDUniversal(USB *p); HIDUniversal(USB *p) : HIDComposite(p) {}
// HID implementation uint8_t Poll() override;
bool SetReportParser(uint8_t id, HIDReportParser *prs);
// USBDeviceConfig implementation
uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed);
uint8_t Release();
uint8_t Poll();
virtual uint8_t GetAddress() {
return bAddress;
};
virtual bool isReady() {
return bPollEnable;
};
// UsbConfigXtracter implementation // UsbConfigXtracter implementation
void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep); void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep) override
{
// Send report - do not mix with SetReport()! // If the first configuration satisfies, the others are not considered.
uint8_t SndRpt(uint16_t nbytes, uint8_t *dataptr); if(bNumEP > 1 && conf != bConfNum)
return;
// otherwise HIDComposite does what HIDUniversal needs
HIDComposite::EndpointXtract(conf, iface, alt, proto, ep);
}
}; };
#endif // __HIDUNIVERSAL_H__ #endif // __HIDUNIVERSAL_H__

View file

@ -36,6 +36,10 @@ PS3BT KEYWORD1
PS3USB KEYWORD1 PS3USB KEYWORD1
PS4BT KEYWORD1 PS4BT KEYWORD1
PS4USB KEYWORD1 PS4USB KEYWORD1
PS5BT KEYWORD1
PS5USB KEYWORD1
SwitchProBT KEYWORD1
SwitchProUSB KEYWORD1
#################################################### ####################################################
# Methods and Functions (KEYWORD2) # Methods and Functions (KEYWORD2)
@ -59,8 +63,11 @@ getTemperature KEYWORD2
disconnect KEYWORD2 disconnect KEYWORD2
setAllOff KEYWORD2 setAllOff KEYWORD2
setRumble KEYWORD2
setRumbleOff KEYWORD2 setRumbleOff KEYWORD2
setRumbleOn KEYWORD2 setRumbleOn KEYWORD2
setRumbleLeft KEYWORD2
setRumbleRight KEYWORD2
setLedOff KEYWORD2 setLedOff KEYWORD2
setLedOn KEYWORD2 setLedOn KEYWORD2
setLedToggle KEYWORD2 setLedToggle KEYWORD2
@ -68,6 +75,10 @@ setLedFlash KEYWORD2
moveSetBulb KEYWORD2 moveSetBulb KEYWORD2
moveSetRumble KEYWORD2 moveSetRumble KEYWORD2
setLedHomeOff KEYWORD2
setLedHomeOn KEYWORD2
setLedHomeToggle KEYWORD2
attachOnInit KEYWORD2 attachOnInit KEYWORD2
PS3Connected KEYWORD2 PS3Connected KEYWORD2
@ -135,6 +146,9 @@ SHARE LITERAL1
OPTIONS LITERAL1 OPTIONS LITERAL1
TOUCHPAD LITERAL1 TOUCHPAD LITERAL1
CREATE LITERAL1
MICROPHONE LITERAL1
LeftHatX LITERAL1 LeftHatX LITERAL1
LeftHatY LITERAL1 LeftHatY LITERAL1
RightHatX LITERAL1 RightHatX LITERAL1
@ -196,6 +210,7 @@ XBOXUSB KEYWORD1
XBOXONE KEYWORD1 XBOXONE KEYWORD1
XBOXOLD KEYWORD1 XBOXOLD KEYWORD1
XBOXRECV KEYWORD1 XBOXRECV KEYWORD1
XBOXONESBT KEYWORD1
#################################################### ####################################################
# Methods and Functions (KEYWORD2) # Methods and Functions (KEYWORD2)
@ -227,6 +242,9 @@ BACK LITERAL1
XBOX LITERAL1 XBOX LITERAL1
SYNC LITERAL1 SYNC LITERAL1
VIEW LITERAL1
MENU LITERAL1
BLACK LITERAL1 BLACK LITERAL1
WHITE LITERAL1 WHITE LITERAL1
@ -308,6 +326,7 @@ TopRight LITERAL1
BotRight LITERAL1 BotRight LITERAL1
TopLeft LITERAL1 TopLeft LITERAL1
BotLeft LITERAL1 BotLeft LITERAL1
CAPTURE LITERAL1
#################################################### ####################################################
# Methods and Functions for the IR Camera # Methods and Functions for the IR Camera

View file

@ -1,29 +1,27 @@
{ {
"name": "USB-Host-Shield-20", "name": "USB-Host-Shield-20",
"keywords": "usb, host, ftdi, adk, acm, pl2303, hid, bluetooth, spp, ps3, ps4, buzz, xbox, wii, mass storage", "keywords": "usb, host, ftdi, adk, acm, pl2303, hid, bluetooth, spp, ps3, ps4, ps5, buzz, xbox, wii, switch pro, mass storage",
"description": "Revision 2.0 of MAX3421E-based USB Host Shield Library", "description": "Revision 2.0 of MAX3421E-based USB Host Shield Library",
"authors": "authors":
[ [
{ {
"name": "Oleg Mazurov", "name": "Oleg Mazurov",
"email": "mazurov@circuitsathome.com", "email": "mazurov@circuitsathome.com",
"url": "http://www.circuitsathome.com", "url": "http://www.circuitsathome.com"
"maintainer": true
}, },
{ {
"name": "Alexei Glushchenko", "name": "Alexei Glushchenko",
"email": "alex-gl@mail.ru" "email": "alex-gl@mail.ru"
}, },
{ {
"name": "Kristian Lauszus", "name": "Kristian Sloth Lauszus",
"email": "kristianl@tkjelectronics.com", "email": "lauszus@gmail.com",
"url": "http://tkjelectronics.com", "url": "https://lauszus.com",
"maintainer": true "maintainer": true
}, },
{ {
"name": "Andrew Kroll", "name": "Andrew Kroll",
"email": "xxxajk@gmail.com", "email": "xxxajk@gmail.com"
"maintainer": true
} }
], ],
"repository": "repository":
@ -31,7 +29,7 @@
"type": "git", "type": "git",
"url": "https://github.com/felis/USB_Host_Shield_2.0.git" "url": "https://github.com/felis/USB_Host_Shield_2.0.git"
}, },
"version": "1.3.2", "version": "1.6.0",
"license": "GPL-2.0", "license": "GPL-2.0",
"examples": "examples":
[ [
@ -50,6 +48,7 @@
"teensy", "teensy",
"atmelsam", "atmelsam",
"nordicnrf51", "nordicnrf51",
"nordicnrf52",
"ststm32", "ststm32",
"espressif8266", "espressif8266",
"espressif32" "espressif32"

View file

@ -1,9 +1,9 @@
name=USB Host Shield Library 2.0 name=USB Host Shield Library 2.0
version=1.3.2 version=1.6.0
author=Oleg Mazurov (Circuits@Home) <mazurov@circuitsathome.com>, Kristian Lauszus (TKJ Electronics) <kristianl@tkjelectronics.com>, Andrew Kroll <xxxajk@gmail.com>, Alexei Glushchenko (Circuits@Home) <alex-gl@mail.ru> author=Oleg Mazurov (Circuits@Home) <mazurov@circuitsathome.com>, Kristian Sloth Lauszus <lauszus@gmail.com>, Andrew Kroll <xxxajk@gmail.com>, Alexei Glushchenko (Circuits@Home) <alex-gl@mail.ru>
maintainer=Oleg Mazurov (Circuits@Home) <mazurov@circuitsathome.com>, Kristian Lauszus (TKJ Electronics) <kristianl@tkjelectronics.com>, Andrew Kroll <xxxajk@gmail.com> maintainer=Oleg Mazurov (Circuits@Home) <mazurov@circuitsathome.com>, Kristian Sloth Lauszus <lauszus@gmail.com>, Andrew Kroll <xxxajk@gmail.com>
sentence=Revision 2.0 of MAX3421E-based USB Host Shield Library. sentence=Revision 2.0 of MAX3421E-based USB Host Shield Library.
paragraph=Supports HID devices, FTDI, ADK, ACM, PL2303, Bluetooth HID devices, SPP communication and mass storage devices. Furthermore it supports PS3, PS4, PS Buzz, Wii and Xbox controllers. paragraph=Supports HID devices, FTDI, ADK, ACM, PL2303, Bluetooth HID devices, SPP communication and mass storage devices. Furthermore it supports PS3, PS4, PS5, PS Buzz, Wii, Switch Pro and Xbox controllers.
category=Other category=Other
url=https://github.com/felis/USB_Host_Shield_2.0 url=https://github.com/felis/USB_Host_Shield_2.0
architectures=* architectures=*

View file

@ -44,6 +44,7 @@ bool PTPListParser::Parse(uint8_t **pp, uint16_t *pcntdn, PTP_ARRAY_EL_FUNC pf,
pBuf->valueSize = lenSize; pBuf->valueSize = lenSize;
theParser.Initialize(pBuf); theParser.Initialize(pBuf);
nStage = 1; nStage = 1;
// fall through
case 1: case 1:
if(!theParser.Parse(pp, pcntdn)) if(!theParser.Parse(pp, pcntdn))
@ -53,11 +54,13 @@ bool PTPListParser::Parse(uint8_t **pp, uint16_t *pcntdn, PTP_ARRAY_EL_FUNC pf,
arLen = (pBuf->valueSize >= 4) ? *((uint32_t*)pBuf->pValue) : (uint32_t)(*((uint16_t*)pBuf->pValue)); arLen = (pBuf->valueSize >= 4) ? *((uint32_t*)pBuf->pValue) : (uint32_t)(*((uint16_t*)pBuf->pValue));
arLenCntdn = arLen; arLenCntdn = arLen;
nStage = 2; nStage = 2;
// fall through
case 2: case 2:
pBuf->valueSize = valSize; pBuf->valueSize = valSize;
theParser.Initialize(pBuf); theParser.Initialize(pBuf);
nStage = 3; nStage = 3;
// fall through
case 3: case 3:
for(; arLenCntdn; arLenCntdn--) { for(; arLenCntdn; arLenCntdn--) {

View file

@ -79,6 +79,7 @@ public:
case 0: case 0:
countDown = bytes_to_skip; countDown = bytes_to_skip;
nStage++; nStage++;
// fall through
case 1: case 1:
for(; countDown && (*pcntdn); countDown--, (*pp)++, (*pcntdn)--); for(; countDown && (*pcntdn); countDown--, (*pp)++, (*pcntdn)--);

View file

@ -80,17 +80,15 @@ e-mail : support@circuitsathome.com
#endif #endif
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Set to 1 to use the faster spi4teensy3 driver. // Set to 1 to use the faster spi4teensy3 driver on Teensy 3.x
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#ifndef USE_SPI4TEENSY3 #ifndef USE_SPI4TEENSY3
#if defined(CORE_TEENSY) && (defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__))
#define USE_SPI4TEENSY3 1 #define USE_SPI4TEENSY3 1
#endif #else
// Disabled on the Teensy LC, as it is incompatible for now
#if defined(__MKL26Z64__)
#undef USE_SPI4TEENSY3
#define USE_SPI4TEENSY3 0 #define USE_SPI4TEENSY3 0
#endif #endif
#endif
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// AUTOMATIC Settings // AUTOMATIC Settings
@ -159,6 +157,11 @@ e-mail : support@circuitsathome.com
#define SPI SPI_Master #define SPI SPI_Master
#define MFK_CASTUINT8T (uint8_t) // RBLs return type for sizeof needs casting to uint8_t #define MFK_CASTUINT8T (uint8_t) // RBLs return type for sizeof needs casting to uint8_t
#endif #endif
#ifdef NRF52_SERIES
#include <SPI.h>
#include <nrf_gpio.h>
#define MFK_CASTUINT8T (uint8_t) // NRF return type for sizeof needs casting to uint8_t
#endif
#if defined(__PIC32MX__) || defined(__PIC32MZ__) #if defined(__PIC32MX__) || defined(__PIC32MZ__)
#include <../../../../hardware/pic32/libraries/SPI/SPI.h> // Hack to use the SPI library #include <../../../../hardware/pic32/libraries/SPI/SPI.h> // Hack to use the SPI library
#endif #endif

View file

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* USB-MIDI class driver for USB Host Shield 2.0 Library * USB-MIDI class driver for USB Host Shield 2.0 Library
* Copyright (c) 2012-2018 Yuuichi Akagawa * Copyright (c) 2012-2021 Yuuichi Akagawa
* *
* Idea from LPK25 USB-MIDI to Serial MIDI converter * Idea from LPK25 USB-MIDI to Serial MIDI converter
* by Collin Cunningham - makezine.com, narbotic.com * by Collin Cunningham - makezine.com, narbotic.com
@ -25,6 +25,9 @@
*/ */
#include "usbh_midi.h" #include "usbh_midi.h"
// To enable serial debugging see "settings.h"
//#define EXTRADEBUG // Uncomment to get even more debugging data
////////////////////////// //////////////////////////
// MIDI MESAGES // MIDI MESAGES
// midi.org/techspecs/ // midi.org/techspecs/
@ -79,24 +82,16 @@
//| 0xF | 1 |Single Byte //| 0xF | 1 |Single Byte
//+-----+-----------+------------------------------------------------------------------- //+-----+-----------+-------------------------------------------------------------------
const uint8_t USBH_MIDI::epDataInIndex = 1;
const uint8_t USBH_MIDI::epDataOutIndex = 2;
const uint8_t USBH_MIDI::epDataInIndexVSP = 3;
const uint8_t USBH_MIDI::epDataOutIndexVSP = 4;
USBH_MIDI::USBH_MIDI(USB *p) : USBH_MIDI::USBH_MIDI(USB *p) :
pUsb(p), pUsb(p),
bAddress(0), bAddress(0),
bNumEP(1),
bPollEnable(false), bPollEnable(false),
isMidiFound(false),
readPtr(0) { readPtr(0) {
// initialize endpoint data structures // initialize endpoint data structures
for(uint8_t i=0; i<MIDI_MAX_ENDPOINTS; i++) { for(uint8_t i=0; i<MIDI_MAX_ENDPOINTS; i++) {
epInfo[i].epAddr = 0; epInfo[i].epAddr = 0;
epInfo[i].maxPktSize = (i) ? 0 : 8; epInfo[i].maxPktSize = (i) ? 0 : 8;
epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER; epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER;
} }
// register in USB subsystem // register in USB subsystem
if (pUsb) { if (pUsb) {
@ -113,15 +108,20 @@ uint8_t USBH_MIDI::Init(uint8_t parent, uint8_t port, bool lowspeed)
UsbDevice *p = NULL; UsbDevice *p = NULL;
EpInfo *oldep_ptr = NULL; EpInfo *oldep_ptr = NULL;
uint8_t num_of_conf; // number of configurations uint8_t num_of_conf; // number of configurations
uint8_t bConfNum = 0; // configuration number
uint8_t bNumEP = 1; // total number of EP in the configuration
USBTRACE("\rMIDI Init\r\n"); USBTRACE("\rMIDI Init\r\n");
#ifdef DEBUG_USB_HOST
Notify(PSTR("USBH_MIDI version "), 0x80), D_PrintHex((uint8_t) (USBH_MIDI_VERSION / 10000), 0x80), D_PrintHex((uint8_t) (USBH_MIDI_VERSION / 100 % 100), 0x80), D_PrintHex((uint8_t) (USBH_MIDI_VERSION % 100), 0x80), Notify(PSTR("\r\n"), 0x80);
#endif
//for reconnect //for reconnect
for(uint8_t i=epDataInIndex; i<=epDataOutIndex; i++) { for(uint8_t i=epDataInIndex; i<=epDataOutIndex; i++) {
epInfo[i].epAddr = (i==epDataInIndex) ? 0x81 : 0x01;
epInfo[i].maxPktSize = 0;
epInfo[i].bmSndToggle = 0; epInfo[i].bmSndToggle = 0;
epInfo[i].bmRcvToggle = 0; epInfo[i].bmRcvToggle = 0;
// If you want to retry if you get a NAK response when sending, enable the following:
// epInfo[i].bmNakPower = (i==epDataOutIndex) ? 10 : USB_NAK_NOWAIT;
} }
// get memory address of USB device address pool // get memory address of USB device address pool
@ -147,10 +147,10 @@ uint8_t USBH_MIDI::Init(uint8_t parent, uint8_t port, bool lowspeed)
p->epinfo = epInfo; p->epinfo = epInfo;
p->lowspeed = lowspeed; p->lowspeed = lowspeed;
// Get device descriptor // First Device Descriptor Request (Initially first 8 bytes)
rcode = pUsb->getDevDescr( 0, 0, sizeof(USB_DEVICE_DESCRIPTOR), (uint8_t*)buf ); // https://techcommunity.microsoft.com/t5/microsoft-usb-blog/how-does-usb-stack-enumerate-a-device/ba-p/270685#_First_Device_Descriptor
vid = udd->idVendor; rcode = pUsb->getDevDescr( 0, 0, 8, (uint8_t*)buf );
pid = udd->idProduct;
// Restore p->epinfo // Restore p->epinfo
p->epinfo = oldep_ptr; p->epinfo = oldep_ptr;
@ -186,6 +186,13 @@ uint8_t USBH_MIDI::Init(uint8_t parent, uint8_t port, bool lowspeed)
} }
p->lowspeed = lowspeed; p->lowspeed = lowspeed;
// Second Device Descriptor Request (Full)
rcode = pUsb->getDevDescr( bAddress, 0, sizeof(USB_DEVICE_DESCRIPTOR), (uint8_t*)buf );
if( rcode ){
goto FailGetDevDescr;
}
vid = udd->idVendor;
pid = udd->idProduct;
num_of_conf = udd->bNumConfigurations; num_of_conf = udd->bNumConfigurations;
// Assign epInfo to epinfo pointer // Assign epInfo to epinfo pointer
@ -203,30 +210,43 @@ uint8_t USBH_MIDI::Init(uint8_t parent, uint8_t port, bool lowspeed)
bTransferTypeMask = bmUSB_TRANSFER_TYPE; bTransferTypeMask = bmUSB_TRANSFER_TYPE;
setupDeviceSpecific(); setupDeviceSpecific();
isMidiFound = false; // STEP1: Check if attached device is a MIDI device and fill endpoint data structure
USBTRACE("\r\nSTEP1: MIDI Start\r\n");
for(uint8_t i = 0; i < num_of_conf; i++) { for(uint8_t i = 0; i < num_of_conf; i++) {
rcode = parseConfigDescr(bAddress, i); MidiDescParser midiDescParser(this, true); // Check for MIDI device
if( rcode ) rcode = pUsb->getConfDescr(bAddress, 0, i, &midiDescParser);
if(rcode) // Check error code
goto FailGetConfDescr; goto FailGetConfDescr;
if (bNumEP > 1) bNumEP += midiDescParser.getNumEPs();
if(bNumEP > 1) {// All endpoints extracted
bConfNum = midiDescParser.getConfValue();
break; break;
} // for }
}
USBTRACE2("\r\nNumEP:", bNumEP); USBTRACE2("STEP1: MIDI,NumEP:", bNumEP);
//Found the MIDI device?
if( bNumEP == 1 ){ //Device not found.
USBTRACE("MIDI not found.\r\nSTEP2: Attempts vendor specific bulk device\r\n");
// STEP2: Check if attached device is a MIDI device and fill endpoint data structure
for(uint8_t i = 0; i < num_of_conf; i++) {
MidiDescParser midiDescParser(this, false); // Allow all devices, vendor specific class with Bulk transfer
rcode = pUsb->getConfDescr(bAddress, 0, i, &midiDescParser);
if(rcode) // Check error code
goto FailGetConfDescr;
bNumEP += midiDescParser.getNumEPs();
if(bNumEP > 1) {// All endpoints extracted
bConfNum = midiDescParser.getConfValue();
break;
}
}
USBTRACE2("\r\nSTEP2: Vendor,NumEP:", bNumEP);
}
if( bNumEP < 2 ){ //Device not found. if( bNumEP < 2 ){ //Device not found.
rcode = 0xff; rcode = 0xff;
goto FailGetConfDescr; goto FailGetConfDescr;
} }
if( !isMidiFound ){ //MIDI Device not found. Try last Bulk transfer device
USBTRACE("MIDI not found. Attempts bulk device\r\n");
epInfo[epDataInIndex].epAddr = epInfo[epDataInIndexVSP].epAddr;
epInfo[epDataInIndex].maxPktSize = epInfo[epDataInIndexVSP].maxPktSize;
epInfo[epDataOutIndex].epAddr = epInfo[epDataOutIndexVSP].epAddr;
epInfo[epDataOutIndex].maxPktSize = epInfo[epDataOutIndexVSP].maxPktSize;
}
// Assign epInfo to epinfo pointer // Assign epInfo to epinfo pointer
rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo); rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo);
USBTRACE2("Conf:", bConfNum); USBTRACE2("Conf:", bConfNum);
@ -235,9 +255,12 @@ uint8_t USBH_MIDI::Init(uint8_t parent, uint8_t port, bool lowspeed)
// Set Configuration Value // Set Configuration Value
rcode = pUsb->setConf(bAddress, 0, bConfNum); rcode = pUsb->setConf(bAddress, 0, bConfNum);
if (rcode) { if (rcode)
goto FailSetConfDescr; goto FailSetConfDescr;
}
if(pFuncOnInit)
pFuncOnInit(); // Call the user function
bPollEnable = true; bPollEnable = true;
USBTRACE("Init done.\r\n"); USBTRACE("Init done.\r\n");
return 0; return 0;
@ -249,92 +272,10 @@ FailSetConfDescr:
return rcode; return rcode;
} }
/* get and parse config descriptor */
uint8_t USBH_MIDI::parseConfigDescr( uint8_t addr, uint8_t conf )
{
uint8_t buf[ DESC_BUFF_SIZE ];
uint8_t* buf_ptr = buf;
uint8_t rcode;
uint8_t descr_length;
uint8_t descr_type;
uint16_t total_length;
USB_ENDPOINT_DESCRIPTOR *epDesc;
bool isMidi = false;
// get configuration descriptor (get descriptor size only)
rcode = pUsb->getConfDescr( addr, 0, 4, conf, buf );
if( rcode ){
return rcode;
}
total_length = buf[2] | ((int)buf[3] << 8);
if( total_length > DESC_BUFF_SIZE ) { //check if total length is larger than buffer
total_length = DESC_BUFF_SIZE;
}
// get configuration descriptor (all)
rcode = pUsb->getConfDescr( addr, 0, total_length, conf, buf ); //get the whole descriptor
if( rcode ){
return rcode;
}
//parsing descriptors
while( buf_ptr < buf + total_length ) {
descr_length = *( buf_ptr );
descr_type = *( buf_ptr + 1 );
switch( descr_type ) {
case USB_DESCRIPTOR_CONFIGURATION :
bConfNum = buf_ptr[5];
break;
case USB_DESCRIPTOR_INTERFACE :
USBTRACE("\r\nConf:"), D_PrintHex(bConfNum, 0x80);
USBTRACE(" Int:"), D_PrintHex(buf_ptr[2], 0x80);
USBTRACE(" Alt:"), D_PrintHex(buf_ptr[3], 0x80);
USBTRACE(" EPs:"), D_PrintHex(buf_ptr[4], 0x80);
USBTRACE(" IntCl:"), D_PrintHex(buf_ptr[5], 0x80);
USBTRACE(" IntSubCl:"), D_PrintHex(buf_ptr[6], 0x80);
USBTRACE("\r\n");
if( buf_ptr[5] == USB_CLASS_AUDIO && buf_ptr[6] == USB_SUBCLASS_MIDISTREAMING ) { //p[5]; bInterfaceClass = 1(Audio), p[6]; bInterfaceSubClass = 3(MIDI Streaming)
isMidiFound = true; //MIDI device found.
isMidi = true;
USBTRACE("MIDI Device\r\n");
}else{
isMidi = false;
USBTRACE("No MIDI Device\r\n");
}
break;
case USB_DESCRIPTOR_ENDPOINT :
epDesc = (USB_ENDPOINT_DESCRIPTOR *)buf_ptr;
USBTRACE("-EPAddr:"), D_PrintHex(epDesc->bEndpointAddress, 0x80);
USBTRACE(" bmAttr:"), D_PrintHex(epDesc->bmAttributes, 0x80);
USBTRACE2(" MaxPktSz:", (uint8_t)epDesc->wMaxPacketSize);
if ((epDesc->bmAttributes & bTransferTypeMask) == USB_TRANSFER_TYPE_BULK) {//bulk
uint8_t index;
if( isMidi )
index = ((epDesc->bEndpointAddress & 0x80) == 0x80) ? epDataInIndex : epDataOutIndex;
else
index = ((epDesc->bEndpointAddress & 0x80) == 0x80) ? epDataInIndexVSP : epDataOutIndexVSP;
epInfo[index].epAddr = (epDesc->bEndpointAddress & 0x0F);
epInfo[index].maxPktSize = (uint8_t)epDesc->wMaxPacketSize;
bNumEP ++;
#ifdef DEBUG_USB_HOST
PrintEndpointDescriptor(epDesc);
#endif
}
break;
default:
break;
}//switch( descr_type
buf_ptr += descr_length; //advance buffer pointer
}//while( buf_ptr <=...
return 0;
}
/* Performs a cleanup after failed Init() attempt */ /* Performs a cleanup after failed Init() attempt */
uint8_t USBH_MIDI::Release() uint8_t USBH_MIDI::Release()
{ {
pUsb->GetAddressPool().FreeAddress(bAddress); pUsb->GetAddressPool().FreeAddress(bAddress);
bNumEP = 1; //must have to be reset to 1
bAddress = 0; bAddress = 0;
bPollEnable = false; bPollEnable = false;
readPtr = 0; readPtr = 0;
@ -346,9 +287,19 @@ void USBH_MIDI::setupDeviceSpecific()
{ {
// Novation // Novation
if( vid == 0x1235 ) { if( vid == 0x1235 ) {
// LaunchPad's endpoint attirbute is interrupt (0x20:S, 0x36:Mini, 0x51:Pro, 0x69:MK2, 0x7b:Launchkey25 MK2) // LaunchPad and LaunchKey endpoint attribute is interrupt
if(pid == 0x20 || pid == 0x36 || pid == 0x51 || pid == 0x69 || pid == 0x7b ) { // https://github.com/YuuichiAkagawa/USBH_MIDI/wiki/Novation-USB-Product-ID-List
// LaunchPad: 0x20:S, 0x36:Mini, 0x51:Pro, 0x69:MK2
if( pid == 0x20 || pid == 0x36 || pid == 0x51 || pid == 0x69 ) {
bTransferTypeMask = 2; bTransferTypeMask = 2;
return;
}
// LaunchKey: 0x30-32, 0x35:Mini, 0x7B-0x7D:MK2
if( ( 0x30 <= pid && pid <= 0x32) || pid == 0x35 || ( 0x7B <= pid && pid <= 0x7D) ) {
bTransferTypeMask = 2;
return;
} }
} }
} }
@ -358,7 +309,10 @@ uint8_t USBH_MIDI::RecvData(uint16_t *bytes_rcvd, uint8_t *dataptr)
{ {
*bytes_rcvd = (uint16_t)epInfo[epDataInIndex].maxPktSize; *bytes_rcvd = (uint16_t)epInfo[epDataInIndex].maxPktSize;
uint8_t r = pUsb->inTransfer(bAddress, epInfo[epDataInIndex].epAddr, bytes_rcvd, dataptr); uint8_t r = pUsb->inTransfer(bAddress, epInfo[epDataInIndex].epAddr, bytes_rcvd, dataptr);
#ifdef EXTRADEBUG
if( r )
USBTRACE2("inTransfer():", r);
#endif
if( *bytes_rcvd < (MIDI_EVENT_PACKET_SIZE-4)){ if( *bytes_rcvd < (MIDI_EVENT_PACKET_SIZE-4)){
dataptr[*bytes_rcvd] = '\0'; dataptr[*bytes_rcvd] = '\0';
dataptr[(*bytes_rcvd)+1] = '\0'; dataptr[(*bytes_rcvd)+1] = '\0';
@ -404,60 +358,52 @@ RecvData_return_from_buffer:
*(outBuf++) = m = recvBuf[readPtr++]; *(outBuf++) = m = recvBuf[readPtr++];
*(outBuf++) = recvBuf[readPtr++]; *(outBuf++) = recvBuf[readPtr++];
*(outBuf++) = recvBuf[readPtr++]; *(outBuf++) = recvBuf[readPtr++];
return lookupMsgSize(m, cin);
}
/* Receive raw data from MIDI device */ return getMsgSizeFromCin(cin);
uint8_t USBH_MIDI::RecvRawData(uint8_t *outBuf)
{
return RecvData(outBuf, true);
} }
/* Send data to MIDI device */ /* Send data to MIDI device */
uint8_t USBH_MIDI::SendData(uint8_t *dataptr, uint8_t nCable) uint8_t USBH_MIDI::SendData(uint8_t *dataptr, uint8_t nCable)
{ {
uint8_t buf[4]; uint8_t buf[4];
uint8_t msg; uint8_t status = dataptr[0];
msg = dataptr[0]; uint8_t cin = convertStatus2Cin(status);
// SysEx long message ? if ( status == 0xf0 ) {
if( msg == 0xf0 ) // SysEx long message
{
return SendSysEx(dataptr, countSysExDataSize(dataptr), nCable); return SendSysEx(dataptr, countSysExDataSize(dataptr), nCable);
} }
buf[0] = (nCable << 4) | (msg >> 4);
if( msg < 0xf0 ) msg = msg & 0xf0;
//Building USB-MIDI Event Packets //Building USB-MIDI Event Packets
buf[0] = (uint8_t)(nCable << 4) | cin;
buf[1] = dataptr[0]; buf[1] = dataptr[0];
buf[2] = dataptr[1];
buf[3] = dataptr[2];
switch(lookupMsgSize(msg)) { uint8_t msglen = getMsgSizeFromCin(cin);
switch(msglen) {
//3 bytes message //3 bytes message
case 3 : case 3 :
if(msg == 0xf2) {//system common message(SPP) buf[2] = dataptr[1];
buf[0] = (nCable << 4) | 3; buf[3] = dataptr[2];
}
break; break;
//2 bytes message //2 bytes message
case 2 : case 2 :
if(msg == 0xf1 || msg == 0xf3) {//system common message(MTC/SongSelect) buf[2] = dataptr[1];
buf[0] = (nCable << 4) | 2;
}
buf[3] = 0; buf[3] = 0;
break; break;
//1 byte message //1 byte message
case 1 : case 1 :
default :
buf[2] = 0; buf[2] = 0;
buf[3] = 0; buf[3] = 0;
break; break;
default :
break;
} }
#ifdef EXTRADEBUG
//Dump for raw USB-MIDI event packet
Notify(PSTR("SendData():"), 0x80), D_PrintHex((buf[0]), 0x80), D_PrintHex((buf[1]), 0x80), D_PrintHex((buf[2]), 0x80), D_PrintHex((buf[3]), 0x80), Notify(PSTR("\r\n"), 0x80);
#endif
return pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, 4, buf); return pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, 4, buf);
} }
@ -478,54 +424,13 @@ void USBH_MIDI::PrintEndpointDescriptor( const USB_ENDPOINT_DESCRIPTOR* ep_ptr )
/*Return */ /*Return */
/* 0 : undefined message */ /* 0 : undefined message */
/* 0<: Vaild message size(1-3) */ /* 0<: Vaild message size(1-3) */
uint8_t USBH_MIDI::lookupMsgSize(uint8_t midiMsg, uint8_t cin) //uint8_t USBH_MIDI::lookupMsgSize(uint8_t midiMsg, uint8_t cin)
uint8_t USBH_MIDI::lookupMsgSize(uint8_t status, uint8_t cin)
{ {
uint8_t msgSize = 0; if( cin == 0 ){
cin = convertStatus2Cin(status);
//SysEx message?
cin = cin & 0x0f;
if( (cin & 0xc) == 4 ) {
if( cin == 4 || cin == 7 ) return 3;
if( cin == 6 ) return 2;
if( cin == 5 ) return 1;
} }
return getMsgSizeFromCin(cin);
if( midiMsg < 0xf0 ) midiMsg &= 0xf0;
switch(midiMsg) {
//3 bytes messages
case 0xf2 : //system common message(SPP)
case 0x80 : //Note off
case 0x90 : //Note on
case 0xa0 : //Poly KeyPress
case 0xb0 : //Control Change
case 0xe0 : //PitchBend Change
msgSize = 3;
break;
//2 bytes messages
case 0xf1 : //system common message(MTC)
case 0xf3 : //system common message(SongSelect)
case 0xc0 : //Program Change
case 0xd0 : //Channel Pressure
msgSize = 2;
break;
//1 byte messages
case 0xf8 : //system realtime message
case 0xf9 : //system realtime message
case 0xfa : //system realtime message
case 0xfb : //system realtime message
case 0xfc : //system realtime message
case 0xfe : //system realtime message
case 0xff : //system realtime message
msgSize = 1;
break;
//undefine messages
default :
break;
}
return msgSize;
} }
/* SysEx data size counter */ /* SysEx data size counter */
@ -538,11 +443,9 @@ uint16_t USBH_MIDI::countSysExDataSize(uint8_t *dataptr)
} }
//Search terminator(0xf7) //Search terminator(0xf7)
while(*dataptr != 0xf7) while(*dataptr != 0xf7) {
{
dataptr++; dataptr++;
c++; c++;
//Limiter (default: 256 bytes) //Limiter (default: 256 bytes)
if(c > MIDI_MAX_SYSEX_SIZE){ if(c > MIDI_MAX_SYSEX_SIZE){
c = 0; c = 0;
@ -558,15 +461,15 @@ uint8_t USBH_MIDI::SendSysEx(uint8_t *dataptr, uint16_t datasize, uint8_t nCable
uint8_t buf[MIDI_EVENT_PACKET_SIZE]; uint8_t buf[MIDI_EVENT_PACKET_SIZE];
uint8_t rc = 0; uint8_t rc = 0;
uint16_t n = datasize; uint16_t n = datasize;
uint16_t pktSize = (n*10/3+7)/10*4; //Calculate total USB MIDI packet size
uint8_t wptr = 0; uint8_t wptr = 0;
uint8_t maxpkt = epInfo[epDataInIndex].maxPktSize; uint8_t maxpkt = epInfo[epDataInIndex].maxPktSize;
if( maxpkt > MIDI_EVENT_PACKET_SIZE ) maxpkt = MIDI_EVENT_PACKET_SIZE;
USBTRACE("SendSysEx:\r\t"); USBTRACE("SendSysEx:\r\t");
USBTRACE2(" Length:\t", datasize); USBTRACE2(" Length:\t", datasize);
#ifdef EXTRADEBUG
uint16_t pktSize = (n+2)/3; //Calculate total USB MIDI packet size
USBTRACE2(" Total pktSize:\t", pktSize); USBTRACE2(" Total pktSize:\t", pktSize);
#endif
while(n > 0) { while(n > 0) {
//Byte 0 //Byte 0
@ -578,17 +481,18 @@ uint8_t USBH_MIDI::SendSysEx(uint8_t *dataptr, uint16_t datasize, uint8_t nCable
buf[wptr++] = *(dataptr++); buf[wptr++] = *(dataptr++);
buf[wptr++] = 0x00; buf[wptr++] = 0x00;
buf[wptr++] = 0x00; buf[wptr++] = 0x00;
n = n - 1; n = 0;
break; break;
case 2 : case 2 :
buf[wptr++] = (nCable << 4) | 0x6; //x6 SysEx ends with following two bytes. buf[wptr++] = (nCable << 4) | 0x6; //x6 SysEx ends with following two bytes.
buf[wptr++] = *(dataptr++); buf[wptr++] = *(dataptr++);
buf[wptr++] = *(dataptr++); buf[wptr++] = *(dataptr++);
buf[wptr++] = 0x00; buf[wptr++] = 0x00;
n = n - 2; n = 0;
break; break;
case 3 : case 3 :
buf[wptr] = (nCable << 4) | 0x7; //x7 SysEx ends with following three bytes. buf[wptr] = (nCable << 4) | 0x7; //x7 SysEx ends with following three bytes.
// fall through
default : default :
wptr++; wptr++;
buf[wptr++] = *(dataptr++); buf[wptr++] = *(dataptr++);
@ -603,19 +507,12 @@ uint8_t USBH_MIDI::SendSysEx(uint8_t *dataptr, uint16_t datasize, uint8_t nCable
if( (rc = pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, wptr, buf)) != 0 ){ if( (rc = pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, wptr, buf)) != 0 ){
break; break;
} }
wptr = 0; //rewind data pointer wptr = 0; //rewind write pointer
} }
} }
return(rc); return(rc);
} }
/* Send raw data to MIDI device */
uint8_t USBH_MIDI::SendRawData(uint16_t bytes_send, uint8_t *dataptr)
{
return pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, bytes_send, dataptr);
}
uint8_t USBH_MIDI::extractSysExData(uint8_t *p, uint8_t *buf) uint8_t USBH_MIDI::extractSysExData(uint8_t *p, uint8_t *buf)
{ {
uint8_t rc = 0; uint8_t rc = 0;
@ -646,3 +543,150 @@ uint8_t USBH_MIDI::extractSysExData(uint8_t *p, uint8_t *buf)
} }
return(rc); return(rc);
} }
// Configuration Descriptor Parser
// Copied from confdescparser.h and modifiy.
MidiDescParser::MidiDescParser(UsbMidiConfigXtracter *xtractor, bool modeMidi) :
theXtractor(xtractor),
stateParseDescr(0),
dscrLen(0),
dscrType(0),
nEPs(0),
isMidiSearch(modeMidi){
theBuffer.pValue = varBuffer;
valParser.Initialize(&theBuffer);
theSkipper.Initialize(&theBuffer);
}
void MidiDescParser::Parse(const uint16_t len, const uint8_t *pbuf, const uint16_t &offset __attribute__((unused))) {
uint16_t cntdn = (uint16_t)len;
uint8_t *p = (uint8_t*)pbuf;
while(cntdn)
if(!ParseDescriptor(&p, &cntdn))
return;
}
bool MidiDescParser::ParseDescriptor(uint8_t **pp, uint16_t *pcntdn) {
USB_CONFIGURATION_DESCRIPTOR* ucd = reinterpret_cast<USB_CONFIGURATION_DESCRIPTOR*>(varBuffer);
USB_INTERFACE_DESCRIPTOR* uid = reinterpret_cast<USB_INTERFACE_DESCRIPTOR*>(varBuffer);
switch(stateParseDescr) {
case 0:
theBuffer.valueSize = 2;
valParser.Initialize(&theBuffer);
stateParseDescr = 1;
// fall through
case 1:
if(!valParser.Parse(pp, pcntdn))
return false;
dscrLen = *((uint8_t*)theBuffer.pValue);
dscrType = *((uint8_t*)theBuffer.pValue + 1);
stateParseDescr = 2;
// fall through
case 2:
// This is a sort of hack. Assuming that two bytes are all ready in the buffer
// the pointer is positioned two bytes ahead in order for the rest of descriptor
// to be read right after the size and the type fields.
// This should be used carefully. varBuffer should be used directly to handle data
// in the buffer.
theBuffer.pValue = varBuffer + 2;
stateParseDescr = 3;
// fall through
case 3:
switch(dscrType) {
case USB_DESCRIPTOR_INTERFACE:
isGoodInterface = false;
break;
case USB_DESCRIPTOR_CONFIGURATION:
case USB_DESCRIPTOR_ENDPOINT:
case HID_DESCRIPTOR_HID:
break;
}
theBuffer.valueSize = dscrLen - 2;
valParser.Initialize(&theBuffer);
stateParseDescr = 4;
// fall through
case 4:
switch(dscrType) {
case USB_DESCRIPTOR_CONFIGURATION:
if(!valParser.Parse(pp, pcntdn))
return false;
confValue = ucd->bConfigurationValue;
break;
case USB_DESCRIPTOR_INTERFACE:
if(!valParser.Parse(pp, pcntdn))
return false;
USBTRACE("Interface descriptor:\r\n");
USBTRACE2(" Inf#:\t\t", uid->bInterfaceNumber);
USBTRACE2(" Alt:\t\t", uid->bAlternateSetting);
USBTRACE2(" EPs:\t\t", uid->bNumEndpoints);
USBTRACE2(" IntCl:\t\t", uid->bInterfaceClass);
USBTRACE2(" IntSubcl:\t", uid->bInterfaceSubClass);
USBTRACE2(" Protocol:\t", uid->bInterfaceProtocol);
// MIDI check mode ?
if( isMidiSearch ){ //true: MIDI Streaming, false: ALL
if( uid->bInterfaceClass == USB_CLASS_AUDIO && uid->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING ) {
// MIDI found.
USBTRACE("+MIDI found\r\n\r\n");
}else{
USBTRACE("-MIDI not found\r\n\r\n");
break;
}
}
isGoodInterface = true;
// Initialize the counter if no two endpoints can be found in one interface.
if(nEPs < 2)
// reset endpoint counter
nEPs = 0;
break;
case USB_DESCRIPTOR_ENDPOINT:
if(!valParser.Parse(pp, pcntdn))
return false;
if(isGoodInterface && nEPs < 2){
USBTRACE(">Extracting endpoint\r\n");
if( theXtractor->EndpointXtract(confValue, 0, 0, 0, (USB_ENDPOINT_DESCRIPTOR*)varBuffer) )
nEPs++;
}
break;
default:
if(!theSkipper.Skip(pp, pcntdn, dscrLen - 2))
return false;
}
theBuffer.pValue = varBuffer;
stateParseDescr = 0;
}
return true;
}
/* Extracts endpoint information from config descriptor */
bool USBH_MIDI::EndpointXtract(uint8_t conf __attribute__((unused)),
uint8_t iface __attribute__((unused)),
uint8_t alt __attribute__((unused)),
uint8_t proto __attribute__((unused)),
const USB_ENDPOINT_DESCRIPTOR *pep)
{
uint8_t index;
#ifdef DEBUG_USB_HOST
PrintEndpointDescriptor(pep);
#endif
// Is the endpoint transfer type bulk?
if((pep->bmAttributes & bTransferTypeMask) == USB_TRANSFER_TYPE_BULK) {
USBTRACE("+valid EP found.\r\n");
index = (pep->bEndpointAddress & 0x80) == 0x80 ? epDataInIndex : epDataOutIndex;
} else {
USBTRACE("-No valid EP found.\r\n");
return false;
}
// Fill the rest of endpoint data structure
epInfo[index].epAddr = (pep->bEndpointAddress & 0x0F);
// The maximum packet size for the USB Host Shield 2.0 library is 64 bytes.
if(pep->wMaxPacketSize > MIDI_EVENT_PACKET_SIZE) {
epInfo[index].maxPktSize = MIDI_EVENT_PACKET_SIZE;
} else {
epInfo[index].maxPktSize = (uint8_t)pep->wMaxPacketSize;
}
return true;
}

View file

@ -1,7 +1,7 @@
/* /*
******************************************************************************* *******************************************************************************
* USB-MIDI class driver for USB Host Shield 2.0 Library * USB-MIDI class driver for USB Host Shield 2.0 Library
* Copyright (c) 2012-2018 Yuuichi Akagawa * Copyright (c) 2012-2021 Yuuichi Akagawa
* *
* Idea from LPK25 USB-MIDI to Serial MIDI converter * Idea from LPK25 USB-MIDI to Serial MIDI converter
* by Collin Cunningham - makezine.com, narbotic.com * by Collin Cunningham - makezine.com, narbotic.com
@ -26,31 +26,67 @@
#if !defined(_USBH_MIDI_H_) #if !defined(_USBH_MIDI_H_)
#define _USBH_MIDI_H_ #define _USBH_MIDI_H_
//#define DEBUG_USB_HOST
#include "Usb.h" #include "Usb.h"
#define MIDI_MAX_ENDPOINTS 5 //endpoint 0, bulk_IN(MIDI), bulk_OUT(MIDI), bulk_IN(VSP), bulk_OUT(VSP) #define USBH_MIDI_VERSION 600
#define MIDI_MAX_ENDPOINTS 3 //endpoint 0, bulk_IN(MIDI), bulk_OUT(MIDI)
#define USB_SUBCLASS_MIDISTREAMING 3 #define USB_SUBCLASS_MIDISTREAMING 3
#define DESC_BUFF_SIZE 256
#define MIDI_EVENT_PACKET_SIZE 64 #define MIDI_EVENT_PACKET_SIZE 64
#define MIDI_MAX_SYSEX_SIZE 256 #define MIDI_MAX_SYSEX_SIZE 256
class USBH_MIDI;
class USBH_MIDI : public USBDeviceConfig namespace _ns_USBH_MIDI {
const uint8_t cin2len[] PROGMEM = {0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1};
const uint8_t sys2cin[] PROGMEM = {0, 2, 3, 2, 0, 0, 5, 0, 0xf, 0, 0xf, 0xf, 0xf, 0, 0xf, 0xf};
}
// Endpoint Descriptor extracter Class
class UsbMidiConfigXtracter {
public:
//virtual void ConfigXtract(const USB_CONFIGURATION_DESCRIPTOR *conf) = 0;
//virtual void InterfaceXtract(uint8_t conf, const USB_INTERFACE_DESCRIPTOR *iface) = 0;
virtual bool EndpointXtract(uint8_t conf __attribute__((unused)), uint8_t iface __attribute__((unused)), uint8_t alt __attribute__((unused)), uint8_t proto __attribute__((unused)), const USB_ENDPOINT_DESCRIPTOR *ep __attribute__((unused))) {
return true;
};
};
// Configuration Descriptor Parser Class
class MidiDescParser : public USBReadParser {
UsbMidiConfigXtracter *theXtractor;
MultiValueBuffer theBuffer;
MultiByteValueParser valParser;
ByteSkipper theSkipper;
uint8_t varBuffer[16 /*sizeof(USB_CONFIGURATION_DESCRIPTOR)*/];
uint8_t stateParseDescr; // ParseDescriptor state
uint8_t dscrLen; // Descriptor length
uint8_t dscrType; // Descriptor type
uint8_t nEPs; // number of valid endpoint
bool isMidiSearch; //Configuration mode true: MIDI, false: Vendor specific
bool isGoodInterface; // Apropriate interface flag
uint8_t confValue; // Configuration value
bool ParseDescriptor(uint8_t **pp, uint16_t *pcntdn);
public:
MidiDescParser(UsbMidiConfigXtracter *xtractor, bool modeMidi);
void Parse(const uint16_t len, const uint8_t *pbuf, const uint16_t &offset);
inline uint8_t getConfValue() { return confValue; };
inline uint8_t getNumEPs() { return nEPs; };
};
/** This class implements support for a MIDI device. */
class USBH_MIDI : public USBDeviceConfig, public UsbMidiConfigXtracter
{ {
protected: protected:
static const uint8_t epDataInIndex; // DataIn endpoint index(MIDI) static const uint8_t epDataInIndex = 1; // DataIn endpoint index(MIDI)
static const uint8_t epDataOutIndex; // DataOUT endpoint index(MIDI) static const uint8_t epDataOutIndex= 2; // DataOUT endpoint index(MIDI)
static const uint8_t epDataInIndexVSP; // DataIn endpoint index(Vendor Specific Protocl)
static const uint8_t epDataOutIndexVSP; // DataOUT endpoint index(Vendor Specific Protocl)
/* mandatory members */ /* mandatory members */
USB *pUsb; USB *pUsb;
uint8_t bAddress; uint8_t bAddress;
uint8_t bConfNum; // configuration number
uint8_t bNumEP; // total number of EP in the configuration
bool bPollEnable; bool bPollEnable;
bool isMidiFound;
uint16_t pid, vid; // ProductID, VendorID uint16_t pid, vid; // ProductID, VendorID
uint8_t bTransferTypeMask; uint8_t bTransferTypeMask;
/* Endpoint data structure */ /* Endpoint data structure */
@ -59,27 +95,36 @@ protected:
uint8_t recvBuf[MIDI_EVENT_PACKET_SIZE]; uint8_t recvBuf[MIDI_EVENT_PACKET_SIZE];
uint8_t readPtr; uint8_t readPtr;
uint8_t parseConfigDescr(uint8_t addr, uint8_t conf);
uint16_t countSysExDataSize(uint8_t *dataptr); uint16_t countSysExDataSize(uint8_t *dataptr);
void setupDeviceSpecific(); void setupDeviceSpecific();
inline uint8_t convertStatus2Cin(uint8_t status) {
return ((status < 0xf0) ? ((status & 0xF0) >> 4) : pgm_read_byte_near(_ns_USBH_MIDI::sys2cin + (status & 0x0F)));
};
inline uint8_t getMsgSizeFromCin(uint8_t cin) {
return pgm_read_byte_near(_ns_USBH_MIDI::cin2len + cin);
};
/* UsbConfigXtracter implementation */
bool EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep);
#ifdef DEBUG_USB_HOST #ifdef DEBUG_USB_HOST
void PrintEndpointDescriptor( const USB_ENDPOINT_DESCRIPTOR* ep_ptr ); void PrintEndpointDescriptor( const USB_ENDPOINT_DESCRIPTOR* ep_ptr );
#endif #endif
public: public:
USBH_MIDI(USB *p); USBH_MIDI(USB *p);
// Misc functions // Misc functions
operator bool() { return (pUsb->getUsbTaskState()==USB_STATE_RUNNING); } operator bool() { return (bPollEnable); }
uint16_t idVendor() { return vid; } uint16_t idVendor() { return vid; }
uint16_t idProduct() { return pid; } uint16_t idProduct() { return pid; }
// Methods for recieving and sending data // Methods for recieving and sending data
uint8_t RecvData(uint16_t *bytes_rcvd, uint8_t *dataptr); uint8_t RecvData(uint16_t *bytes_rcvd, uint8_t *dataptr);
uint8_t RecvData(uint8_t *outBuf, bool isRaw=false); uint8_t RecvData(uint8_t *outBuf, bool isRaw=false);
uint8_t RecvRawData(uint8_t *outBuf); inline uint8_t RecvRawData(uint8_t *outBuf) { return RecvData(outBuf, true); };
uint8_t SendData(uint8_t *dataptr, uint8_t nCable=0); uint8_t SendData(uint8_t *dataptr, uint8_t nCable=0);
inline uint8_t SendRawData(uint16_t bytes_send, uint8_t *dataptr) { return pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, bytes_send, dataptr); };
uint8_t lookupMsgSize(uint8_t midiMsg, uint8_t cin=0); uint8_t lookupMsgSize(uint8_t midiMsg, uint8_t cin=0);
uint8_t SendSysEx(uint8_t *dataptr, uint16_t datasize, uint8_t nCable=0); uint8_t SendSysEx(uint8_t *dataptr, uint16_t datasize, uint8_t nCable=0);
uint8_t extractSysExData(uint8_t *p, uint8_t *buf); uint8_t extractSysExData(uint8_t *p, uint8_t *buf);
uint8_t SendRawData(uint16_t bytes_send, uint8_t *dataptr);
// backward compatibility functions // backward compatibility functions
inline uint8_t RcvData(uint16_t *bytes_rcvd, uint8_t *dataptr) { return RecvData(bytes_rcvd, dataptr); }; inline uint8_t RcvData(uint16_t *bytes_rcvd, uint8_t *dataptr) { return RecvData(bytes_rcvd, dataptr); };
inline uint8_t RcvData(uint8_t *outBuf) { return RecvData(outBuf); }; inline uint8_t RcvData(uint8_t *outBuf) { return RecvData(outBuf); };
@ -88,5 +133,12 @@ public:
virtual uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed); virtual uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed);
virtual uint8_t Release(); virtual uint8_t Release();
virtual uint8_t GetAddress() { return bAddress; }; virtual uint8_t GetAddress() { return bAddress; };
void attachOnInit(void (*funcOnInit)(void)) {
pFuncOnInit = funcOnInit;
}; };
private:
void (*pFuncOnInit)(void) = nullptr; // Pointer to function called in onInit()
};
#endif //_USBH_MIDI_H_ #endif //_USBH_MIDI_H_

View file

@ -149,7 +149,7 @@ protected:
static const uint8_t epInterruptInIndex = 1; // InterruptIN endpoint index static const uint8_t epInterruptInIndex = 1; // InterruptIN endpoint index
static const uint8_t epInterruptOutIndex = 2; // InterruptOUT endpoint index static const uint8_t epInterruptOutIndex = 2; // InterruptOUT endpoint index
static const uint8_t maxHidInterfaces = 3; static const uint8_t maxHidInterfaces = 5;
static const uint8_t maxEpPerInterface = 2; static const uint8_t maxEpPerInterface = 2;
static const uint8_t totalEndpoints = (maxHidInterfaces * maxEpPerInterface + 1); // We need to make room for the control endpoint static const uint8_t totalEndpoints = (maxHidInterfaces * maxEpPerInterface + 1); // We need to make room for the control endpoint

View file

@ -70,7 +70,7 @@ public:
#else #else
USB_SPI.setClockDivider(SPI_CLOCK_DIV2); // This will set the SPI frequency to 8MHz - it could be higher, but it is not supported in the old API USB_SPI.setClockDivider(SPI_CLOCK_DIV2); // This will set the SPI frequency to 8MHz - it could be higher, but it is not supported in the old API
#endif #endif
#elif !defined(RBL_NRF51822) #elif !defined(RBL_NRF51822) && !defined(NRF52_SERIES)
USB_SPI.setClockDivider(4); // Set speed to 84MHz/4=21MHz - the MAX3421E can handle up to 26MHz USB_SPI.setClockDivider(4); // Set speed to 84MHz/4=21MHz - the MAX3421E can handle up to 26MHz
#endif #endif
} }
@ -120,6 +120,8 @@ typedef SPi< P16, P18, P17, P10 > spi;
typedef SPi< P14, P13, P12, P15 > spi; typedef SPi< P14, P13, P12, P15 > spi;
#elif defined(ESP32) #elif defined(ESP32)
typedef SPi< P18, P23, P19, P5 > spi; typedef SPi< P18, P23, P19, P5 > spi;
#elif defined(ARDUINO_NRF52840_FEATHER)
typedef SPi< P26, P25, P24, P5 > spi;
#else #else
#error "No SPI entry in usbhost.h" #error "No SPI entry in usbhost.h"
#endif #endif

View file

@ -62,4 +62,10 @@ const uint16_t XBOX_BUTTONS[] PROGMEM = {
0x0008, // SYNC 0x0008, // SYNC
}; };
inline int8_t getButtonIndexXbox(ButtonEnum b) {
const int8_t index = ButtonIndex(b);
if ((uint8_t) index >= (sizeof(XBOX_BUTTONS) / sizeof(XBOX_BUTTONS[0]))) return -1;
return index;
}
#endif #endif