ESP32 PS3 Controller: Getting analog value of buttons pressed

In this tutorial we are going to learn how to get the analog value representing the amount of pressure when pressing a button of the PS3 controller, using the ESP32 and the Arduino core. The tests from this tutorial were done using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

In this tutorial we are going to learn how to get the analog value representing the amount of pressure when pressing a button of the PS3 controller, using the ESP32 and the Arduino core.

Note that not all the buttons support this feature, but the full list of supported ones can be seen below.

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 Ps3Controller.h library. This will make available an extern variable called Ps3 that we will use to interact with the controller.

#include <Ps3Controller.h>

Moving to the Arduino setup function, we will start by opening a serial connection. This allows us to print the outputs of our program.

Serial.begin(115200);

After that we will call the begin method on the Ps3 extern variable to initialize the Bluetooth layer of the ESP32 and also to make it ready for receiving the PS3 controller connection.

As input of this method we need to pass the Bluetooth address stored on the PS3 controller, as a string. For a tutorial on how to obtain the address stored on the controller, please check here. Note that the mentioned tutorial is for the PS4 controller but the procedure to get the Bluetooth address stored is exactly the same for a PS3 controller.

Ps3.begin("yourDeviceAddress");

Next we will register a callback function that will be executed when an event occurs, such as when a button is pressed. We will call this function onEvent and check it’s implementation later.

Ps3.attach(onEvent);

You can check the full setup function below.

void setup()
{
    Serial.begin(115200);

    Ps3.begin("yourDeviceAddress");

    Serial.println("Initialization finished.");
    
    Ps3.attach(onEvent);

}

Since we are going to work with a callback function that will be invoked when an event occurs, we don’t need to execute any procedure in the Arduino main loop. So, we will just 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 check the implementation of the onEvent function. This function needs to follow a predefined signature: it should return void and receive no parameters.

void onEvent()
{
  // function implementation
}

Now we are going to check how to understand if a given button was pressed and what is its current value.

First, we need to check if the button we want to check was pressed. To do that, we can access the event attribute of the Ps3 extern variable. This attribute is a struct of type ps3_event_t.

Then, we access the analog_changed field of this struct, which is another struct of type ps3_analog_t.

After this, we access the button field, which is a struct of type ps3_analog_button_t. Finally, this struct contains a field per each button for which we can get an analog value (except for the analog sticks). The field names are shown below:

  • up
  • right
  • down
  • left
  • l2
  • r2
  • l1
  • r1
  • triangle
  • circle
  • cross
  • square

All of these fields are declared as uint8_t, which means that they can have a value between 0 and 255. If we look into this file, we can check that these fields will have the difference between the previous analog value of the button and the current one. This means that the value will be greater than 0 if the analog value of the button changes.

It’s important to take in consideration that this value will be equal to zero while the button is pressed but in the same position, since the previous position will be equal to the current one.

For example, if we click the R2 button to the maximum and hold it pressed, the corresponding field will be zero. It will only be different from zero when the button is going from not pressed to pressed, and then from pressed to not pressed (or between intermediate states).

Note also that even if we give a single click from not pressed to fully pressed, multiple intermediate positions may be detected. Consequently, multiple events will be triggered.

So, below we can see the an IF condition to check if the square button was pressed:

if(Ps3.event.analog_changed.button.square){
    // checking the current value of the button
}

As mentioned, the field that we have accessed contains the difference between the previous analog value and the current one, which is why we are only checking if it is different than 0 (an IF condition over an integer evaluates to true if the value is different than 0).

To get the actual analog value of the button, we need to access the data field of the Ps3 extern variable. This field is a struct of type ps3_t.

Then, we will access the analog field of this struct, which is also a struct of type ps3_analog_t.

Again, we access the button field of this struct, which is a struct of type ps3_analog_button_t. Finally, this struct contains the list of possible buttons already shown before.

So, we simply access the button to obtain the analog value. 0 corresponds to not pressed and 255 corresponds to fully pressed.

The complete IF condition plus the checking of the analog value can be seen below, for the square button.

if(Ps3.event.analog_changed.button.square){
     Serial.print("Square New value: ");
     Serial.println(Ps3.data.analog.button.square);
}

The procedure we have followed here is exactly the same for any of the other buttons from the list. Below we can see the onEvent function with all the buttons being covered.

