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
Other SimpleLink Academy Labs
- Completion of BLE Fundamentals
Hardware
For this lab, you need two CC2640R2 Bluetooth-enabled development boards:
Software
- CCS 9 or IAR 8.32.2 installed with support for CC13xx/CC26xx devices.
- SimpleLink™ CC2640R2 SDK 5.30
Recommended reading
- Getting Started Chapter of the TI BLE-Stack User's Guide
- The CC2640R2F Platform Chapter of the TI BLE-Stack User's Guide
- Developing a Custom Application Chapter of the TI BLE-Stack User's Guide
Getting started – Desktop
Install the software
- 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.
- You can read about GAP in the TI BLE-Stack User's Guide and proceed to BLE5-Stack → Generic Access Profile (GAP)
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 theGAPCentralRole_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 asTGAP_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
Bluetooth Low Energy Core Specification
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.