ESP32-BLE-Gamepad icon indicating copy to clipboard operation
ESP32-BLE-Gamepad copied to clipboard

Turbo Functionality

Open DoomyDoomer opened this issue 3 years ago • 4 comments

I'm using ESP32-BLE-Gamepad to build a wireless PC Engine controller to connect to my PC Engine bluetooth adapter. I'm wondering if it's possible to implement turbo functionality and if so how I'd go about doing that?

Example use case could be: two 3 position slider switches, one for each button (II and I buttons) I'd like to use rapid fire on. These switches would set the button's rapid fire speed when the user presses and holds a button. First position of the switches would be no rapid fire, position 2 would be slow rapid fire, position 3 would be fast rapid fire. This would mimic the original PC Engine controllers rapid fire.

DoomyDoomer avatar Jun 27 '22 15:06 DoomyDoomer

You would just poll for the position of the switch at intervals and send the presses as needed

lemmingDev avatar Jul 26 '22 10:07 lemmingDev

Thanks for getting back to me. Can you provide a code block example maybe with debounce included? Just to explain further the use case. I don't want the button to be in a turbo pressed state until the user presses the button. In other words, if the user has the switch for the "B" button set to turbo, the B button won't actually be firing rapidly until the user presses and holds the B button. This would be the same functionality as the original PC Engine controller.

DoomyDoomer avatar Jul 27 '22 13:07 DoomyDoomer

Send though your current sketch

lemmingDev avatar Jul 29 '22 07:07 lemmingDev

Below is my sketch. Right now my turbo functionality isn't in software except for the clock generation. I'm using the same counter IC for turbo as the original PC Engine controller. The counter requires a clock value, basically a square wave input for timing. The original controller was fed 12hz from the console. I tried generating this via PWM but it's broken when using the Arduino IDE and the ESP32-C3-Mini. This is a shame because it should have been really simple to generate but I was able to confirm what others have posted using my oscilloscope, no PWM on the C3-mini. As a very ham fisted last ditch effort I have the clock running as a simple high/low loop with delay to generate the proper clock square wave signal. I have this running on the second core because it was obviously running too quickly and halting the other instructions on the first core. Even though this method works, I believe it could drain the battery faster, which isn't ideal.

All that being said, it would be great to be able to write the two steps of rapid fire ("slow" rapid fire, "fast" rapid fire) in software but I was unable to figure out how to get this working using the available examples. I tried using debounce but there wasn't a clear logical method to "sense" when the switch was active and rapid fire within a loop that's already firing off the press function and how the conditions are setup. Just something I couldn't wrap my head around, so any help is appreciated.

Sketch (I use VLC):

#include <Arduino.h>
#include <BleGamepad.h>
#include <Bounce2.h>

void Task1code (void * pvParameters);
void Task2code (void * pvParameters);

TaskHandle_t Task1;
TaskHandle_t Task2;

// BLEGamePad Settings
BleGamepad bleGamepad("PCE2BT", "Humble Bazooka", 100);

#define LedConnect 0
#define numOfButtons 16
#define numOfHats 1
#define uS_TO_S_FACTOR 1000000  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 10        /* Time ESP32 will go to sleep (in seconds) */

RTC_DATA_ATTR int bootCount = 0;

ulong elapsed = 0;
ulong turboElapsed = 0;
unsigned long startMillis;  //some global variables available anywhere in the program
unsigned long currentMillis;
const unsigned long period = 1000;  //the value is a number of milliseconds
bool bleWaiting = false;

Bounce rapidFire = Bounce();

byte previousButtonStates[numOfButtons];
byte currentButtonStates[numOfButtons];
byte buttonPins[numOfButtons] = {2, 3, 8, 9}; // GPIOs
byte physicalButtons[numOfButtons] = {4, 1, 11, 12}; // II, I, SELECT, RUN

byte previousHatStates[numOfHats * 4];
byte currentHatStates[numOfHats * 4];
byte hatPins[numOfHats * 4] = {5, 7, 6, 4}; // in order UP, LEFT, DOWN, RIGHT. 4 pins per hat switch (Eg. List 12 pins if there are 3 hat switches)

void deepSleep()
{
  Serial.print("Going Into Sleep...");
  // to do
}

void waitConnection()
{
  if(elapsed < millis())
  {
    elapsed = millis() + 500; // blink 0.5 second
    digitalWrite(LedConnect,!digitalRead(LedConnect));
  }
  
  bleWaiting = true;
}

