ESP32-C3/S3 BLE5 Multi-Advertising Guide

Summary : Unlock advanced Bluetooth capabilities on your ESP32-C3 or S3. This article explains how to use the Bluedroid stack to run multiple, distinct BLE advertising sets simultaneously from a single chip, bridging the gap between legacy BLE 4.x compatibility and high-performance BLE 5 features.

Mastering BLE5 Multi-Advertising on ESP32-C3 & ESP32-S3

With the introduction of Bluetooth 5.0 in the newer ESP32 system-on-chips (SoCs)—specifically the ESP32-C3 and ESP32-S3—developers gained powerful new radio capabilities. Among the most useful additions is Multi-Advertising.

Traditionally, a BLE peripheral could only broadcast one set of advertising data at a time. If you wanted to change that data or use different advertising intervals, you had to stop advertising, update parameters, and restart.

Multi-Advertising changes the game. It allows a single ESP32-C3/S3 to act like multiple virtual devices simultaneously, each broadcasting unique data, using different physical layers (PHYs) like Coded PHY for long-range or 2M PHY for speed, and maintaining individual connection states.

Thanks to the efforts of maintainers like chegewara, utilizing this complex feature in the Arduino IDE is straightforward. Let’s break down the example code.

1. Prerequisites and Safety Checks

The example code begins with crucial compiler checks to ensure you are targeting the right hardware and software configuration.

C++

#ifndef CONFIG_BLUEDROID_ENABLED
#error "NimBLE does not support multi advertising yet. Try using Bluedroid."
#elif !defined(CONFIG_BT_BLE_50_FEATURES_SUPPORTED)
#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3"
#else
// ... includes

Key Takeaway: You must use the Bluedroid Bluetooth stack in your ESP-IDF configuration (default for Arduino). Furthermore, you cannot run this code on an original ESP32-WROOM-32 or other BLE 4.2-only chips; they simply lack the necessary hardware support for BLE 5.0 Multi-Advertising.

2. Defining Advertising Sets

The heart of this example lies in defining distinct advertising parameters (esp_ble_gap_ext_adv_params_t) for each instance we want to run. This example sets up four unique advertisements.

Instance 0: Connectable (Coded PHY Primary)

This set is connectable. It starts advertising on the standard channels using Coded PHY (Long Range) but expects the connection to transition to the standard 1M PHY.

C++

esp_ble_gap_ext_adv_params_t ext_adv_params_1M = {
  .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE,
  // ... intervals, mapping
  .primary_phy = ESP_BLE_GAP_PHY_CODED,
  .secondary_phy = ESP_BLE_GAP_PHY_1M,
  .sid = 0, // Unique Set ID
};

Instance 1: High-Speed (2M PHY)

This scannable advertisement focuses on throughput. It advertised on the primary channels at 1M but points scanners to the auxiliary channels using the high-speed 2M PHY.

C++

esp_ble_gap_ext_adv_params_t ext_adv_params_2M = {
  .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE,
  .primary_phy = ESP_BLE_GAP_PHY_1M,
  .secondary_phy = ESP_BLE_GAP_PHY_2M,
  .sid = 1,
};

Instance 2: Legacy Backward Compatibility (Crucial)

This is the most important configuration if you need your project to be detected by older smartphones or BLE 4.2 hardware. You must use the ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND type and keep both PHYs set to 1M. BLE 5.0 Extended Advertisements are invisible to BLE 4.x scanners. This set bridges that gap.

C++

esp_ble_gap_ext_adv_params_t legacy_adv_params = {
  .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND,
  .primary_phy = ESP_BLE_GAP_PHY_1M,
  .secondary_phy = ESP_BLE_GAP_PHY_1M,
  .sid = 2,
};

Instance 3: Long-Range (Coded PHY Secondary)

Similar to Instance 1, but this points scanners to auxiliary channels using Coded PHY, optimizing for maximum range.

C++

esp_ble_gap_ext_adv_params_t ext_adv_params_coded = {
  // ...
  .primary_phy = ESP_BLE_GAP_PHY_1M,
  .secondary_phy = ESP_BLE_GAP_PHY_CODED,
  .sid = 3,
};

3. The Payload and Addresses

The code defines raw hex byte arrays for the advertising data and scan response data.

A highlight of BLE 5.0 Extended Advertising is the ability to break free from the old 31-byte advertising data limit. Take a look at raw_scan_rsp_data_coded used by Instance 3:

C++

static uint8_t raw_scan_rsp_data_coded[] = {
    // ... many bytes ...
    'V', 'E', 'R', 'Y', '_', 'L', 'O', 'N', 'G', '_', 'D', 'E', 'V', 'I', 'C', 'E', '_',
    'N', 'A', 'M', 'E', '_', 'S', 'E', 'N', 'T', '_', 'U', 'S', 'I', 'N', 'G', '_', 'E', 'X', 'T',
    'E', 'N', 'D', 'E', 'D', '_', 'A', 'D', 'V', 'E', 'R', 'T', 'I', 'S', 'I', 'N', 'G', 0X0};

This device name would never fit in a legacy packet, demonstrating the power of BLE 5.

Furthermore, the code defines unique Random MAC addresses for each instance. This ensures that scanners treat them as separate physical entities.

C++

uint8_t addr_1m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x01};
uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02};
// ...

4. Orchestration in Setup

In the setup() function, the BLEMultiAdvertising object handles the heavy lifting of registering these sets with the controller.

We initialize the object by stating the maximum number of simultaneous sets we intend to run (4 in this case): BLEMultiAdvertising advert(4);.

We then walk through the configuration index (0 to 3) for each virtual device, assigning the parameters, the data payloads, and the unique address:

C++

void setup() {
  Serial.begin(115200);
  BLEDevice::init("");

  // Instance 0 setup
  advert.setAdvertisingParams(0, &ext_adv_params_1M);
  advert.setAdvertisingData(0, sizeof(raw_adv_data_1m), &raw_adv_data_1m[0]);
  advert.setInstanceAddress(0, addr_1m);
  // (Optional) advert.setDuration(0); // Run forever

  // ... Repeat for instances 1, 2, and 3 ...

  delay(1000); // Small delay to let parameters settle

  // 5. Start all instances simultaneously
  // advert.start(how_many_sets, start_index);
  advert.start(4, 0);
}

The final command, advert.start(4, 0), instructs the ESP32 to begin firing off all four configured advertising sets simultaneously.

When running this code, a nearby scanner (like the Nordic nRF Connect mobile app) will detect four separate “devices” with the names defined in the payload arrays (e.g., ESP_MULTI_ADV_1M, VERY_LONG_DEVICE_NAME_SENT..., etc.), showcasing the remarkable flexibility of the new BLE 5.0 silicon in the C3 and S3.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *