Update analog input support

This commit is contained in:
ShikyC 2024-01-04 20:47:25 -08:00
parent 89b4b0af68
commit 73470d1507
8 changed files with 1191 additions and 2 deletions

View File

@ -0,0 +1,136 @@
#define CHANNELS 4
// SAMPLE_CACHE_LENGTH must be power of 2 (8, 16, 32, etc.)
// See cache.h for implementation
#define SAMPLE_CACHE_LENGTH 16
// The thresholds are also dependent on SAMPLE_CACHE_LENGTH, if you
// changed SAMPLE_CACHE_LENGTH, you should also adjust thresholds
#define MAX_THRES 5000
#define HIT_THRES 1000
// If the reset time is too short, the game may not be able to
// receive the input. From testing I found 40 seems to be the
// minimum value so that the game won't miss any hit. If the game
// occassionally miss the drum input, increase this value
#define RESET_TIME 40
// Sensitivity multipliers for each channel, 1.0 as the baseline
#define L_DON_SENS 1.0
#define L_KAT_SENS 1.0
#define R_DON_SENS 1.0
#define R_KAT_SENS 1.0
// Input pins for each channel
#define L_DON_IN 4
#define L_KAT_IN 5
#define R_DON_IN 6
#define R_KAT_IN 7
// Output LED pins for each channel (just for visualization)
#define L_DON_LED 10
#define L_KAT_LED 11
#define R_DON_LED 12
#define R_KAT_LED 13
#include "USB.h"
#include "Joystick_ESP32S2.h"
#include "cache.h"
Cache<int, SAMPLE_CACHE_LENGTH> inputWindow[CHANNELS];
unsigned long power[CHANNELS];
unsigned long lastPower[CHANNELS];
bool triggered;
unsigned long triggeredTime[CHANNELS];
const byte inPins[] = {L_DON_IN, L_KAT_IN, R_DON_IN, R_KAT_IN};
const byte outPins[] = {L_DON_LED, L_KAT_LED, R_DON_LED, R_KAT_LED};
const float sensitivities[] = {L_DON_SENS, L_KAT_SENS, R_DON_SENS, R_KAT_SENS};
uint axisValues[] = {0, 0, 0, 0};
uint shifter = 0;
int outputValue = 0;
uint resetTimer = 0;
short maxIndex;
float maxPower;
unsigned long lastTime;
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 10, 4,
true, true, true, true, true, true,
false, false, false, false, false);
void setup() {
Serial.begin(250000);
for (byte i = 0; i < CHANNELS; i++) {
power[i] = 0;
lastPower[i] = 0;
triggered = false;
pinMode(inPins[i], INPUT);
pinMode(outPins[i], OUTPUT);
}
maxIndex = -1;
maxPower = 0;
lastTime = micros();
USB.PID(0x4869);
USB.VID(0x4869);
USB.productName("Taiko Controller");
USB.manufacturerName("GitHub Community");
USB.begin();
Joystick.begin(false);
Joystick.setXAxisRange(-1024, 1023);
Joystick.setYAxisRange(-1024, 1023);
Joystick.setZAxisRange(-1024, 1023);
Joystick.setRxAxisRange(-1024, 1023);
Joystick.setRyAxisRange(-1024, 1023);
Joystick.setRzAxisRange(-1024, 1023);
}
void loop() {
for (byte i = 0; i < CHANNELS; i++) {
inputWindow[i].put(analogRead(inPins[i]));
power[i] = sensitivities[i] * (power[i] - inputWindow[i].get(1) + inputWindow[i].get());
if (lastPower[i] > maxPower && power[i] < lastPower[i]) {
maxPower = lastPower[i];
maxIndex = i;
}
lastPower[i] = power[i];
}
if (!triggered && maxPower >= HIT_THRES) {
triggered = true;
digitalWrite(outPins[maxIndex], HIGH);
outputValue = (int)(1023 * (maxPower >= MAX_THRES ? 1 : maxPower / MAX_THRES));
}
if (triggered && resetTimer >= RESET_TIME) {
triggered = false;
resetTimer = 0;
digitalWrite(outPins[maxIndex], LOW);
maxPower = 0;
maxIndex = -1;
outputValue = 0;
}
for (byte i = 0; i < CHANNELS; i++) {
if (triggered && i == maxIndex) {
axisValues[i] = outputValue;
} else {
axisValues[i] = 0;
}
}
Joystick.setXAxis(axisValues[0]);
Joystick.setYAxis(axisValues[1]);
Joystick.setRxAxis(axisValues[2]);
Joystick.setRyAxis(axisValues[3]);
Joystick.sendState();
if (triggered) {
resetTimer++;
}
}

View File