void setup()
{
  pinMode(18, OUTPUT);

  // Setup Buttons
  for (byte currentPinIndex = 0; currentPinIndex < numOfButtons; currentPinIndex++)
  {
    pinMode(buttonPins[currentPinIndex], INPUT_PULLUP);
    previousButtonStates[currentPinIndex] = HIGH;
    currentButtonStates[currentPinIndex] = HIGH;
  }

  // Setup Hat Switches
  for (byte currentPinIndex = 0; currentPinIndex < numOfHats * 4; currentPinIndex++)
  {
    pinMode(hatPins[currentPinIndex], INPUT_PULLUP);
    previousHatStates[currentPinIndex] = HIGH;
    currentHatStates[currentPinIndex] = HIGH;
  }

  Serial.begin(115200);

  pinMode(LedConnect, OUTPUT);
  digitalWrite(LedConnect, LOW); // Led OFF

  BleGamepadConfiguration bleGamepadConfig;
  bleGamepadConfig.setAutoReport(false);
  bleGamepadConfig.setButtonCount(numOfButtons);
  bleGamepadConfig.setHatSwitchCount(numOfHats);
  bleGamepad.begin(&bleGamepadConfig);

  digitalWrite(LedConnect, HIGH);

  Serial.println("Waiting connection ");
  elapsed = 0;
  turboElapsed = 0;

  while(!bleGamepad.isConnected())
  {
    waitConnection();
  }

  bleWaiting = false;
  digitalWrite(LedConnect, HIGH);
  Serial.println("Connected");

  xTaskCreatePinnedToCore(
    Task1code,
    "Task1",
    10000,
    NULL,
    1,
    &Task1,
    0
  );                            
  delay(500); 

  xTaskCreatePinnedToCore(
    Task2code,  
    "Task2",
    10000,
    NULL,
    1,
    &Task2,
    1
  );          
    
  delay(500); 

}

void Task1code( void * pvParameters )
{
  for(;;)
  {
    if (bleGamepad.isConnected())
    {
      if(bleWaiting) // lost and recovered connection
      {
        digitalWrite(LedConnect, HIGH);
        bleWaiting = false;
      }
      // Deal with buttons
        for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
        {
          currentButtonStates[currentIndex] = digitalRead(buttonPins[currentIndex]);

          if (currentButtonStates[currentIndex] != previousButtonStates[currentIndex])
          {
            if (currentButtonStates[currentIndex] == LOW)
            {
                bleGamepad.press(physicalButtons[currentIndex]);
            }
            else
            {
              bleGamepad.release(physicalButtons[currentIndex]);
            }
          }
        }

        // Update hat switch pin states
        for (byte currentHatPinsIndex = 0; currentHatPinsIndex < numOfHats * 4; currentHatPinsIndex++)
        {
          currentHatStates[currentHatPinsIndex] = digitalRead(hatPins[currentHatPinsIndex]);
        }

        // Update hats
        signed char hatValues[4] = {0, 0, 0, 0};

        for (byte currentHatIndex = 0; currentHatIndex < numOfHats; currentHatIndex++)
        {
          signed char hatValueToSend = 0;

          for (byte currentHatPin = 0; currentHatPin < 4; currentHatPin++)
          {
            // Check for direction
            if (currentHatStates[currentHatPin + currentHatIndex * 4] == LOW)
            {
              hatValueToSend = currentHatPin * 2 + 1;

              // Account for last diagonal
              if (currentHatPin == 0)
              {
                if (currentHatStates[currentHatIndex * 4 + 3] == LOW)
                {
                  hatValueToSend = 8;
                  break;
                }
              }

              // Account for first 3 diagonals
              if (currentHatPin < 3)
              {
                if (currentHatStates[currentHatPin + currentHatIndex * 4 + 1] == LOW)
                {
                  hatValueToSend += 1;
                  break;
                }
              }
            }
          }

          hatValues[currentHatIndex] = hatValueToSend;
        }

        // Set hat values
        bleGamepad.setHats(hatValues[0], hatValues[1], hatValues[2], hatValues[3]);
        
        // Update previous states to current states and send report
        if (currentButtonStates != previousButtonStates || currentHatStates != previousHatStates)
        {
          for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
          {
            previousButtonStates[currentIndex] = currentButtonStates[currentIndex];
          }

          for (byte currentIndex = 0; currentIndex < numOfHats * 4; currentIndex++)
          {
            previousHatStates[currentIndex] = currentHatStates[currentIndex];
          }

          bleGamepad.sendReport();
        }

        delay(20);
    }
    else
    {
      waitConnection();
    }
  }
}

void Task2code( void * pvParameters )
{
  for(;;)
  {
    digitalWrite(18,HIGH);
    delay(12);
    digitalWrite(18,LOW);
    delay(12);
  }
}


void loop() {
 
}

DoomyDoomer avatar Jul 29 '22 13:07 DoomyDoomer