ESP32: PS4 controller button events

In this tutorial we will learn how to process button pressed events on the PS4 controller, when connected to the ESP32. The tests from this tutorial were done using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

In this tutorial we will learn how to process button pressed events on the PS4 controller, when connected to the ESP32. We will be using the Arduino core and this library.

As mentioned, we will work with button pressed events, which means that we need to provide an event handling function to process them. This approach is more efficient than polling the controller to check if a button is currently being pressed.

For an introductory tutorial on how to get started using the PS4 controller connected to the ESP32, please check here. This tutorial covers how to obtain the Bluetooth address stored on the controller, which is needed for the library initialization and to allow the connection of the controller to the ESP32 microcontroller.

The tests from this tutorial were done using a DFRobot’s ESP32 module integrated in a ESP32 development board.

The code

We will start by including the PS4Controller.h library, so we have access to the PS4 extern variable. This variable will allow us to interact with the controller.

#include <PS4Controller.h>

Moving on to the Arduino setup function, we will start by opening a serial connection. That way, we will be able to output the results of our program.

Serial.begin(115200);

After that we will specify the handling function that will be executed when an event occurs. We will call this function onEvent and check how to implement it later.

To specify this event handling function we simply need to call the attach method on the PS4 extern variable, passing as input our handling function.

PS4.attach(onEvent);

Then we are going to call the begin method on the PS4 extern variable to perform the initialization of the Bluetooth layer and make the ESP32 ready to listen to incoming PS4 controller connections.

As input we need to pass, as a string, the Bluetooth address stored in the PS4 controller. You can learn how to figure out this address by following this previous tutorial.

Note that this method returns as output a Boolean value indicating if the procedure was successful or not. For simplicity we are not doing an error check but please take in consideration that you should do it in a real application scenario to make sure everything went well.

PS4.begin("yourDeviceMAC");

The complete setup function can be seen below.

void setup()
{
  Serial.begin(115200);
  
  PS4.attach(onEvent);
  
  PS4.begin("yourDeviceMAC");
  Serial.println("Initialization finished.");
}

For this tutorial we are not going to do a check if the controller is already connected or not, but if you want to do it you can check this guide. Naturally, only after a controller is connected the button pressed events will start being generated.

Since we are working with events, we won’t need to have any code in the Arduino main loop. So, we are going to delete the corresponding FreeRTOS task with a call to the vTaskDelete function, passing as input the value NULL.

void loop()
{
  vTaskDelete(NULL);
}

To finalize, we will take a look at the implementation of the onEvent function. This function needs to follow a predefined signature: it must return void and receive no arguments.

void onEvent(){
  // function implementation
}

We will see an example for the cross button, to detect if it was pressed and if it stops being pressed.

Note that these two events are generated when the button goes from unpressed to pressed and also from pressed to unpressed (transitions).

While the button is being pressed (without transition, just holding the state) these two events are not generated.

So, in the event handling function, we will first check if the button is currently being pressed (not the transitory, just if it is pressed at the moment regardless of its previous status).

To do it, we first access the data attribute of the PS4 extern variable. This attribute is a struct of type ps4_t. Then we will access the button field of this struct, which is also a struct, of type ps4_button_t.

Finally, this struct contains an element per each of the supported buttons. The names of the attributes can be seen below:

  • options
  • l3
  • r3
  • share
  • up
  • right
  • down
  • left
  • upright
  • upleft
  • downright
  • downleft
  • l2
  • r2
  • l1
  • r1
  • triangle
  • circle
  • cross
  • square
  • ps
  • touchpad

So, we can see if the cross button is being pressed with the following code:

if(PS4.data.button.cross){
    Serial.println("cross is being pressed");
}

Note that the callback function will be called while the button is being pressed, even if the button state doesn’t change.

Now, to check if the button had a change of status (transitory from not pressed to pressed or vice-versa) we can follow a similar approach. In this case, we start by accessing the event attribute of the PS4 extern variable. This attribute is a struct of type ps4_event_t.

Then we can either access the button_down or button_up fields if we want to check if the button transitioned from not pressed to pressed or from pressed to not pressed, respectively.

Both fields are structs of type ps4_button_t, which has a field per each button supported (the list was already shown before).

So, we can check if the button was pressed / unpressed with the following conditional statements:

if(PS4.event.button_up.cross){
  Serial.println("cross up");
}

if(PS4.event.button_down.cross){
  Serial.println("cross down");
}

The full callback function code can be seen below.

void onEvent(){

  if(PS4.data.button.cross){
    Serial.println("cross is being pressed");
  }

  if(PS4.event.button_up.cross){
    Serial.println("cross up");
  }

  if(PS4.event.button_down.cross){
    Serial.println("cross down");
  }
}

The final code can be seen below.

#include <PS4Controller.h>