@ -0,0 +1,694 @@
/*
Joystick_ESP32S2.cpp
Copyright (c) 2015-2017, Matthew Heironimus
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Edited by Schnoog to make it running on ESP32-S2/s3 devices
*/
#include "Joystick_ESP32S2.h"
#define JOYSTICK_REPORT_ID_INDEX 7
#define JOYSTICK_AXIS_MINIMUM 0
#define JOYSTICK_AXIS_MAXIMUM 65535
#define JOYSTICK_SIMULATOR_MINIMUM 0
#define JOYSTICK_SIMULATOR_MAXIMUM 65535
#define JOYSTICK_INCLUDE_X_AXIS B00000001
#define JOYSTICK_INCLUDE_Y_AXIS B00000010
#define JOYSTICK_INCLUDE_Z_AXIS B00000100
#define JOYSTICK_INCLUDE_RX_AXIS B00001000
#define JOYSTICK_INCLUDE_RY_AXIS B00010000
#define JOYSTICK_INCLUDE_RZ_AXIS B00100000
#define JOYSTICK_INCLUDE_RUDDER B00000001
#define JOYSTICK_INCLUDE_THROTTLE B00000010
#define JOYSTICK_INCLUDE_ACCELERATOR B00000100
#define JOYSTICK_INCLUDE_BRAKE B00001000
#define JOYSTICK_INCLUDE_STEERING B00010000
Joystick_::Joystick_(
uint8_t hidReportId,
uint8_t joystickType,
uint8_t buttonCount,
uint8_t hatSwitchCount,
bool includeXAxis,
bool includeYAxis,
bool includeZAxis,
bool includeRxAxis,
bool includeRyAxis,
bool includeRzAxis,
bool includeRudder,
bool includeThrottle,
bool includeAccelerator,
bool includeBrake,
bool includeSteering)
{
// Set the USB HID Report ID
_hidReportId = hidReportId;
// Save Joystick Settings
_buttonCount = buttonCount;
_hatSwitchCount = hatSwitchCount;
_includeAxisFlags = 0;
_includeAxisFlags |= (includeXAxis ? JOYSTICK_INCLUDE_X_AXIS : 0);
_includeAxisFlags |= (includeYAxis ? JOYSTICK_INCLUDE_Y_AXIS : 0);
_includeAxisFlags |= (includeZAxis ? JOYSTICK_INCLUDE_Z_AXIS : 0);
_includeAxisFlags |= (includeRxAxis ? JOYSTICK_INCLUDE_RX_AXIS : 0);
_includeAxisFlags |= (includeRyAxis ? JOYSTICK_INCLUDE_RY_AXIS : 0);
_includeAxisFlags |= (includeRzAxis ? JOYSTICK_INCLUDE_RZ_AXIS : 0);
_includeSimulatorFlags = 0;
_includeSimulatorFlags |= (includeRudder ? JOYSTICK_INCLUDE_RUDDER : 0);
_includeSimulatorFlags |= (includeThrottle ? JOYSTICK_INCLUDE_THROTTLE : 0);
_includeSimulatorFlags |= (includeAccelerator ? JOYSTICK_INCLUDE_ACCELERATOR : 0);
_includeSimulatorFlags |= (includeBrake ? JOYSTICK_INCLUDE_BRAKE : 0);
_includeSimulatorFlags |= (includeSteering ? JOYSTICK_INCLUDE_STEERING : 0);
// Build Joystick HID Report Description
// Button Calculations
uint8_t buttonsInLastByte = _buttonCount % 8;
uint8_t buttonPaddingBits = 0;
if (buttonsInLastByte > 0)
{
buttonPaddingBits = 8 - buttonsInLastByte;
}
// Axis Calculations
uint8_t axisCount = (includeXAxis == true)
+ (includeYAxis == true)
+ (includeZAxis == true)
+ (includeRxAxis == true)
+ (includeRyAxis == true)
+ (includeRzAxis == true);
uint8_t simulationCount = (includeRudder == true)
+ (includeThrottle == true)
+ (includeAccelerator == true)
+ (includeBrake == true)
+ (includeSteering == true);
uint8_t tempHidReportDescriptor[150];
hidReportDescriptorSize = 0;
// USAGE_PAGE (Generic Desktop)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x05;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// USAGE (Joystick - 0x04; Gamepad - 0x05; Multi-axis Controller - 0x08)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = joystickType;
// COLLECTION (Application)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xa1;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// REPORT_ID (Default: 3)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x85;
tempHidReportDescriptor[hidReportDescriptorSize++] = _hidReportId;
if (_buttonCount > 0) {
// USAGE_PAGE (Button)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x05;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
// USAGE_MINIMUM (Button 1)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x19;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// USAGE_MAXIMUM (Button 32)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x29;
tempHidReportDescriptor[hidReportDescriptorSize++] = _buttonCount;
// LOGICAL_MINIMUM (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x15;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// LOGICAL_MAXIMUM (1)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x25;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// REPORT_SIZE (1)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x75;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// REPORT_COUNT (# of buttons)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x95;
tempHidReportDescriptor[hidReportDescriptorSize++] = _buttonCount;
// UNIT_EXPONENT (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x55;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// UNIT (None)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x65;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// INPUT (Data,Var,Abs)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02;
if (buttonPaddingBits > 0) {
// REPORT_SIZE (1)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x75;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// REPORT_COUNT (# of padding bits)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x95;
tempHidReportDescriptor[hidReportDescriptorSize++] = buttonPaddingBits;
// INPUT (Const,Var,Abs)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x03;
} // Padding Bits Needed
} // Buttons
if ((axisCount > 0) || (_hatSwitchCount > 0)) {
// USAGE_PAGE (Generic Desktop)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x05;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
}
if (_hatSwitchCount > 0) {
// USAGE (Hat Switch)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x39;
// LOGICAL_MINIMUM (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x15;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// LOGICAL_MAXIMUM (7)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x25;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x07;
// PHYSICAL_MINIMUM (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x35;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// PHYSICAL_MAXIMUM (315)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x46;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x3B;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// UNIT (Eng Rot:Angular Pos)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x65;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x14;
// REPORT_SIZE (4)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x75;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x04;
// REPORT_COUNT (1)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x95;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// INPUT (Data,Var,Abs)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02;
if (_hatSwitchCount > 1) {
// USAGE (Hat Switch)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x39;
// LOGICAL_MINIMUM (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x15;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// LOGICAL_MAXIMUM (7)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x25;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x07;
// PHYSICAL_MINIMUM (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x35;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// PHYSICAL_MAXIMUM (315)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x46;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x3B;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// UNIT (Eng Rot:Angular Pos)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x65;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x14;
// REPORT_SIZE (4)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x75;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x04;
// REPORT_COUNT (1)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x95;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// INPUT (Data,Var,Abs)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02;
} else {
// Use Padding Bits
// REPORT_SIZE (1)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x75;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// REPORT_COUNT (4)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x95;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x04;
// INPUT (Const,Var,Abs)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x03;
} // One or Two Hat Switches?
} // Hat Switches
if (axisCount > 0) {
// USAGE (Pointer)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x01;
// LOGICAL_MINIMUM (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x15;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// LOGICAL_MAXIMUM (65535)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x27;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0XFF;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0XFF;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// REPORT_SIZE (16)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x75;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x10;
// REPORT_COUNT (axisCount)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x95;
tempHidReportDescriptor[hidReportDescriptorSize++] = axisCount;
// COLLECTION (Physical)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xA1;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
if (includeXAxis == true) {
// USAGE (X)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x30;
}
if (includeYAxis == true) {
// USAGE (Y)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x31;
}
if (includeZAxis == true) {
// USAGE (Z)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x32;
}
if (includeRxAxis == true) {
// USAGE (Rx)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x33;
}
if (includeRyAxis == true) {
// USAGE (Ry)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x34;
}
if (includeRzAxis == true) {
// USAGE (Rz)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x35;
}
// INPUT (Data,Var,Abs)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02;
// END_COLLECTION (Physical)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xc0;
} // X, Y, Z, Rx, Ry, and Rz Axis
if (simulationCount > 0) {
// USAGE_PAGE (Simulation Controls)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x05;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02;
// LOGICAL_MINIMUM (0)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x15;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// LOGICAL_MAXIMUM (65535)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x27;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0XFF;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0XFF;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
// REPORT_SIZE (16)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x75;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x10;
// REPORT_COUNT (simulationCount)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x95;
tempHidReportDescriptor[hidReportDescriptorSize++] = simulationCount;
// COLLECTION (Physical)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xA1;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x00;
if (includeRudder == true) {
// USAGE (Rudder)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xBA;
}
if (includeThrottle == true) {
// USAGE (Throttle)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xBB;
}
if (includeAccelerator == true) {
// USAGE (Accelerator)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xC4;
}
if (includeBrake == true) {
// USAGE (Brake)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xC5;
}
if (includeSteering == true) {
// USAGE (Steering)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x09;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xC8;
}
// INPUT (Data,Var,Abs)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x81;
tempHidReportDescriptor[hidReportDescriptorSize++] = 0x02;
// END_COLLECTION (Physical)
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xc0;
} // Simulation Controls
// END_COLLECTION
tempHidReportDescriptor[hidReportDescriptorSize++] = 0xc0;
// Create a copy of the HID Report Descriptor template that is just the right size
//org: uint8_t *customHidReportDescriptor = new uint8_t[hidReportDescriptorSize];
customHidReportDescriptor = new uint8_t[hidReportDescriptorSize];
memcpy(customHidReportDescriptor, tempHidReportDescriptor, hidReportDescriptorSize);
// Register HID Report Description
HID.addDevice(this, hidReportDescriptorSize);
// Setup Joystick State
if (buttonCount > 0) {
_buttonValuesArraySize = _buttonCount / 8;
if ((_buttonCount % 8) > 0) {
_buttonValuesArraySize++;
}
_buttonValues = new uint8_t[_buttonValuesArraySize];
}
// Calculate HID Report Size
_hidReportSize = _buttonValuesArraySize;
_hidReportSize += (_hatSwitchCount > 0);
_hidReportSize += (axisCount * 2);
_hidReportSize += (simulationCount * 2);
// Initialize Joystick State
_xAxis = 0;
_yAxis = 0;
_zAxis = 0;
_xAxisRotation = 0;
_yAxisRotation = 0;
_zAxisRotation = 0;
_throttle = 0;
_rudder = 0;
_accelerator = 0;
_brake = 0;
_steering = 0;
for (int index = 0; index < JOYSTICK_HATSWITCH_COUNT_MAXIMUM; index++)
{
_hatSwitchValues[index] = JOYSTICK_HATSWITCH_RELEASE;
}
for (int index = 0; index < _buttonValuesArraySize; index++)
{
_buttonValues[index] = 0;
}
}
uint16_t Joystick_::_onGetDescriptor(uint8_t* buffer){
memcpy(buffer, customHidReportDescriptor,hidReportDescriptorSize);
return hidReportDescriptorSize;
}
void Joystick_::begin(bool initAutoSendState, uint8_t intervalMs)
{
HID.begin();
_autoSendState = initAutoSendState;
sendState();
}
void Joystick_::end()
{
}
void Joystick_::setButton(uint8_t button, uint8_t value)
{
if (value == 0)
{
releaseButton(button);
}
else
{
pressButton(button);
}
}
void Joystick_::pressButton(uint8_t button)
{
if (button >= _buttonCount) return;
int index = button / 8;
int bit = button % 8;
bitSet(_buttonValues[index], bit);
if (_autoSendState) sendState();
}
void Joystick_::releaseButton(uint8_t button)
{
if (button >= _buttonCount) return;
int index = button / 8;
int bit = button % 8;
bitClear(_buttonValues[index], bit);
if (_autoSendState) sendState();
}
void Joystick_::setXAxis(int32_t value)
{
_xAxis = value;
if (_autoSendState) sendState();
}
void Joystick_::setYAxis(int32_t value)
{
_yAxis = value;
if (_autoSendState) sendState();
}
void Joystick_::setZAxis(int32_t value)
{
_zAxis = value;
if (_autoSendState) sendState();
}
void Joystick_::setRxAxis(int32_t value)
{
_xAxisRotation = value;
if (_autoSendState) sendState();
}
void Joystick_::setRyAxis(int32_t value)
{
_yAxisRotation = value;
if (_autoSendState) sendState();
}
void Joystick_::setRzAxis(int32_t value)
{
_zAxisRotation = value;
if (_autoSendState) sendState();
}
void Joystick_::setRudder(int32_t value)
{
_rudder = value;
if (_autoSendState) sendState();
}
void Joystick_::setThrottle(int32_t value)
{
_throttle = value;
if (_autoSendState) sendState();
}
void Joystick_::setAccelerator(int32_t value)
{
_accelerator = value;
if (_autoSendState) sendState();
}
void Joystick_::setBrake(int32_t value)
{
_brake = value;
if (_autoSendState) sendState();
}
void Joystick_::setSteering(int32_t value)
{
_steering = value;
if (_autoSendState) sendState();
}
void Joystick_::setHatSwitch(int8_t hatSwitchIndex, int16_t value)
{
if (hatSwitchIndex >= _hatSwitchCount) return;
_hatSwitchValues[hatSwitchIndex] = value;
if (_autoSendState) sendState();
}
int Joystick_::buildAndSet16BitValue(bool includeValue, int32_t value, int32_t valueMinimum, int32_t valueMaximum, int32_t actualMinimum, int32_t actualMaximum, uint8_t dataLocation[])
{
int32_t convertedValue;
uint8_t highByte;
uint8_t lowByte;
int32_t realMinimum = min(valueMinimum, valueMaximum);
int32_t realMaximum = max(valueMinimum, valueMaximum);
if (includeValue == false) return 0;
if (value < realMinimum) {
value = realMinimum;
}
if (value > realMaximum) {
value = realMaximum;
}
if (valueMinimum > valueMaximum) {
// Values go from a larger number to a smaller number (e.g. 1024 to 0)
value = realMaximum - value + realMinimum;
}
convertedValue = map(value, realMinimum, realMaximum, actualMinimum, actualMaximum);
highByte = (uint8_t)(convertedValue >> 8);
lowByte = (uint8_t)(convertedValue & 0x00FF);
dataLocation[0] = lowByte;
dataLocation[1] = highByte;
return 2;
}
int Joystick_::buildAndSetAxisValue(bool includeAxis, int32_t axisValue, int32_t axisMinimum, int32_t axisMaximum, uint8_t dataLocation[])
{
return buildAndSet16BitValue(includeAxis, axisValue, axisMinimum, axisMaximum, JOYSTICK_AXIS_MINIMUM, JOYSTICK_AXIS_MAXIMUM, dataLocation);
}
int Joystick_::buildAndSetSimulationValue(bool includeValue, int32_t value, int32_t valueMinimum, int32_t valueMaximum, uint8_t dataLocation[])
{
return buildAndSet16BitValue(includeValue, value, valueMinimum, valueMaximum, JOYSTICK_SIMULATOR_MINIMUM, JOYSTICK_SIMULATOR_MAXIMUM, dataLocation);
}
void Joystick_::sendState()
{
uint8_t data[_hidReportSize];
int index = 0;
// Load Button State
for (; index < _buttonValuesArraySize; index++)
{
data[index] = _buttonValues[index];
}
// Set Hat Switch Values
if (_hatSwitchCount > 0) {
// Calculate hat-switch values
uint8_t convertedHatSwitch[JOYSTICK_HATSWITCH_COUNT_MAXIMUM];
for (int hatSwitchIndex = 0; hatSwitchIndex < JOYSTICK_HATSWITCH_COUNT_MAXIMUM; hatSwitchIndex++)
{
if (_hatSwitchValues[hatSwitchIndex] < 0)
{
convertedHatSwitch[hatSwitchIndex] = 8;
}
else
{
convertedHatSwitch[hatSwitchIndex] = (_hatSwitchValues[hatSwitchIndex] % 360) / 45;
}
}
// Pack hat-switch states into a single byte
data[index++] = (convertedHatSwitch[1] << 4) | (B00001111 & convertedHatSwitch[0]);
} // Hat Switches
// Set Axis Values
index += buildAndSetAxisValue(_includeAxisFlags & JOYSTICK_INCLUDE_X_AXIS, _xAxis, _xAxisMinimum, _xAxisMaximum, &(data[index]));
index += buildAndSetAxisValue(_includeAxisFlags & JOYSTICK_INCLUDE_Y_AXIS, _yAxis, _yAxisMinimum, _yAxisMaximum, &(data[index]));
index += buildAndSetAxisValue(_includeAxisFlags & JOYSTICK_INCLUDE_Z_AXIS, _zAxis, _zAxisMinimum, _zAxisMaximum, &(data[index]));
index += buildAndSetAxisValue(_includeAxisFlags & JOYSTICK_INCLUDE_RX_AXIS, _xAxisRotation, _rxAxisMinimum, _rxAxisMaximum, &(data[index]));
index += buildAndSetAxisValue(_includeAxisFlags & JOYSTICK_INCLUDE_RY_AXIS, _yAxisRotation, _ryAxisMinimum, _ryAxisMaximum, &(data[index]));
index += buildAndSetAxisValue(_includeAxisFlags & JOYSTICK_INCLUDE_RZ_AXIS, _zAxisRotation, _rzAxisMinimum, _rzAxisMaximum, &(data[index]));
// Set Simulation Values
index += buildAndSetSimulationValue(_includeSimulatorFlags & JOYSTICK_INCLUDE_RUDDER, _rudder, _rudderMinimum, _rudderMaximum, &(data[index]));
index += buildAndSetSimulationValue(_includeSimulatorFlags & JOYSTICK_INCLUDE_THROTTLE, _throttle, _throttleMinimum, _throttleMaximum, &(data[index]));
index += buildAndSetSimulationValue(_includeSimulatorFlags & JOYSTICK_INCLUDE_ACCELERATOR, _accelerator, _acceleratorMinimum, _acceleratorMaximum, &(data[index]));
index += buildAndSetSimulationValue(_includeSimulatorFlags & JOYSTICK_INCLUDE_BRAKE, _brake, _brakeMinimum, _brakeMaximum, &(data[index]));
index += buildAndSetSimulationValue(_includeSimulatorFlags & JOYSTICK_INCLUDE_STEERING, _steering, _steeringMinimum, _steeringMaximum, &(data[index]));
if (HID.ready()) {
HID.SendReport(_hidReportId, data, sizeof(data),0);
}
}

View File

@ -0,0 +1,217 @@
/*
Joystick.h
Copyright (c) 2015-2017, Matthew Heironimus
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Edited by Schnoog to make it running on ESP32-S2/s3 devices
*/
#ifndef JOYSTICK_h
#define JOYSTICK_h
#include <stdint.h>
#include <Arduino.h>
#include "USB.h"
#include "USBHID.h"
//================================================================================
// Joystick (Gamepad)
#define JOYSTICK_DEFAULT_REPORT_ID 0x03
#define JOYSTICK_DEFAULT_BUTTON_COUNT 32
#define JOYSTICK_DEFAULT_AXIS_MINIMUM 0
#define JOYSTICK_DEFAULT_AXIS_MAXIMUM 1023
#define JOYSTICK_DEFAULT_SIMULATOR_MINIMUM 0
#define JOYSTICK_DEFAULT_SIMULATOR_MAXIMUM 1023
#define JOYSTICK_DEFAULT_HATSWITCH_COUNT 2
#define JOYSTICK_HATSWITCH_COUNT_MAXIMUM 2
#define JOYSTICK_HATSWITCH_RELEASE -1
#define JOYSTICK_TYPE_JOYSTICK 0x04
#define JOYSTICK_TYPE_GAMEPAD 0x05
#define JOYSTICK_TYPE_MULTI_AXIS 0x08
class Joystick_: public USBHIDDevice
{
private:
// Joystick State
int32_t _xAxis;
int32_t _yAxis;
int32_t _zAxis;
int32_t _xAxisRotation;
int32_t _yAxisRotation;
int32_t _zAxisRotation;
int32_t _throttle;
int32_t _rudder;
int32_t _accelerator;
int32_t _brake;
int32_t _steering;
int16_t _hatSwitchValues[JOYSTICK_HATSWITCH_COUNT_MAXIMUM];
uint8_t *_buttonValues = NULL;
// Joystick Settings
bool _autoSendState;
uint8_t _buttonCount;
uint8_t _buttonValuesArraySize = 0;
uint8_t _hatSwitchCount;
uint8_t _includeAxisFlags;
uint8_t _includeSimulatorFlags;
int32_t _xAxisMinimum = JOYSTICK_DEFAULT_AXIS_MINIMUM;
int32_t _xAxisMaximum = JOYSTICK_DEFAULT_AXIS_MAXIMUM;
int32_t _yAxisMinimum = JOYSTICK_DEFAULT_AXIS_MINIMUM;
int32_t _yAxisMaximum = JOYSTICK_DEFAULT_AXIS_MAXIMUM;
int32_t _zAxisMinimum = JOYSTICK_DEFAULT_AXIS_MINIMUM;
int32_t _zAxisMaximum = JOYSTICK_DEFAULT_AXIS_MAXIMUM;
int32_t _rxAxisMinimum = JOYSTICK_DEFAULT_AXIS_MINIMUM;
int32_t _rxAxisMaximum = JOYSTICK_DEFAULT_AXIS_MAXIMUM;
int32_t _ryAxisMinimum = JOYSTICK_DEFAULT_AXIS_MINIMUM;
int32_t _ryAxisMaximum = JOYSTICK_DEFAULT_AXIS_MAXIMUM;
int32_t _rzAxisMinimum = JOYSTICK_DEFAULT_AXIS_MINIMUM;
int32_t _rzAxisMaximum = JOYSTICK_DEFAULT_AXIS_MAXIMUM;
int32_t _rudderMinimum = JOYSTICK_DEFAULT_SIMULATOR_MINIMUM;
int32_t _rudderMaximum = JOYSTICK_DEFAULT_SIMULATOR_MAXIMUM;
int32_t _throttleMinimum = JOYSTICK_DEFAULT_SIMULATOR_MINIMUM;
int32_t _throttleMaximum = JOYSTICK_DEFAULT_SIMULATOR_MAXIMUM;
int32_t _acceleratorMinimum = JOYSTICK_DEFAULT_SIMULATOR_MINIMUM;
int32_t _acceleratorMaximum = JOYSTICK_DEFAULT_SIMULATOR_MAXIMUM;
int32_t _brakeMinimum = JOYSTICK_DEFAULT_SIMULATOR_MINIMUM;
int32_t _brakeMaximum = JOYSTICK_DEFAULT_SIMULATOR_MAXIMUM;
int32_t _steeringMinimum = JOYSTICK_DEFAULT_SIMULATOR_MINIMUM;
int32_t _steeringMaximum = JOYSTICK_DEFAULT_SIMULATOR_MAXIMUM;
uint8_t _hidReportId;
uint8_t _hidReportSize;
USBHID HID;
protected:
int buildAndSet16BitValue(bool includeValue, int32_t value, int32_t valueMinimum, int32_t valueMaximum, int32_t actualMinimum, int32_t actualMaximum, uint8_t dataLocation[]);
int buildAndSetAxisValue(bool includeAxis, int32_t axisValue, int32_t axisMinimum, int32_t axisMaximum, uint8_t dataLocation[]);
int buildAndSetSimulationValue(bool includeValue, int32_t value, int32_t valueMinimum, int32_t valueMaximum, uint8_t dataLocation[]);
public:
uint8_t *customHidReportDescriptor;
int hidReportDescriptorSize;
Joystick_(
uint8_t hidReportId = JOYSTICK_DEFAULT_REPORT_ID,
uint8_t joystickType = JOYSTICK_TYPE_JOYSTICK,
uint8_t buttonCount = JOYSTICK_DEFAULT_BUTTON_COUNT,
uint8_t hatSwitchCount = JOYSTICK_DEFAULT_HATSWITCH_COUNT,
bool includeXAxis = true,
bool includeYAxis = true,
bool includeZAxis = true,
bool includeRxAxis = true,
bool includeRyAxis = true,
bool includeRzAxis = true,
bool includeRudder = true,
bool includeThrottle = true,
bool includeAccelerator = true,
bool includeBrake = true,
bool includeSteering = true);
void begin(bool initAutoSendState = true, uint8_t interval_ms = 2);
void end();
// Set Range Functions
inline void setXAxisRange(int32_t minimum, int32_t maximum)
{
_xAxisMinimum = minimum;
_xAxisMaximum = maximum;
}
inline void setYAxisRange(int32_t minimum, int32_t maximum)
{
_yAxisMinimum = minimum;
_yAxisMaximum = maximum;
}
inline void setZAxisRange(int32_t minimum, int32_t maximum)
{
_zAxisMinimum = minimum;
_zAxisMaximum = maximum;
}
inline void setRxAxisRange(int32_t minimum, int32_t maximum)
{
_rxAxisMinimum = minimum;
_rxAxisMaximum = maximum;
}
inline void setRyAxisRange(int32_t minimum, int32_t maximum)
{
_ryAxisMinimum = minimum;
_ryAxisMaximum = maximum;
}
inline void setRzAxisRange(int32_t minimum, int32_t maximum)
{
_rzAxisMinimum = minimum;
_rzAxisMaximum = maximum;
}
inline void setRudderRange(int32_t minimum, int32_t maximum)
{
_rudderMinimum = minimum;
_rudderMaximum = maximum;
}
inline void setThrottleRange(int32_t minimum, int32_t maximum)
{
_throttleMinimum = minimum;
_throttleMaximum = maximum;
}
inline void setAcceleratorRange(int32_t minimum, int32_t maximum)
{
_acceleratorMinimum = minimum;
_acceleratorMaximum = maximum;
}
inline void setBrakeRange(int32_t minimum, int32_t maximum)
{
_brakeMinimum = minimum;
_brakeMaximum = maximum;
}
inline void setSteeringRange(int32_t minimum, int32_t maximum)
{
_steeringMinimum = minimum;
_steeringMaximum = maximum;
}
// Set Axis Values
void setXAxis(int32_t value);
void setYAxis(int32_t value);
void setZAxis(int32_t value);
void setRxAxis(int32_t value);
void setRyAxis(int32_t value);
void setRzAxis(int32_t value);
// Set Simulation Values
void setRudder(int32_t value);
void setThrottle(int32_t value);
void setAccelerator(int32_t value);
void setBrake(int32_t value);
void setSteering(int32_t value);
void setButton(uint8_t button, uint8_t value);
void pressButton(uint8_t button);
void releaseButton(uint8_t button);
void setHatSwitch(int8_t hatSwitch, int16_t value);
void sendState();
uint16_t _onGetDescriptor(uint8_t* buffer);
};
#endif // JOYSTICK_h

