ESP32 ESP-IDF Bluetooth Classic Getting Started Tutorial

In this tutorial, we will learn how to use Bluetooth Classic on ESP32 with ESP-IDF. We’ll establish bidirectional data communication between the ESP32 and an Android phone over Bluetooth Classic. We will see an example of controlling the onboard LED of the ESP32 by sending ON and OFF commands from an Android app.

ESP32 esp-idf bluetooth classic tutorial

We will program ESP32 with ESP-IDF in VS Code. Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

Bluetooth Classic

Bluetooth Classic, also referred to as “Bluetooth BR/EDR” (Basic Rate/Enhanced Data Rate), stands as one of the primary Bluetooth communication protocols alongside Bluetooth Low Energy (BLE). With its inception in 1999, Bluetooth Classic emerged as the original version of Bluetooth, offering a reliable solution for applications that necessitate faster data transfer rates.

Range

Bluetooth Classic is a wireless communication technology that functions in the 2.4 GHz ISM (Industrial, Scientific, and Medical) band. It facilitates reliable connectivity between devices within a close range, usually no more than 10 meters. With a maximum data transfer speed of up to 3 Mbps, Bluetooth Classic is ideal for various applications like streaming audio, transferring files, and connecting wireless peripherals such as keyboards, mice, and speakers.

Advantages

Bluetooth Classic offers backward compatibility with older Bluetooth devices, enabling seamless connectivity between different generations of Bluetooth-enabled products. This crucial feature has led to its widespread acceptance across a wide range of industries, including consumer electronics, automotive systems, and industrial applications.

Bluetooth Classic, which was once the prevailing wireless communication protocol, is now being increasingly overshadowed by Bluetooth Low Energy. While Bluetooth Classic excels in applications requiring higher data rates and continuous data streaming, the advent of Bluetooth Low Energy has prompted developers to consider this more energy-efficient option. With the ever-evolving technology landscape, it is essential for developers to weigh the specific requirements of their projects when choosing between Bluetooth Classic and Bluetooth Low Energy, as each protocol caters to different use cases in wireless communication.

ESP32 Bluetooth Classic

The ESP32 microcontroller is incredibly versatile and powerful. One of its standout features is its ability to function both as a Bluetooth Classic device (using Bluetooth Basic Rate/Enhanced Data Rate) and a Bluetooth Low Energy (BLE) device.

Using Bluetooth Classic on the ESP32 allows for seamless connections with other Bluetooth Classic devices like smartphones, computers, and peripherals. This enables the exchange of data over short distances, making it possible to send and receive information such as audio, files, and commands. Bluetooth Classic is particularly useful for applications that require higher data transfer rates, making it perfect for tasks like audio streaming or sending large amounts of data quickly.

In addition to Bluetooth Classic, the ESP32 also supports BLE, which operates with lower power consumption. BLE is well-suited for wireless sensor applications and projects where energy efficiency is a priority. Combined with the ESP32’s Bluetooth Classic capabilities, developers have the freedom to choose the most suitable Bluetooth technology for their specific project requirements.

The ESP32’s support for both Bluetooth Classic and BLE offers a wide range of possibilities for wireless applications. Whether it’s in the Internet of Things (IoT), home automation, or smart devices field, the ESP32 empowers developers to create innovative and functional solutions.

Android Smart Phone Terminal Application

Today, we will explore the process of connecting an ESP32 development board to an Android smartphone. This exciting venture requires two essential components: an Android phone and a Bluetooth terminal application. By utilizing a Bluetooth terminal app, we can establish a seamless connection between these devices, enabling efficient communication. Let’s get started by ensuring you have both an Android phone and a Bluetooth terminal app readily available for the upcoming steps.

To access the Serial Bluetooth Terminal app, simply head over to the Play Store and download it. You can find the app by searching for its name or by using the following link: Serial Bluetooth Terminal. Once installed, you can launch the app and start using its features.

Serial Bluetooth app
Install App

Create ESP32 ESP-IDF Project for Bluetooth Classic

In this section, let’s create an ESP-IDF project to use Bluetooth Classic of ESP32.

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory. For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, choose ESP32 module. Click ‘Choose Template’ button to proceed forward.

create esp32 esp-idf project

In the Extension, select the ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create a project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens the project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.

Code

This code uses Bluetooth classic of ESP32 to establish a Serial Port Profile (SPP) server. The program sets up an SPP server, which can receive and send data over a Bluetooth connection. It uses the ESP-IDF framework to configure and manage Bluetooth communication.

The Bluetooth Serial Port Profile (SPP) server is a Bluetooth profile that emulates the behavior of a traditional serial port (RS-232) over a wireless Bluetooth connection. It allows for the wireless transfer of data between devices as if they were connected via a physical serial cable. This profile is commonly used in applications where a wireless serial communication link is needed.

