Skip to content

Commit

Permalink
Turbo updates (#863)
Browse files Browse the repository at this point in the history
* remove double initialization of gamepad GPIOs

* move gamepad debouncing to one GPIO debouncer

a couple classes had their own debouncer or reference to the gamepad
debouncer. this seemed inefficient and caused potential for incongruent
behavior, thinking that instead we should just debounce the GPIO pins
(for buttons). this does that. this doesn't yet clean up all the code
(specifically the old debouncer) but it does get everything on the new
debouncer.

things appear to work as before, maybe they solve some debouncing
problems for folks, work to be done

* review: debounceDelay should be a uint32_t

* cleanups now that we have a new centralized pin debouncer

* review: move debouncedGpio from an inline static to storage

* do the pin debounce before setting gamepad state

this fixes boot time shortcuts that were broken by my refactor

* skip debouncing if debounce time is configured to 0

* apply Feral's debouncer fix --- apply debounceDelay always

the previous debouncer logic would try to only wait for the
debounceDelay on button depresses, IIRC in an attempt to get the real
button press immediately, but the more I look at it, the more I come to
the same conclusion as Feral that such logic is flawed, and will
register flapping switches because it's eagerly not debouncing them.

with this change, button presses only register if it's been
debounceDelay (default 5ms) since the last change. buttons will still
register immediately as long as the delay has been met, so in practice
this may only affect a latency tester and some kind of superhuman who
mashes buttons at sub-5ms speeds.

this applies Feral's logic (from the button debouncer) to the refactored
pin debouncer.

Co-authored-by: FeralAI <[email protected]>

* Tune turbo

* Throttle ADC shot reads/saves, update LED states

* Reset turbo flicker when all buttons disabled

* Fix issue with turbo flagging

* Tune turbo loop timing

* Update default turbo rate

---------

Co-authored-by: Brian S. Stephan <[email protected]>
  • Loading branch information
FeralAI and bsstephan authored Feb 27, 2024
1 parent fad0fa8 commit 165a136
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 70 deletions.
17 changes: 10 additions & 7 deletions headers/addons/turbo.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#endif

#ifndef DEFAULT_SHOT_PER_SEC
#define DEFAULT_SHOT_PER_SEC 15
#define DEFAULT_SHOT_PER_SEC 10
#endif // DEFAULT_SHOT_PER_SEC

// TURBO Button Mask
Expand Down Expand Up @@ -94,28 +94,31 @@ class TurboInput : public GPAddon {
virtual void process(); // TURBO Setting of buttons (Enable/Disable)
virtual std::string name() { return TurboName; }
private:
void read(const TurboOptions&); // Read TURBO Buttons and Dials
void updateInterval(uint8_t shotCount);
void updateTurboShotCount(uint8_t turboShotCount);
Pin_t turboPin; // Pin for Turbo from Gamepad/BoardConfig
Mask_t turboPinMask; // Pin mask for Turbo pin
bool bDebState; // Debounce TURBO Button State
uint32_t uDebTime; // Debounce TURBO Button Time
uint32_t debChargeState; // Debounce Charge Button State
uint32_t debChargeTime[4]; // Debounce Charge Button Time
uint16_t lastPressed; // Last buttons pressed (for Turbo Enable)
uint16_t lastDpad; // Last d-pad pressed (for Turbo Change)
uint8_t lastDpad; // Last d-pad pressed (for Turbo Change)
uint16_t turboButtonsPressed; // Turbo Buttons Enabled
uint16_t alwaysEnabled; // Turbo SHMUP Always Enabled
uint32_t uIntervalMS; // Turbo Interval
bool bTurboState; // Turbo Buttons State
uint32_t uIntervalUS; // Turbo Interval in microseconds
uint32_t chargeState; // Turbo Charge Button States
bool bTurboFlicker; // Turbo Enable Buttons Toggle OFF Flag ??
uint32_t nextTimer; // Turbo Timer
uint64_t nextTimer; // Turbo Timer
uint8_t adcShmupDial; // Turbo ADC Dial Input
uint64_t nextAdcRead; // ADC read timer
bool hasShmupDial; // Flag for shmup dial presence
uint16_t dialValue; // Turbo Dial Value (Raw)
uint16_t incrementValue; // Turbo Dial Increment Value
uint8_t turboDialIncrements; // Turbo Increments based on max/min
uint8_t shmupBtnPin[4]; // Turbo SHMUP Non-Turbo Pins
uint16_t shmupBtnPinMask[4];// Cache for shmup button pin masks
uint16_t shmupBtnMask[4]; // Turbo SHMUP Non-Turbo Button Masks
uint16_t lastButtons; // Last buttons (for Turbo Reset on Release)
bool hasLedPin; // Flag for LED pin presence
};
#endif // TURBO_H_
3 changes: 2 additions & 1 deletion headers/gamepad.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ class Gamepad {
void process();
void read();
void save();

void hotkey();
void clearState();

/**
* @brief Flag to indicate analog trigger support.
Expand Down
136 changes: 76 additions & 60 deletions src/addons/turbo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
#include "config.pb.h"

#include <algorithm>
#include <cmath>

#define TURBO_SHOT_MIN 2
#define TURBO_SHOT_MAX 30
#define TURBO_DIAL_INCREMENTS (0xFFF / (TURBO_SHOT_MAX - TURBO_SHOT_MIN)) // 12-bit ADC
#define TURBO_LOOP_OFFSET 50 // Extra time to compensate for loop runtime variance, turbo lags a bit otherwise

bool TurboInput::available() {
// Turbo Button initialized by void Gamepad::setup()
Expand All @@ -29,23 +32,24 @@ bool TurboInput::available() {
void TurboInput::setup()
{
const TurboOptions& options = Storage::getInstance().getAddonOptions().turboOptions;
Gamepad * gamepad = Storage::getInstance().GetGamepad();
uint32_t now = getMillis();

// Turbo Dial
uint8_t shotCount = std::clamp<uint8_t>(options.shotCount, TURBO_SHOT_MIN, TURBO_SHOT_MAX);
if ( isValidPin(options.shmupDialPin) ) {
if (isValidPin(options.shmupDialPin)) {
hasShmupDial = true;
adc_gpio_init(options.shmupDialPin);
adcShmupDial = 26 - options.shmupDialPin;
adc_select_input(adcShmupDial);
dialValue = adc_read(); // setup initial Dial + Turbo Speed
shotCount = (dialValue / turboDialIncrements) + TURBO_SHOT_MIN;
shotCount = (dialValue / TURBO_DIAL_INCREMENTS) + TURBO_SHOT_MIN;
} else {
dialValue = 0;
}

// Setup Turbo LED if available
if ( isValidPin(options.ledPin) ) {
if (isValidPin(options.ledPin)) {
hasLedPin = true;
gpio_init(options.ledPin);
gpio_set_dir(options.ledPin, GPIO_OUT);
gpio_put(options.ledPin, 1);
Expand All @@ -62,6 +66,7 @@ void TurboInput::setup()
gpio_init(shmupBtnPin[i]);
gpio_set_dir(shmupBtnPin[i], GPIO_IN);
gpio_pull_up(shmupBtnPin[i]);
shmupBtnPinMask[i] = 1 << shmupBtnPin[i];
}
}
shmupBtnMask[0] = options.shmupBtnMask1; // Charge Buttons Assignment
Expand All @@ -77,105 +82,115 @@ void TurboInput::setup()
turboButtonsPressed = 0;
}

if (isValidPin(turboPin)) {
turboPinMask = 1 << turboPin;
}

lastButtons = 0;
bDebState = false;
uDebTime = now;
debChargeState = 0;
for(uint8_t i = 0; i < 4; i++) {
debChargeTime[i] = now;
}
turboDialIncrements = 0xFFF / (TURBO_SHOT_MAX - TURBO_SHOT_MIN); // 12-bit ADC
incrementValue = 0;
lastPressed = 0;
lastDpad = 0;
uIntervalMS = (uint32_t)(1000.0 / shotCount);
bTurboState = false;
bTurboFlicker = false;
nextTimer = getMillis();
}

void TurboInput::read(const TurboOptions & options)
{
// Get Charge Buttons
if ( options.shmupModeEnabled ) {
chargeState = 0;
for (uint8_t i = 0; i < 4; i++) {
if ( shmupBtnPin[i] != (uint8_t)-1 ) { // if pin, get the GPIO
chargeState |= (!gpio_get(shmupBtnPin[i]) ? shmupBtnMask[i] : 0);
}
}
}

// Get TURBO Key State
bTurboState = !gpio_get(turboPin);
updateInterval(shotCount);
nextTimer = getMicro();
}

void TurboInput::process()
{
Gamepad * gamepad = Storage::getInstance().GetGamepad();
const TurboOptions& options = Storage::getInstance().getAddonOptions().turboOptions;
uint16_t buttonsPressed = gamepad->state.buttons & TURBO_BUTTON_MASK;
uint16_t dpadPressed = gamepad->state.dpad & GAMEPAD_MASK_DPAD;

// Get Turbo Button States
read(options);
uint8_t dpadPressed = gamepad->state.dpad & GAMEPAD_MASK_DPAD;

// Set TURBO Enable Buttons
if (bTurboState) {
// Check for TURBO pin enabled
if (gamepad->debouncedGpio & turboPinMask) {
if (buttonsPressed && (lastPressed != buttonsPressed)) {
turboButtonsPressed ^= buttonsPressed; // Toggle Turbo
gamepad->turboState.buttons = turboButtonsPressed; //turboButtonsPressed & TURBO_BUTTON_MASK; //&= TURBO_BUTTON_MASK;
if ( options.shmupModeEnabled ) {
gamepad->turboState.buttons = turboButtonsPressed; //turboButtonsPressed & TURBO_BUTTON_MASK; //&= TURBO_BUTTON_MASK;
if (options.shmupModeEnabled) {
turboButtonsPressed |= alwaysEnabled; // SHMUP Always-on Buttons Set
}
// Turn off button once turbo is toggled
gamepad->state.buttons &= ~(TURBO_BUTTON_MASK);
}

if (dpadPressed & GAMEPAD_MASK_DOWN && (lastDpad != dpadPressed)) {
if ( options.shotCount > TURBO_SHOT_MIN ) { // can't go lower than 2-shots per second
if (options.shotCount > TURBO_SHOT_MIN) { // can't go lower than 2-shots per second
updateTurboShotCount(options.shotCount - 1);
}
} else if ( dpadPressed & GAMEPAD_MASK_UP && (lastDpad != dpadPressed)) {
if ( options.shotCount < TURBO_SHOT_MAX ) { // can't go higher than 60-shots per second
}
else if (dpadPressed & GAMEPAD_MASK_UP && (lastDpad != dpadPressed)) {
if (options.shotCount < TURBO_SHOT_MAX) { // can't go higher than 30-shots per second
updateTurboShotCount(options.shotCount + 1);
}
}
lastPressed = buttonsPressed; // save last pressed
lastDpad = dpadPressed;

// Clear gamepad outputs since we're in turbo adjustment mode
gamepad->clearState();
return; // Holding TURBO cancels turbo functionality
} else {
lastPressed = 0; // disable last pressed
lastDpad = 0; // disable last dpad
}

// Get Charge Buttons
if (options.shmupModeEnabled) {
chargeState = 0;
for (uint8_t i = 0; i < 4; i++) {
if (shmupBtnPinMask[i] > 0) { // if pin, get the GPIO
chargeState |= (!(gamepad->debouncedGpio & shmupBtnPinMask[i]) ? shmupBtnMask[i] : 0);
}
}
}

uint64_t now = getMicro();

// Use the dial to modify our turbo shot speed (don't save on dial modify)
if ( isValidPin(options.shmupDialPin) ) {
if (hasShmupDial && nextAdcRead < now) {
adc_select_input(adcShmupDial);
uint16_t rawValue = adc_read();
if ( rawValue != dialValue ) {
updateTurboShotCount((rawValue / turboDialIncrements) + TURBO_SHOT_MIN);
dialValue = adc_read();
uint8_t shotCount = (dialValue / TURBO_DIAL_INCREMENTS) + TURBO_SHOT_MIN;
if (shotCount != options.shotCount) {
updateInterval(shotCount);
}
dialValue = rawValue;
nextAdcRead = now + 100000; // Sample every 100ms
}

// Reset Turbo flicker on a new button press
if((lastButtons & turboButtonsPressed) == 0 && (gamepad->state.buttons & turboButtonsPressed) != 0){
if ((lastButtons & turboButtonsPressed) == 0 && (gamepad->state.buttons & turboButtonsPressed) != 0) {
bTurboFlicker = false; // reset flicker state to ON
nextTimer = getMillis() + uIntervalMS; // interval to flicker-off button
nextTimer = now + uIntervalUS - TURBO_LOOP_OFFSET; // interval to flicker-off button
}
// Check if we've reached the next timer right before applying turbo state
else if (turboButtonsPressed && nextTimer < now) {
bTurboFlicker ^= true;
nextTimer = now + uIntervalUS - TURBO_LOOP_OFFSET;
}
lastButtons = gamepad->state.buttons;

// Set TURBO LED if a button is going or turbo is too fast
if ( isValidPin(options.ledPin) ) {
if ((gamepad->state.buttons & turboButtonsPressed) && !bTurboFlicker) {
gpio_put(options.ledPin, 0);
} else {
// Set TURBO LED
// OFF: No turbo buttons enabled
// ON: 1 or more turbo buttons enabled
// BLINK: OFF on turbo shot, ON on turbo flicker
if (hasLedPin) {
if (!turboButtonsPressed) {
gpio_put(options.ledPin, 1);
}
else {
gpio_put(options.ledPin, (gamepad->state.buttons & turboButtonsPressed) && !bTurboFlicker);
}
}

// Button updates
lastButtons = gamepad->state.buttons;

// set charge buttons (mix mode)
if ( options.shmupModeEnabled ) {
if (options.shmupModeEnabled) {
gamepad->state.buttons |= chargeState; // Inject Mask into button states
}

Expand All @@ -187,19 +202,20 @@ void TurboInput::process()
gamepad->state.buttons &= ~(turboButtonsPressed);
}
}
}

if (getMillis() < nextTimer) {
return; // don't flip if we haven't reached the next timer
}

bTurboFlicker ^= true; // Button ON/OFF State Reverse
nextTimer = getMillis() + uIntervalMS; // interval to flicker-off button
void TurboInput::updateInterval(uint8_t shotCount)
{
uIntervalUS = (uint32_t)std::floor(1000000.0 / (shotCount * 2));
}

void TurboInput::updateTurboShotCount(uint8_t shotCount)
{
TurboOptions& options = Storage::getInstance().getAddonOptions().turboOptions;
options.shotCount = std::clamp<uint8_t>(shotCount, TURBO_SHOT_MIN, TURBO_SHOT_MAX);
Storage::getInstance().save();
uIntervalMS = (uint32_t)(1000.0 / options.shotCount);
shotCount = std::clamp<uint8_t>(shotCount, TURBO_SHOT_MIN, TURBO_SHOT_MAX);
if (shotCount != options.shotCount) {
options.shotCount = shotCount;
Storage::getInstance().save();
}
updateInterval(shotCount);
}
12 changes: 12 additions & 0 deletions src/gamepad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,18 @@ void Gamepad::hotkey()
}
}

void Gamepad::clearState() {
state.dpad = 0;
state.buttons = 0;
state.aux = 0;
state.lx = GAMEPAD_JOYSTICK_MID;
state.ly = GAMEPAD_JOYSTICK_MID;
state.rx = GAMEPAD_JOYSTICK_MID;
state.ry = GAMEPAD_JOYSTICK_MID;
state.lt = 0;
state.rt = 0;
}

/**
* @brief Take a hotkey action if it hasn't already been taken, modifying state/options appropriately.
*/
Expand Down
6 changes: 4 additions & 2 deletions src/gp2040.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@ void GP2040::setup() {
addons.LoadAddon(new I2CAnalog1219Input(), CORE0_INPUT);
addons.LoadAddon(new SPIAnalog1256Input(), CORE0_INPUT);
addons.LoadAddon(new JSliderInput(), CORE0_INPUT);
addons.LoadAddon(new ReverseInput(), CORE0_INPUT);
addons.LoadAddon(new TurboInput(), CORE0_INPUT);
addons.LoadAddon(new WiiExtensionInput(), CORE0_INPUT);
addons.LoadAddon(new SNESpadInput(), CORE0_INPUT);
addons.LoadAddon(new PlayerNumAddon(), CORE0_USBREPORT);
addons.LoadAddon(new SliderSOCDInput(), CORE0_INPUT);
addons.LoadAddon(new TiltInput(), CORE0_INPUT);

// Input override addons
addons.LoadAddon(new ReverseInput(), CORE0_INPUT);
addons.LoadAddon(new TurboInput(), CORE0_INPUT); // Turbo overrides button states and should be close to the end
addons.LoadAddon(new InputMacro(), CORE0_INPUT);

InputMode inputMode = gamepad->getOptions().inputMode;
Expand Down

0 comments on commit 165a136

Please sign in to comment.