Chapter 62: BLE Advertising Configurations

Chapter Objectives

By the end of this chapter, you will be able to:

  • Understand the purpose and types of BLE advertising.
  • Differentiate between advertising data and scan response data.
  • Configure common advertising data types like Flags, Local Name, Service UUIDs, and Manufacturer Specific Data.
  • Set advertising parameters such as interval, type, and channel map using ESP-IDF.
  • Implement connectable, non-connectable, scannable, and non-scannable advertisements.
  • Understand the basics of legacy versus extended advertising (BLE 5.0).
  • Utilize ESP-IDF APIs to start and stop advertising.
  • Troubleshoot common advertising configuration issues.

Introduction

Before a BLE central device can connect to your ESP32 peripheral, it first needs to discover its existence. This discovery process relies on advertising. Advertising is the mechanism by which a BLE peripheral broadcasts small packets of data, announcing its presence and often providing basic information about itself, such as its name or the services it offers.

Think of advertising as a BLE device raising its hand and saying, “Hello, I’m here, and this is a bit about me!” Properly configuring these advertisements is crucial. It determines how easily your device can be found, what initial information a scanning device receives, whether your device is connectable, and even impacts power consumption.

This chapter will guide you through the various aspects of configuring BLE advertisements on your ESP32 using ESP-IDF v5.x. We’ll cover the different types of advertising packets, the data they can contain, how to set advertising parameters, and the ESP-IDF APIs used to manage the advertising process.

Theory

Advertising is a fundamental GAP (Generic Access Profile) role. A device that advertises is in the “Advertiser” role. This is typically a peripheral device wanting to be discovered by a central.

Purpose of Advertising

Purpose Description Example Use Case
Device Discovery Allows central devices (scanners like smartphones or other MCUs) to find nearby BLE peripherals that are announcing their presence. A fitness tracker advertising its availability to be paired with a phone.
Connection Establishment Connectable advertisements enable a central device to initiate a BLE connection with the advertising peripheral. A smart lightbulb advertising so a home assistant can connect and control it.
Broadcasting Data Non-connectable advertisements can be used to send small amounts of data to any listening device without forming an explicit connection. This is a one-way communication. An iBeacon transmitting a UUID for proximity detection in a retail store, or a sensor broadcasting temperature updates.

Advertising Channels

BLE uses 3 dedicated advertising channels (channels 37, 38, and 39) located at the edges of the 2.4 GHz band to minimize interference with WiFi channels. Advertisements are sent sequentially on these channels.

Advertising Packet (PDU – Protocol Data Unit) Types

There are several types of advertising PDUs, each serving a different purpose:

  • ADV_IND (Connectable Undirected Advertising):
    • The most common type.
    • Indicates the device is connectable and discoverable by any central.
    • Can contain up to 31 bytes of advertising data.
    • Can optionally provide a scan response.
%%{init: {"theme": "base", "themeVariables": {
    "primaryColor": "#DBEAFE", "primaryTextColor": "#1E40AF", "primaryBorderColor": "#2563EB",
    "lineColor": "#6B7280", "textColor": "#1F2937",
    "fontSize": "14px", "fontFamily": "\"Open Sans\", sans-serif"
}}}%%
sequenceDiagram
    actor Advertiser
    actor Scanner

    Advertiser->>+Scanner: Sends ADV_IND (AdvData)
    Note over Advertiser,Scanner: Device is connectable, open to any central

    alt Active Scan (Scanner requests more info)
        Scanner->>+Advertiser: Sends SCAN_REQ
        Advertiser->>-Scanner: Sends SCAN_RSP (ScanRspData)
    end

    Scanner->>+Advertiser: Sends CONNECT_REQ / LL_CONNECTION_REQ
    Note over Advertiser,Scanner: Connection attempt initiated
    Advertiser-->>-Scanner: (Connection Establishes or Fails)
    
    %% Styling Participants
    %% participant Advertiser ###EDE9FE
    %% participant Scanner ###DBEAFE

    %% Notes:
    %% - ADV_IND allows any central to connect.
    %% - Advertiser broadcasts its presence with Advertising Data (AdvData).
    %% - An active scanner can send a SCAN_REQ to get more info.
    %% - Advertiser replies with SCAN_RSP (Scan Response Data) if configured.
    %% - Scanner initiates connection with CONNECT_REQ.
  • ADV_DIRECT_IND (Connectable Directed Advertising):
    • Used to quickly reconnect to a known central device.
    • Contains the advertiser’s address and the target central’s address.
    • High Duty Cycle Directed Advertising: Short duration, fast connection. No advertising data payload.
    • Low Duty Cycle Directed Advertising: Longer duration. No advertising data payload.
  • ADV_NONCONN_IND (Non-Connectable Undirected Advertising):
    • Used for broadcasting data; the device is not connectable.
    • Can contain up to 31 bytes of advertising data.
    • Cannot have a scan response.
    • Example: Beacons like iBeacon or Eddystone.