The code initializes a GPIO LED on pin 2 and responds to Bluetooth data received over the SPP profile. When data is received, it checks if the data is “ON” or “OFF” and toggles the LED accordingly. Additionally, it handles Bluetooth events, such as initialization, connection, and data reception. The application can be controlled remotely over Bluetooth using a client that sends commands like “ON” or “OFF” to control the LED’s state.

It also handles Bluetooth authentication and security settings. If a client tries to connect to the ESP32 device, the code specifies how authentication should occur, such as using a fixed PIN or other methods.

#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_bt_api.h"
#include "esp_bt_device.h"
#include "esp_spp_api.h"
#include "driver/gpio.h"

#include "time.h"
#include "sys/time.h"


#define SPP_SERVER_NAME "SPP_SERVER"
#define EXAMPLE_DEVICE_NAME "ESP32test"
#define SPP_SHOW_DATA 0
#define SPP_SHOW_SPEED 1
#define SPP_SHOW_MODE SPP_SHOW_DATA /*Choose show mode: show data or speed*/

#define LED_PIN 2

const char *tag = "Bluetooth";

static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB;


static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE;
static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE;

static void init_led(void)
{
    gpio_reset_pin(LED_PIN);
    //gpio_pad_select_gpio(LED_PIN);
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);

    ESP_LOGI(tag, "Init led completed");
}

static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)
{
    switch (event)
    {
    case ESP_SPP_INIT_EVT:
        ESP_LOGI(tag, "ESP_SPP_INIT_EVT");
        esp_spp_start_srv(sec_mask, role_slave, 0, SPP_SERVER_NAME);
        break;
    case ESP_SPP_DISCOVERY_COMP_EVT:
        ESP_LOGI(tag, "ESP_SPP_DISCOVERY_COMP_EVT");
        break;
    case ESP_SPP_OPEN_EVT:
        ESP_LOGI(tag, "ESP_SPP_OPEN_EVT");
        break;
    case ESP_SPP_CLOSE_EVT:
        ESP_LOGI(tag, "ESP_SPP_CLOSE_EVT");
        break;
    case ESP_SPP_START_EVT:
        ESP_LOGI(tag, "ESP_SPP_START_EVT");
        esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME);
        esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
        break;
    case ESP_SPP_CL_INIT_EVT:
        ESP_LOGI(tag, "ESP_SPP_CL_INIT_EVT");
        break;
    case ESP_SPP_DATA_IND_EVT:
        ESP_LOGI(tag, "ESP_SPP_DATA_IND_EVT len=%d handle=%d",
                 param->data_ind.len, param->data_ind.handle);
        esp_log_buffer_hex("", param->data_ind.data, param->data_ind.len);
        
        char received_data[5];
       snprintf(received_data, sizeof(received_data), "%s", param->data_ind.data);

        if (strncmp(received_data, "ON", 2) == 0)
        {
            gpio_set_level(LED_PIN, 1); // turn on the LED
            ESP_LOGI(tag, "Setting LED to ON");
        }
        else if (strncmp(received_data, "OFF", 3) == 0)
        {
            gpio_set_level(LED_PIN, 0); // turn off the LED
            ESP_LOGI(tag, "Setting LED to OFF");
        }
        esp_spp_write(param->data_ind.handle, param->data_ind.len, param->data_ind.data);
        break;
    case ESP_SPP_CONG_EVT:
        ESP_LOGI(tag, "ESP_SPP_CONG_EVT");
        break;
    case ESP_SPP_WRITE_EVT:
        ESP_LOGI(tag, "ESP_SPP_WRITE_EVT");
        break;
    case ESP_SPP_SRV_OPEN_EVT:
        ESP_LOGI(tag, "ESP_SPP_SRV_OPEN_EVT");
        break;
    case ESP_SPP_SRV_STOP_EVT:
        ESP_LOGI(tag, "ESP_SPP_SRV_STOP_EVT");
        break;
    case ESP_SPP_UNINIT_EVT:
        ESP_LOGI(tag, "ESP_SPP_UNINIT_EVT");
        break;
    default:
        break;
    }
}

