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) to0x4000
(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
:
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.
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 byesp_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
orESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT
.
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
.
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
.
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 afteradv_data
configuration).
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
.
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:
- Initialize Bluetooth controller and Bluedroid stack.
- Register GAP callback (
esp_ble_gap_register_callback
). - Set device name (
esp_ble_gap_set_device_name
). - Configure advertising data (
esp_ble_gap_config_adv_data
). Wait forESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT
. - (Optional) Configure scan response data (
esp_ble_gap_config_adv_data
withset_scan_rsp = true
). Wait forESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT
. - Configure advertising parameters using
esp_ble_adv_params_t
structure. - Start advertising (
esp_ble_gap_start_advertising
with the configured parameters). Wait forESP_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
-> SelectBluedroid
.Component config
->Bluetooth
->Bluetooth controller
->Controller Mode
->BLE Only
.- Save and exit.
2. Code (main/advertising_main.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
:
// ... (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
:
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 likeesp_ble_gap_config_ext_adv_data_raw
,esp_ble_gap_config_ext_scan_rsp_data_raw
, andesp_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.
- Extended Advertising (
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
- 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.
- Modify Example 1 to create a non-connectable advertiser (
- 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.
- Configure your ESP32 to use
- 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).
- 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).
- Construct a raw byte array for advertising data that includes:
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()
, andesp_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
- ESP-IDF API Reference -
esp_gap_ble_api.h
: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/esp_gap_ble.html - Bluetooth Core Specification: Volume 3, Part C (Generic Access Profile - Advertising modes) and Volume 6, Part B (Link Layer - Advertising PDU details): https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/generic-access-profile.html
- Bluetooth SIG - Assigned Numbers Document: For definitions of AD Types, Company IDs, etc. : https://www.bluetooth.com/specifications/assigned-numbers/
- ESP-IDF
ble_adv
example:examples/bluetooth/bluedroid/ble/ble_adv
in your ESP-IDF installation demonstrates various advertising configurations including extended advertising.