%%{init: {"theme": "base", "themeVariables": {
    "primaryColor": "#DBEAFE", "primaryTextColor": "#1E40AF", "primaryBorderColor": "#2563EB",
    "lineColor": "#6B7280", "textColor": "#1F2937",
    "fontSize": "14px", "fontFamily": "\"Open Sans\", sans-serif"
}}}%%
sequenceDiagram
    actor Advertiser
    actor Listener

    loop Broadcast Loop
        Advertiser->>+Listener: Sends ADV_NONCONN_IND (AdvData)
        Note over Advertiser,Listener: Broadcasting data, not connectable
        Listener-->>-Advertiser: (No response expected/allowed from Listener for this PDU)
    end

    %% Styling Participants
    %% participant Advertiser ###EDE9FE
    %% participant Listener ###DBEAFE

    %% Notes:
    %% - ADV_NONCONN_IND is for broadcasting data one-way.
    %% - The advertiser is not connectable.
    %% - No SCAN_REQ or CONNECT_REQ is allowed from the listener in response to ADV_NONCONN_IND.
    %% - Useful for beacons or simple data dissemination.
  • ADV_SCAN_IND (Scannable Undirected Advertising):
    • The device is not connectable but can provide more information via a scan response.
    • Can contain up to 31 bytes of advertising data.
    • A central can send a Scan Request, and the advertiser replies with Scan Response data (up to 31 bytes).
  • ADV_EXT_IND (Extended Advertising – BLE 5.0+):
    • Allows for much larger advertising data payloads (up to 255 bytes per PDU, and can be chained for even larger sets up to 1650 bytes).
    • Can advertise on data channels in addition to primary advertising channels, reducing congestion.
    • Supports periodic advertising for synchronized data broadcasting.
    • More complex to configure than legacy advertising.
PDU Type (Code) Full Name Connectable? Scan Response? Max Adv Data (Legacy) Primary Use
ADV_IND Connectable Undirected Advertising Yes Yes 31 bytes General purpose, discoverable and connectable by any central. Most common.
ADV_DIRECT_IND Connectable Directed Advertising Yes (to specific central) No 0 bytes (address only) Quickly reconnect to a known central device. High duty cycle for fast connection, low duty cycle for longer attempts.
ADV_NONCONN_IND Non-Connectable Undirected Advertising No No 31 bytes Broadcasting data to any listener without allowing connections (e.g., beacons).
ADV_SCAN_IND Scannable Undirected Advertising No Yes 31 bytes Device is not connectable but can provide more information via a scan response if actively scanned.
ADV_EXT_IND Extended Advertising (BLE 5.0+) Varies (can be connectable or not) Yes (Auxiliary Scan Response) Up to 1650 bytes (chained) Large data payloads, advertising on data channels, periodic advertising. More complex.

This chapter primarily focuses on legacy advertising types (ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND) as they are universally supported and simpler to start with. Extended advertising is a more advanced topic.

Advertising Data and Scan Response Data

For legacy advertising types like ADV_IND and ADV_SCAN_IND:

  • Advertising Data (AdvData): Up to 31 bytes of payload included in the primary advertising PDU. This is what a passive scanner sees immediately.
  • Scan Response Data (ScanRspData): Up to 31 bytes of additional payload. This is sent only if the advertiser receives a “Scan Request” PDU from an active scanner.

Both AdvData and ScanRspData are structured as a sequence of AD (Advertising Data) Structures. Each AD Structure consists of:

  • Length (1 byte): Length of the Type and Data fields (n).
  • Type (1 byte): AD Type, defining what kind of data is present (e.g., Flags, Local Name). Defined by Bluetooth SIG.
  • Data (n-1 bytes): The actual data corresponding to the AD Type.

Common AD Types (from Bluetooth SIG Assigned Numbers):

  • 0x01 – Flags: Describes LE capabilities and modes. Crucial for discovery.
    • Bit 0: LE Limited Discoverable Mode
    • Bit 1: LE General Discoverable Mode
    • Bit 2: BR/EDR Not Supported (Controller only supports BLE)
    • Bit 3: Simultaneous LE and BR/EDR to Same Device Capable (Controller)
    • Bit 4: Simultaneous LE and BR/EDR to Same Device Capable (Host)
AD Type (Hex) Name Brief Description
0x01 Flags Describes LE capabilities (e.g., discoverable mode, BR/EDR support). Crucial for discovery.
0x02 / 0x03 Incomplete / Complete List of 16-bit Service Class UUIDs Advertises supported 16-bit service UUIDs.
0x04 / 0x05 Incomplete / Complete List of 32-bit Service Class UUIDs Advertises supported 32-bit service UUIDs.
0x06 / 0x07 Incomplete / Complete List of 128-bit Service Class UUIDs Advertises supported 128-bit service UUIDs.
0x08 / 0x09 Shortened / Complete Local Name The device’s human-readable name.
0x0A TX Power Level Transmit power in dBm. Can be used for proximity estimation.
0x16 Service Data – 16-bit UUID Associates data with a specific 16-bit service UUID.
0x20 Service Data – 32-bit UUID Associates data with a specific 32-bit service UUID.
0x21 Service Data – 128-bit UUID Associates data with a specific 128-bit service UUID.
0xFF Manufacturer Specific Data Custom data defined by the manufacturer. Requires a 2-byte company ID.

Advertising Parameters

These parameters control how advertising occurs:

  • Advertising Interval Min & Max (adv_int_min, adv_int_max):
    • Defines the range for the time between the start of two consecutive advertising events.
    • Value is N * 0.625 ms. Range: 0x0020 (20 ms) to 0x4000 (10.24 s).
    • Shorter interval: Faster discovery, higher power consumption.
    • Longer interval: Slower discovery, lower power consumption.
    • The actual interval is chosen pseudo-randomly within this range by the controller for each advertising event to reduce collisions.
  • Advertising Type (adv_type):
    • ADV_TYPE_IND: Connectable undirected (ADV_IND).
    • ADV_TYPE_DIRECT_IND_HIGH: Connectable high duty cycle directed (ADV_DIRECT_IND).
    • ADV_TYPE_SCAN_IND: Scannable undirected (ADV_SCAN_IND).
    • ADV_TYPE_NONCONN_IND: Non-connectable undirected (ADV_NONCONN_IND).
    • ADV_TYPE_DIRECT_IND_LOW: Connectable low duty cycle directed (ADV_DIRECT_IND).
  • Own Address Type (own_addr_type):
    • BLE_ADDR_TYPE_PUBLIC: Use public device address.
    • BLE_ADDR_TYPE_RANDOM: Use static random device address.
    • BLE_ADDR_TYPE_RPA_PUBLIC: Use Resolvable Private Address (RPA) derived from public identity.
    • BLE_ADDR_TYPE_RPA_RANDOM: Use RPA derived from static random identity.
    • (Requires IRK to be set up for RPA usage).
  • Peer Address and Type (peer_addr_type, peer_addr): Used only for directed advertising (ADV_DIRECT_IND).
  • Advertising Channel Map (channel_map):
    • Bitmask specifying which advertising channels to use (37, 38, 39).
    • ADV_CHNL_37_BIT, ADV_CHNL_38_BIT, ADV_CHNL_39_BIT, ADV_CHNL_ALL.
    • Using all three is recommended for robustness.
  • Advertising Filter Policy (adv_filter_policy):
    • Controls how scan and connection requests are processed based on a whitelist.
    • ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY: Default. Allow scan and connect requests from any device.
    • ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY: Allow scan requests from whitelist, connect from any.
    • ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST: Allow scan requests from any, connect from whitelist.
    • ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST: Allow scan and connect requests only from whitelist.
    • (Whitelist management is a separate GAP feature).
Parameter (in esp_ble_adv_params_t) Description Typical Values / Notes
adv_int_min, adv_int_max Min/Max advertising interval. Defines range for time between advertising events. Value = N * 0.625 ms. Range: 0x0020 (20 ms) to 0x4000 (10.24 s). Shorter interval = faster discovery, higher power. Longer = slower discovery, lower power. Example: 0x20 (20ms), 0xA0 (100ms).
adv_type Advertising PDU type. ADV_TYPE_IND (connectable undirected), ADV_TYPE_NONCONN_IND (non-connectable), ADV_TYPE_SCAN_IND (scannable), ADV_TYPE_DIRECT_IND_HIGH/LOW.
own_addr_type Advertiser’s own Bluetooth device address type. BLE_ADDR_TYPE_PUBLIC, BLE_ADDR_TYPE_RANDOM, BLE_ADDR_TYPE_RPA_PUBLIC, BLE_ADDR_TYPE_RPA_RANDOM.
peer_addr_type, peer_addr Peer’s address type and address. Used only for directed advertising (ADV_TYPE_DIRECT_IND_...). Specify target central’s address for directed advertising.
channel_map Bitmask specifying which advertising channels (37, 38, 39) to use. ADV_CHNL_37_BIT, ADV_CHNL_38_BIT, ADV_CHNL_39_BIT. ADV_CHNL_ALL (recommended for robustness).
adv_filter_policy Controls how scan and connection requests are processed based on a whitelist. ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY (default), ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY, etc. Whitelist management is a separate GAP feature.

ESP-IDF APIs for Advertising

Key functions from esp_gap_ble_api.h:

  1. esp_ble_gap_set_device_name(const char *name):
    • Sets the device name that can be included in advertising or scan response data using the “Complete Local Name” or “Shortened Local Name” AD types.
    • This name is also often used by the OS when displaying discovered devices.
  2. esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data):
    • Configures the content of the advertising data packet or the scan response data packet.
    • The esp_ble_adv_data_t structure has fields like:
      • set_scan_rsp: true if configuring scan response data, false for advertising data.
      • include_name: true to automatically include the device name (set by esp_ble_gap_set_device_name).
      • include_txpower: true to include TX power level.
      • flag: Value for the Flags AD type (e.g., ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT).
      • manufacturer_len, p_manufacturer_data: For Manufacturer Specific Data.
      • service_data_len, p_service_data: For Service Data.
      • service_uuid_len, p_service_uuid: For advertising service UUIDs.
    • The stack constructs the raw AD structures based on these fields.
    • Triggers ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT or ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT.
  3. esp_ble_gap_config_adv_data_raw(uint8_t *raw_adv, uint32_t raw_adv_len):
    • Allows setting the raw advertising data bytes directly, giving full control over AD structures.
    • Triggers ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT.
  4. esp_ble_gap_config_scan_rsp_data_raw(uint8_t *raw_scan_rsp, uint32_t raw_scan_rsp_len):
    • Allows setting the raw scan response data bytes directly.
    • Triggers ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT.
  5. esp_ble_gap_set_adv_params(esp_ble_adv_params_t *adv_params):
    • Configures the advertising parameters (interval, type, address type, channel map, filter policy).
    • Triggers ESP_GAP_BLE_ADV_PARAM_SET_COMPLETE_EVT (this event is not explicitly listed in all IDF versions’ public headers but the operation is fundamental; often, advertising is started after adv_data configuration).
  6. esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params):
    • Starts advertising using the provided parameters.
    • The advertising data (and scan response data, if configured) should be set before calling this.
    • Triggers ESP_GAP_BLE_ADV_START_COMPLETE_EVT.
  7. esp_ble_gap_stop_advertising(void):
    • Stops ongoing advertising.
    • Triggers ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT.
API Function Purpose Key Event Triggered on Completion
esp_ble_gap_set_device_name() Sets the device name. This name can be included in advertising/scan response. Implicitly used by stack; no direct event for name setting itself, but name is used in subsequent advertising configuration.
esp_ble_gap_config_adv_data() Configures advertising data or scan response data content using esp_ble_adv_data_t struct. ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT (for adv data)
ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT (for scan rsp data)
esp_ble_gap_config_adv_data_raw() Configures raw advertising data bytes directly. ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT
esp_ble_gap_config_scan_rsp_data_raw() Configures raw scan response data bytes directly. ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT
esp_ble_gap_set_adv_params() Configures advertising parameters (interval, type, etc.) using esp_ble_adv_params_t. (Note: Often parameters are passed directly to esp_ble_gap_start_advertising) (Historically ESP_GAP_BLE_ADV_PARAM_SET_COMPLETE_EVT, but often implicit in start.) Modern IDF examples typically configure params and pass to start.
esp_ble_gap_start_advertising() Starts advertising using the provided parameters. Adv/ScanRsp data must be configured prior. ESP_GAP_BLE_ADV_START_COMPLETE_EVT
esp_ble_gap_stop_advertising() Stops ongoing advertising. ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT

Advertising Lifecycle in ESP-IDF

A typical sequence for starting advertising:

  1. Initialize Bluetooth controller and Bluedroid stack.
  2. Register GAP callback (esp_ble_gap_register_callback).
  3. Set device name (esp_ble_gap_set_device_name).
  4. Configure advertising data (esp_ble_gap_config_adv_data). Wait for ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT.
  5. (Optional) Configure scan response data (esp_ble_gap_config_adv_data with set_scan_rsp = true). Wait for ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT.
  6. Configure advertising parameters using esp_ble_adv_params_t structure.
  7. Start advertising (esp_ble_gap_start_advertising with the configured parameters). Wait for ESP_GAP_BLE_ADV_START_COMPLETE_EVT.
%%{init: {"theme": "base", "themeVariables": {
  "primaryColor": "#DBEAFE", "primaryTextColor": "#1E40AF", "primaryBorderColor": "#2563EB",
  "lineColor": "#6B7280", "textColor": "#1F2937",
  "fontSize": "14px", "fontFamily": "'Open Sans', sans-serif"
}}}%%
graph TD
    A["<b>Start: Initialize BLE Stack</b><br>esp_bt_controller_init()<br>esp_bt_controller_enable()<br>esp_bluedroid_init()<br>esp_bluedroid_enable()"]:::start
    B["Register GAP Callback<br><code>esp_ble_gap_register_callback()</code>"]:::process
    C["Register GATTS Callback & App<br><code>esp_ble_gatts_register_callback()</code><br><code>esp_ble_gatts_app_register()</code><br>(Crucial for connectable peripheral)"]:::process
    D{"GATTS App Reg Event?<br><code>ESP_GATTS_REG_EVT</code>"}:::decision
    E["Set Device Name<br><code>esp_ble_gap_set_device_name()</code>"]:::process
    F["Configure Advertising Data<br><code>esp_ble_gap_config_adv_data(&adv_data)</code>"]:::process
    G{"Adv Data Set Event?<br><code>ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT</code>"}:::decision
    H["(Optional) Configure Scan Response Data<br><code>esp_ble_gap_config_adv_data(&scan_rsp_data)</code>"]:::process
    I{"Scan Rsp Set Event?<br><code>ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT</code>"}:::decision
    J["Define Advertising Parameters<br>(Fill <code>esp_ble_adv_params_t</code>)"]:::process
    K["Start Advertising<br><code>esp_ble_gap_start_advertising(&adv_params)</code>"]:::process
    L{"Adv Start Complete Event?<br><code>ESP_GAP_BLE_ADV_START_COMPLETE_EVT</code>"}:::decision
    M["<b>Advertising Active</b>"]:::success
    N["Stop Advertising<br><code>esp_ble_gap_stop_advertising()</code><br>(When needed)"]:::process
    O{"Adv Stop Complete Event?<br><code>ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT</code>"}:::decision
    P["<b>Advertising Stopped</b>"]:::stopped
    Q["Handle Error"]:::error
    R["Handle Error"]:::error

    A --> B --> C -->|Success| D
    D -->|Yes| E --> F -->|Success| G
    G -->|Yes, No Scan Rsp| J
    G -->|Yes, Use Scan Rsp| H -->|Success| I -->|Yes| J
    J --> K -->|Success| L
    L -->|Status OK| M --> N -->|Success| O
    O -->|Yes| P
    L -->|Status Fail| Q
    O -->|Status Fail| R

    classDef start fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    classDef stopped fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    classDef error fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B