void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
    switch (event)
    {
    case ESP_BT_GAP_AUTH_CMPL_EVT:
    {
        if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS)
        {
            ESP_LOGI(tag, "authentication success: %s", param->auth_cmpl.device_name);
            esp_log_buffer_hex(tag, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
        }
        else
        {
            ESP_LOGE(tag, "authentication failed, status:%d", param->auth_cmpl.stat);
        }
        break;
    }
    case ESP_BT_GAP_PIN_REQ_EVT:
    {
        ESP_LOGI(tag, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
        if (param->pin_req.min_16_digit)
        {
            ESP_LOGI(tag, "Input pin code: 0000 0000 0000 0000");
            esp_bt_pin_code_t pin_code = {0};
            esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
        }
        else
        {
            ESP_LOGI(tag, "Input pin code: 1234");
            esp_bt_pin_code_t pin_code;
            pin_code[0] = '1';
            pin_code[1] = '2';
            pin_code[2] = '3';
            pin_code[3] = '4';
            esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
        }
        break;
    }

#if (CONFIG_BT_SSP_ENABLED == true)
    case ESP_BT_GAP_CFM_REQ_EVT:
        ESP_LOGI(tag, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", param->cfm_req.num_val);
        esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
        break;
    case ESP_BT_GAP_KEY_NOTIF_EVT:
        ESP_LOGI(tag, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey);
        break;
    case ESP_BT_GAP_KEY_REQ_EVT:
        ESP_LOGI(tag, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
        break;
#endif

    case ESP_BT_GAP_MODE_CHG_EVT:
        ESP_LOGI(tag, "ESP_BT_GAP_MODE_CHG_EVT mode:%d", param->mode_chg.mode);
        break;

    default:
    {
        ESP_LOGI(tag, "event: %d", event);
        break;
    }
    }
    return;
}

void app_main(void)
{
    init_led();
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK)
    {
        ESP_LOGE(tag, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK)
    {
        ESP_LOGE(tag, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    if ((ret = esp_bluedroid_init()) != ESP_OK)
    {
        ESP_LOGE(tag, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    if ((ret = esp_bluedroid_enable()) != ESP_OK)
    {
        ESP_LOGE(tag, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK)
    {
        ESP_LOGE(tag, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK)
    {
        ESP_LOGE(tag, "%s spp register failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

    if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK)
    {
        ESP_LOGE(tag, "%s spp init failed: %s\n", __func__, esp_err_to_name(ret));
        return;
    }

#if (CONFIG_BT_SSP_ENABLED == true)
    /* Set default parameters for Secure Simple Pairing */
    esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
    esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
    esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
#endif

    /*
     * Set default parameters for Legacy Pairing
     * Use variable pin, input pin code when pairing
     */
    esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
    esp_bt_pin_code_t pin_code;
    esp_bt_gap_set_pin(pin_type, 0, pin_code);
}

Download the complete project from this link:

Demonstration

In this section, we will see a demo. First, let’s flash the code to ESP32. To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor
ESP32 Web Server Control Output GPIO Project Flash Chip

After the code flashes successfully, you can view all the informational logs.

Now go to your mobile app which you installed earlier.

Enable Bluetooth in the settings of your smartphone and then install the ‘Serial Bluetooth Terminal’ app. Once installed, open the app and tap the three horizontal bars located at the top-left corner of the screen.

Serial Bluetooth app demo1

Proceed by selecting the Devices tab.

Serial Bluetooth demo2

Please choose the ‘ESP32test’ option from the list of available devices in the ‘available devices’ section.

Serial Bluetooth demo3

Once you have chosen your ESP32 module, simply click on the ‘link’ icon located at the top of the screen. Initially, you will receive a message saying ‘Connecting to ESP32test.’ Once the connection is established successfully, you will be notified with a message saying ‘Connected.’

Serial Bluetooth demo4

Now send ON and OFF messages from the Android app as shown below, you will see that the onboard LED of ESP32 will also turn on and turn off according to these commands:

esp32 esp-idf bluetooth with android app

As you can also see the on serial console, messages are getting displayed about the LED getting on and off.

ESP32 esp-idf bluetooth classic tutorial with example

Conclusion

In summary, we learned how to establish two-way communication with ESP32 and Android via Bluetooth Classic using esp-idf framework.

You may also like to read:

5 thoughts on “ESP32 ESP-IDF Bluetooth Classic Getting Started Tutorial”

  1. I can’t build it. i have fatal error: esp_bt.h: no such file or directory. If I use BT example, i can build it. Please could you explain what i have wrong?

    Reply
  2. Hi guys, I tried your example project but I have an issue. I receive an event 10 message on my terminal. I can see the device in on my phone in Bluetooth devices but no in the app. Pls. See what I get in my VS Code terminal:
    I (592) cpu_start: Starting scheduler on PRO CPU.
    I (0) cpu_start: Starting scheduler on APP CPU.
    I (602) gpio: GPIO[2]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
    I (612) Bluetooth: Init led completed
    I (632) BTDM_INIT: BT controller compile version [2c56073]
    I (632) system_api: Base MAC address is not set
    I (632) system_api: read default base MAC address from EFUSE
    I (642) phy_init: phy_version 4670,719f9f6,Feb 18 2021,17:07:07
    I (1122) Bluetooth: ESP_SPP_INIT_EVT
    I (1122) Bluetooth: ESP_SPP_START_EVT
    I (1132) Bluetooth: event: 10

    Reply

Leave a Comment