31
ESP32-S3-Analog/cache.h Normal file
View File

@ -0,0 +1,31 @@
/***************************************************************
* *
* Taiko Sanro - Arduino *
* Cache data structure *
* *
* Chris *
* wisaly@gmail.com *
* *
***************************************************************/
#ifndef CACHE_H
#define CACHE_H
template <class T, int L>
class Cache {
public:
Cache() { memset(data_, 0, sizeof(data_)); }
inline void put(T value) {
current_ = (current_ + 1) & (L - 1);
data_[current_] = value;
}
inline T get(int offset = 0) const {
return data_[(current_ + offset) & (L - 1)];
}
private:
T data_[L];
int current_ = 0;
};
#endif // CACHE_H

Binary file not shown.

View File

@ -91,3 +91,40 @@ This project aims to help you develop your own hardware taiko at home.
Using a bridge rectifier, all negative values are converted to positive. In other words, it's like the `abs()` function, ensuring that we don't lose any negative voltages. Using a bridge rectifier, all negative values are converted to positive. In other words, it's like the `abs()` function, ensuring that we don't lose any negative voltages.
![Why using bridge rectifiers](./images/bridge_signal.png) ![Why using bridge rectifiers](./images/bridge_signal.png)
# Taiko Controller - Analog Input Mode (Beta)
With ESP32-S2 or ESP32-S3 controllers, instead of keyboard emulation, the drum controller can work as a gamepad and send its axes values to the game (which also must support analog input). In this way, the game can recognize different force levels of the hit.
If you prefer to use the Arduino Micro/Leonardo board, please refer to the [Arduino XInput Library](https://github.com/dmadison/ArduinoXInput) to implement the gamepad.
## What You Need
1. Make your drum or use Taiko Force Lv.5.
2. Flash `ESP32-S3-Analog/ESP32-S3-Analog.ino` to your controller.
3. A working ***game***, with these modifications:
- Backup and replace the `bnusio.dll` file in the game folder with the one here in the `extra/` folder.
This file is compiled from [this fork](https://github.com/ShikyC/TaikoArcadeLoader/tree/Refactor) and you can compile it by yourself if you want.
*This modified library only works with a specific game version. If it breaks your game, please clone the original repository, make the corrensponding modifications, and compile it.*
- Open the `gamecontrollerdb.txt` file in the game folder and add one entry under `#Windows`:
`030052a8694800006948000000000000,Taiko Controller,+leftx:+a0,+lefty:+a1,+rightx:+a3,+righty:+a4,platform:Windows,`
This will tell the game that our ESP32 controller is a gamepad called "Taiko Controller", and map the axis to the standard SDL2 library so that the game can recognize the analog inputs.
- Open the `config.toml` file and add the following lines at the end:
```
[controller]
analog = true
```
Note that with `analog = true`, all the keyboard drum inputs are disabled. Sorry for this but it need further refactoring to make them work together. If you want to switch back to keyboard inputs, set `analog = false`.
4. Launch the game and enjoy!

View File

@ -90,4 +90,41 @@
ブリッジ整流器を使用すると、すべての負の値が正の値に変換されます。言い換えれば、それは`abs()`関数のようなもので、負の電圧を失わないことを保証します。 ブリッジ整流器を使用すると、すべての負の値が正の値に変換されます。言い換えれば、それは`abs()`関数のようなもので、負の電圧を失わないことを保証します。
![ブリッジ整流器の使用理由](./images/bridge_signal.png) ![ブリッジ整流器の使用理由](./images/bridge_signal.png)
# Taiko Controller - アナログ入力モードBeta
ESP32-S2またはESP32-S3コントローラーを使用すると、キーボードエミュレーションの代わりに、ドラムコントローラーがゲームパッドとして機能し、その軸の値をゲームに送信できますゲームもアナログ入力をサポートしている必要があります。この方法では、ゲームはヒットの異なる力のレベルを認識できます。
Arduino Micro/Leonardoボードを使用する場合は、[Arduino XInputライブラリ](https://github.com/dmadison/ArduinoXInput)を参照して、ゲームパッドを実装してください。
## 必要なもの
1. 自分のドラムを作るか、Taiko Force Lv.5を使用してください。詳細はメインブランチをチェックしてください。
2. `ESP32-S3-Analog/ESP32-S3-Analog.ino`をコントローラーにフラッシュします。
3. 以下の変更を加えたゲーム:
- ゲームフォルダ内の`bnusio.dll`ファイルをバックアップし、`extra/`フォルダーこちらのファイルで置き換えてください。
このファイルは[このフォーク](https://github.com/ShikyC/TaikoArcadeLoader/tree/Refactor)からコンパイルされており、必要に応じて自分でコンパイルすることができます。
*この変更されたライブラリは特定のゲームバージョンでのみ機能します。ゲームが壊れた場合は、元のリポジトリをクローンして、対応する変更を加え、コンパイルしてください。*
- ゲームフォルダ内の`gamecontrollerdb.txt`ファイルを開き、`#Windows`の下に次のエントリを追加します:
`030052a8694800006948000000000000,Taiko Controller,+leftx:+a0,+lefty:+a1,+rightx:+a3,+righty:+a4,platform:Windows,`
これにより、ゲームはESP32コントローラーが「Taiko Controller」というゲームパッドであることを認識し、標準のSDL2ライブラリに軸をマップするため、ゲームがアナログ入力を認識できるようになります。
- `config.toml`ファイルを開き、最後に次の行を追加します:
```
[controller]
analog = true
```
`analog = true`で、すべてのキーボードドラム入力が無効になります。これはさらなるリファクタリングが必要ですが、一緒に機能させるために申し訳ありません。キーボード入力に戻したい場合は、`analog = false`に設定してください。
4. ゲームを起動して楽しんでください!

View File

@ -90,4 +90,41 @@
使用桥式整流器,所有负值都转换为正值。换句话说,就像`abs()`函数,确保我们不会丢失任何负电压。 使用桥式整流器,所有负值都转换为正值。换句话说,就像`abs()`函数,确保我们不会丢失任何负电压。
![为什么使用桥式整流器](./images/bridge_signal.png) ![为什么使用桥式整流器](./images/bridge_signal.png)
# Taiko Controller - 模拟输入模式
使用ESP32-S2或ESP32-S3控制器代替键盘仿真鼓控制器可以作为游戏手柄工作并将其轴值发送给游戏游戏也必须支持模拟输入。这样游戏可以识别击打的不同力度级别。
如果您更喜欢使用Arduino Micro/Leonardo板请参考[Arduino XInput库](https://github.com/dmadison/ArduinoXInput)来实现游戏手柄。
## 你需要什么
1. 制作你的鼓或使用Taiko Force Lv.5。详情请查看主分支。
2. 将`ESP32-S3-Analog/ESP32-S3-Analog.ino`刷写到你的控制器。
3. 一个能正常运行的***游戏***,并进行以下修改:
- 备份并替换游戏文件夹中的`bnusio.dll`文件,使用这里`extra/`文件夹中的文件。
这个文件是从[这个仓库](https://github.com/ShikyC/TaikoArcadeLoader/tree/Refactor)编译的,如果你愿意,你也可以自己编译。
*这个修改过的库只适用于特定版本的游戏。如果它破坏了你的游戏,请克隆[原始仓库](https://github.com/BroGamer4256/TaikoArcadeLoader),进行相应的修改,并编译它。*
- 打开游戏文件夹中的`gamecontrollerdb.txt`文件,并在`#Windows`下添加一条条目:
`030052a8694800006948000000000000,Taiko Controller,+leftx:+a0,+lefty:+a1,+rightx:+a3,+righty:+a4,platform:Windows,`
这将告诉游戏我们的ESP32控制器是一个名为“Taiko Controller”的游戏手柄并将轴映射到标准SDL2库以便游戏能够识别模拟输入。
- 打开`config.toml`文件,并在末尾添加以下行:
```
[controller]
analog = true
```
请注意,使用`analog = true`时,所有键盘鼓输入都将被禁用。对此表示抱歉,但它需要进一步重构才能同时工作。如果你想切换回键盘输入,请设置`analog = false`。
4. 开始游玩咚!