void onEvent()
{
   if(Ps3.event.analog_changed.button.l1){
       Serial.print("L1 New value: ");
       Serial.println(Ps3.data.analog.button.l1);
   }
   
   if(Ps3.event.analog_changed.button.l2){
       Serial.print("L2 New value: ");
       Serial.println(Ps3.data.analog.button.l2);
   }

   if(Ps3.event.analog_changed.button.r1){
       Serial.print("R1 New value: ");
       Serial.println(Ps3.data.analog.button.r1);
   }

   if(Ps3.event.analog_changed.button.r2){
       Serial.print("R2 New value: ");
       Serial.println(Ps3.data.analog.button.r2);
   }
   
   if(Ps3.event.analog_changed.button.cross){
       Serial.print("Cross New value: ");
       Serial.println(Ps3.data.analog.button.cross);
   }

   if(Ps3.event.analog_changed.button.triangle){
       Serial.print("Triangle New value: ");
       Serial.println(Ps3.data.analog.button.triangle);
   }

   if(Ps3.event.analog_changed.button.square){
       Serial.print("Square New value: ");
       Serial.println(Ps3.data.analog.button.square);
   }

   if(Ps3.event.analog_changed.button.circle){
       Serial.print("Circle New value: ");
       Serial.println(Ps3.data.analog.button.circle);
   }

   if(Ps3.event.analog_changed.button.up){
       Serial.print("Up New value: ");
       Serial.println(Ps3.data.analog.button.up);
   }

   if(Ps3.event.analog_changed.button.down){
       Serial.print("Down New value: ");
       Serial.println(Ps3.data.analog.button.down);
   }

   if(Ps3.event.analog_changed.button.left){
       Serial.print("Left New value: ");
       Serial.println(Ps3.data.analog.button.left);
   }

   if(Ps3.event.analog_changed.button.right){
       Serial.print("Right New value: ");
       Serial.println(Ps3.data.analog.button.right);
   }
}

The final code can be seen below.

#include <Ps3Controller.h>

void onEvent()
{
   if(Ps3.event.analog_changed.button.l1){
       Serial.print("L1 New value: ");
       Serial.println(Ps3.data.analog.button.l1);
   }
   
   if(Ps3.event.analog_changed.button.l2){
       Serial.print("L2 New value: ");
       Serial.println(Ps3.data.analog.button.l2);
   }

   if(Ps3.event.analog_changed.button.r1){
       Serial.print("R1 New value: ");
       Serial.println(Ps3.data.analog.button.r1);
   }

   if(Ps3.event.analog_changed.button.r2){
       Serial.print("R2 New value: ");
       Serial.println(Ps3.data.analog.button.r2);
   }
   
   if(Ps3.event.analog_changed.button.cross){
       Serial.print("Cross New value: ");
       Serial.println(Ps3.data.analog.button.cross);
   }

   if(Ps3.event.analog_changed.button.triangle){
       Serial.print("Triangle New value: ");
       Serial.println(Ps3.data.analog.button.triangle);
   }

   if(Ps3.event.analog_changed.button.square){
       Serial.print("Square New value: ");
       Serial.println(Ps3.data.analog.button.square);
   }

   if(Ps3.event.analog_changed.button.circle){
       Serial.print("Circle New value: ");
       Serial.println(Ps3.data.analog.button.circle);
   }

   if(Ps3.event.analog_changed.button.up){
       Serial.print("Up New value: ");
       Serial.println(Ps3.data.analog.button.up);
   }

   if(Ps3.event.analog_changed.button.down){
       Serial.print("Down New value: ");
       Serial.println(Ps3.data.analog.button.down);
   }

   if(Ps3.event.analog_changed.button.left){
       Serial.print("Left New value: ");
       Serial.println(Ps3.data.analog.button.left);
   }

   if(Ps3.event.analog_changed.button.right){
       Serial.print("Right New value: ");
       Serial.println(Ps3.data.analog.button.right);
   }
}

void setup()
{
    Serial.begin(115200);

    Ps3.begin("yourDeviceAddress");

    Serial.println("Initialization finished.");
    
    Ps3.attach(onEvent);

}

void loop()
{
  vTaskDelete(NULL);
}

Testing the code

To test the code, simply compile it and upload it to your device using the Arduino IDE. After the procedure finishes, open the serial monitor. You should get the message indicating the initialization was finished, like defined on the Arduino setup.

After that turn on the controller by clicking the PS button. Note that we are not printing any message indicating if the controller is connected or not, but you can check how to do it with an event by following this tutorial.

After that, you can start pressing the buttons with different levels of pressure and check the analog values getting printed, as shown in figure 1. Note that the L buttons are the easier ones to control the level of pressure since they are more sensitive.

Output of the program on the Arduino IDE serial monitor .
Figure 1 – Output of the program on the Arduino IDE serial monitor .

Leave a Reply

%d bloggers like this: