Note

As of 2021, this document follows the Appropriate Language nomenclature directive from Bluetooth SIG. The SDK project names, APIs and code are not yet ported and use the old nomenclature. For details on how to correlate between the two, please open the PDF document Appropriate Language Mapping Tables.

Introduction

This workshop is a detailed description of how to use the advertising and scanning features of the TI BLE-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 advertising and scanning functions, and the subsequent tasks will explore the wireless BLE interface and how to make some small changes to the BLE application.

For the tasks in this module, a pair of evaluation kits (CC2640R2 LaunchPad) running a TI application (simple_central or simple_peripheral) is required.

It is recommended to read the TI BLE-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 Spec V4.2] which specifies how BLE devices should operate and communicate with other devices.

Prerequisites

Hardware

For this lab, you need two CC2640R2 Bluetooth-enabled development boards:

Software

Getting started – Desktop

Install the software

  1. Run the SimpleLink CC2640R2 SDK installer: simplelink_cc2640r2_sdk_xx_xx_xx_xx.exe. This gives you the SDK with TI-RTOS included at the default location

C:\ti\simplelink_cc2640r2_sdk_x_xx_xx_xx.

Modify/Load the software

Connecting multiple boards at the same time

As mentioned before, you will use two boards for this lab. You can assign a specific LaunchPad to each CCS project. You can find instructions to do that here.

  • Load Board #1 with simple_central project that supports the scanning procedure found here: <SIMPLELINK_CC2640R2_SDK_INSTALL_DIR>\examples\rtos\CC2640R2_LAUNCHXL\blestack\simple_central\tirtos
  • Load Board #2 with simple_peripheral project that supports the advertising procedure found here: <SIMPLELINK_CC2640R2_SDK_INSTALL_DIR>\examples\rtos\CC2640R2_LAUNCHXL\blestack\simple_peripheral\tirtos

Note: Be sure to build/load 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 CC2640R2 SDK to experiment with the tasks.

Background

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

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

The following GAP roles are defined in the Bluetooth Core Specification V4.2 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 the Central role initiates the establishment of a physical connection.

The GAP layer API gives very fine-grained control of the device behavior. To remove some complexity, the sample applications use an interface called GAPRole which provide a subset of streamlined APIs for each GAP role such as Central, Observer and Peripheral. Usually a project implements one of these profile roles, however multiple GAP roles may be supported at the same time. A GAP profile is initialized by passing in the desired GAP role(s) as a parameter to the GAP_DeviceInit() function.

The GAPRole APIs provide high level functions while the GAP APIs are used to configure the low level states that the BLE devices operate in. The project provides GAPRole interface in a file under the project's Profiles folder along with helper functions and parameters. For example, for a peripheral project, peripheral.c provides APIs for the application to accept establishment of connections and advertising procedures. The GAPRole also passes events to the application via callbacks.

Which GAP Roles are connectionless (do not support the connected state)?

Choose the correct answer(s).

Advertising

Advertising Basics

Bluetooth devices send advertising packets (PDUs) to broadcast data, and to allow other devices (scanners) to find and connect to them. 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 four different types of advertising packets listed in the table below.

Advertising PDU Description Max Adv Data Len Max Scan Res Len Allow Connect
ADV_IND Used to send connectable undirected advertisement 31 bytes 31 bytes Yes
ADV_DIRECT_IND Used to send connectable directed advertisement N/A N/A Yes
ADV_SCAN_IND Used to send scannable undirected advertisement 31 bytes 31 bytes No
ADV_NONCONN_IND Used to send non-connectable undirected advertisement 31 bytes N/A No

The above table shows that all the advertising modes use the 31 payload bytes for advertisement data except directed advertising, which uses the 6-byte device address of the initiating device. Directed advertisements have an intended receiving device (scanner), while undirected advertisements do not have a specific intended receiver. In addition, all the advertising types enable sending a scan response, except for the directed and non-connectable advertising. See the Bluetooth Core Specification V4.2 Vol. 6, Part B, Section 4.4 for more information about the different unconnected states.

Which type of advertising does not receive (RX) any packets (is transmit (TX) only)?

Choose the correct answer(s).

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 20 ms to 10.24 s
Advertising Types Different PDUs are sent for different types of advertising Connectable undirected, connectable directed, scannable undirected, non-connectable
Advertising Channels Advertising packets are sent on three channels Different combinations of channels 37, 38 and 39.

The device advertises on three channels. The advertising channels are channel 37 (2402 MHz), channel 38 (2426 MHz), and channel 39 (2480 MHz). These channels are selected to minimize interference from Wi-Fi channels. 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. 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 following tasks will demonstrate advertising using the simple_peripheral_cc2640r2lp_app project.

Note

You can observe changes to advertising and scanning parameters in different ways, however, one of the simplest methods is to configure GPIOs so that you can see the RF output of your devices using a logic analyzer. Refer to the Debugging → Debugging RF Output section in the BLE Stack User's Guide that shows you how to configure GPIOs for RF output.

Advertising Task 1 – Change Advertising Parameters

In this task, we will configure the device to advertise on one channel every 200 ms using the simple_peripheral project. 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.

The following API will set up the GAPRole to advertise on channel 37 in simple_peripheral.c:


/*********************************************************************
 * CONSTANTS
 */
// === SOLUTION [Add/change GAP advertising params] ===
// Advertising interval when device is discoverable (units of 625us, 320=200ms)
#define DEFAULT_ADVERTISING_INTERVAL          320
#define DEFAULT_ADVERTISING_CHANNEL_MAP       GAP_ADVCHAN_37
// ==== END SOLUTION ====

// ...

/*********************************************************************
 * LOCAL VARIABLES
 */
 // === SOLUTION [Disable Advertising before Setting Param Values] ===
// Device will not be advertising until after advertising params are set
uint8_t initialAdvertEnable = FALSE;
// ==== END SOLUTION ====

// ...

static void SimplePeripheral_init(void)
{
    ICall_registerApp(&selfEntity, &syncEvent);

// === SOLUTION [Disable Advertising before Setting Param Values] ===

    GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t),
                               &initialAdvertEnable);
// ==== END SOLUTION ====

// ...

// Set advertising interval.
  {
    uint16_t advInt = DEFAULT_ADVERTISING_INTERVAL;

    GAP_SetParamValue(TGAP_LIM_DISC_ADV_INT_MIN, advInt);
    GAP_SetParamValue(TGAP_LIM_DISC_ADV_INT_MAX, advInt);
    GAP_SetParamValue(TGAP_GEN_DISC_ADV_INT_MIN, advInt);
    GAP_SetParamValue(TGAP_GEN_DISC_ADV_INT_MAX, advInt);
  }

// ...

  // === SOLUTION [Configure advertising channel map and enable advertising] ===
  // Configure advertising channel map.
  uint8_t advChannelMap = DEFAULT_ADVERTISING_CHANNEL_MAP;
  GAPRole_SetParameter(GAPROLE_ADV_CHANNEL_MAP, sizeof(uint8_t), &advChannelMap);

  // Enable advertising now that new parameter values are set
  uint8_t initialAdvertEnable = TRUE;

  //Enable advertising again after advertising interval is set.
  //Remove this line from where Peripheral GAPRole Parameters are set.
  GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t),
                       &initialAdvertEnable);

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

// ...

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

For Non-Connectable advertising during a connection the interval is set through the TGAP_FAST_INTERVAL_2_INT_MIN parameter:

uint16_t advInt = DEFAULT_ADVERTISING_INTERVAL;
GAP_SetParamValue(TGAP_FAST_INTERVAL_2_INT_MIN, advInt);
GAP_SetParamValue(TGAP_FAST_INTERVAL_2_INT_MAX, advInt);

The default value of the advertising channel map is set to GAP_ADVCHAN_ALL: advertise on all three channels. 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 is best suited for a door lock application powered by battery?

Choose the correct answer.

Which advertising interval is best suited for a temperature sensor application powered by battery?

Choose the correct answer.

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 about 180 s, enough time for any devices trying to scan and/or connect. To use limited advertising, in simple_peripheral.c, set DEFAULT_DISCOVERABLE_MODE to GAP_ADTYPE_FLAGS_LIMITED inside the advertData array. To do this, modify the following define. This will configure the device to use limited advertising mode:

// Limited discoverable mode advertises for 180 seconds, and then stops
// General discoverable mode advertises indefinitely
#define DEFAULT_DISCOVERABLE_MODE             GAP_ADTYPE_FLAGS_LIMITED

Enable limited advertising

You can control how long the device will pause (not send any advertisements) with the GAPROLE_ADVERT_OFF_TIME GAPRole parameter like this:

uint16_t advertOffTime = 30000;  // 30 seconds in milliseconds units.
GAPRole_SetParameter(GAPROLE_ADVERT_OFF_TIME, sizeof(uint16_t),
                    &advertOffTime);

Limited advertising off time

You control the active duration (default 180 seconds) in limited discoverable mode where the device is advertising with the TGAP_LIM_ADV_TIMEOUT GAP parameter like this:

uint16_t limitedActiveDuration = 30;  // 30 Seconds
bStatus_t status = GAP_SetParamValue(TGAP_LIM_ADV_TIMEOUT, limitedActiveDuration);

Limited advertising active duration

Limited advertising can be used to further 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 V4.2 Vol. 3, Part C, Section 9.2.5 for more information on the limited discovery procedure.

The parameters set in SimplePeripheral_init() are used when the advertising procedure is started. Advertising can be started many ways by the application. Look at peripheral.c under the Profiles folder to see all the ways advertising can be started. One way to start advertising is used initially in simple_peripheral.c:

uint8_t initialAdvertEnable = TRUE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initialAdvertEnable);

Starting the advertising procedure

Changing Advertising Interval at Runtime

To change advertising interval when advertising is enabled, you need to stop and re-start advertising again for the new changes to take effect. Please refer to Advertising Task 3 for an example on how to correctly change advertising interval runtime.

Advertising Task 2 – Change the Advertising Data

In this task, we will change the advertisement data periodically (in this case, every 5 s). In order to make sure our advertising data successfully updates, we will toggle an LED.

Caution

If you previously configured GPIOs to show RF output, you should comment those lines out for this task, or change the GPIOs the RF output has been mapped to.

First, add the following code to the Local Variables section of simple_peripheral.c:

// GAP GATT Attributes
static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "Simple Peripheral";

// === SOLUTION [Add LED Support] ===
/* Pin driver handles */
static PIN_Handle ledPinHandle;

/* Global memory storage for a PIN_Config table */
static PIN_State ledPinState;

/*
 * Initial LED pin configuration table
 *   - LEDs Board_LED0 is off.
 */
PIN_Config ledPinTable[] = {
  Board_LED0 | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
  PIN_TERMINATE
};
// ==== END SOLUTION ====