Practical Examples

Prerequisites:

  • An ESP32 board (ESP32, ESP32-C3, ESP32-S3, ESP32-C6, ESP32-H2).
  • VS Code with the Espressif IDF Extension.
  • A BLE scanner application (e.g., “nRF Connect for Mobile,” “LightBlue Explorer”) on a smartphone or another ESP32 running a scanner.

Example 1: Basic Connectable Advertisement with Device Name and Flags

This example sets up a simple advertisement making the ESP32 discoverable and connectable.

1. Project Setup:

  • Create a new ESP-IDF project.
  • Run idf.py menuconfig:
    • Component config -> Bluetooth -> Enable [*] Bluetooth.
    • Component config -> Bluetooth -> Bluetooth Host -> Select Bluedroid.
    • Component config -> Bluetooth -> Bluetooth controller -> Controller Mode -> BLE Only.
    • Save and exit.

2. Code (main/advertising_main.c):

C
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h" // For GATTS app registration if creating a server
#include "esp_bt_main.h"
#include "esp_bt_defs.h"

#define ADV_TAG "ADV_EXAMPLE"
#define DEVICE_NAME "ESP32_Advertiser"

// Advertising parameters
static esp_ble_adv_params_t adv_params = {
    .adv_int_min        = 0x20, // Minimum advertising interval (20 ms * 0.625 ms/unit = 12.5 ms) -> N*0.625ms, so 0x20 = 32*0.625 = 20ms
    .adv_int_max        = 0x40, // Maximum advertising interval (40 ms * 0.625 ms/unit = 25 ms) -> N*0.625ms, so 0x40 = 64*0.625 = 40ms
    .adv_type           = ADV_TYPE_IND, // Connectable undirected advertising
    .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
    .channel_map        = ADV_CHNL_ALL,
    .adv_filter_policy  = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

// Advertising data configuration
static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp        = false, // This is advertising data, not scan response
    .include_name        = true,  // Include Complete Local Name
    .include_txpower     = true,  // Include TX Power Level
    .min_interval        = 0x0006, // Slave connection min interval, Time = min_interval * 1.25 msec, 6*1.25=7.5msec
    .max_interval        = 0x0010, // Slave connection max interval, Time = max_interval * 1.25 msec, 16*1.25=20msec
    .appearance          = 0x00,
    .manufacturer_len    = 0,
    .p_manufacturer_data = NULL,
    .service_data_len    = 0,
    .p_service_data      = NULL,
    .service_uuid_len    = 0, // Not advertising service UUIDs in this basic example
    .p_service_uuid      = NULL,
    .flag                = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// Scan response data configuration (optional, can be empty if not used)
static esp_ble_adv_data_t scan_rsp_data = {
    .set_scan_rsp        = true, // This is scan response data
    .include_name        = true,
    .include_txpower     = true,
    .appearance          = 0x00,
    .manufacturer_len    = 0,
    .p_manufacturer_data = NULL,
    // You could add service UUIDs or other data here
    .service_uuid_len    = 0,
    .p_service_uuid      = NULL,
    .flag                = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};


static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
    switch (event) {
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        ESP_LOGI(ADV_TAG, "Advertising data set complete");
        // If you have scan response data, configure it here or check a flag
        // For simplicity, we start advertising directly. In a more complex app,
        // you might set scan response data and then start advertising in its completion event.
        esp_ble_gap_start_advertising(&adv_params);
        break;
    case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
        ESP_LOGI(ADV_TAG, "Scan response data set complete");
        // If adv data was already set, you can start advertising here
        // esp_ble_gap_start_advertising(&adv_params);
        break;
    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
        if (param->adv_start_cmpl.status == ESP_BT_STATUS_SUCCESS) {
            ESP_LOGI(ADV_TAG, "Advertising started successfully");
        } else {
            ESP_LOGE(ADV_TAG, "Advertising start failed, error status = %x", param->adv_start_cmpl.status);
        }
        break;
    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
        if (param->adv_stop_cmpl.status == ESP_BT_STATUS_SUCCESS) {
            ESP_LOGI(ADV_TAG, "Advertising stopped successfully");
        } else {
            ESP_LOGE(ADV_TAG, "Advertising stop failed");
        }
        break;
    // Handle GATTS events if this device is also a server
    case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
         ESP_LOGI(ADV_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
                  param->update_conn_params.status,
                  param->update_conn_params.min_int,
                  param->update_conn_params.max_int,
                  param->update_conn_params.conn_int,
                  param->update_conn_params.latency,
                  param->update_conn_params.timeout);
        break;
    default:
        break;
    }
}

// Dummy GATTS callback for profile registration (needed if you plan to be connectable as a server)
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
    if (event == ESP_GATTS_REG_EVT) {
        if (param->reg.status == ESP_GATT_OK) {
            ESP_LOGI(ADV_TAG, "GATTS App registered, app_id %04x, gatts_if %d", param->reg.app_id, gatts_if);
            // Set device name AFTER GATTS registration if it's also a server
            esp_ble_gap_set_device_name(DEVICE_NAME);
            // Configure advertising data
            esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
            if (ret){
                ESP_LOGE(ADV_TAG, "config adv data failed, error code = %x", ret);
            }
            // Optionally configure scan response data
            // ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
            // if (ret){
            //    ESP_LOGE(ADV_TAG, "config scan rsp data failed, error code = %x", ret);
            // }
        } else {
            ESP_LOGE(ADV_TAG, "GATTS App registration failed, app_id %04x, status %d", param->reg.app_id, param->reg.status);
            return;
        }
    }
    // Handle other GATTS events (connect, disconnect, read, write etc.)
}


void app_main(void) {
    esp_err_t ret;

    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_CLASSIC_BT));

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret) {
        ESP_LOGE(ADV_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret) {
        ESP_LOGE(ADV_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bluedroid_init();
    if (ret) {
        ESP_LOGE(ADV_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
        return;
    }
    ret = esp_bluedroid_enable();
    if (ret) {
        ESP_LOGE(ADV_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
        return;
    }

    ret = esp_ble_gap_register_callback(gap_event_handler);
    if (ret) {
        ESP_LOGE(ADV_TAG, "gap register error, error code = %x", ret);
        return;
    }
    // Register GATTS interface (even if not defining services yet, needed for connectable advertising as a peripheral)
    ret = esp_ble_gatts_register_callback(gatts_event_handler);
    if (ret) {
        ESP_LOGE(ADV_TAG, "gatts register error, error code = %x", ret);
        return;
    }
    ret = esp_ble_gatts_app_register(0); // Dummy app_id for GATTS
    if (ret) {
        ESP_LOGE(ADV_TAG, "gatts app register error, error code = %x", ret);
        return;
    }
    
    // It's good practice to set security parameters if you plan to pair/bond
    // esp_ble_gap_set_security_param(...); // See Chapter 60

    ESP_LOGI(ADV_TAG, "Advertising Example Initialized");
    // Advertising will start after GATTS registration and adv_data config in this example's flow
}

3. Build, Flash, and Monitor:

  • idf.py build
  • idf.py flash monitor

4. Observe Output & Test with BLE Scanner:

  • Serial monitor: Logs for GATTS registration, then advertising data set, then advertising started.
  • BLE Scanner App:
    • You should see a device named “ESP32_Advertiser”.
    • Its advertising packet should contain Flags (General Discoverable, BR/EDR Not Supported) and TX Power Level.
    • You should be able to connect to it (though this example doesn’t define GATT services, so the connection might not show much beyond basic info).

Example 2: Advertising Service UUIDs and Manufacturer Data

This example shows how to include a list of service UUIDs and custom manufacturer data in the advertisement.

Modify adv_data and scan_rsp_data in advertising_main.c:

C
// ... (other definitions) ...

// Example 16-bit Service UUID (e.g., Battery Service)
#define GATT_UUID_BATTERY_SERVICE   0x180F
static uint16_t primary_service_uuid = GATT_UUID_BATTERY_SERVICE;

// Example Manufacturer Specific Data
// First 2 bytes are Company ID (e.g., Espressif is 0x02E5, but use a test ID for examples)
// https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/
#define COMPANY_ID_TEST             0xFFFF // Use a test ID or your assigned one
static uint8_t manuf_data[] = {
    (uint8_t)(COMPANY_ID_TEST & 0xFF), (uint8_t)(COMPANY_ID_TEST >> 8), // Company ID LSB, MSB
    0x12, 0x34, 0x56 // Your custom data
};

static esp_ble_adv_data_t adv_data_custom = {
    .set_scan_rsp        = false,
    .include_name        = false, // We'll add name manually or in scan response if space is tight
    .include_txpower     = false,
    .min_interval        = 0x0006,
    .max_interval        = 0x0010,
    .appearance          = 0x00,
    .manufacturer_len    = sizeof(manuf_data),
    .p_manufacturer_data = manuf_data,
    .service_data_len    = 0,
    .p_service_data      = NULL,
    .service_uuid_len    = sizeof(primary_service_uuid), // Length of all service UUIDs
    .p_service_uuid      = (uint8_t*)&primary_service_uuid, // Pointer to array of UUIDs
    .flag                = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// Scan response data can include the name if it didn't fit in adv_data
static esp_ble_adv_data_t scan_rsp_data_custom = {
    .set_scan_rsp        = true,
    .include_name        = true, // Put full name in scan response
    .include_txpower     = true,
    .appearance          = 0x00,
    .manufacturer_len    = 0, // Manufacturer data already in adv packet
    .p_manufacturer_data = NULL,
    .service_data_len    = 0,
    .p_service_data      = NULL,
    .service_uuid_len    = 0, // Service UUIDs already in adv packet
    .p_service_uuid      = NULL,
    .flag                = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// In gatts_event_handler, after ESP_GATTS_REG_EVT:
// esp_ble_gap_set_device_name(DEVICE_NAME); // Still set the name
// esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data_custom);
// // ... handle error ...
// // Then configure scan response:
// ret = esp_ble_gap_config_adv_data(&scan_rsp_data_custom);
// // ... handle error ...
// // Advertising will start in ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT or ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT
// // if scan response is not used.

Changes in gap_event_handler:

C
static bool adv_data_configured = false;
static bool scan_rsp_configured = false;

// ...
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
    ESP_LOGI(ADV_TAG, "Advertising data set complete");
    adv_data_configured = true;
    if (scan_rsp_configured) { // If scan response was already set (or not used)
        esp_ble_gap_start_advertising(&adv_params);
    }
    break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
    ESP_LOGI(ADV_TAG, "Scan response data set complete");
    scan_rsp_configured = true;
    if (adv_data_configured) { // If advertising data was already set
        esp_ble_gap_start_advertising(&adv_params);
    }
    break;
// ...

Build, Flash, Observe:

  • Scanner app should show the advertised service UUID (e.g., “Battery Service”) and the raw manufacturer data. If you perform an active scan (or the scanner does automatically), it should also retrieve the device name from the scan response.
  • Remember that the total size of advertising data and scan response data is limited (31 bytes each for legacy). You need to manage what goes where.

Variant Notes

  • ESP32-S2: This variant does not have Bluetooth hardware, so BLE advertising is not applicable.
  • ESP32 (Original): Supports legacy advertising as described. Some later silicon revisions (ECO3+) with updated ESP-IDF versions might have limited BLE 5.0 advertising extension features, but full support is more common in newer chips.
  • ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2: These variants have more robust support for BLE 5.0 and later features, including:
    • Extended Advertising (ADV_EXT_IND): Allows significantly larger advertising payloads (up to 1650 bytes through chaining), advertising on secondary (data) channels, and setting different PHYs (LE Coded for long range, LE 2M for higher speed) for advertising packets. ESP-IDF provides APIs like esp_ble_gap_config_ext_adv_data_raw, esp_ble_gap_config_ext_scan_rsp_data_raw, and esp_ble_gap_ext_adv_set_params, esp_ble_gap_ext_adv_start. These are more complex to configure.
    • Periodic Advertising: Allows for time-synchronized broadcasting of data to multiple devices without connections.
    • Multiple Advertising Sets: Allows a device to advertise different sets of data simultaneously (e.g., appear as two different virtual devices).
    • While the basic advertising APIs (esp_ble_gap_config_adv_data, esp_ble_gap_start_advertising) work on these chips for legacy advertising, leveraging their full BLE 5.x capabilities requires using the extended advertising APIs.
ESP32 Variant BLE Version Support (Typical) Legacy Advertising Extended Advertising (BLE 5.0) Periodic Advertising (BLE 5.0) Multiple Advertising Sets (BLE 5.0) Notes
ESP32 (Original) BLE 4.2 (some ECO3+ have BLE 5.0 features) Limited* Limited* Limited* *BLE 5.0 features depend on silicon revision and ESP-IDF version. Full support is better in newer chips.
ESP32-S2 N/A (No Bluetooth) Does not have Bluetooth hardware.
ESP32-S3 BLE 5.0 Robust support for BLE 5.0 features.
ESP32-C3 BLE 5.0 Good support for BLE 5.0 features.
ESP32-C6 BLE 5.0 / 5.3 (check datasheet) Supports BLE 5.0 and potentially later features. Also supports 802.15.4 (Zigbee/Thread).
ESP32-H2 BLE 5.0 / 5.2 (check datasheet) Primarily focused on 802.15.4, but with strong BLE 5.x support.

For this chapter, focusing on legacy advertising ensures broad compatibility and understanding of fundamentals. Extended advertising is a more advanced topic suitable for a later chapter or specialized applications.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Advertising Data Too Long esp_ble_gap_config_adv_data() returns an error (e.g., ESP_ERR_INVALID_SIZE), or advertising fails to start. Console may show BT_APPL: app_ble_load_adv_data_value, the payload len is error. Carefully sum the lengths of all AD structures (Length byte + Type byte + Data bytes). Max 31 bytes for legacy advertising data and 31 bytes for scan response data. Prioritize essential data for the advertising packet. Move less critical data to the scan response. For larger data needs, consider BLE 5.0 extended advertising if supported by your ESP32 variant and target devices.
Incorrect Flags Configuration Device may not be discoverable by some scanners, or scanners might misinterpret its capabilities (e.g., shown as non-connectable when it should be). Always include the Flags AD type (0x01). For a typical connectable BLE-only peripheral, use flags (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT). Ensure ESP_BLE_ADV_FLAG_GEN_DISC (General Discoverable Mode) is set for general discovery.
Advertising Not Starting esp_ble_gap_start_advertising() returns an error, or the ESP_GAP_BLE_ADV_START_COMPLETE_EVT indicates failure (e.g., status ESP_BT_STATUS_BUSY or ESP_BT_STATUS_NOMEM). No advertising packets seen on scanner. Ensure the correct sequence:
1. Initialize BT controller & Bluedroid.
2. Register GAP & GATTS callbacks.
3. Register GATTS app interface (esp_ble_gatts_app_register) – crucial for connectable advertising.
4. In ESP_GATTS_REG_EVT (after app registration): Set device name, configure advertising data, configure scan response data (optional).
5. In ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT (or ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT if used): Call esp_ble_gap_start_advertising(&adv_params).
Check logs for specific error codes.
Device Name Not Appearing Scanner shows “N/A”, “Unknown”, or MAC address instead of the configured device name. Ensure esp_ble_gap_set_device_name() is called successfully before esp_ble_gap_config_adv_data().
If using esp_ble_adv_data_t, set .include_name = true.
If the name plus other AD elements exceed 31 bytes in AdvData, the name might be truncated or omitted by the stack. Consider placing the name in the Scan Response data if AdvData is full.
Non-Connectable Advertisement When Connection is Expected Scanners see the device but the “Connect” option is greyed out or connection attempts fail immediately. Check adv_params.adv_type. It must be ADV_TYPE_IND for general connectable undirected advertising. ADV_TYPE_NONCONN_IND (non-connectable) or ADV_TYPE_SCAN_IND (scannable but not connectable) will prevent connections. Also, ensure GATTS app is registered.
Scan Response Data Not Appearing Active scan does not show additional data (e.g., full device name, extra service UUIDs) that was configured in scan response. Ensure esp_ble_gap_config_adv_data() was called with .set_scan_rsp = true for the scan response data structure.
Ensure the advertising type supports scan responses (e.g., ADV_TYPE_IND or ADV_TYPE_SCAN_IND). ADV_TYPE_NONCONN_IND does not support scan responses.
Verify the scanner is performing an active scan. Some tools might do passive scans by default.
Incorrect Advertising Interval Device discovered too slowly or too quickly, impacting power consumption or discovery speed. adv_int_min or adv_int_max values are incorrect. Values for adv_params.adv_int_min and adv_params.adv_int_max are N * 0.625 ms. Range: 0x0020 (32 * 0.625ms = 20ms) to 0x4000 (16384 * 0.625ms = 10.24s). Ensure adv_int_min <= adv_int_max. Common values: 0x20 (20ms) to 0xA0 (100ms).

Exercises

  1. Non-Connectable Beacon:
    • Modify Example 1 to create a non-connectable advertiser (ADV_TYPE_NONCONN_IND).
    • Include only Manufacturer Specific Data in the advertisement. Do not include device name or TX power.
    • Verify with a scanner that the device is visible but not connectable, and that it shows the manufacturer data.
  2. Scan Response Only:
    • Configure your ESP32 to use ADV_TYPE_SCAN_IND.
    • Put minimal data in the main advertising packet (e.g., just Flags).
    • Put the device name and a list of service UUIDs in the scan response packet.
    • Verify that a passive scan shows minimal info, while an active scan (if your scanner tool supports forcing it, or by default) retrieves the richer scan response data.
  3. Rotating Advertising Data (Conceptual):
    • Think about how you might make an ESP32 advertise one set of data (e.g., "Device A, Temp: 25C") for 10 seconds, then stop, change the advertising data (e.g., "Device A, Humidity: 45%"), and start advertising again for 10 seconds, and repeat.
    • Outline the steps and ESP-IDF functions/events involved. (You don't need to write full code, just the logic flow).
  4. Raw Advertising Data:
    • Construct a raw byte array for advertising data that includes:
      • Flags (General Discoverable, BR/EDR Not Supported).
      • Complete Local Name: "MyRawESP".
    • Use esp_ble_gap_config_adv_data_raw() to set this data.
    • Verify with a scanner. (Hint: You'll need to manually create the Length-Type-Value structures).

Summary

  • BLE advertising allows peripherals to be discovered and broadcast information.
  • Advertising occurs on 3 dedicated channels using different PDU types (e.g., ADV_IND for connectable, ADV_NONCONN_IND for broadcast).
  • Advertising data and scan response data (each up to 31 bytes for legacy) are composed of AD Structures (Length, Type, Value).
  • Common AD types include Flags, Local Name, Service UUIDs, and Manufacturer Specific Data.
  • esp_ble_adv_params_t configures interval, type, address, channel map, and filter policy.
  • ESP-IDF APIs like esp_ble_gap_set_device_name(), esp_ble_gap_config_adv_data(), and esp_ble_gap_start_advertising() manage the advertising process.
  • BLE 5.0 introduced Extended Advertising for larger payloads and more flexibility, supported by newer ESP32 variants using different APIs.

Further Reading

Leave a Comment

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

Scroll to Top