This workshop is a detailed description of how to use the advertising and scanning features of the TI BLE5-Stack. All tasks in this lab session are targeted to be completed within a 2-4 hour time frame. You should have an intermediate level of knowledge of the C programming language as well as experience with embedded software development to be able to complete the tasks.

The first section describes the GAP layer of the Bluetooth low energy protocol, and the subsequent tasks will explore the advertising and scanning and how to make some small changes to the BLE application.

The hardware kits supporting the TI BLE5-Stack are the CC26x2R1 and the CC1352. For the tasks in this module, a pair of evaluation kits (CC26x2R1 LaunchPads or CC1352 Launchpads) running TI applications (simple_central and simple_peripheral) is required.

It is recommended to read the Bluetooth® Low Energy Stack (BLE5-Stack) User's Guide alongside this lab for details and further information. Some references will also be made to this document as well as the Bluetooth Core Specification Version 5 which specifies how BLE devices should operate and communicate with other devices.


Software for desktop development



This module requires the following kits:


Getting started – Desktop

Install the Software

Run the SimpleLink SDK installer. This gives you the SDK with TI-RTOS included at the default location C:\ti\simplelink_ccxxx2_sdk_1_60_00_xx.

Modify/Load the Software

  • Load Board #1 with simple_central project that supports the scanning procedure. The simple_central project is found in the ble5stack examples folder, e.g. <SIMPLELINK_CC26X2R1_SDK_INSTALL_DIR>\examples\rtos\CC26X2R1_LAUNCHXL\ble5stack\simple_central\tirtos
  • Load Board #2 with simple_peripheral project that supports the advertising procedure. The simple_peripheral project is found in the ble5stack examples folder, e.g. <SIMPLELINK_CC26X2R1_SDK_INSTALL_DIR>\examples\rtos\CC26X2R1_LAUNCHXL\ble5stack\simple_peripheral\tirtos

Note: Be sure to build both the stack and app projects.

The following tasks will show how to modify the above projects to showcase scanning and advertising features in the respective projects. The user will have to modify these existing projects in the SimpleLink SDK to experiment with the tasks.



The Generic Access Profile (GAP) layer is a top layer in the host protocol stack that defines how BLE devices behave in standby and connecting states. The purpose of GAP is to ensure interoperability with other Bluetooth devices. GAP also describes discovery, link establishment and security procedures. The GAP APIs can be called from the application layer.

The following figure shows the GAP layer in relation to other layers in the software hierarchy:

The following GAP roles are defined in the Bluetooth core specification and described in gap.h:

GAP Role Description
BROADCASTER A device that only sends advertising events.
OBSERVER A device that only receives advertising events.
PERIPHERAL A device that accepts the establishment of an LE physical link using the connection establishment procedure.
CENTRAL A device that supports that initiates the establishment of a physical connection.

The GAP layer API gives very fine-grained control of the device behavior. To help using GAP, the BLE5-Stack contains four modules with higher lever APIs the application can use:

GAP Advertiser

The GAP Advertiser module lets you create advertisement sets. The application can then enable and disable the sets. This way, the application can quickly change the advertisement parameters and advertisement data. For example, an application can create an advertisement set for legacy advertising and one for long range advertising.

GAP Scanner

The GAP Scanner performs Extended Scanning and Legacy Scanning operations. It controls the Scanner GAP state.

GAP Initiator

The initiator module is used to initiate the connection to a peripheral device. Unlike the GAP Advertiser and GAP Scanner modules, GAP Initiator doesn’t have any callback function to call or to be called. Only a device initiated with the GAP Central Role can become an initiator.


The GAP Bond Manager is a configurable module that offloads most of the security mechanisms from the application.

In this Simplelink Academy lab, we will elaborate on the the gap_advertiser and the gap_scanner modules.


Which GAP Roles are connectionless (do not support the connected state)? Choose the correct answer(s).


Advertising Basics

Bluetooth devices can send advertising packets (PDUs) to broadcast data, or to allow other Bluetooth devices to find them and connect to them.

Bluetooth low energy uses 40 different RF channels. Three of these channels are called primary advertising channels, and are used for communication outside of connections. This includes:

  • advertisements
  • scan requests
  • scan responses
  • connection requests.

The remaining 37 channels are primarily used for data exchanges within BLE connections. In the following figure, this is illustrated. The primary advertising channels are placed to avoid the Wi-Fi channels since Wi-Fi transmissions cause noise on the BLE channels.

On advertisement channels, both advertisement, scan request and connection request packets are sent. So if there is too much noise on the advertisement channel, the devices will be unable to establish a connection. This is why the advertisement channels are placed the farthest away from the Wi-Fi channels. In addition, they are placed far away from each other.

With Bluetooth 5, new advertisement types are added that expand the possibilities of sending data in advertisement packets. The new advertisement packet types are listed in the Advertising Packets section. Bluetooth 5 opens for advertising on the 37 data channels. When advertisements are sent on the data channels, they are called secondary advertising channels.


Which of the following PDUs can not be sent on a primary advertisement channel?

Advertising Parameters

The following table summarizes the parameters that can be configured for advertising.

Advertising Parameter Description Range
Advertising Interval Time between the start of two consecutive advertising events 20ms to 10.24s
Advertising Types Different PDUs are sent for different types of advertising See table under Advertising Packets
Advertising Channels Legacy advertising packets are sent on three channels Different combinations of channels 37, 38 and 39.

The advertising channels for legacy advertising are channel 37 (2402 MHz), channel 38 (2426 MHz), and channel 39 (2480 MHz). The device can advertise on one, two or three of these. The following diagram shows an advertising event when advertising on all three channels.

Note that the same data (ADV_IND) is sent on all three channels. Since the packet is quite small (remember that the advertisement data is no more than 31 bytes), it takes less than 10 ms to send it. The device can be modified to advertise only on selected channels. Advertising on fewer channels will save power, however using more channels will increase the likelihood of a peer device receiving the packet. The user can configure the advertising interval based on the application use case. For example, if a door lock is advertising at a slower interval, it will take longer for the peer device to connect to the door lock which would adversely affect user experience.


Which advertising interval would be better suited for a door lock application powered by battery?

Which advertising interval would be better suited for a temperature sensor application powered by battery?

Advertising Packets

The advertising data consists up to 31 bytes of user configurable data. An additional 31 bytes can be sent as a scan response to a scan request. There are eight different types of advertising packets, listed in the table below. The last four (in bold type) have been added with Bluetooth 5. They are called extended advertisement PDUs. The packets that start with ADV_ are transmitted on the primary advertising channels. The packets that start with AUX_ are transmitted on the secondary advertisement channels.

Advertising PDU Description Max adv data len Allow scan req Allow connect
ADV_IND Used to send connectable undirected advertisement 31 bytes Yes Yes
ADV_DIRECT_IND Used to send connectable directed advertisement N/A No Yes
ADV_SCAN_IND Used to send scannable undirected advertisement 31 bytes Yes No
ADV_NONCONN_IND Used to send non-connectable undirected advertisement 31 bytes No No
ADV_EXT_IND Used to indicate that an advertisement will be sent on a secondary advertisement channel. No adv data allowed. No No
AUX_ADV_IND Used to send connectable directed advertisement on a secondary advertisement channel. 254 bytes Yes Yes
AUX_SYNC_IND Used for periodic advertisements on secondary advertisement channels. 254 bytes No No
AUX_CHAIN_IND Used to chain advertisement packets, allowing the advertisement data to extend beyond one packet. 254 bytes N/A N/A

For example, if you want to advertise on LE Coded PHY (long range), the device would use the ADV_EXT_IND to advertise on the primary advertising channels. The ADV_EXT_IND contain a pointer to a AUX_ADV_IND that would be transmitted on a secondary advertisement channel.

An other example: If the device wants to send a lot of advertisement data, it can advertise with ADV_EXT_IND as usual. The ADV_EXT_IND would contain a pointer to a AUX_ADV_IND. If there is a lot of adv data to send, the AUX_ADV_IND could be sent on the LE 2M PHY for speed. The AUX_ADV_IND can contain a pointer to a AUX_CHAIN_IND which contains the remaining adv data. In this case, you are not allowed to advertise in connectable or scannable mode.

The above table shows that the legacy advertising modes use the 31 payload bytes for advertisement data except directed advertising, which uses the 6-byte device address of the initiating device as adv data. Directed advertisements have an intended receiving device (scanner), while undirected advertisements do not have a specific intended receiver. In addition, all the legacy advertising types enable sending a scan response, except for the directed and non-connectable advertising.

It may be worth noting that for directed advertising, the advertisement duration can not be higher than 1.28 seconds.

Bluetooth 5 Advertisement PDUs

The rules for directed/undirected, scannable/non-scannable and connectable/non-connectable are a bit more complex for the extended advertising PDUs. The extended advertising PDUs contain an additional header where this information is stored.

The Common Extended Advertising Payload Format is given below. No fields of the extended header are mandatory.

Scannable and Connectable

The extended advertisement PDUs can not be both scannable and connectable. They can be either scannable or connectable (or neither). This is indicated in the AdvMode field of the packet.

Scannable ADV_EXT_IND and AUX_ADV_IND can not contain any advertising data (just the header).


ADV_EXT_IND can be sent on the LE 1M PHY, or a LE Coded PHY. The other extended advertising PDUs can be sent on any PHY.


