ESP32 PS4 Motor-Control Coding help

I am using the bluepad32 controller host from Ricardo Quesada and referencing the work of Rachel De Barros to drive DC motors and such with a generic PS4 controller over bluetooth on and ESP32 (also generic. seems to work with DOIT ESP32 DEV KIT selected.
I have no experience in programming but following web-based instruction I am able to use Left joystick to apply speed/direction as desired.
I have spent an inordinate amount of time trying to us Button A on the controller (bottom of the group on the right) to Toggle a hobby pump motor on and off. I can get the momentary method to work but it needs to toggle.
With the code below it toggles but if the button is held down it will cycle on/off at the rate of the delay (300) set at the bottom.
There are sooo many articles out there but all require an input button as and argument. I am just starting a briefly viewed a posting referred on this site that used "char incomingByte = Serial.read();" but I have no Idea how to proceed with that.
Is is possible, practical to use a more general method? I am not wrapping my brain with how to implement the toggle and debouncing instructions to the code below.
Please se my attempt starting at line 117. Apologies in advance for the mess.


```cpp

#include <Bluepad32.h>
#include <ESP32Servo.h>



//Assign Pin connections
const int builtInLed = 2;
int ENApin = 14;  // motor 1 speed
int IN1pin = 27;  // motor 1 dir1
int IN2pin = 26;  // motor 1 dir2
int IN3pin = 25;  // motor 2 dir1
int IN4pin = 33;  // motor 2 dir2
int ENBpin = 21;  // motor 2 speed
int xServoPin = 12;
int yServoPin = 13;
int IN1pump = 23;
int IN2pump = 22;

//assign Unique servo names
Servo xServo;
Servo yServo;


// Setting PWM properties
const int freq = 1000;
const int pwmChannel = 0;
const int resolution = 8;

//Toggle pin
int TogglePin = 19;
int ToggleState = LOW;
int LastToggleState = LOW;



ControllerPtr myControllers[BP32_MAX_GAMEPADS];

// This callback gets called any time a new gamepad is connected.
// Up to 4 gamepads can be connected at the same time.
void onConnectedController(ControllerPtr ctl) {
  bool foundEmptySlot = false;
  for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
    if (myControllers[i] == nullptr) {
      Serial.printf("CALLBACK: Controller is connected, index=%d\n", i);
      // Additionally, you can get certain gamepad properties like:
      // Model, VID, PID, BTAddr, flags, etc.
      ControllerProperties properties = ctl->getProperties();
      Serial.printf("Controller model: %s, VID=0x%04x, PID=0x%04x\n", ctl->getModelName().c_str(), properties.vendor_id, properties.product_id);
      myControllers[i] = ctl;
      foundEmptySlot = true;
      break;
    }
  }

  if (!foundEmptySlot) {
    Serial.println("CALLBACK: Controller connected, but could not found empty slot");
  }
}

void onDisconnectedController(ControllerPtr ctl) {
  bool foundController = false;

  for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
    if (myControllers[i] == ctl) {
      Serial.printf("CALLBACK: Controller disconnected from index=%d\n", i);
      myControllers[i] = nullptr;
      foundController = true;
      break;
    }
  }

  if (!foundController) {
    Serial.println("CALLBACK: Controller disconnected, but not found in myControllers");
  }
}

// ========= SEE CONTROLLER VALUES IN SERIAL MONITOR ========= //

void dumpGamepad(ControllerPtr ctl) {
  Serial.printf(
    "idx=%d, dpad: 0x%02x, buttons: 0x%04x, axis L: %4d, %4d, axis R: %4d, %4d, brake: %4d, throttle: %4d, "
    "misc: 0x%02x, gyro x:%6d y:%6d z:%6d, accel x:%6d y:%6d z:%6d\n",
    ctl->index(),        // Controller Index
    ctl->dpad(),         // D-pad
    ctl->buttons(),      // bitmask of pressed buttons
    ctl->axisX(),        // (-511 - 512) left X Axis
    ctl->axisY(),        // (-511 - 512) left Y axis
    ctl->axisRX(),       // (-511 - 512) right X axis
    ctl->axisRY(),       // (-511 - 512) right Y axis
    ctl->brake(),        // (0 - 1023): brake button
    ctl->throttle(),     // (0 - 1023): throttle (AKA gas) button
    ctl->miscButtons(),  // bitmask of pressed "misc" buttons
    ctl->gyroX(),        // Gyro X
    ctl->gyroY(),        // Gyro Y
    ctl->gyroZ(),        // Gyro Z
    ctl->accelX(),       // Accelerometer X
    ctl->accelY(),       // Accelerometer Y
    ctl->accelZ()        // Accelerometer Z
  );
}

// ========= GAME CONTROLLER ACTIONS SECTION ========= //

void processGamepad(ControllerPtr ctl) {
  // There are different ways to query whether a button is pressed.
  // By query each button individually:
  //  a(), b(), x(), y(), l1(), etc...

  //== PS4 X button = 0x0001 ==//
  /*
  if (ctl->buttons() == 0x0001) {
    // code for when X button is pushed
    digitalWrite(builtInLed,HIGH);
    */
  //Rewrite above to toggle
  if (ctl->a() == 0x0001) {
    ToggleState = 1;
    if (ToggleState != LastToggleState);
    {

      digitalWrite(builtInLed, !digitalRead(builtInLed));

      Serial.print("ToggleState ");
      Serial.println(ToggleState);
      Serial.print("LastToggleState ");
      Serial.println(LastToggleState);

      int pumpState = digitalRead(builtInLed);  // set pumpState which is linked to builtInLed.
      digitalWrite(IN1pump, HIGH);
      digitalWrite(IN2pump, LOW);       //Set IN2pump to LOW. Pump is on/off only (full power, no speed or direction)
      digitalWrite(ENBpin, pumpState);  //Set ENBpin to HIGH. Pump is on/off only (full power, no speed or direction)
      Serial.print("pumpState ");       //To check code in serial monitor
      Serial.println(pumpState);
    }
    LastToggleState = ToggleState;
  }

  if (ctl->buttons() != 0x0001) {
    // code for when X button is released
  }

  //== PS4 Square button = 0x0004 ==//
  if (ctl->buttons() == 0x0004) {
    // code for when square button is pushed
  }
  if (ctl->buttons() != 0x0004) {
    // code for when square button is released
  }

  //== PS4 Triangle button = 0x0008 ==//
  if (ctl->buttons() == 0x0008) {
    // code for when triangle button is pushed
  }
  if (ctl->buttons() != 0x0008) {
    // code for when triangle button is released
  }

  //== PS4 Circle button = 0x0002 ==//
  if (ctl->buttons() == 0x0002) {
    // code for when circle button is pushed
  }
  if (ctl->buttons() != 0x0002) {
    // code for when circle button is released
  }

  //== PS4 Dpad UP button = 0x01 ==//
  if (ctl->buttons() == 0x01) {
    // code for when dpad up button is pushed
  }
  if (ctl->buttons() != 0x01) {
    // code for when dpad up button is released
  }

  //==PS4 Dpad DOWN button = 0x02==//
  if (ctl->buttons() == 0x02) {
    // code for when dpad down button is pushed
  }
  if (ctl->buttons() != 0x02) {
    // code for when dpad down button is released
  }

  //== PS4 Dpad LEFT button = 0x08 ==//
  if (ctl->buttons() == 0x08) {
    // code for when dpad left button is pushed
  }
  if (ctl->buttons() != 0x08) {
    // code for when dpad left button is released
  }

  //== PS4 Dpad RIGHT button = 0x04 ==//
  if (ctl->buttons() == 0x04) {
    // code for when dpad right button is pushed
  }
  if (ctl->buttons() != 0x04) {
    // code for when dpad right button is released
  }

  //== PS4 R1 trigger button = 0x0020 ==//
  if (ctl->buttons() == 0x0020) {
    // code for when R1 button is pushed
  }
  if (ctl->buttons() != 0x0020) {
    // code for when R1 button is released
  }

  //== PS4 R2 trigger button = 0x0080 ==//
  if (ctl->buttons() == 0x0080) {
    // code for when R2 button is pushed
  }
  if (ctl->buttons() != 0x0080) {
    // code for when R2 button is released
  }

  //== PS4 L1 trigger button = 0x0010 ==//
  if (ctl->buttons() == 0x0010) {
    // code for when L1 button is pushed
  }
  if (ctl->buttons() != 0x0010) {
    // code for when L1 button is released
  }

  //== PS4 L2 trigger button = 0x0040 ==//
  if (ctl->buttons() == 0x0040) {
    // code for when L2 button is pushed
  }
  if (ctl->buttons() != 0x0040) {
    // code for when L2 button is released
  }

  //== LEFT JOYSTICK - UP ==//
  //Code for Forward
  /*if (ctl->axisY() <= -25) {
    // map joystick values to motor speed
    int motorSpeed = map(ctl->axisY(), -25, -508, 50, 255);
    digitalWrite(IN1pin, LOW);
    digitalWrite(IN2pin, HIGH);
    analogWrite(ENApin, motorSpeed);
  } else {
   // analogWrite(ENApin, LOW);
   */
  if (ctl->axisY() <= -25) {
    // map joystick values to motor speed
    int motorSpeed = map(ctl->axisY(), -25, -512, 50, 255);
    digitalWrite(IN1pin, LOW);
    digitalWrite(IN2pin, HIGH);
    analogWrite(ENApin, motorSpeed);
  }

  //== LEFT JOYSTICK - DOWN ==//
  //Code for reverse
  else if (ctl->axisY() >= 25) {
    // map joystick values to motor speed
    int motorSpeed = map(ctl->axisY(), 25, 512, 50, 255);
    digitalWrite(IN1pin, HIGH);
    digitalWrite(IN2pin, LOW);
    analogWrite(ENApin, motorSpeed);
  }

  else {
    analogWrite(ENApin, 0);
  }
  //== LEFT JOYSTICK - LEFT ==//
  if (ctl->axisX() <= 1) {
    // code for when left joystick is pushed left
  }

  //== LEFT JOYSTICK - RIGHT ==//
  if (ctl->axisX() >= 6) {
    // code for when left joystick is pushed right
  }
  /*
  //== LEFT JOYSTICK DEADZONE ==//
  if (ctl->axisY() > -25 && ctl->axisY() < 25) {
  //if (ctl->axisY() >= -24 && ctl->axisY() <= 24) {
    digitalWrite(ENApin, LOW);
  
    // code for when left joystick is at idle
  }
*/

  //== RIGHT JOYSTICK - X AXIS ==//
  if (ctl->axisRX()) {
    // code for when right joystick moves along x-axis
  }

  //== RIGHT JOYSTICK - Y AXIS ==//
  if (ctl->axisRY()) {
    // code for when right joystick moves along y-axis
  }

  dumpGamepad(ctl);
}

void processControllers() {
  for (auto myController : myControllers) {
    if (myController && myController->isConnected() && myController->hasData()) {
      if (myController->isGamepad()) {
        processGamepad(myController);
      } else {
        Serial.println("Unsupported controller");
      }
    }
  }
}

// Arduino setup function. Runs in CPU 1
void setup() {
  digitalWrite(IN1pump, LOW);  //start with motors off
  digitalWrite(IN2pump, LOW);  //Set IN2pump to LOW. Pump is on/off only (full power, no speed or direction)
  digitalWrite(ENBpin, LOW);   //Set ENBpin to HIGH. Pump is on/off only (full power, no speed or direction)



  //Assign DC Motor &builtInLed pin INPUT/OUTPUT
  pinMode(builtInLed, OUTPUT);
  pinMode(ENApin, OUTPUT);
  pinMode(IN1pin, OUTPUT);
  pinMode(IN2pin, OUTPUT);
  pinMode(IN3pin, OUTPUT);
  pinMode(IN4pin, OUTPUT);
  pinMode(ENBpin, OUTPUT);
  pinMode(IN1pump, OUTPUT);
  pinMode(IN2pump, OUTPUT);

  pinMode(TogglePin, OUTPUT);


  xServo.attach(xServoPin);
  yServo.attach(yServoPin);

  Serial.begin(115200);
  Serial.printf("Firmware: %s\n", BP32.firmwareVersion());
  const uint8_t* addr = BP32.localBdAddress();
  Serial.printf("BD Addr: %2X:%2X:%2X:%2X:%2X:%2X\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);

  // Setup the Bluepad32 callbacks
  BP32.setup(&onConnectedController, &onDisconnectedController);

  // "forgetBluetoothKeys()" should be called when the user performs
  // a "device factory reset", or similar.
  // Calling "forgetBluetoothKeys" in setup() just as an example.
  // Forgetting Bluetooth keys prevents "paired" gamepads to reconnect.
  // But it might also fix some connection / re-connection issues.
  BP32.forgetBluetoothKeys();

  // Enables mouse / touchpad support for gamepads that support them.
  // When enabled, controllers like DualSense and DualShock4 generate two connected devices:
  // - First one: the gamepad
  // - Second one, which is a "virtual device", is a mouse.
  // By default, it is disabled.
  BP32.enableVirtualDevice(false);
}

// Arduino loop function. Runs in CPU 1.
void loop() {


  // This call fetches all the controllers' data.
  // Call this function in your main loop.
  bool dataUpdated = BP32.update();
  if (dataUpdated)
    processControllers();

  // The main loop must have some kind of "yield to lower priority task" event.
  // Otherwise, the watchdog will get triggered.
  // If your main loop doesn't have one, just add a simple `vTaskDelay(1)`.
  // Detailed info here:
  // https://stackoverflow.com/questions/66278271/task-watchdog-got-triggered-the-tasks-did-not-reset-the-watchdog-in-time

  // vTaskDelay(1);
  delay(300);
}

I'm not familiar with anything that you use. But in the above you should check for the state change. So remember the last value of ctl->a() and react on the difference between the last value and the current value.

1 Like

Ya, just don't know how to write the code to use that difference. Thanx

Which exact Bluepad32 library are you using? I tried to compile your code using the below library for DOIT ESP32 DEV KIT and I get all kinds of errors related to that library.

ESP board package version 3.1.1

Example error

C:\Users\bugge\AppData\Local\Temp\.arduinoIDE-unsaved2025114-11884-1vv581t.l5yel\sketch_feb14a\sketch_feb14a.ino:400:8: error: 'class Bluepad32' has no member named 'enableVirtualDevice'
  400 |   BP32.enableVirtualDevice(false);

I see no library, the #include is part of the example sketch but there is none installed in my library folder. Is it possible that it is bundled in the esp32 Boards manager? Links copy/paste from by boards manager:

Rachel's video containing the links:

Also, there should be 2) esp32 selectable Board indexes presented. After installing the above, one will say esp32_bluepad32. Select the DOIT board from there.

Thanks

OK, my confusion because you used the word library in the opening post. Got that sorted now.

I should have seen this earlier; what is that semi-colon doing there?

Semicolon is there because I thought it should be. Reading about that now.. I took it out and the code has improved behavior. I was getting matching states on the serial monitor for ToggleState and LastToggleState. Now they are opposite where expected. I added serial prints for the end of the block and they are showing correctly matching also.
After pairing the controller, when i press the 'A' button, the LED turns on as expected and stays on as desired after release but will not change again after pressing again.
Here is the current code in that block:

  if (ctl->a() == 0x0001) {
    ToggleState = 1;
    if (ToggleState != LastToggleState) {

      digitalWrite(builtInLed, !digitalRead(builtInLed));

      Serial.print("ToggleState ");
      Serial.println(ToggleState);
      Serial.print("LastToggleState ");
      Serial.println(LastToggleState);

      int pumpState = digitalRead(builtInLed);  // set pumpState which is linked to builtInLed.
      digitalWrite(IN1pump, HIGH);
      digitalWrite(IN2pump, LOW);       //Set IN2pump to LOW. Pump is on/off only (full power, no speed or direction)
      digitalWrite(ENBpin, pumpState);  //Set ENBpin to HIGH. Pump is on/off only (full power, no speed or direction)
      //Serial.print("pumpState ");       //To check code in serial monitor
      //Serial.println(pumpState);

      LastToggleState = ToggleState;
      Serial.print("ToggleState ");
      Serial.println(ToggleState);
      Serial.print("LastToggleState ");
      Serial.println(LastToggleState);
    }
  }

Good news! Removing the ' ; ' , that you pointed out, and adding some code to the end of the block has got toggle working as expected..
Thank you so much! I would not have had any suspicion that an misplaced semicolon would end the block!

  if (ctl->buttons() == 0x0001) {
    ToggleState = 1;
    if (ToggleState != LastToggleState) {

      digitalWrite(builtInLed, !digitalRead(builtInLed));

      Serial.print("ToggleState1= ");
      Serial.println(ToggleState);
      Serial.print("LastToggleState1= ");
      Serial.println(LastToggleState);

      int pumpState = digitalRead(builtInLed);  // set pumpState which is linked to builtInLed.
      digitalWrite(IN1pump, HIGH);
      digitalWrite(IN2pump, LOW);       //Set IN2pump to LOW. Pump is on/off only (full power, no speed or direction)
      digitalWrite(ENBpin, pumpState);  //Set ENBpin to HIGH. Pump is on/off only (full power, no speed or direction)
      //Serial.print("pumpState ");       //To check code in serial monitor
      //Serial.println(pumpState);

      LastToggleState = ToggleState;
      Serial.print("ToggleState2= ");
      Serial.println(ToggleState);
      Serial.print("LastToggleState2= ");
      Serial.println(LastToggleState);
    }
  }

  if (ctl->buttons() != 0x0001) {
    // code for when X button is released
    ToggleState = 0;
    LastToggleState = 0;
    Serial.print("ToggleState !=");
    Serial.println(ToggleState);
    Serial.print("LastToggleState != ");
    Serial.println(LastToggleState);
  }

Thanks to sterretje! I'm just starting so I would not have considered the semicolon error. It was ending the block effectively making it impossible to debug (for a novice) as nothing shows up as an error.

sterretje Thank you for the help you give. I am a couple of steps behind guyzly_58 . I got my sketch loaded on my esp32Devkit V1. I know at this point I need the mac address of my controller. I ordered one on amazon but I do not know how to get the mac address. I borrowed one and I was able to see the address on my phone then I borrowed a second one and paired it to my phone but it would not show me the address and I finally I got my own. It will pair to my phone but it will not show me the address. What other way do you suggest I get the address? And once I get it where do I put it in the sketch?
I do get a laugh when I see your Emoji, well I know it fits me perfectly LOL
Thanks for your help

guyzly It seems like we are working very much on a like project . You are a couple of steps ahead. How did you pair your ps4 to the ESP32. I too was following Racheal De Barros, but I dont quite understand how she got them to pair.
Thanks for your help.
Thanks for your help.

Assuming you have the sketch from Racheal's site, after loading she pushed the reset on the ESP32 (EN button) followed by the Home+Share on the controller. In subsequent pairings you only need to hit Home button but occasionally I need to Home+Share when they don't pair via Home. I think that can be fixed by commenting out part of the code but I am unsure how to proceed with that.
I did not need to input the MAC address.
Also you may want to try selecting the Doit board as in Racheal's post.

Thanks guyzly_58 for the guidance. As far as the sketch I use the one from ricardoquesada. I could not get esp32 bluepad from Rachel DE Barros site to work. sterretje referred me to ricardoquesada and that is where I got the esp32 blue pad and the scetch. Well fixing to try and pair.
Thank You

I believe Ricardo (original author of all this wonderful coding) uses the six axis tool shown on his tutorial.
Random nerd has this guide:

U could also try copy/paste and modify the code i posted at the top of this page.

:face_with_spiral_eyes: Well something is wrong I tried pairing and all it prints on the serial monitor is question marks separated by alpha symbols. going to try the above instructions.
Thank You

Baud rate 115200?

:face_with_spiral_eyes: Bad news. I downloaded sixexispairing tool and changed the mac address to the same one the esp32 was using but I got the same thing. A series of question marks and the baud rate is 115200. Tomorrow I'm going to go pick up the first ps4 I started out with. That one shows the MAC address on my phone. In the mean time any body have any ideas what could be happening? sterretje any ideas?

Well I got a Stadia8G6J=3c9 controller, I thought I had seen the MAC address on my phone but it is not showing it now. But it will pair with my phone. I tried sixaxispairing tool but it will not display the MAC address anyone know what else I can try to get the address?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.