/*********************************************************************

simple_peripheral.c :: Local Variables – Add LED Configuration

To use the LED, the PIN driver must be opened. This can be done at the end of SimplePeripheral_init():

#endif // !defined (USE_LL_CONN_PARAM_UPDATE)

// === SOLUTION [Add Open PIN Driver] ===
  ledPinHandle = PIN_open(&ledPinState, ledPinTable);
// ==== END SOLUTION ====

  Display_print0(dispHandle, 0, 0, "BLE Peripheral");

simple_peripheral.c :: SimplePeripheral_init() – Open the PIN Driver

Next, we will start the clock for our periodic event. simple_peripheral already has an event that is called periodically - the SBP_PERIODIC_EVENT. In the default simple_peripheral.c, the periodic clock is first started from the GAPROLE_CONNECTED state of SimplePeripheral_processStateChangeEvt(gaprole_States_t newState). Since we are not expecting to form a connection in this case, we will move the call to Util_startClock(&periodicClock); from case GAPROLE_CONNECTED: to the end of SimplePeripheral_init:

#endif // !defined (USE_LL_CONN_PARAM_UPDATE)

  ledPinHandle = PIN_open(&ledPinState, ledPinTable);

// === SOLUTION [Start Periodic Clock] ===
  Util_startClock(&periodicClock);
// ==== END SOLUTION ====

  Display_print0(dispHandle, 0, 0, "BLE Peripheral");

simple_peripheral.c :: SimplePeripheral_init() – Start Periodic Clock

The advertisement data can be changed at runtime by changing the contents of the advertData[] variable, and updating it by using GAPRole_SetParameter(). To make the data easier to work with, we will define a couple symbols in the Constants section of simple_peripheral.c:

// Bitwise OR of all events to pend on
#define SBP_ALL_EVENTS                        (SBP_ICALL_EVT        | \
                                               SBP_QUEUE_EVT        | \
                                               SBP_PERIODIC_EVT)

// === SOLUTION [Add Advertising Defines] ===
#define ADV_MAX_LEN 31
#define ADVDATA_MANUF_DATA_IDX 5
// ==== END SOLUTION ====

/*********************************************************************

simple_peripheral.c :: Constants – Adding advertising defines

Additionally, we must set the size of advertData[] to ADV_MAX_LEN. Make the following change to the variable declaration:

// Advertisement data (max size = 31 bytes, though this is
// best kept short to conserve power while advertising)
static uint8_t advertData[ADV_MAX_LEN] =
{
//...

simple_peripheral.c :: – Set advertData to max size (31 bytes)

Finally, we will add the following code to the SBP_PERIODIC_EVENT case in SimplePeripheral_taskFxn():

  if (events & SBP_PERIODIC_EVT)
  {

    Util_startClock(&periodicClock);

// === SOLUTION [Change advertising data] ===
    uint8_t i = 0;
    bStatus_t status = 0;

    static uint8_t advData = 0;

    advertData[i++] = 0x02;   // length of this data
    advertData[i++] = GAP_ADTYPE_FLAGS;
    advertData[i++] = DEFAULT_DISCOVERABLE_MODE | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED;

    //Setup up custom user data initially
    advertData[i++] = ADV_MAX_LEN - i - 1; // length of this data
    advertData[i++] = GAP_ADTYPE_MANUFACTURER_SPECIFIC; // length of this data

    //populate the remaining with custom ADV data (i.e. 0x00,0x01, 0x02, etc.)
    for(i = ADVDATA_MANUF_DATA_IDX; i < ADV_MAX_LEN; i++)
    {
      advertData[i] = advData++;
    }
    status = GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData);

    if(status == SUCCESS)
    {
      //Toggle LED to indicate successful advertising data change
      PIN_setOutputValue(ledPinHandle, Board_LED0, PIN_getOutputValue(Board_LED0)^1);
    }
// ==== END SOLUTION ====

// ...

simple_peripheral.c :: SimplePeripheral_taskFxn() – Changing advertising data

advertData[] is defined in the beginning of simple_peripheral.c, but it can be called again to change the advertising data at runtime.

Note that advertising data should follow a certain format described in the Bluetooth Core Specification V4.2 Vol. 3, Part C, Ch. 11. The different data types that can be used in the advertisement data are further explained in the Core Specification Supplement (CSSv7) along with examples.

Advertising Task 3 – Change Advertising Interval at Runtime

The advertising parameters are only sent to the link layer when GAP_MakeDiscoverable() is called in GAP role peripheral.c, multi.c or broadcaster.c after the advertising has been enabled through the GAP Role parameter GAPROLE_ADVERT_ENABLED. This means that, for a new advertising interval to be set correctly, the advertising must first be stopped/disabled. Then the new parameters must be set in one of the GAP Role state change event from the table below. After boot, if you do not enable initial advertising you can set the parameters in GAPROLE_STARTED/GAP_DEVICE_INIT_DONE_EVENT. If you first have to disable advertising, then wait until the GAPROLE_WAITING/GAP_END_DISCOVERABLE_DONE_EVENT events happen before you change advertising interval and re-enable advertising.

Profile Role Init Done (After Boot) Advertising Idle
Peripheral GAPROLE_STARTED GAPROLE_WAITING
Broadcaster GAPROLE_STARTED GAPROLE_WAITING
Multi GAP_DEVICE_INIT_DONE_EVENT GAP_END_DISCOVERABLE_DONE_EVENT

Now let's implement this in simple_peripheral and change the advertising interval periodically. You will utilize the SBP_PERIODIC_EVT from
Advertising Task 2, except adjust the duration to 10 s to make it easier to observe the change in advertising interval. Implement the code changes below and observe the advertising interval with a packet sniffer of you choice.

// How often to perform periodic event (in msec)
#define SBP_PERIODIC_EVT_PERIOD    10000  // 10 seconds.

simple_peripheral.c :: CONSTANTS – Adjust period

if (events & SBP_PERIODIC_EVT)
{
    Util_startClock(&periodicClock);
    // ...
    // === SOLUTION [Disable advertising in SBP_PERIODIC_EVT] ===
    // Disable advertising
    uint8_t advEnabled = 0;
    GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t),
                        &advEnabled);
    // ==== END SOLUTION ====

simple_peripheral.c :: SimplePeripheral_taskFxn() – Disable advertising.

    case GAPROLE_WAITING:
      Util_stopClock(&periodicClock);
      attRsp_freeAttRsp(bleNotConnected);

      Display_print0(dispHandle, 2, 0, "Disconnected");

      // === SOLUTION [Change advertising interval] ===
      static uint8_t evenOdd = 1;
      uint16_t advInt;
      if (evenOdd) {
          advInt = 1600; // 1 second interval
      } else {
          advInt = 240; // 160 milliseconds interval
      }
      evenOdd ^= 1; // Toggle evenOdd

      GAP_SetParamValue(TGAP_LIM_DISC_ADV_INT_MIN, advInt);
      GAP_SetParamValue(TGAP_LIM_DISC_ADV_INT_MAX, advInt);
      GAP_SetParamValue(TGAP_GEN_DISC_ADV_INT_MIN, advInt);
      GAP_SetParamValue(TGAP_GEN_DISC_ADV_INT_MAX, advInt);

      // Enable advertising
      uint8_t advEnabled = 1;
      GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t),
                           &advEnabled);
      Util_startClock(&periodicClock);
      // ==== END SOLUTION ====

simple_peripheral.c :: SimplePeripheral_processStateChangeEvt() – Change advertising interval and re-enable advertising.

If you completed task Advertising Task 2, the red LED was toggling every 5 s (when the advertisement payload changed). After this code change, the LED will also togle when the advertising interval changes except at a 10 s interval. You can observe the advertising interval with a Bluetooth packet sniffer.

After you have completed Scanning Task 1, come back to this quiz and adjust advertising interval accordingly in simple_peripheral. Also refer to the tips in Choosing advertising interval: Fast or slow? and Optimizing Scanning: How to get data fast?.

Which advertising interval is best suited to get an acceptable detection rate for the settings used in Scanning Task 1?

Choose the correct answer.

Advertising Task 4 – Change Filter Policy

Since the advertisement data is broadcasted, any scanning device can pick it up. Furthermore, the scanner can send a scan request to request more data from the advertiser. The advertiser will then send a scan response. However, the advertiser can choose to send the scan response data only to certain devices that are contained in its Filter Accept List. Also, the advertiser can limit which devices can connect to it (to only the devices contained in the Filter Accept List).

Use the following code to restrict scan response (and thereby connection) to devices which are added to the Filter Accept List. Remember to change the bdAddressPeer[] to the BDA of your central device.

static void SimplePeripheral_init(void)
{
// ...
// === SOLUTION [Change filter policy] ===
  //set ADV filter policy to allow scan and connect request from white list only
  uint8_t advFilterPolicy = GAP_FILTER_POLICY_WHITE;

  //remember to change the values below to the BDA of your central device
  static uint8 bdAddressPeer[6] = {0x00,0x90,0x78,0x56,0x34,0x12};
  HCI_LE_AddWhiteListCmd(ADDRMODE_PUBLIC, bdAddressPeer);

  GAPRole_SetParameter(GAPROLE_ADV_FILTER_POLICY, sizeof(uint8_t), &advFilterPolicy);
// ==== END SOLUTION ====

  // Set the GAP Characteristics
  GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName);
// ...

simple_peripheral.c :: SimplePeripheral_init() – Configuring advertising filter policy

Scanning

Scanning Basics

When not connected, Bluetooth devices can either advertise their presence by transmitting advertisement packets or scan for nearby devices that are advertising. This 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 V4.2. The Time Inter Frame Space (T_IFS) is the time interval between two consecutive packets on the same channel index and is set to 150 µs by the BLE specification.

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

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

Which type of scanning does not transmit (TX) any packets, only receives (RX) advertisement packets?

Choose the correct answer(s).

The following tasks will demonstrate scanning using the simple_central_cc2640r2lp_app project.

Scanning Task 1 – Change Scanning Parameters

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

  • Scan interval: 150 ms
  • Scan window: 150 ms
  • Scan duration: 5 s
  • Scan type: Passive

When setting the scan interval equal to the scan window the device will scan continuously. On the other hand, if the scan interval is larger than the scan window the device will duty-cycle scanning.

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. In simple_central.c, change the default scan parameters as shown below:

// === SOLUTION [Add GAP discovery settings] ===
  // Scan duration in ms
  #define DEFAULT_SCAN_DURATION                 5000
  // Scan interval in units of 0.625 ms
  #define DEFAULT_SCAN_INTERVAL                 240 //(240 * 0.625) = 150 ms
  // Scan window in units of 0.625 ms
  #define DEFAULT_SCAN_WINDOW                   240 //(240 * 0.625) = 150 ms
  // FALSE to use passive scan
  #define DEFAULT_DISCOVERY_ACTIVE_SCAN         FALSE
// ==== END SOLUTION ====

// ...

static void SimpleCentral_init(void)
{
// ...
    //Setup the Central GAPRole Profile.
    {
    uint8_t scanRes = DEFAULT_MAX_SCAN_RES;

    GAPCentralRole_SetParameter(GAPCENTRALROLE_MAX_SCAN_RES, sizeof(uint8_t),
                                &scanRes);
    }

  // Set GAP Parameters to set the discovery duration
  GAP_SetParamValue(TGAP_GEN_DISC_SCAN, DEFAULT_SCAN_DURATION);
  GAP_SetParamValue(TGAP_LIM_DISC_SCAN, DEFAULT_SCAN_DURATION);

// === SOLUTION [Add GAP discovery settings] ===
  GAP_SetParamValue(TGAP_GEN_DISC_SCAN_INT, DEFAULT_SCAN_INTERVAL); //period for one scan channel
  GAP_SetParamValue(TGAP_GEN_DISC_SCAN_WIND, DEFAULT_SCAN_WINDOW); //active scanning time within interval
// ==== END SOLUTION ====

  GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN,
                  (void *)attDeviceName);

simple_central.c :: SimpleCentral_init() – Configuring scan parameters

The above parameters are used to set up how the device will behave during the discovery process. If the application is scanning at a low duty cycle, it might take longer to receive the advertisement report thus also delaying connection to the peer device which can adversely affect user experience. Additionally, the following parameters can be configured when starting discovery:

Scan Parameter Default Value Description
DEFAULT_DISCOVERY_MODE GAP_ADTYPE_FLAGS_GENERAL Choose between limited, general, or all discoverable mode
DEFAULT_DISCOVERY_ACTIVE_SCAN TRUE Choose between active and passive scanning
DEFAULT_DISCOVERY_WHITE_LIST FALSE Choose to use Filter Accept List or no Filter Accept List

These parameters are passed into GAPCentralRole_StartDiscovery() to start scanning when BTN2 is pressed.

static void SimpleCentral_handleKeys(uint8_t shift, uint8_t keys)
{
  // ...
  if (keys & KEY_RIGHT)
  {
      // Start or stop discovery
      // ...
        if (!scanningStarted)
        {
        // ...
          GAPCentralRole_StartDiscovery(DEFAULT_DISCOVERY_MODE,
                                        DEFAULT_DISCOVERY_ACTIVE_SCAN,
                                        DEFAULT_DISCOVERY_WHITE_LIST);
        }
        // === SOLUTION [Cancel discovery if enabled] ===
        else
        {
          GAPCentralRole_CancelDiscovery();
        }
        // ==== END SOLUTION ====
      // ...
}

simple_central.c :: SimpleCentral_handleKeys() – Starting/stopping device discovery procedure

            else if(pEvent->gap.hdr.status == GAP_LLERROR_COMMAND_DISALLOWED)
            {
                Display_print0(dispHandle, 3, 0, "COMMAND DISALLOWED");
            }
            // === SOLUTION [Add case for cancelled operation] ===
            else if(pEvent->gap.hdr.status == bleGAPUserCanceled)
            {
                Display_print0(dispHandle, 3, 0, "USER CANCELLED OPERATION");
                scanningStarted = 0;
            }
            // ==== END SOLUTION ====
            else
            {
                Display_print0(dispHandle, 3, 0, "ERROR");
            }
}

simple_central.c :: SimpleCentral_processRoleEvent() – Catch cancelled operation

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 usually called after a connection is established.

Note that the above parameters are used for general scanning. Different parameters are used for scanning after connection has been established. For example TGAP_CONN_SCAN_INT and TGAP_CONN_SCAN_WIND are used when scanning after a connection has been established.

If a set of devices are known (i.e. the device addresses are known), the application can use the Filter Accept List to only involve those devices in the BLE procedures. The set of devices that the BLE controller uses for device filtering is called the Filter Accept List. When scanning with Filter Accept List enabled, the device will only return scan results for those devices in the Filter Accept List. See the Bluetooth Core Specification V4.2 Vol 6, Part B, Section 4.3.3 for more information on scanner filter policy.

To add devices to the Filter Accept List, simply call the HCI_LE_AddWhiteListCmd() and pass in the device address and its type. For example, to add a device with public address of 0x001234567890 :

static void SimpleCentral_init(void)
{
// ...
  static uint8 bdAddressPeer[6] = {0x90,0x78,0x56,0x34,0x12,0x00};
  HCI_LE_AddWhiteListCmd(ADDRMODE_PUBLIC, bdAddressPeer);
// ...

Adding a device to the Filter Accept List

Note the device has only one Filter Accept List, so manually modifying the Filter Accept List is not a good idea. It would interfere with other Filter Accept List operations, including the auto sync of Filter Accept List during device pairing and bonding (if enabled).

Scanning Task 2 – Print scanning results

Every time a device is discovered, a GAP_DEVICE_INFO_EVENT occurs. When scanning ends, aGAP_DEVICE_DISCOVERY_EVENT is sent to the application. When performing passive scanning, the device only receives BLE packets, it does not send any. On the other hand, in active scanning, the controller will request more information (a scan response packet) from the advertiser (after it receives an advertisement packet). The advertiser will then send additional data in the form of a scan response which gets passed to the scanner application.

After receiving which type of advertisement can an active scanner send a scan request?

Choose the correct answer(s).

The information received during a scan, used to discover devices advertising, is encapsulated in the gapDeviceInfoEvent_t structure. The following code demonstrates printing the received advertising data:

/*********************************************************************
 * CONSTANTS
 */
// === SOLUTION===
const char *AdvTypeStrings[] = {"Connectable undirected","Connectable directed", "Scannable undirected", "Non-connectable undirected", "Scan response"};
// ==== END SOLUTION ====

// ...

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

  //*pStr++ = '0';
  //*pStr++ = 'x';

  for (charCnt = 0; charCnt < length; charCnt++)
  {
    *pStr++ = hex[*pData >> 4];
    *pStr++ = hex[*pData++ & 0x0F];
    if(!((charCnt+1) >= length)) {
        *pStr++ = ':';
    }
  }
  pStr = NULL;

  return str;
}
// ==== END SOLUTION ====

// ...

static void SimpleCentral_processRoleEvent(gapCentralRoleEvent_t *pEvent)
{
switch (pEvent->gap.opcode)
        {
        // ...
        case GAP_DEVICE_INFO_EVENT:
     {
        // ...
// === SOLUTION [Print scan results] ===
        //Print scan response data or advertising data
        if(pEvent->deviceInfo.eventType == GAP_ADRPT_SCAN_RSP)
        {
            Display_print1(dispHandle, 4, 0, "ScanResponseAddr: %s",
                           Util_convertBdAddr2Str(pEvent->deviceInfo.addr));

            Display_print1(dispHandle, 5, 0, "ScanResponseData: %s",
                           Util_convertBytes2Str(pEvent->deviceInfo.pEvtData,
                           pEvent->deviceInfo.dataLen));
        }
        else
        {
            Display_print1(dispHandle, 6, 0, "Advertising Addr: %s",
                           Util_convertBdAddr2Str(pEvent->deviceInfo.addr));
            Display_print1(dispHandle, 7, 0, "RSSI: %d", pEvent->deviceInfo.rssi );
            Display_print1(dispHandle, 8, 0, "Advertising Type: %s",
                           AdvTypeStrings[pEvent->deviceInfo.eventType]);
            Display_print1(dispHandle, 9, 0, "Advertising Data: %s",
                           Util_convertBytes2Str(pEvent->deviceInfo.pEvtData,
                           pEvent->deviceInfo.dataLen));
        }

// ==== END SOLUTION ====
// ...
  }

simple_central.c :: SimpleCentral_processRoleEvent() – Adding functionality to print scan results

Canceling Discovery

Note that when canceling discovery (by calling GAPCentralRole_CancelDiscovery()), the application will always return "0 devices found" in the GAP_DEVICE_DISCOVERY_EVENT.

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.

// === SOLUTION [Scan indefinitely] ===
// Scan duration in ms
#define DEFAULT_SCAN_DURATION                 0
// ...
static void SimpleCentral_init(void)
{
// ...
  GAP_SetParamValue(TGAP_GEN_DISC_SCAN, DEFAULT_SCAN_DURATION);
  GAP_SetParamValue(TGAP_LIM_DISC_SCAN, DEFAULT_SCAN_DURATION);

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

simple_central.c :: SimpleCentral_init() – Configuring scan duration to scan indefinitely.

Note

Be mindful that, if scanning indefinitely, two things must be taked into consideraton:

  • The number of scanned results will still be limited by DEFAULT_MAX_SCAN_RES such as in a regular time limited scan.
  • A GAP_DEVICE_DISCOVERY_EVENT will never be received since the scanning process never ends. The exception is when the GAPCentralRole_CancelDiscovery() API is called to cancel the discovery. Regardless of the number of devices the scanner has discovered, the application will return "0 Devices Found".

Enable Unlimited Scanning

Certain applications require the ability to scan for an unknown number (i.e. unlimited) of devices. The Unlimited Scanning feature was developed for this reason. When enabling the unlimited scan feature, the stack passes the scan result to the application whenever a broadcaster/advertiser device is found. The BLE-Stack will not save the result in the stack and thus further save RAM usage. The application layer will then receive the scan result one by one under GAP_DEVICE_INFO_EVENT state.

To enable this feature, simply set ENABLE_UNLIMITED_SCAN_RES to TRUE. This allows scanning without an upper limit of discovered devices.

Similar to indefinite scanning, the number of scanned results is governed by DEFAULT_MAX_SCAN_RES as shown below.

Value Description
0 Enable unlimited scan. Scan results are only available under GAP_DEVICE_INFO_EVENT
~1-256 Maximum number of scan reports to application. Scan results are available under both GAP_DEVICE_INFO_EVENT and GAP_DEVICE_DISCOVERY_EVENT

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:

GAP_SetParamValue(TGAP_FILTER_ADV_REPORTS, FALSE);

Configuring duplicate filter for advertisements.

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.

Caution

Note that when filtering is turned on, the scanner filters advertisements by device address only. This means that even if the advertising data is changed, it will not get passed to the application.

Example UART Output from Tasks 1-3

In the figure below, 2 serial COM ports emulators are opened in CCS which show the printed result of simple_peripheral and simple_central due to the above changes in both Scanning Task 1-3 and Advertising Task 1-3.

Once the updated projects are loaded, remember to press the right button (BTN2) on the central device to start scanning.

  static uint8 bdAddressPeer[6] = {0x6A,0x5C,0xAA,0x2D,0x07,0x98};
  HCI_LE_AddWhiteListCmd(ADDRTYPE_PUBLIC, bdAddressPeer);

Enable whitelist on simple_central.

Miscellaneous Guidelines

Choosing advertising interval: Fast or slow?

The advertising interval should be set based on use case. Both fast or 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 scanning 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.

Scanning in multirole configurations: Any limitations?

  • Note that the scanning during a connection uses different set of scan parameters (such as TGAP_CONN_SCAN_INT) than outside a connection(such as TGAP_GEN_DISC_SCAN_INT).
  • Scanning is allowed in most multirole configurations, however, scanning while initiating connection is not allowed. (You cannot do multiple simultaneous scan procedures on one single radio.)
  • Many BLE procedures can be running simultaneously on the same device, but what happens when their timings collide? The controller will automatically interleave advertising/connection events with scanning when operating in a multirole configuration.

References

TI BLE-Stack User's Guide

Bluetooth Low Energy Core Specification

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