Which type of legacy advertising does not receive (RX) any packets (is transmit (TX) only)? Choose the correct answer(s).

What information can the Common Extended Advertising Payload Format extended header contain? Choose the correct answer(s).

AUX_SYNC_IND is not supported in the Simplelink CC2640R2 SDK 1.50, and will not be further explored in this Simplelink Academy lab.

The following tasks will demonstrate advertising using the simple_peripheral project.

Advertising Task 1 – Change Advertising Parameters

In some cases we want to save power. One way to do this is by increasing the advertisement interval (advertise less frequently), and just advertise on one channel. In this task, we will configure the device to advertise on one channel every 500 ms using the simple_peripheral project. The default values of the advertising parameters for simple_peripheral are shown below:

 * Default parameters for legacy, scannable, connectable advertising
  .eventProps = GAP_ADV_PROP_CONNECTABLE | GAP_ADV_PROP_SCANNABLE | GAP_ADV_PROP_LEGACY, // Advertising Event Properties
  .primIntMin = 160,                                  // Minimum advertising interval (n * 0.625 ms)
  .primIntMax = 160,                                  // Maximum advertising interval (n * 0.625 ms)
  .primChanMap = GAP_ADV_CHAN_ALL,                    // Primary Channel Map
  .peerAddrType = ADDRTYPE_PUBLIC_OR_PUBLIC_ID,       // Peer Address Type (for Directed Advertising)
  .peerAddr = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa }, // Peer Address (for Directed Advertising)
  .filterPolicy = GAP_ADV_WL_POLICY_ANY_REQ,          // Advertising Filter Policy
  .txPower = GAP_ADV_TX_POWER_NO_PREFERENCE,          // Advertising Tx Power
  .primPhy = GAP_ADV_PRIM_PHY_1_MBPS,                 // The PHY on which primary advertising packets are transmitted.
  .secPhy = GAP_ADV_SEC_PHY_1_MBPS,                   // The PHY on which secondary advertising packets are transmitted.
  .sid = 0                                            // Advertising SID (set identity)

gap_advertiser.h – Default advertising parameters for simple_peripheral.

In simple_peripheral, the device is initialized before the advertisements are configured. The advertisement configuration is done in SimplePeripheral_processGapMessage, GAP_DEVICE_INIT_DONE_EVENT, look for the following code:

// Setup and start Advertising
// For more information, see the GAP section in the User's Guide:

// Temporary memory for advertising parameters for set #1. These will be copied
// by the GapAdv module
GapAdv_params_t advParamLegacy = GAPADV_PARAMS_LEGACY_SCANN_CONN;

