mirror of
https://github.com/felis/USB_Host_Shield_2.0.git
synced 2024-03-22 11:31:26 +01:00
Merge branch 'felis:master' into master
This commit is contained in:
commit
caeb12805b
80 changed files with 5833 additions and 1053 deletions
71
.github/workflows/main.yml
vendored
Normal file
71
.github/workflows/main.yml
vendored
Normal 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
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
*.zip
|
*.zip
|
||||||
*.rar
|
*.rar
|
||||||
build/
|
build/
|
||||||
|
venv/
|
||||||
|
|
106
.travis.yml
106
.travis.yml
|
@ -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
251
AMBX.cpp
Normal 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
159
AMBX.h
Normal 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
38
AMBXEnums.h
Normal 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
268
BTD.cpp
|
@ -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
48
BTD.h
|
@ -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
273
BTHID.cpp
|
@ -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
28
BTHID.h
|
@ -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
111
MiniDSP.cpp
Normal 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
191
MiniDSP.h
Normal 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;
|
||||||
|
};
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
6
PS4BT.h
6
PS4BT.h
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
2
PS4USB.h
2
PS4USB.h
|
@ -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
233
PS5BT.h
Normal 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
174
PS5Parser.cpp
Normal 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
415
PS5Parser.h
Normal 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
94
PS5Trigger.cpp
Normal 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
168
PS5Trigger.h
Normal 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
157
PS5USB.h
Normal 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
|
12
PSBuzz.cpp
12
PSBuzz.cpp
|
@ -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;
|
||||||
|
|
2
PSBuzz.h
2
PSBuzz.h
|
@ -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);
|
||||||
|
|
73
README.md
73
README.md
|
@ -1,8 +1,8 @@
|
||||||
# USB Host Library Rev.2.0
|
# USB Host Library Rev. 2.0
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
8
SPP.cpp
8
SPP.cpp
|
@ -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
8
SPP.h
|
@ -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
94
SwitchProBT.h
Normal 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
253
SwitchProParser.cpp
Normal 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
385
SwitchProParser.h
Normal 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
156
SwitchProUSB.h
Normal 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
|
5
Usb.cpp
5
Usb.cpp
|
@ -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
36
Wii.cpp
|
@ -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
2
Wii.h
|
@ -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
|
||||||
|
|
||||||
|
|
76
XBOXOLD.cpp
76
XBOXOLD.cpp
|
@ -292,26 +292,78 @@ 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]);
|
||||||
return click;
|
const bool click = (ButtonClickState & mask);
|
||||||
|
ButtonClickState &= ~mask;
|
||||||
|
return click;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t XBOXOLD::getAnalogHat(AnalogHatEnum a) {
|
int16_t XBOXOLD::getAnalogHat(AnalogHatEnum a) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
26
XBOXONE.cpp
26
XBOXONE.cpp
|
@ -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) {
|
||||||
D_PrintHex<uint8_t > (rcode, 0x80);
|
Notify(PSTR("\r\nXboxCommand failed. Return: "), 0x80);
|
||||||
|
D_PrintHex<uint8_t > (rcode, 0x80);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return rcode;
|
return rcode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -123,9 +124,10 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
|
virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
|
||||||
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
88
XBOXONESBT.h
Normal 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
221
XBOXONESParser.cpp
Normal 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
129
XBOXONESParser.h
Normal 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
|
16
XBOXRECV.cpp
16
XBOXRECV.cpp
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
};
|
};
|
||||||
/**@}*/
|
/**@}*/
|
||||||
|
|
||||||
|
|
14
XBOXUSB.cpp
14
XBOXUSB.cpp
|
@ -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
149
avrpins.h
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
170
examples/Bluetooth/PS5BT/PS5BT.ino
Normal file
170
examples/Bluetooth/PS5BT/PS5BT.ino
Normal 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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
152
examples/Bluetooth/SwitchProBT/SwitchProBT.ino
Normal file
152
examples/Bluetooth/SwitchProBT/SwitchProBT.ino
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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] = {
|
||||||
|
|
47
examples/MiniDSP/MiniDSP.ino
Normal file
47
examples/MiniDSP/MiniDSP.ino
Normal 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
150
examples/PS5USB/PS5USB.ino
Normal 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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
examples/SwitchProUSB/SwitchProUSB.ino
Normal file
133
examples/SwitchProUSB/SwitchProUSB.ino
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
133
examples/Xbox/XBOXONESBT/XBOXONESBT.ino
Normal file
133
examples/Xbox/XBOXONESBT/XBOXONESBT.ino
Normal 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
61
examples/ambx/AMBX.ino
Normal 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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
361
hiduniversal.cpp
361
hiduniversal.cpp
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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__
|
||||||
|
|
19
keywords.txt
19
keywords.txt
|
@ -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
|
||||||
|
|
17
library.json
17
library.json
|
@ -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"
|
||||||
|
|
|
@ -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=*
|
||||||
|
|
|
@ -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--) {
|
||||||
|
|
|
@ -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)--);
|
||||||
|
|
||||||
|
|
15
settings.h
15
settings.h
|
@ -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
|
||||||
|
|
454
usbh_midi.cpp
454
usbh_midi.cpp
|
@ -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
|
||||||
for (uint8_t i=0; i<num_of_conf; i++) {
|
USBTRACE("\r\nSTEP1: MIDI Start\r\n");
|
||||||
rcode = parseConfigDescr(bAddress, i);
|
for(uint8_t i = 0; i < num_of_conf; i++) {
|
||||||
if( rcode )
|
MidiDescParser midiDescParser(this, true); // Check for MIDI device
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
86
usbh_midi.h
86
usbh_midi.h
|
@ -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_
|
||||||
|
|
2
usbhid.h
2
usbhid.h
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue