ESP32 Bluetooth BLE Remote Control

This project is an example of how to connect an inexpensive Bluetooth BLE joystick to an ESP32. The code was written using the Arduino IDE Ver 1.8.5 with the ESP32 addon. The BLE joystick used is a commercial device that you can purchase on the Internet for less than $20.00 or from your local fiveBELoW store for $5.00.

The joystick that I used for this project is a Spektrum VR Control Bluetooth Remote Controller. It is being marketed as working with Android and IOS tablets as a joystick/mouse for use with VR headsets.

The VR Control Bluetooth Remote Controller has a single joystick with X and Y axis, two trigger buttons in front and six buttons on the handle. Two of the buttons are for power on/off and mode control. The other four buttons can be used for whatever you want. This project is a skeleton or framework that handles all of the Bluetooth interfacing and decoding of the buttons & joystick. All you have to do is add function calls to handle what you want the buttons and joystick to do. You do not need to know anything about Bluetooth to use this framework.

There are numerous web sites with detailed instructions for installing the Arduino IDE and the ESP32 addon. I am not going to attempt to repeat that information here. Google it and follow the directions.

The framework is an adaptation of a BLE client example published by IoT Sharing. You can find it here. You can study this code to get an idea of how BLE works. Expressif has a complete example for a GATT BLE client and explains the operation in detail (not written for Arduino IDE). You can get it here.

This is not a BLE tutorial. I will not be explaining how the code works in detail. I will use BLE terminology to describe some of the features of the joystick. The part of the code that you need to modify for your project will be explained in detail to help you to modify it. The scope is limited to keep this Instructable short and focused on using the joystick.

Step 1: A Little About Bluetooth Low Energy (BLE)

This is not intended to be a tutorial on BLE. When I started this project, I did not know the difference between BLE and Classic Bluetooth. I just wanted to see if I could get the joystick I bought to work with the ESP32. In the following text I am using BLE terminology to give a simplified overview of how BLE works.

BLE uses a client/server architecture. One device is a server that provide services. The other device is a client that consumes services. In order to keep the power requirements down, BLE only transmits small packets of information when a change occurs. In the case of the joystick, the joystick device is a server. As a server, it advertises itself and will transmit a list of the services it provides when asked. The joystick device advertises five services. The only service that we are interested in is the BLE HID (Human Interface Device) service. A BLE service has what are known as Characteristics associated with it. A Characteristic is typically a source of data. The HID service of the joystick has ten Characteristics. Some of the Characteristics are duplicates and are ignored. We are only interested in the BLE Report Characteristics that have Read and Notify capabilities. Three of the characteristics meet these requirements and provide data about the position of the joystick and the state of the buttons. When notification is enabled, the server will send data packets when a change is detected on the associated characteristic.

The framework verifies that the server it finds has the BLE HID service and will then enable Notifications on the three Report Characteristics that provide joystick and button state information. Then, when a button is pressed or released or the joystick is moved, the ESP32 receives a packet of data telling it what the new joystick position is and/or the state of some buttons.

Step 2: Scanning and Connection Indicators

The framework defines two LEDS, GREENLED and BLUELED and assigns them to two of the ESP32's GPIO pins. The GREENLED is lit up when the ESP32 is searching for the BLE joystick. When the joystick is found the GREENLED is turned off and the BLUELED is lit up to indicate that the connection has been established and you are ready to go. If the connection is lost, the BLUELED is turned off, the ESP32 is reset, the GREENLED is lit and scanning starts again. If the joystick is not found within thirty seconds then scanning stops and the GREENLED turns off. After five seconds, scanning starts again and the GREENLED is turned on.

The end result is that the ESP32 will continue to scan for the joystick until it finds it. Once the connection is made, if it is then lost, the ESP32 will reset itself and start scanning over again. The ESP32 is reset because there are no ESP32 SDK functions to reset the Bluetooth stack to restart scanning.

Step 3: Deciphering Joystick and Button Events.

One callback event on the ESP32 receives three different data packets from the server for the three Characteristics that were setup to provide Notifications. One packet is four bytes long. Three of the bytes contain the X axis position, Y axis position and the trigger buttons, which are bit mapped in the byte. The other two packets are two bytes each and have a single byte that has the bit mapped button state. The received packets are decoded and copied into a byte array in memory. The joystick axis data goes into the X and Y data bytes and each of the three bit mapped button bytes is directed into the appropriate byte for those buttons.

A FreeRTOS task is created to handle the data received by the notifications. One task for the joystick and trigger buttons, one task for the A & B buttons and one task for the C & D buttons. Each of these tasks has clearly marked areas where you should your add code to do what you want with the event. Look for the "//===== add your code here =====" comment in the body of the task and add your code after it. Each task has a comment indicating what it is used for and uses a Serial.println() to print a message about the event that occurred.

Here is an example from the A/B button task;

void taskButtonAB(void *parameter)
{ uint8_t buttons;

//===== if the task requires any one time initialization, put it here ===== while(true) { // give up the CPU, wait for new data vTaskSuspend(NULL); // we just woke up, new data is available buttons = VrBoxData[VB_BTNAB]; Serial.printf("A/B Buttons: %02X\n", buttons); if (buttons & VB_BUTTON_A) { // button A pressed or is being held down Serial.println("Button A"); //===== add your code here ===== }

if (buttons & VB_BUTTON_B) { // button B pressed or is being held down Serial.println("Button B");

//===== add your code here ===== } } // for } // taskButtonAB

Step 4: VR Box Operation: the Joystick

If the joystick is left in the center position, no joystick notifications are sent. Once the joystick is moved off center, a notification message with joystick data and trigger button data is sent about every 15mS. When the joystick is moved back to center, a notification that it has moved to center is not sent. In other words, it tells you the joystick has moved off center, but not that it has moved to center. The end result is that you receive messages indicating the joystick is moving toward center, but not that it has reached center. Very annoying. The two trigger buttons are included with the joystick data. Pressing one of the trigger buttons after returning the joystick to center will update the joystick position to zero. The Framework has a timeout timer built-in to it that automatically simulates a joystick notification message a short time after all joystick/trigger button notification messages stop arriving. The timer sets the joystick to zero. The joystick has a range of about +/- 25 on each axis.

Step 5: VR Box Operation: Trigger Buttons

The trigger buttons will send a notification message once when pressed and again when released. The pressed notification message will indicate the button that was pressed. The release notification message indicates that both buttons are released.

Holding the lower trigger button will prevent the server from detecting that the upper trigger button has been pressed. Holding the upper trigger button and pressing the lower trigger button results in the server sending a notification message that the lower trigger button is pressed (the upper trigger button will be zero!). Releasing the lower trigger button will cause the server to send a notification that the upper trigger button is pressed and the lower trigger is released.

In other words, the lower trigger button is dominant over the upper trigger button and will override it when both are pressed. You have to determine how to handle the case of both buttons being pressed.

Step 6: VR Box Operation: A/B Buttons

The A and B buttons act like the joystick and continuously send notification messages when pressed and held down. The messages stop when the button is released. The A and B buttons work similar to the Trigger buttons in that the A button dominates the B button just like the lower trigger button dominates the upper trigger button.

Step 7: VR Box Operation: C/D Buttons

The C and D buttons send a notification message once when pressed and again when released. If held down, no further messages are sent until they are released. Holding down either one of the C or D buttons will prevent the server from detecting activity on the other button.

Step 8: Conclusion

The operation of the buttons is in my opinion a little wonky. The Framework provides for places to put your code to act on when a button is pressed. If you also need to detect button releases, that is left for you to figure out how to do.

It is entirely up to you to determine what you want each button to do and what moving the joystick should do. How you handle the differences in the trigger, A & B and the C & D buttons is up to you.

Look in the code for the; taskJoyStick(), taskButtonAB(), taskButtonCD() functions and add your code after the "//===== add your code here =====" comment.

You will need up to four functions to handle the joystick (forward, backward, right & left) and up to six functions to handle the various buttons. Implement them all or just what you need. The choice is yours.

If you use this framework. I would love a shout out about what you used it for and if you found it easy to use.

If you have questions about how it works or need help getting it to work, contact me.

The code is available on GitHub here.




    • Plastics Contest

      Plastics Contest
    • Optics Contest

      Optics Contest
    • Make it Glow Contest 2018

      Make it Glow Contest 2018

    6 Discussions

    Martin Prochazka

    4 months ago

    Hello BigjBehr, I am trying upload code to Heltec ESP32 and have some problem with compiling error:

    C:\Users\Peter Brown\Documents\Gatt-VRBOX-Tasks\Gatt-VRBOX-Tasks.ino: In function 'void esp_gap_cb(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t*)':

    Gatt-VRBOX-Tasks:222: error: cannot convert 'bool' to 'esp_ble_addr_type_t' for argument '3' to 'esp_err_t esp_ble_gattc_open(esp_gatt_if_t, uint8_t*, esp_ble_addr_type_t, bool)'

    esp_ble_gattc_open(test_profile.gattc_if, scan_result->scan_rst.bda, true);


    exit status 1

    cannot convert 'bool' to 'esp_ble_addr_type_t' for argument '3' to 'esp_err_t esp_ble_gattc_open(esp_gatt_if_t, uint8_t*, esp_ble_addr_type_t, bool)'


    Ble_client from sketches compile well.

    What version of BLE library did you use ? I already tryied those from expressiff and BLE from 0.4.0 to 0.4.7 Thank you for the tip.

    4 replies
    BigjBehrMartin Prochazka

    Reply 4 months ago


    I was able to compile without any errors. After I realized that this is a problem with the ESP32 SDK for the Arduino, I updated to the latest version and am now getting the same error that you are getting. I made a change to the code to fix the error. The code now compiles, but generates a run-time error. I am looking into fixing the run-time error. I do not know how long it will take me. Once I have everything working correctly I will update the code on github and let you know that the new code is there.

    Martin ProchazkaBigjBehr

    Reply 4 months ago

    Thank you BigjBehr, I will definitelly try new code. Yes, compiled with Arduino 1.8.5 IDE and actualized ESP32 SDK from last repository. I was thinking my compile error was becouse of friday afternoon. Controlling Esp with bluetooth joystick will be fantastic not only on my ebike.

    BigjBehrMartin Prochazka

    Reply 4 months ago


    Bad news. I have been going over the problems caused by the new SDK from Expressif. I am unable to fix what the new SDK broke. After spending time searching the Internet, it seems that lots of people are having the same or similar issues with the changes Expressif made to the SDK. It looks like the only solution is to wait for Expressif to acknowledge and fix their problems. If you can somehow download and install the previous SDK the project will compile and function. To be clear, I have corrected the issue with the code not compiling. The current issue is that a runtime error is occurring that is causing the ESP32 to do a reset. I cannot tell exactly when the error occurs. The ESP32 finds the server, connects and then crashes sometime after that. Looks like it might be a watchdog timeout that causes it to reset.

    Martin ProchazkaBigjBehr

    Reply 4 months ago

    Do you know version of ESP32 SDK you compiled ? Or just previous ? My next idea is make bluetooth joystick also with Esp chip and connect it to next one through ESP Now.


    4 months ago


    This is a little strange. The error you are having is due to
    the ‘esp_ble_gattc_open’ function requiring four parameters and only being
    passed three. When I wrote this it only required three parameters. So it must
    be a library difference.

    The code was written for the Arduino IDE (ver 1.8.5) using
    the ESP32 add on library ver 0.0.0 (as far as I can tell). I was not aware
    there was another release.

    Are you using the Arduino IDE ?