// Create Advertisement set #1 and assign handle
status = GapAdv_create(&SimplePeripheral_advCallback, &advParamLegacy,

// Load advertising data for set #1 that is statically allocated by the app
status = GapAdv_loadByHandle(advHandleLegacy, GAP_ADV_DATA_TYPE_ADV,
                             sizeof(advertData), advertData);

// Load scan response data for set #1 that is statically allocated by the app
status = GapAdv_loadByHandle(advHandleLegacy, GAP_ADV_DATA_TYPE_SCAN_RSP,
                             sizeof(scanRspData), scanRspData);

// Set event mask for set #1
status = GapAdv_setEventMask(advHandleLegacy,
                             GAP_ADV_EVT_MASK_START_AFTER_ENABLE |
                             GAP_ADV_EVT_MASK_END_AFTER_DISABLE |

// Enable legacy advertising for set #1
status = GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);

simple_peripheral.c :: SimplePeripheral_processGapMessage() :: GAP_DEVICE_INIT_DONE_EVENT – Setup and start advertising.

As you can see, the advertisement parameters are loaded from GAPADV_PARAMS_LEGACY_SCANN_CONN (shown above). Then the advertisement set is created. The advertisement data and scan response data are loaded to the advertisement set. The event mask is set and lastly the advertisement set is enabled.

The default value of the advertising channel map is set to GAP_ADV_CHAN_ALL; advertise on all three channels. We will make a new GapAdv_params_t variable to hold our parameters:

  • Advertisement interval: 500 ms
  • Only advertise on channel 37

Initialize the new GapAdv_params_t variable before you set the values that are different from GAPADV_PARAMS_LEGACY_SCANN_CONN. You can find all necessary bit flags in gap_advertiser.h. E.g. the list of available defines for the channel map is given in the GapAdv_advChannels_t struct. Note that the advertisement interval is given in units of 0.625 ms, so you will have to calculate the correct value for this parameter.

Place the code to change the advertising parameters in SimplePeripheral_processGapMessage(), before the advertisement set is created:

  // Temporary memory for advertising parameters for set #1. These will be copied
  // by the GapAdv module
  GapAdv_params_t advParamLegacy = GAPADV_PARAMS_LEGACY_SCANN_CONN;

  // Set the advertisement interval to 500 ms. 500 ms / 0.625 ms = 800.
  advParamLegacy.primIntMin = 800;
  advParamLegacy.primIntMax = 800;
  // Set the advertisement channel map to only include channel 37.
  advParamLegacy.primChanMap = GAP_ADV_CHAN_37;

  // Create Advertisement set #1 and assign handle
  status = GapAdv_create(&SimplePeripheral_advCallback, &advParamLegacy,

simple_peripheral.c :: SimplePeripheral_processGapMessage() :: GAP_DEVICE_INIT_DONE_EVENT – Configuring advertising interval and advertising channel.

Advertising is started when GapAdv_enable() is called. You can see the corresponding advertising event in gap_advertising.h.

Advertising Event Mask Description GAP Event
GAP_ADV_EVT_MASK_SCAN_REQ_NOTI Sent when a scan request is received. GAP_EVT_SCAN_REQ_RECEIVED
GAP_ADV_EVT_MASK_SET_TERMINATED Sent when an advertisement set is terminated due to a connection establishment. GAP_EVT_ADV_SET_TERMINATED
GAP_ADV_EVT_MASK_START_AFTER_ENABLE Sent on the first advertisement after a GapAdv_enable(). GAP_EVT_ADV_START_AFTER_ENABLE
GAP_ADV_EVT_MASK_START Sent at the beginning of each advertisement. GAP_EVT_ADV_START
GAP_ADV_EVT_MASK_END_AFTER_DISABLE Sent after advertising stops due to a GapAdv_disable(). GAP_EVT_ADV_END_AFTER_DISABLE
GAP_ADV_EVT_MASK_ALL Mask to enable/disable all advertising events. N/A

Advertising Task 2 – Change Advertising Interval at Runtime

In this task we will advertise as previously, but when the device is discovered it will change the advertising interval to advertise more frequently. Imagine a BLE beacon that transmits data to passing phones. If no phones are around there is no reason to advertise often, since every advertisement consumes power. When the beacon receives a scan request it knows that someone is around and it makes sense to advertise more frequently. In the previous task we changed the advertising interval to 500 ms. We will keep this, but when the device is discovered for the first time it will start advertising with 100 ms interval.

In order to see if Simple Peripheral has received any scan requests, we have to add GAP_ADV_EVT_MASK_SCAN_REQ_NOTI to the GapAdv event mask. Replace the existing call to GapAdv_setEventMask() with the following:

  // Set event mask for set #1
  status = GapAdv_setEventMask(advHandleLegacy,
                               GAP_ADV_EVT_MASK_SCAN_REQ_NOTI |
                               GAP_ADV_EVT_MASK_START_AFTER_ENABLE |
                               GAP_ADV_EVT_MASK_END_AFTER_DISABLE |

simple_peripheral.c :: SimplePeripheral_processGapMessage() :: GAP_DEVICE_INIT_DONE_EVENT – Configuring the GapAdv event mask.

Whenever a scan request is received, an event will be sent to the GapAdv callback function (SimplePeripheral_advCallback()), which in turn will post it to the SimplePeripheral_processAdvEvent() function. We can use this event to set the advertisement interval, using the GapAdv_setParam() API. However, before the GapAdv_setParam() is used, advertising has to be disabled.

The scan request event is received in SimplePeripheral_processAdvEvent(), as GAP_EVT_SCAN_REQ_RECEIVED.

  • Inside this event we need to disable advertising (because GapAdv_setParam() can not be used while advertising is enabled.)
  • The event is posted every time the device receives a scan request. However we only want to update the advertisement interval once, so check whether this one is already updated.
      // Check whether the advertisement interval has been changed already.
      uint8_t advInterval = 0;
      GapAdv_getParam(advHandleLegacy, GAP_ADV_PARAM_PRIMARY_INTERVAL_MIN, &advInterval);

      if (advInterval != 160)

simple_peripheral.c :: SimplePeripheral_processAdvEvent() :: GAP_EVT_SCAN_REQ_RECEIVED – The first time the device receives a scan request, disable advertising.

When advertisement is disabled, GAP posts an event, GAP_EVT_ADV_END_AFTER_DISABLE. Inside this event we know that advertising is disabled, so we can use GapAdv_setParam().

  • Find the list of advertising parameters that can be changed with GapAdv_setParam() in gap_advertiser.h. Find the parameter you would like to change.
  • Again, the advertisement interval is given in units of 0.625 ms. Calculate the correct value for 100 ms advertisement interval.
  • Make sure the advertisement interval is only updated once.
  • Remember to re-enable advertising.

  • You can read about GapAdv_setParam() in gap_advertiser.h or the API reference guide, under GAP.

      Display_printf(dispHandle, SP_ROW_ADVSTATE, 0, "Adv Set %d Disabled",
                     *(uint8_t *)(pEventData->pBuf));
      // Check whether the advertisement interval has been changed
      uint32_t advInterval = 0;
      GapAdv_getParam(advHandleLegacy, GAP_ADV_PARAM_PRIMARY_INTERVAL_MIN, &advInterval);
      if (advInterval != 160)
        // Set the advertisement interval to 100 ms. 100 ms / 0.625 ms = 160.
        advInterval = 160;
        GapAdv_setParam(advHandleLegacy, GAP_ADV_PARAM_PRIMARY_INTERVAL_MIN, &advInterval);
        GapAdv_setParam(advHandleLegacy, GAP_ADV_PARAM_PRIMARY_INTERVAL_MAX, &advInterval);

        // Re-enable advertising.

simple_peripheral.c :: SimplePeripheral_processAdvEvent() :: GAP_EVT_ADV_END_AFTER_DISABLE – Configuring the advertising interval at runtime.

Advertising Task 3 – Change the Advertisement Data

In this task, we will change the advertisement data. The advertisement data is stored in the advertData variable:

// Advertisement data
static uint8_t advertData[] =
  0x02,   // length of this data

  // service UUID, to notify central devices what services are included
  // in this peripheral
  0x03,   // length of this data
  GAP_ADTYPE_16BIT_MORE,      // some of the UUID's, but not all

simple_peripheral.c – Advertisement data variable.

As you can see, the structure of the advertisement data is always as following:

  • Length of the following data
  • GAP AD type
  • Data.

For example the default advertisement data:

Length GAP_ADTYPE Explanation Data
0x03 GAP_ADTYPE_16BIT_MORE This flag contains some of the 16bit Service UUIDs of the device; more UUIDs are available. LO_UINT16(SIMPLEPROFILE_SERV_UUID), HI_UINT16(SIMPLEPROFILE_SERV_UUID)

The length always includes the length of the GAP_ADTYPE. Note that advertising data should follow a certain format described in the Bluetooth Core Spec. The different data types that can be used in the advertisement data are further explained in the Core Specification Supplement (CSSv7, Part A, Chapter 1) along with examples.

  • There are several ways to change the advertisement data. You can read about them in the The GAP chapter of the BLE5-Stack User's Guide.

Here, we will change the advertisement data when advertising is first enabled. This is shown in the flow diagram below:

When advertising is enabled, the application receives an event from GAP Advertiser, GAP_EVT_ADV_START_AFTER_ENABLE. This event is processes in SimplePeripheral_processAdvEvent(). We will update the advertisement data with the Update Advertising/Scan Response Data that is Used for Multiple Advertising Handles method described in The GAP chapter of the BLE5-Stack User's Guide:

        if (*(uint8_t *)(pEventData->pBuf) == advHandleLegacy)
            bStatus_t status = FAILURE;

            //Disable advertising and prepare the buffer.
            status = GapAdv_prepareLoadByBuffer(advertData, FALSE);

            // Change the advertisement data
            advertData[3] = 0x03; //Length of this data
            advertData[4] = GAP_ADTYPE_MANUFACTURER_SPECIFIC;
            advertData[5] = 0xAA;
            advertData[6] = 0xBB;

            // Set event mask -> No longer get event for "advertisement started".
            status = GapAdv_setEventMask(advHandleLegacy,
                                         GAP_ADV_EVT_MASK_END_AFTER_DISABLE |

            // Reload buffer and re-enable advertising
            status = GapAdv_loadByBuffer(sizeof advertData, advertData);
        Display_printf(dispHandle, SP_ROW_ADVSTATE, 0, "Adv Set %d Enabled",
                       *(uint8_t *)(pEventData->pBuf));

simple_peripheral.c :: SimplePeripheral_processAdvEvent() :: GAP_EVT_ADV_START_AFTER_ENABLE – Change advertisement data.


  • GapAdv_prepareLoadByBuffer() disables advertising for all advertisement sets who uses the advertData buffer.
  • GapAdv_loadByBuffer() loads the advData to the relevant advertisement sets and re-enables advertising.
  • By using the GAP_ADTYPE_MANUFACTURER_SPECIFIC data type, we can put whatever we want in the advertisement data. (In this case it's 0xAA and 0xBB but you can change it to something else if you want.)

Limited Advertising

Limited advertising can be used to conserve power by advertising for a limited amount of time. General advertisers will continue advertising indefinitely, while limited advertisers advertise for a given amount of times, e.g. 30 seconds, then stop. This is usually enough time for any devices trying to scan and/or connect to the device to discover it. You can also set a number of advertisement events. In this case, the device will advertise until the number of advertisement events is reached (or until something else disables advertising).

To use limited advertising in simple_peripheral.c, call GapAdv_enable() with the following parameters:

// To advertise for a limited amount of time, use the option GAP_ADV_ENABLE_OPTIONS_USE_DURATION
GapAdv_enableOptions_t myOption = GAP_ADV_ENABLE_OPTIONS_USE_DURATION;
// The time to advertise before stopping is given in units of 10 ms. The range is 10 ms - 655 540 ms
uint16 advDuration = 3000; //Advertise for 30 s (30 000 / 10 ms = 3000 )

GapAdv_enable(advHandleLegacy, myOption, advDuration);

Limited advertising in time

To limit the number of advertisement events, call GapAdv_enable() with the following parameters:

// To advertise for a limited amount of advertisement events, use the option GAP_ADV_ENABLE_OPTIONS_USE_MAX_EVENTS
GapAdv_enableOptions_t myOption = GAP_ADV_ENABLE_OPTIONS_USE_MAX_EVENTS;
// The maximum number of advertisements to send before stopping has the range 1-256
uint16 advDuration = 60;

GapAdv_enable(advHandleLegacy, myOption, advDuration);

Limited advertising in time

In addition, the flag in the advertisement data should be changed to GAP_ADTYPE_FLAGS_LIMITED. You can do this directly in the advertisement data buffer, or you can use the provided define:

// General discoverable mode: advertise indefinitely
// Limited discoverable mode advertises for a limited time or number of adv events

Configuring limited advertising flag

Limited advertising can be used to classify/filter devices. For example, devices that are scanning in limited discovery mode can only receive advertisement data from limited advertising devices. See the Bluetooth core specification for more information on the limited discovery procedure.

If you haven't loaded the simple_peripheral project into your device yet - do it now! Build the stack library project first, then build and flash the app project.


Scanning Basics

When not connected, Bluetooth low energy devices can either advertise their presence by transmitting advertisement packets or scan for nearby devices that are advertising. The process of scanning for devices is called device discovery. There are two types of scanning; active and passive. The difference is that an active scanner can send a scan request to request additional information from the advertiser, while a passive scanner can only receive data from advertising device. Note that the terms discovery and scanning may be used interchangeably. The figure below shows the sequence where the scanner sends a scan request to an advertiser during an advertising event.

When it comes to the timing of a scan, there are a few parameters you need to get familiar with. Each of these parameters has a range dictated by the Bluetooth core specification. The Time Inter Frame Space (T_IFS) is the time interval between two consecutive packets on the same channel index and is set to 150us by the BLE specification.

Scan Parameter Description Range
Scan Interval The interval between the start of two consecutive scan windows 10ms to 10.24s
Scan Window The duration in which the Link Layer scans on one channel 10ms to 10.24s
Scan Duration The duration in which the device stays in the scanning state 10ms to infinity

The following diagram visually shows the scanning parameters:

Note that the order of the scanning channels are not configurable. The device will scan on channel 37 (2402 MHz), then channel 38 (2426 MHz), and then channel 39 (2480 MHz) respectively and in that order on every scanning interval for the length of time defined by the scanning window.


Which type of scanning does not transmit (TX) any packets, only receives (RX) advertisement packets? Choose the correct answer(s).

After receiving which type of advertisement can an active scanner send a scan request? Choose the correct answer(s).

Scanning Packets

Scannable advertisement packets on secondary advertising channels can also incite a scan request and scan response. These are called AUX_SCAN_REQ and AUX_SCAN_RSP. All scanning related packets are summarized in the following table:

Scanning PDU Transmitting device Payload
SCAN_REQ Scanner Scanner's address + advertiser's address
SCAN_RSP Advertiser Advertiser's address + 0-31 bytes scan response data
AUX_SCAN_REQ Scanner Scanner's address + advertiser's address
AUX_SCAN_RSP Advertiser Header + 0-254 bytes data

Scan requests and scan responses are always sent on the same channel and PHY as the advertising packet that prompted them.


Which of the scan PDUs can be sent on the LE 2M PHY?

Scanning Task 1 – Change Scanning Parameters

In this task, we will scan continuously with the following scan parameters:

  • Scan interval: 150 milliseconds(ms)
  • Scan window: 150 milliseconds(ms)
  • Scan duration: 5 seconds(s)
  • Scan type: Passive
  • Scan PHY: LE 1M PHY (Legacy scanning)

These parameters are set three different ways:

  • First we will set the parameters that are PHY-specific
  • Then we will set the PHY-agnostic parameters
  • Lastly we will give the scan duration as part of the GapScan_enable() command.

Scroll down to SimpleCentral_processGapMsg() :: GAP_DEVICE_INIT_DONE_EVENT and change the parameters to match the specifications given above. The PHY-specific parameters must be set for PHY we're going to scan on (in this case the LE 1M PHY) and can be set with GapScan_setPhyParams(). The following parameters are PHY-specific:

  • Scan type (passive or active)
  • Scan interval
  • Scan window

The scan PHY must be set with GapScan_setParam().

      // Register callback to process Scanner events
      GapScan_registerCb(SimpleCentral_scanCb, NULL);

      // Set Scanner Event Mask

// === SOLUTION [Configuring scan parameters] ===

    // Set Scan PHY parameters
      uint8_t scanPhy = SCAN_PRIM_PHY_1M;
      // Scan interval (in 625us)
      uint16_t scanInterval = 240; // 240 * 225 us = 150 ms.
      // Scan window   (in 625us)
      uint16_t scanWindow = 240;
      GapScan_setPhyParams(scanPhy, SCAN_TYPE_PASSIVE,
                           scanInterval, scanWindow);

      // Set Scanning Primary PHY
      GapScan_setParam(SCAN_PARAM_PRIM_PHYS, &temp8);

// ==== END SOLUTION ====

      // Set Advertising report fields to keep
      temp16 = SC_ADV_RPT_FIELDS;
      GapScan_setParam(SCAN_PARAM_RPT_FIELDS, &temp16);
      // Set LL Duplicate Filter
      temp8 = SCAN_FLT_DUP_ENABLE;
      GapScan_setParam(SCAN_PARAM_FLT_DUP, &temp8);

simple_central.c :: SimpleCentral_processGapMsg() :: GAP_DEVICE_INIT_DONE_EVENT – Configuring scan parameters

Next, scroll down to SimpleCentral_doDiscoverDevices(). Change GapScan_enable() so that the scan duration is 5 seconds. This command will start device discovery when called.

  // Reset number of scan results to 0 before starting scan
  numScanRes = 0;
  // Scan duration in 10 ms
  uint16_t scanDuation = 500; // 500 * 10 ms = 5 s.
  GapScan_enable(0, scanDuation, 0);

simple_central.c :: SimpleCentral_doDiscoverDevices() – Enable scanning for 5 seconds.

The above parameters are used to set up how the device will behave during the discovery process.

  • In gap_scanner.h you can find a full description on all parameters that can be configured for the GAP Scanner module.

Two Kinds of Discovery

Note that this discovery procedure to discover nearby devices is not to be confused with the characteristic discovery procedure which is can be called after a connection is established.

If you haven't loaded the simple_central project into your device yet – now is the time! Build the stack library project first, then build and flash the app project.

Scanning Task 2 – Print Scanning Results

Every time the device receives an advertisement or scan response packet, the application receives a GAP_EVT_ADV_REPORT event from the GAP layer. This event is caught in SimpleCentral_scanCb(), then sent it to SimpleCentral_processAppMsg().

The device info is encapsulated in the GapScan_Evt_AdvRpt_t structure:

typedef struct {
  uint8_t  evtType;                 // Bits 0 to 4 indicate connectable, scannable, directed, scan rsp, and legacy respectively
  GAP_Addr_Types_t addrType;        // Public, random, public ID, random ID, or anonymous
  uint8_t  addr[B_ADDR_LEN];        // Address of the advertising device
  GapScan_ScannedPhy_t primPhy;     // PHY of the primary advertising channel
  GapScan_ScannedPhy_t secPhy;      // PHY of the secondary advertising channel
  uint8_t  advSid;                  // SID(0x00-0x0f) of the advertising PDU
  int8_t   txPower;                 // -127 dBm <= TX power <= 126 dBm
  int8_t   rssi;                    // -127 dBm <= RSSI <= 20 dBm
  GAP_Addr_Types_t directAddrType;  // Type of TargetA address in the directed advertising PDU
  uint8_t  directAddr[B_ADDR_LEN];  // TargetA address
  uint16_t periodicAdvInt;          // Periodic advertising interval. 0 means no periodic advertising.
  uint16_t dataLen;                 // Length of the data
  uint8_t  *pData;                  // Pointer to advertising or scan response data
} GapScan_Evt_AdvRpt_t;

gap_scanner.h :: GapScan_Evt_AdvRpt_t

The following code snippets demonstrates printing the received advertising data. Copy-paste it into simple_central.c. (Util_convertBdAddr2Str() is defined in util.h.)

char *Util_convertBytes2Str(uint8_t *pData, uint8_t length)
  uint8_t     charCnt;
  char        hex[] = "0123456789ABCDEF";
  static char str[(3*31)+1];
  char        *pStr = str;

  for (charCnt = 0; charCnt < length; charCnt++)
    *pStr++ = hex[*pData >> 4];
    *pStr++ = hex[*pData++ & 0x0F];
    *pStr++ = ':';
  str[(3*length)-1] = '\0';
  pStr = NULL;

  return str;

simple_central.c – Add a function to convert bytes to a string.

      GapScan_Evt_AdvRpt_t* pAdvRpt = (GapScan_Evt_AdvRpt_t*) (pMsg->pData);

      // === SOLUTION [Print scan results] ===
      //Print scan response data or advertising data
      if(pAdvRpt->evtType == ADV_RPT_EVT_TYPE_SCAN_RSP)
          Display_print1(dispHandle, 4, 0, "ScanResponseAddr: %s", Util_convertBdAddr2Str(pAdvRpt->addr));
          Display_print1(dispHandle, 5, 0, "ScanResponseData: %s", Util_convertBytes2Str(pAdvRpt->pData, pAdvRpt->dataLen));
          Display_print2(dispHandle, 6, 0, "Advertising Addr: %s RSSI: %d", Util_convertBdAddr2Str(pAdvRpt->addr), pAdvRpt->rssi);
          Display_print1(dispHandle, 7, 0, "Advertising Data: %s", Util_convertBytes2Str(pAdvRpt->pData, pAdvRpt->dataLen));
      // === END SOLUTION [Print scan results] ===


simple_central.c :: SimpleCentral_processAppMsg() :: SC_EVT_ADV_REPORT – Print the received advertisement and scan response packets.

Re-build and flash the project. You may notice that you only receive advertisement packets and no scan response packets. Why?

Scanning Task 3 – Scan Indefinitely

When the application needs to scan for nearby devices at all times, i.e. always be in the scanning state, set DEFAULT_SCAN_DURATION to 0. This will set the device in continuous scanning. Alternatively, we can set the device to scan continuously for a while, then take a break periodically. We will do this by setting the scan duration and scan period as follows:

  • Scan duration: 1 s
  • Scan period: 5 s

This will make the device scan for 1 second, then pause for 4 seconds, before scanning for 1 second etc.

// === SOLUTION [Scan indefinitely] ===
  //Scan period. Ignored if duration is zero. 1.28 sec unit
  uint16_t scanPeriod = 4; // 4 * 1.28 s = 5.12 s.
  //Scan duration. 10 ms unit
  uint16_t scanDuration = 100; //100 * 10 ms = 1 s.
  GapScan_enable(scanPeriod, scanDuration, 0);

// ==== END SOLUTION ====

simple_central.c :: SimpleCentral_doDiscoverDevices() – Configuring scan duration and scan period to scan indefinitely.

Note that even in indefinite scanning, the number of scanned results will still be limited by DEFAULT_MAX_SCAN_RES. In this case, DEFAULT_MAX_SCAN_RES is set to 0. When zero, this parameter is ignored.


Regardless of the number of devices the scanner has discovered, the application will return "0 Devices Found" when discovery is ended.

Filter Duplicate Advertisers

For some applications, processing multiple advertisements by the same peer device can be useful. E.g. when collecting information from a beacon that updates its advertisement data frequently. By default, the scanning device will filter advertising data. To turn off the filter for duplicate advertisers, use the following API:

// ...
      // Set Advertising report fields to keep
      temp16 = SC_ADV_RPT_FIELDS;
      GapScan_setParam(SCAN_PARAM_RPT_FIELDS, &temp16);
      // Set LL Duplicate Filter
      GapScan_FilterDuplicate_t scanFilter = SCAN_FLT_DUP_DISABLE;
      GapScan_setParam(SCAN_PARAM_FLT_DUP, &scanFilter);
// ...

simple_central.c :: SimpleCentral_processGapMsg() :: GAP_DEVICE_INIT_DONE_EVENT – Turn off the duplicate scanning filter.

This will enable the controller to receive all the advertisement packets it sees over the air, and send it to the application for further processing.

In order to see all the received packets as a log, disable the ANSI interface for the UART driver. Do this by opening the Project -> Properties -> Build -> ARM Compiler -> Predefined Symbols and set BOARD_DISPLAY_USE_UART_ANSI=0.

Miscellaneous Guidelines

Choosing advertising interval: Fast or slow?

The advertising interval should be set based on use case. Both fast and slow advertising have pros and cons:

  • Slow advertising: higher advertising interval, lower power consumption, low probability of short discovery time.
  • Fast advertising: lower advertising interval, higher power consumption, high probability of short discovery time.

Optimizing Scanning: How to get data fast?

In a time-constrained application, when the user needs to receive the data as fast as possible, make sure that the scan window is more than the advertising interval + 10ms to guarantee discovery. (The 10 ms extra account for the 0 ms to 10 ms of pseudo-random delay in between each advertising event, assuming no interference.) Following this rule will increase the chance of receiving the advertising packet on the first scan.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.