void onEvent(){

  // Check if the buttons is being presses
  if(PS4.data.button.cross){
    Serial.println("cross is being pressed");
  }

  // Check if the button went from pressed to not pressed
  if(PS4.event.button_up.cross){
    Serial.println("cross up");
  }

  // Check if the button went from unpressed to pressed
  if(PS4.event.button_down.cross){
    Serial.println("cross down");
  }
}

void setup()
{
  Serial.begin(115200);
  
  PS4.attach(onEvent);
  
  PS4.begin("yourDeviceMAC");
  Serial.println("Initialization finished.");
}

void loop()
{
  vTaskDelete(NULL);
}

Testing the code

To test the code, simply compile it and upload it to you device, using the Arduino IDE. Once the procedure is finished, open the IDE serial monitor.

You should get a message indicating the initialization is finished. From this point onward, the ESP32 is ready to receive a controller connection.

After this, you should connect the PS4 controller by clicking the PS button. After that you can press the cross button and then stop pressing it. You should get an output similar to figure 1.

Output of the program when the cross button is pressed.
Figure 1 – Output of the program when the cross button is pressed.

Code to cover all the buttons

To finalize this tutorial, we will test a code snippet that covers all the buttons supported. For simplicity, we will just check the changes from unpressed to pressed and vice-versa.

You can check below the code with the callback function that handles all the buttons.

#include <PS4Controller.h>

void onEvent(){

  if(PS4.event.button_up.right){
    Serial.println("right arrow up");
  }

  if(PS4.event.button_down.right){
    Serial.println("right arrow down");
  }

  if(PS4.event.button_up.left){
    Serial.println("left arrow up");
  }

  if(PS4.event.button_down.left){
    Serial.println("left arrow down");
  }

  if(PS4.event.button_up.down){
    Serial.println("down arrow up");
  }

  if(PS4.event.button_down.down){
    Serial.println("down arrow down");
  }

  if(PS4.event.button_up.up){
    Serial.println("up arrow up");
  }

  if(PS4.event.button_down.up){
    Serial.println("up arrow down");
  }

  if(PS4.event.button_up.r3){
    Serial.println("r3 stick up");
  }

  if(PS4.event.button_down.r3){
    Serial.println("r3 stick down");
  }

  if(PS4.event.button_up.l3){
    Serial.println("l3 stick up");
  }

  if(PS4.event.button_down.l3){
    Serial.println("l3 stick down");
  }

  if(PS4.event.button_up.l2){
    Serial.println("l2 button up");
  }

  if(PS4.event.button_down.l2){
    Serial.println("l2 buton down");
  }

  if(PS4.event.button_up.l1){
    Serial.println("l1 button up");
  }

  if(PS4.event.button_down.l1){
    Serial.println("l1 button down");
  }

  if(PS4.event.button_up.r2){
    Serial.println("r2 button up");
  }

  if(PS4.event.button_down.r2){
    Serial.println("r2 buton down");
  }

  if(PS4.event.button_up.r1){
    Serial.println("r1 button up");
  }

  if(PS4.event.button_down.r1){
    Serial.println("r1 button down");
  }

  if(PS4.event.button_up.touchpad){
    Serial.println("touchpad button up");
  }

  if(PS4.event.button_down.touchpad){
    Serial.println("touchpad button down");
  }

  if(PS4.event.button_up.options){
    Serial.println("options button up");
  }

  if(PS4.event.button_down.options){
    Serial.println("options button down");
  }

  if(PS4.event.button_up.share){
    Serial.println("share button up");
  }

  if(PS4.event.button_down.share){
    Serial.println("share button down");
  }

  if(PS4.event.button_up.ps){
    Serial.println("ps button up");
  }

  if(PS4.event.button_down.ps){
    Serial.println("ps button down");
  }

  if(PS4.event.button_up.triangle){
    Serial.println("triangle button up");
  }

  if(PS4.event.button_down.triangle){
    Serial.println("triangle button down");
  }

  if(PS4.event.button_up.circle){
    Serial.println("circle button up");
  }

  if(PS4.event.button_down.circle){
    Serial.println("circle button down");
  }

  if(PS4.event.button_up.square){
    Serial.println("square button up");
  }

  if(PS4.event.button_down.square){
    Serial.println("square button down");
  }

  if(PS4.event.button_up.cross){
    Serial.println("cross button up");
  }

  if(PS4.event.button_down.cross){
    Serial.println("cross button down");
  }
}

void setup()
{
  Serial.begin(115200);
  
  PS4.attach(onEvent);
  
  PS4.begin("01:01:01:01:01:01");
  Serial.println("Initialization finished.");
}

void loop()
{
  vTaskDelete(NULL);
}

Upon uploading and running the code and connecting a controller to the ESP32, you can start experimenting with all the buttons. You should get a result similar to figure 2, which exemplifies some of the buttons being pressed.

Output of the program that handles key pressed events for the buttons of the PS4 controller.
Figure 2 – Output of the program that handles key pressed events for the buttons of the PS4 controller.

Leave a Reply

%d bloggers like this: