Bluetooth Low Energy Protocol Stack

Overview

This section describes the functionality of the Bluetooth low energy protocol stack and provides a list of APIs to interface with the protocol stack. The stack project and its associated files serve to implement the Bluetooth low energy protocol stack task. This is the highest priority task in the system and it implements the Bluetooth low energy protocol stack as shown in Figure 27.

Most of the Bluetooth low energy protocol stack is object code in a single library file (TI does not provide the protocol stack source code as a matter of policy). A developer must understand the functionality of the various protocol stack layers and how they interact with the application and profiles. This section explains these layers.

Introduction

Version 4.2 of the Bluetooth specification allows for two systems of wireless technology: Basic Rate (BR: BR/EDR for Basic Rate/Enhanced Data Rate) and Bluetooth low energy. The Bluetooth low energy system was created to transmit small packets of data, while consuming significantly less power than BR/EDR devices.

The TI BLE Protocol stack supports the following 4.2 features:

  • LE Secure Connections
  • LE Data Length extension
  • LE Privacy 1.2

The TI BLE Protocol stack also supports the following 4.1 features:

  • LE L2CAP Connection-Oriented Channel Support
  • LE Link Layer Topology
  • LE Ping
  • Slave Feature Exchange
  • Connection Parameter Request

Optional features can be selectively enabled at build time. See Optimizing Bluetooth low energy Stack Memory Usage.

Bluetooth low energy Protocol Stack Basics

../_images/image4.jpeg

Figure 27. Bluetooth low energy Protocol Stack.

Figure 27. shows the Bluetooth low energy protocol stack architecture.

The Bluetooth low energy protocol stack (or protocol stack) consists of the controller and the host. This separation of controller and host derives from the implementation of classic Bluetooth BR/EDR devices, where the two sections are implemented separately. Any profiles and applications sit on top of the GAP and GATT layers of the protocol stack.

The physical layer (PHY) is a 1-Mbps adaptive frequency-hopping GFSK (Gaussian frequency-shift keying) radio operating in the unlicensed 2.4-GHz ISM (industrial, scientific, and medical) band.

The Generic Access Profile (GAP) controls the RF state of the device, with the device in one of five states:

  • Standby
  • Advertising
  • Scanning
  • Initiating
  • Connected

Advertisers transmit data without connecting, while scanners scan for advertisers. An initiator is a device that responds to an advertiser with a request to connect. If the advertiser accepts the connection request, both the advertiser and initiator enter a connected state. When a device is connected, it connects as either master or slave. The device initiating the connection becomes the master and the device accepting the request becomes the slave.

The HCI layer provides communication between the host and controller through a standardized interface. This layer can be implemented either through a software API or by a hardware interface such as UART, SPI, or USB. The Specification of the Bluetooth System describes Standard HCI commands and events. TI’s proprietary commands and events are specified in TI Bluetooth low energy Vendor-Specific HCI Reference Guide.

The L2CAP layer provides data encapsulation services to the upper layers, allowing for logical end-to-end communication of data.

The Security Manager layer defines the methods for pairing and key distribution, and provides functions for the other layers of the protocol stack to securely connect and exchange data with another device.

The Generic Access Profile (GAP) layer directly interfaces with the application and/or profiles, to handle device discovery and connection-related services for the device. GAP handles the initiation of security features.

The ATT layer allows a device to expose certain pieces of data or attributes, to another device. The Generic Attribute Profile (GATT) layer is a service framework that defines the sub-procedures for using ATT. Data communications that occur between two devices in a Bluetooth low energy connection are handled through GATT sub-procedures. The application and/or profiles will directly use GATT.

The Application

This section describes the application portion of the simple_peripheral project, which includes the following:

Note

The GAPRole Task is also part of the application project workspace, but is discussed with The Stack. The functionality of the GAPRole Task relates closely to the protocol stack.

Pre-main initialization

The main function is contained in source file main.c located in the IDE Start-up folder. This function is the starting point at run time. The purpose of main is to bring up the target with interrupts disabled, drivers initialized, power management on, TI-RTOS tasks created or constructed, and start the SYS/BIOS kernel scheduler with interrupts enabled. The main function does not return. Main.c exists in the application project; in other words main.c will be allocated within flash reserved for the application.

Listing 27. A basic main function.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main()
{
  /* Register Application callback to trap asserts raised in the Stack */
  RegisterAssertCback(AssertHandler);

  PIN_init(BoardGpioInitTable);

  #ifndef POWER_SAVING
    /* Set constraints for Standby, powerdown and idle mode */
    Power_setConstraint(PowerCC26XX_SB_DISALLOW);
    Power_setConstraint(PowerCC26XX_IDLE_PD_DISALLOW);
  #endif // POWER_SAVING

  /* Initialize ICall module */
  ICall_init();

  /* Start tasks of external images - Priority 5 */
  ICall_createRemoteTasks();

  /* Kick off profile - Priority 3 */
  GAPRole_createTask();

  SimpleBLEPeripheral_createTask();

  /* enable interrupts and start SYS/BIOS */
  BIOS_start();

  return 0;
}

See TI-RTOS Overview for how the application and GAPRole tasks are constructed through TI-RTOS.

Note

that the ICall module must be initialized by ICall_init() and the stack task is created via ICall_createRemoteTasks().

ICall

Introduction

Indirect Call Framework (ICall) is a module that provides a mechanism for the application to interface with the Bluetooth low energy protocol stack services (that is, BLE-Stack APIs) as well as certain primitive services provided by TI-RTOS (for example, thread synchronization). ICall allows the application and protocol stack to operate efficiently, communicate, and share resources in a unified TI-RTOS environment.

The central component of the ICall architecture is the dispatcher, which facilitates the application program interface between the application and the BLE-Stack task across the dual-image boundary as well as in Library configuration. Although most ICall interactions are abstracted within the BLE-Stack APIs (for example, GAP, HCI, and so forth), the application developer must understand the underlying architecture for the BLE-Stack to operate properly in the multithreaded RTOS environment.

The ICall module source code is provided in the ICall and ICall BLE IDE folders in the application project.

../_images/image68.jpeg

Figure 28. ICall Application – Protocol Stack Abstraction.

ICall BLE-Stack Protocol Service

As Figure 28. shows, the ICall core use case involves messaging between a server entity (that is, the BLE-Stack task) and a client entity (for example, the application task).

Note

The ICall framework is not the GATT server and client architecture, as defined by the Bluetooth Low Energy protocol.

The reasoning for this architecture is as follows:

  • To enable independent updating of the application and Bluetooth Low Energy protocol stack
  • To maintain API consistency as software is ported from legacy platforms (that is, OSAL for the CC254x) to TI-RTOS of the CC2640R2F.

The ICall BLE-Stack service serves as the application interface to BLE-Stack APIs. When a BLE-Stack API is called by the application internally, the ICall module routes (that is, dispatches) the command to the BLE-Stack and routes messages from the BLE-Stack to the application when appropriate.

Because the ICall module is part of the application project, the application task can access ICall with direct function calls. Because the BLE-Stack executes at the highest priority, the application task blocks until the response is received. Certain protocol stack APIs may respond immediately, but the application thread blocks as the API is dispatched to the BLE-Stack through ICall. Other BLE-Stack APIs may also respond asynchronously to the application through ICall (for example, event updates) with the response sent to the event handler of the application task.

ICall Primitive Service

ICall includes a primitive service that abstracts various operating system-related functions. Due to shared resources and to maintain interprocess communication, the application must use the following ICall primitive service functions:

  • Messaging and Thread Synchronization
  • Heap Allocation and Management

Some of these are abstracted to Util functions (see the relevant module in TI-RTOS Overview).

Messaging and Thread Synchronization

The Messaging and Thread Synchronization functions provided by ICall enable an application to communicate with the BLE-Stack in the multithreaded TI-RTOS environment.

In ICall, messaging between two tasks occurs by sending a block of message from one thread to the other through a message queue. The sender allocates a memory block, writes the content of the message into the memory block, and then sends (that is, enqueues) the memory block to the recipient. Notification of message delivery occurs using a event flag. The receiver wakes up on the event flag post, copies the message memory block (or blocks), processes the message, and returns (frees) the memory block to the heap.

The stack uses ICall for notifying and sending messages to the application. ICall delivers these service messages, the application task receives them, and the messages are processed in the context of the application.

Heap Allocation and Management

ICall provides the application with global heap APIs for dynamic memory allocation. The size of the ICall heap is configured with the HEAPMGR_SIZE preprocessor-defined symbol in the application project. See Dynamic Memory Allocation for more details on managing dynamic memory. ICall uses this heap for all protocol stack messaging and to obtain memory for other ICall services. TI recommends that the application uses these ICall APIs to allocate dynamic memory.

ICall Initialization and Registration

To instantiate and initialize the ICall service, the application must call the functions in Required code to utilize ICall. in main() before starting the TI-RTOS kernel scheduler:

Listing 28. Required code to utilize ICall.
1
2
3
4
5
/* Initialize ICall module */
ICall_init();

/* Start tasks of external images - Priority 5 */
ICall_createRemoteTasks();

Calling ICall_init() initializes the ICall primitive service (for example, heap manager) and framework. Calling ICall_createRemoteTasks() creates but does not start the BLE-Stack task. Before using ICall protocol services, the server and client must enroll and register with ICall. The server enrolls a service, which is defined at build time. Service function handler registration uses a globally defined unique identifier for each service. For example, Bluetooth low energy uses ICALL_SERVICE_CLASS_BLE for receiving BLE-Stack task messages through ICall.

To enroll the BLE-Stack service (server) with ICall in osal_icall_ble.c see Listing 29.

Listing 29. ICall Enrollment
1
2
/* Enroll the service that this stack represents */
ICall_enrollService(ICALL_SERVICE_CLASS_BLE, NULL, &entity, &syncHandle);

The registration mechanism is used by the client to send and/or receive messages through the ICall dispatcher.

For a client (for example, application task) to use the BLE-Stack APIs, the client must first register its task with ICall. This registration usually occurs in the task initialization function of the application. Listing 30. is an example from simple_peripheral_init in simple_peripheral.c

Listing 30. ICall Registration
1
2
3
// Register the current thread as an ICall dispatcher application
// so that the application can send and receive messages.
ICall_registerApp(&selfEntity, &syncEvent);

The application supplies the selfEntity and syncEvent inputs. These inputs are initialized for the task of the client (for example, application) when the ICall_registerApp() returns are initialized. These objects are subsequently used by ICall to facilitate messaging between the application and server tasks. The syncEvent argument represents the Events Module handle for signaling and the selfEntity represents the destination message queue of the task. Each task registering with ICall have unique syncEvent and selfEntity identifiers.

Note

BLE-Stack APIs defined in ICallBLEApi.c and other ICall primitive services are not available before ICall registration.

ICall Thread Synchronization

The ICall module uses TI-RTOS Events Module for thread synchronization instead of Semaphores.

To allow a client or a server thread to block until it receives a message, ICall provides the API functions which blocks until the semaphore associated with the caller TI-RTOS thread is posted.

Listing 31. ICall Blocking/Pending Calls
1
UInt Event_pend(Event_Handle handle, UInt andMask, UInt orMask, UInt32 timeout);

handle is the constructed Event_Handle instance. andMask and orMask are the for the user to select which Event flags to block/ pend on.``timeout`` is a time-out period in milliseconds. If not already returned after this time-out period, the function returns with events consumed.

Event_pend blocks the current task until the desired Events are posted. Allowing an application or a server thread to block/yield the processor resource to other lower priority threads or conserves energy by shutting down power and/or clock domains when possible.

There are the total of 32 events. These can be defined for application specific purposes. Note there is an event specifically for ICall Messages and Queues.

The Event associated with an TI-RTOS thread is signaled/posted by when Event_post is called with the desired flags

Event_post is used so an application or a server can add its own event to unblock Event_pend and synchronize the thread with appropriate flags. Event_post constructed Event_Handle instance, as well as an eventFlag mask to select the desired flags .

Listing 32. ICall Signaling/Posting Call
1
Void Event_post(Event_Handle handle, UInt eventMask);

The Event handle associated with the thread is obtained through either ICall_enrollService() call or ICall_registerApp() call.

Danger

Do not call an ICall function from a stack callback. This action can cause ICall to abort (with ICall_abort()) and break the system.

For more information on the TI-RTOS Events Module, see Event.

For more information on migrating from earlier devices see CC2640 to CC2640R2.

ICall API Translation Layer

Although there are no API changes to ICall or Stack API, in order for projects to realize the benefits of improved ICall the APIs need to be remapped.

The remapping is all done with icall_api.h, and by including icall_api_lite.c in the build. Effectively, icall_api.h redefines all the ICall/Stack API while keeping their original function prototypes. This redefinition is done to utilize a different message format for the dispatcher to handle.

In order for the redefinition to take effect correctly, icall_api.h MUST be the last file to be included in the source file. This ensures that the redefinition correctly occurs. If icall_api.h is not the last file to be included, it’s possible that original definitions could be used due to gapbondmgr.h, gapgattserver.h, or any other ICall/Stack API being included in another header file.

Danger

For any source file utilizing ICall/Stack API, #include "icall_api.h" must be the last include statement. Erratic runtime behavior or link time errors may result.

The new message format is designed to be compatible with the improved ICall translation layer, defined in icall_lite_translation.c in the BLE-Stack project. All messages will be processed with icall_liteTranslation in the BLE_Stack context.

Warning

Only Tasks/Threads registered with ICall via ICall_registerApp() should use ICall/Stack API.

Application will abort when unknown task utilizes ICall/Stack API.

Example ICall Usage

Figure 29. shows an example command being sent from the application to the BLE-Stack through the ICall framework with a corresponding return value passed back to the application.

ICall_init() initializes the ICall module instance and ICall_createRemoteTasks() creates a task per external image with an entry function at a known address.

After initializing ICall, the application task registers with ICall through ICall_registerApp().

After the SYS/BIOS scheduler starts and the application task runs, the application sends a protocol command defined in ble_dispatch_JT.c such as GAP_GetParamValue().

Danger

Although the protocol command is declared in gap.h and defined on the BLE-Stack side via ble_dispatch_JT.c. The declaration ** MUST** be overridden by icall_api.h.

The protocol command is not executed in the thread of the application but is encapsulated in an ICall message and routed to the BLE-Stack task via the ICall framework. This command is sent to the ICall dispatcher where it is dispatched and executed in the BLE-Stack context. The application thread meanwhile blocks until the corresponding command status message is received. The BLE-Stack finishes executing the command, then sends a command status message response is through ICall back to the application thread. An example diagram of this exchange can be seen in Figure 29.

../_images/image69.jpeg

Figure 29. ICall Messaging Example

Application and Stack Library Builds

ICall now enables merged Application and Stack images. This eliminates the boundary introduced with split Application and Stack images with global linking. In other words, the linker will be able to selectively link elements of the BLE-Stack that are used.

Converting Library Build to Split App/Stack Image

All BLE-Stack 3.0.0 projects will support improved ICall and the Library build configuration. However, not all projects will have ICall in non-Library Build configuration. In other words, not all projects will have a split application/stack build configuration.

A split Application and Stack configuration is required for OAD applications where only the stack or the application image is planned to be upgraded. Library configuration only supports full application + stack image off-chip OAD.

This section outlines how to convert a ICall Library Project to a split application and stack project.

Application Side (IAR)
  1. Select the Application Project as Active Project

    Delete StackLibrary IDE Folder or exclude library file from build.

  2. Open Project Properties

    Various ways to do this:

    • Right Click on Project > Options
    • From Toolbar : Project > Options
  3. Go to C/C++ Compiler Options, Preprocessor tab

    Select C/C++ Compiler from the Category List

    Select Preprocessor Tab

  4. Remove STACK_LIBRARY Symbol

    Under Defined Symbols remove STACK_LIBRARY

  5. Go to Linker Options, Config Tab

    Select Linker from the Category List

    Select Config tab in the Window

  6. Apply the following changes:

    • Linker configuration file:
      1
      $SRC_EX$/common/cc26xx/iar/cc26xx_app.icf
      

      Instead of

      1
      $SRC_EX$/common/cc26xx/iar/cc26xx_app_and_stack.icf
      
    • Configuration file symbol definitions:
      1
      CC2650=2
      

      Remove

      1
      FLASH_ROM_BUILD=2
      
  7. Go to Linker Options, Library Tab

    Select Linker from the Category List

    Select Library tab in the Window

  8. Modify Additional Linker Library Paths

    Change Additional Libraries text field to use:

    1
    $ROM$\ble_rom_releases\cc26xx_r2\Final_Release\common_r2.symbols
    

    Instead of:

    1
    $ROM$\ble_rom_releases\cc26xx_r2\Final_Release\ble_r2.symbols
    

    Lastly, remove:

    1
    $TI_RTOS_DRIVERS_BASE$\ti\drivers\rf\lib\rf_singleMode_cc26x0r2.aem3
    

    Pre-Modifications:

    1
    2
    3
    4
    5
    6
    $ROM$\ble_rom_releases\cc26xx_r2\Final_Release\ble_r2.symbols
    $CC26XXWARE$\driverlib\bin\iar\driverlib.lib
    $TI_RTOS_KERNEL$\packages\ti\dpl\lib\dpl_cc26x0r2.aem3
    $TI_RTOS_DRIVERS_BASE$\ti\drivers\lib\drivers_cc26x0r2.aem3
    $TI_RTOS_DRIVERS_BASE$\ti\drivers\rf\lib\rf_singleMode_cc26x0r2.aem3
    $TI_RTOS_DRIVERS_BASE$\ti\display\lib\display.aem3
    

    Post-Modifications:

    1
    2
    3
    4
    5
    $ROM$\ble_rom_releases\cc26xx_r2\Final_Release\common_r2.symbols
    $CC26XXWARE$\driverlib\bin\iar\driverlib.lib
    $TI_RTOS_KERNEL$\packages\ti\dpl\lib\dpl_cc26x0r2.aem3
    $TI_RTOS_DRIVERS_BASE$\ti\drivers\lib\drivers_cc26x0r2.aem3
    $TI_RTOS_DRIVERS_BASE$\ti\display\lib\display.aem3
    
  9. Go to Linker Options, Extra Options

    Select Linker from the Category List

    Select Extra Options tab in the Window

  10. Remove lib_linker.cmd

    The window should have the following content after removal

    1
    2
    3
    --keep __vector_table
    -f $PROJ_DIR$\..\config\configPkg\linker.cmd
    -f $PROJ_DIR$\..\config\iar_boundary.xcl
    
Stack Side (IAR)
  1. Select the Stack Project as Active Project

  2. Open Project Properties

    Various ways to do this:

    • Right Click on Project > Options
    • From Toolbar : Project > Options
  3. Go to General Options, Output Tab

    Select General Options from the Category List

    Select the Output tab

  4. Select Executable for Output File

    Select the Executable Radio Button under Output File.

    Note

    This will reset Debugger Settings!

  5. Go to C/C++ Compiler Options, Preprocessor tab

    Select C/C++ Compiler from the Category List

    Select Preprocessor Tab

  6. Remove STACK_LIBRARY Symbol

    Under Defined Symbols remove STACK_LIBRARY

  7. Go to Build Options

    Select Build Options from Category List

  8. Add Boundary Tool to Post Build

    This tool hands information to the Application regarding Stack Entry Point.

    Add the following to Post_Build command line:

    1
    "$TOOLS_BLE$/frontier/frontier.exe" iar "$PROJ_DIR$/$CONFIG_NAME$/List/simple_peripheral_cc2640r2lp_stack.map" "$PROJ_DIR$/../config/iar_boundary.bdef" "$PROJ_DIR$/../config/iar_boundary.xcl"
    
  9. Go to Linker Options, Config Tab

    Select Linker from the Category List

    Select Config tab in the Window

  10. Add lib_linker.cmd

    This tool resolves undefined rom function references.

    Select Use command line options

    Enter the following into the Command Line Options Textfield

    1
    -f $PROJ_DIR$/../config/lib_linker.cmd
    
  11. Reconfigure Debugger Settings

    Use XDS110 for the CC2640R2 Launchpad. Use the application project as reference.

At this point, you will have a functional ICall Non-Library build of the project.

Simple Peripheral Task

Simple Peripheral Task, or the application task, is the lowest priority task in the system. The code for this task is in simple_peripheral.c and simple_peripheral in the Application IDE folder.

Application Initialization Function

TI-RTOS Overview describes how a task is constructed. After the task is constructed and the SYS/BIOS kernel scheduler is started, the function that was passed during task construction is run when the task is ready (for example, SimpleBLEPeripheral_taskFxn). This function must first call an application initialization function.

Listing 33. simple_peripheral Task Function Psuedo Code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1)
{
  //Initialize application
  SimpleBLEPeripheral_init();

  //Application main loop
  for (;;)
  {

  }
}

This initialization function (simple_peripheral_init) configures several services for the task and sets several hardware and software configuration settings and parameters. The following list contains some common examples:

  • Initializing the GATT client
  • Registering for callbacks in various profiles
  • Setting up the GAPRole
  • Setting up the Bond Manager
  • Setting up the GAP layer
  • Configuring hardware modules such as LCD or SPI.

For more information on these examples, see their respective sections in this guide.

Note

In the application initialization function, ICall_registerApp() must be called before any stack API is called.

Event Processing in the Task Function

simple_peripheral implements a event driven task function. The task function enters an infinite loop so that it continuously processes as an independent task and does not run to completion. In this infinite loop, the task remains blocked and waits until proper events flags signal a new reason for processing:

Listing 34. ICall task remains blocked and waits until signaled for processing.
1
2
3
4
5
// Waits for an event to be posted associated with the calling thread.
// Note that an event associated with a thread is posted when a
// message is queued to the message receive queue of the thread
events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
                    ICALL_TIMEOUT_FOREVER);

When an event or other stimulus occurs and is processed, the task waits for event flags and remains in a blocked state until there is another reason to process.

Task Events

Task events are set when the BLE-Stack sets an event in the application task through the Events Module. An example of a task event is when the HCI_EXT_ConnEventNoticeCmd() is called to indicate the end of a connection event. An example of a task event that signals the end of a connection event is shown in the task function of the simple_peripheral:

Listing 35. SBP task checks for task events.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if (events)
{
  ICall_EntityID dest;
  ICall_ServiceEnum src;
  ICall_HciExtEvt *pMsg = NULL;

  if (ICall_fetchServiceMsg(&src, &dest,
                            (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
  {
    uint8 safeToDealloc = TRUE;

    if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
    {
      ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;

      // Check for BLE stack events first
      if (pEvt->signature == 0xffff)
      {
        if (pEvt->event_flag & SBP_CONN_EVT_END_EVT)
        {
          // Try to retransmit pending ATT Response (if any)
          SimpleBLEPeripheral_sendAttRsp();
        }
      }
      else
      {
        // Process inter-task message
        safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg);
      }
    }

    if (pMsg && safeToDealloc)
    {
      ICall_freeMsg(pMsg);
    }
  }

  // Additional Event Processing

Note

In the code, the pEvt->signature is always equal to 0xFFFF if the event is coming from the BLE-Stack.

When selecting an event value for an intertask event, the value must be unique for the given task and must be a power of 2 (so only 1 bit is set). Because the pEvt->event variable is initialized as uint16_t, this initialization allows for a maximum of 16 events. The only event values that cannot be used are those already used for BLE-Stack OSAL global events (stated in bcomdef.h):

Listing 36. BLE OSAL events are defined in bcomdef.h.
1
2
3
4
5
/************************************************************
* BLE OSAL GAP GLOBAL Events
*/

#define GAP_EVENT_SIGN_COUNTER_CHANGED 0x4000 //!< The device level sign counter changed

Note

These intertask events are a different set of events than the intratask events mentioned in Requesting and Proccessing Stack Events.

Intertask Messages

These messages are passed from another task (such as the BLE-Stack Service) through ICall to the application task.

Some possible examples are as follows:

  • A confirmation sent from the protocol stack in acknowledgment of a successful over-the-air indication
  • An event corresponding to an HCI command (see HCI)
  • A response to a GATT client operation (see Using the GATT Layer Directly)

Listing 35. an example from the main task loop of the simple_peripheral.

Using TI-RTOS Events Module

All BLE-Stack 3.00.00 projects use the TI-RTOS Event module aquire ICall stack message event. Usage is described in ICall Thread Synchronization and more documentation on the Event module can be found in the TI-RTOS Kernel User Guide.

Processing Queued Application Messages

Application messages enqueued using the Util_enqueueMsg() function are dequeued for processing in the order in which they occurred. The application should dequeue and free messages when UTIL_QUEUE_EVENT_ID events are posted.

Shown in Listing 37. you can see how simple_peripheral processes application messages.

Listing 37. Queued messages are processed in the order they occurred.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#define SBP_QUEUE_EVT   UTIL_QUEUE_EVENT_ID // Event_Id_30

// If TI-RTOS queue is not empty, process app message.
if (events & SBP_QUEUE_EVT)
{
    while (!Queue_empty(appMsgQueue))
    {
        sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue);
        if (pMsg)
        {
            // Process message.
            SimpleBLEPeripheral_processAppMsg(pMsg);

            // Free the space from the message.
            ICall_free(pMsg);
        }
    }
}

Requesting and Proccessing Stack Events

Some APIs have the option to notify the application when specific events occur in the BLE-Stack. The API which enabled the notification of such events will contain a taskEvent argument. This taskEvent must be unique for a given ICall-aware task. The application can process the requested stack events by checking if the taskEvent is contained in the uint16_t event_flag variable of the ICall_Stack_Event data structure.

Note

The event_flag is not to be confused with events posted by the TI-RTOS Event module.

Shown in Listing 38. and Listing 39. you can see how simple_peripheral requests and processes enabled stack events.

Listing 38. Application requesting to be notified when a connection interval ends.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Application specific event ID for HCI Connection Event End Events
#define SBP_HCI_CONN_EVT_END_EVT              0x0001

static uint8_t SimpleBLEPeripheral_processGATTMsg(gattMsgEvent_t *pMsg)
{
    // See if GATT server was unable to transmit an ATT response
    if (pMsg->hdr.status == blePending)
    {
        // No HCI buffer was available. Let's try to retransmit the response
        // on the next connection event.
        if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity,
                                       SBP_HCI_CONN_EVT_END_EVT) == SUCCESS)
        {
            // First free any pending response
            SimpleBLEPeripheral_freeAttRsp(FAILURE);

            // Hold on to the response message for retransmission
            pAttRsp = pMsg;
Listing 39. Processing requested BLE-Stack events
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Application specific event ID for HCI Connection Event End Events
#define SBP_HCI_CONN_EVT_END_EVT              0x0001

...

static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1)
{
    ...

    // Application main loop
    for (;;)
    {
        uint32_t events;

        // Waits for an event to be posted associated with the calling thread.
        // Note that an event associated with a thread is posted when a
        // message is queued to the message receive queue of the thread
        events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
                            ICALL_TIMEOUT_FOREVER);

        if (events)
        {
            ICall_EntityID dest;
            ICall_ServiceEnum src;
            ICall_HciExtEvt *pMsg = NULL;

            if (ICall_fetchServiceMsg(&src, &dest,
                                    (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
            {
                uint8 safeToDealloc = TRUE;

                if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
                {
                    ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;

                    // Check for BLE stack events first
                    if (pEvt->signature == 0xffff)
                    {
                        if (pEvt->event_flag & SBP_HCI_CONN_EVT_END_EVT)
                        {
                            // Try to retransmit pending ATT Response (if any)
                            SimpleBLEPeripheral_sendAttRsp();
                        }
                    }
                    else
                    {
                        // Process inter-task message
                        safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg);
                    }
            }

Callbacks

The application code also includes various callbacks to protocol stack layers, profiles, and TI-RTOS modules. To ensure thread safety, processing must be minimized in the actual callback and the bulk of the processing should occur in the application context. Two functions are defined per callback. One is the callback itself, that is called upon by another module or task. The second is the function to handle the event generated by the call back in the application context. Consider the GAPRole state change callback, which is called when a GAPRole state change occurs.

Danger

No blocking TI-RTOS function calls or protocol stack APIs should be performed in a callback function. Such function calls may result in an abort or undefined behavior. Always perform protocol stack and TI-RTOS blocking calls from the application task context.

Note

All callbacks are called in the context of the calling task or module (for example, the GAPRole task). To minimize processing in the calling context, this function should enqueue an event that the application pends on.

Listing 40. simple_peripheral state change callback.
1
2
3
4
static void SimpleBLEPeripheral_stateChangeCB(gaprole_States_t newState)
{
  SimpleBLEPeripheral_enqueueMsg(SBP_STATE_CHANGE_EVT, newState);
}

Listing 40. shows the callback function that is sent to the GAP role via SimpleBLEPeripheral_gapRoleCBs and GAPRole_StartDevice(). The callback simply places a message in the queue to signal the application to wake up. Once the callback’s context returns and it’s parent task goes to sleep, the application wakes up due to the enqueue from the callback. Listing 41. is called when the event is popped from the application queue and processed.

Listing 41. simple_peripheral state change event.
1
2
3
4
static void SimpleBLEPeripheral_processStateChangeEvt(gaprole_States_t newState)
{
  //...
}

See Peripheral Role for a flow diagram of this process.

The Stack

Generic Access Profile (GAP)

The GAP layer of the Bluetooth low energy protocol stack is responsible for connection functionality. This layer handles the access modes and procedures of the device including device discovery, link establishment, link termination, initiation of security features, and device configuration. See GAP State Diagram. for more details.

../_images/image72.jpeg

Figure 30. GAP State Diagram.

Based on the role for which the device is configured, GAP State Diagram. shows the states of the device. The following describes these states.

  • Standby: The device is in the initial idle state upon reset.
  • Advertiser: The device is advertising with specific data letting any initiating devices know that it is a connectible device (this advertisement contains the device address and can contain some additional data such as the device name).
  • Scanner: When receiving the advertisement, the scanning device sends a scan request to the advertiser. The advertiser responds with a scan response. This process is called device discovery. The scanning device is aware of the advertising device and can initiate a connection with it.
  • Initiator: When initiating, the initiator must specify a peer device address to which to connect. If an advertisement is received matching that address of the peer device, the initiating device then sends out a request to establish a connection (link) with the advertising device with the connection parameters described in Connection Parameters.
  • Slave/Master: When a connection is formed, the device functions as a slave if the advertiser and a master if the initiator.

Connection Parameters

This section describes the connection parameters which are sent by the initiating device with the connection request and can be modified by either device when the connection is established. These parameters are as follows:

  • Connection Interval - In Bluetooth low energy connections, a frequency-hopping scheme is used. The two devices each send and receive data from one another only on a specific channel at a specific time. These devices meet a specific amount of time later at a new channel (the link layer of the Bluetooth low energy protocol stack handles the channel switching). This meeting is where the two devices send and receive data is known as a connection event. If there is no application data to be sent or received, the two devices exchange link layer data to maintain the connection. The connection interval is the amount of time between two connection events in units of 1.25 ms. The connection interval can range from a minimum value of 6 (7.5 ms) to a maximum of 3200 (4.0 s). See Connection Event and Interval for more details.
../_images/image73.jpeg

Figure 31. Connection Event and Interval

Different applications may require different connection intervals. As described in Connection Parameter Considerations, these requirements affect the power consumption of the device. For more detailed information on power consumption, see Measuring Bluetooth Smart Power Consumption Application Report (SWRA478).

  • Slave Latency - This parameter gives the slave (peripheral) device the option of skipping a number of connection events. This ability gives the peripheral device some flexibility. If the peripheral does not have any data to send, it can skip connection events, stay asleep, and save power. The peripheral device selects whether to wake or not on a per connection event basis. The peripheral can skip connection events but must not skip more than allowed by the slave latency parameter or the connection fails. See Slave Latency for more details.
../_images/image74.jpeg

Figure 32. Slave Latency

  • Supervision Time-out - This time-out is the maximum amount of time between two successful connection events. If this time passes without a successful connection event, the device terminates the connection and returns to an unconnected state. This parameter value is represented in units of 10 ms. The supervision time-out value can range from a minimum of 10 (100 ms) to 3200 (32.0 s). The time-out must be larger than the effective connection interval (see Effective Connection Interval for more details).

Effective Connection Interval

The effective connection interval is equal to the amount of time between two connection events, assuming that the slave skips the maximum number of possible events if slave latency is allowed (the effective connection interval is equal to the actual connection interval if slave latency is set to 0).

The slave latency value represents the maximum number of events that can be skipped. This number can range from a minimum value of 0 (meaning that no connection events can be skipped) to a maximum of 499. The maximum value must not make the effective connection interval (see the following formula) greater than 16 s. The interval can be calculated using the following formula:

Effective Connection Interval = (Connection Interval) * (1 + [Slave Latency])

Consider the following example:

  • Connection Interval: 80 (100 ms)
  • Slave Latency: 4
  • Effective Connection Interval: (100 ms) * (1 + 4) = 500 ms

When no data is being sent from the slave to the master, the slave transmits during a connection event once every 500 ms.

Connection Parameter Considerations

In many applications, the slave skips the maximum number of connection events. Consider the effective connection interval when selecting or requesting connection parameters. Selecting the correct group of connection parameters plays an important role in power optimization of the Bluetooth low energy device. The following list gives a general summary of the trade-offs in connection parameter settings.

Reducing the connection interval does as follows:

  • Increases the power consumption for both devices
  • Increases the throughput in both directions
  • Reduces the time for sending data in either direction

Increasing the connection interval does as follows:

  • Reduces the power consumption for both devices
  • Reduces the throughput in both directions
  • Increases the time for sending data in either direction

Reducing the slave latency (or setting it to zero) does as follows:

  • Increases the power consumption for the peripheral device
  • Reduces the time for the peripheral device to receive the data sent from a central device

Increasing the slave latency does as follows:

  • Reduces power consumption for the peripheral during periods when the peripheral has no data to send to the central device
  • Increases the time for the peripheral device to receive the data sent from the central device

Connection Parameter Limitations with Multiple Connections

There are additional constraints that exist when connected to multiple devices or performing multiple GAP roles simultaneously. See the MultiRole example in`TI SimpleLink GitHub Code page <https://github.com/ti-simplelink/ble_examples>`_ for an example of this.

Connection Parameter Update

In some cases, the central device requests a connection with a peripheral device containing connection parameters that are unfavorable to the peripheral device. In other cases, a peripheral device might have the desire to change parameters in the middle of a connection, based on the peripheral application. The peripheral device can request the central device to change the connection settings by sending a Connection Parameter Update Request. For Bluetooth 4.1 and 4.2-capable devices, this request is handled directly by the Link Layer. For Bluetooth 4.0 devices, the L2CAP layer of the protocol stack handles the request. The Bluetooth low energy stack automatically selects the update method.

This request contains four parameters: minimum connection interval, maximum connection interval, slave latency, and time-out. These values represent the parameters that the peripheral device needs for the connection (the connection interval is given as a range). When the central device receives this request, it can accept or reject the new parameters.

Sending a Connection Parameter Update Request is optional and is not required for the central device to accept or apply the requested parameters. Some applications try to establish a connection at a faster connection interval to allow for a faster service discovery and initial setup. These applications later request a longer (slower) connection interval to allow for optimal power usage.

Depending on the GAPRole, connection parameter updates can be sent asynchronously with the GAPRole_SendUpdateParam() or GAPCentralRole_UpdateLink() command. The peripheral GAPRole can be configured to automatically send a parameter update a certain amount of time after establishing a connection. For example, the simple_peripheral application uses the following preprocessor-defined symbols:

Listing 42. Predefined symbols in simple_peripheral.
1
2
3
4
5
6
#define DEFAULT_ENABLE_UPDATE_REQUEST         GAPROLE_LINK_PARAM_UPDATE_INITIATE_BOTH_PARAMS
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL     80
#define DEFAULT_DESIRED_MAX_CONN_INTERVAL     800
#define DEFAULT_DESIRED_SLAVE_LATENCY         0
#define DEFAULT_DESIRED_CONN_TIMEOUT          1000
#define DEFAULT_CONN_PAUSE_PERIPHERAL         6

Six seconds after a connection is established, the GAP layer automatically sends a connection parameter update. This action can be disabled by changing DEFAULT_ENABLE_UPDATE_REQUEST to e.g. GAPROLE_LINK_PARAM_UPDATE_WAIT_REMOTE_PARAMS. See Peripheral Role for an explanation of how the parameters are configured, and Peripheral for a more detailed description of these parameters.

Connection Termination

Either the master or the slave can terminate a connection for any reason. One side initiates termination and the other side must respond before both devices exit the connected state.

GAP Abstraction

The application and profiles can directly call GAP API functions to perform Bluetooth low energy-related functions such as advertising or connecting. Most of the GAP functionality is handled by the GAPRole Task. GAP Abstraction shows this abstraction hierarchy.

../_images/image75.jpeg

Figure 33. GAP Abstraction

Access the GAP layer through direct calls or through the GAPRole task as described in GAPRole Task. Use the GAPRole task rather than direct calls when possible. Configuring the GAP Layer describes the functions and parameters that are not handled or configured through the GAPRole task and must be modified directly through the GAP layer.

Configuring the GAP Layer

The GAP layer functionality is mostly defined in library code. The function headers can be found in gap.h in the protocol stack project. Most of these functions are used by the GAPRole and do not need to be called directly. For reference, see /cc2640/ble-sdg/api-gap. Several parameters exist which may be desirable to modify before starting the GAPRole. These parameters can be set or get through the GAP_SetParamValue() and GAP_GetParamValue() functions and include advertising and scanning intervals, windows, and so forth (see the API for more information). The following is the configuration of the GAP layer in simple_peripheral_init():

Listing 43. GAP configuration in simple_peripheral_init().
1
2
3
4
5
6
7
8
9
  // 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);
}

The above code sets the advertising interval for limited and general advertising modes. By default, the peripheral advertises in general discoverable mode. To use limited discoverable mode, the corresponding fields inside the advertising data packet should be changed by defining DEFAULT_DISCOVERABLE_MODE to GAP_ADTYPE_FLAGS_LIMITED.

GAPRole Task

The GAPRole task is a separate task which offloads handling most of the GAP layer functionality from the application. This task is enabled and configured by the application during initialization. Based on this configuration, many Bluetooth low energy protocol stack events are handled directly by the GAPRole task and never passed to the application. Callbacks exist that the application can register with the GAPRole task so that the application task can be notified of certain events and proceed accordingly.

Based on the configuration of the device, the GAP layer always operates in one of four roles:

  • Broadcaster - The device is an advertiser that is non connectable.
  • Observer - The device scans for advertisements but cannot initiate connections.
  • Peripheral - The device is an advertiser that is connectable and operates as slave in a single link-layer connection.
  • Central - The device scans for advertisements and initiates connections and operates as a master in a single or multiple link-layer connections. The Bluetooth low energy central protocol stack supports up to three simultaneous connections.

The Bluetooth low energy specification allows for certain combinations of multiple-roles, which are supported by the Bluetooth low energy protocol stack. For configuration of the Bluetooth low energy stack features, see Creating a Custom Bluetooth low energy Application

For supported GAPRole API, see BLE-Stack API Reference.

Peripheral Role

The peripheral GAPRole task is defined in peripheral.c and peripheral.h. Peripheral describes the full API including commands, configurable parameters, events, and callbacks. The steps to use this module are as follows:

  1. Initialize the GAPRole parameters. This initialization should occur in the application initialization function. (for example simple_peripheral_init shown in Listing 44.).
Listing 44. Setup of GAP Peripheral Role
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 // Setup the GAP Peripheral Role Profile

 {

    uint8_t initialAdvertEnable = TRUE;

    uint16_t advertOffTime = 0;

    uint8_t enableUpdateRequest = DEFAULT_ENABLE_UPDATE_REQUEST;
    uint16_t desiredMinInterval = DEFAULT_DESIRED_MIN_CONN_INTERVAL;
    uint16_t desiredMaxInterval = DEFAULT_DESIRED_MAX_CONN_INTERVAL;
    uint16_t desiredSlaveLatency = DEFAULT_DESIRED_SLAVE_LATENCY;
    uint16_t desiredConnTimeout = DEFAULT_DESIRED_CONN_TIMEOUT;

    // Set the GAP Role Parameters

    GAPRole_setParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t),
    &initialAdvertEnable);
    GAPRole_setParameter(GAPROLE_ADVERT_OFF_TIME, sizeof(uint16_t),
    &advertOffTime); GAPRole_setParameter(GAPROLE_SCAN_RSP_DATA,
    sizeof(scanRspData), scanRspData);
    GAPRole_setParameter(GAPROLE_ADVERT_DATA, sizeof(advertData),
    advertData); GAPRole_setParameter(GAPROLE_PARAM_UPDATE_ENABLE,
    sizeof(uint8_t), &enableUpdateRequest);
    GAPRole_setParameter(GAPROLE_MIN_CONN_INTERVAL,
    sizeof(uint16_t), &desiredMinInterval);
    GAPRole_setParameter(GAPROLE_MAX_CONN_INTERVAL,
    sizeof(uint16_t), &desiredMaxInterval);
    GAPRole_setParameter(GAPROLE_SLAVE_LATENCY, sizeof(uint16_t),
    &desiredSlaveLatency);
    GAPRole_setParameter(GAPROLE_TIMEOUT_MULTIPLIER,
    sizeof(uint16_t), &desiredConnTimeout);
 }
  1. Initialize the GAPRole task and pass application callback functions to GAPRole. This should also occur in the application initialization function.
Listing 45. Registering Callbacks and Initialization.
1
2
 // Start the Device
 VOID GAPRole_StartDevice(&SimpleBLEPeripheral_gapRoleCBs);
  1. Send GAPRole commands from the application. Figure 34. is an example of the application using GAPRole_TerminateConnection(), assuming a connection has already been established.
Listing 46. Terminating Connection
1
 GAPRole_TerminateConnection();
../_images/gaprole_term.jpeg

Figure 34. Context Diagram of Application using GAPRole_TerminateConnection(). Green corresponds to the app context and red corresponds to the protocol stack context.

Note

The return value only indicates whether the attempt to terminate the connection initiated successfully. The actual termination of connection event is returned asynchronously and is passed to the application through a callback.

  1. The GAPRole task processes most of the GAP-related events passed to it from the Bluetooth low energy protocol stack. The GAPRole task also forwards some events to the application. When a link is terminated, the GAPRole automatically restarts advertising.

Central Role

The central GAPRole task is defined in central.c and central.h. Central describes the full API including commands, configurable parameters, events, and callbacks. See the simple_central example project for an example of implementing the central GAPRole. The steps to use this module are as follows.

  1. Initialize the GAPRole parameters. This initialization should occur in the application initialization function (for example in SimpleBLECentral_init). GAP parameters can also be set in this initialization function.
// Setup Central Profile
{
   uint8_t scanRes = DEFAULT_MAX_SCAN_RES;

   GAPCentralRole_SetParameter(GAPCENTRALROLE_MAX_SCAN_RES,
   sizeof(uint8_t), &scanRes);
}
  1. Start the GAPRole task. This involves passing function pointers to application callback function to the central GAPRole. This should also occur in the application initialization function.
VOID GAPCentralRole_StartDevice(&SimpleBLECentral_roleCB);
  1. Send GAPRole commands from the application. Figure 35. is an example of the application using GAPCentralRole_StartDiscovery().
../_images/gaprole_startdisc.jpg

Figure 35. Context Diagram of Application using GAPCentralRole_StartDiscovery(). Green corresponds to the application task context, orange is the GAPRole task context and red corresponds to the protocol stack context.

The return value from the protocol stack indicates only whether or not the attempt to perform device discovery was initiated. The actual device discovered is returned asynchronously as a GAP event forwarded through the central GAPRole callbacks as described below.

  1. The GAPRole task performs some processing on the GAP events it receives from the protocol stack. The task also forwards some events to the application. Figure 36. is an example tracing the GAP_DEVICE_DISCOVERY_EVENT from the protocol stack to the application. Green corresponds to the app context. Orange corresponds to the GAPRole context. Red corresponds to the protocol stack context.
../_images/gap_dev_disc.jpg

Figure 36. Context Diagram of Stack sending a GAP_DEVICE_DISCOVERY_EVENT Event. Green corresponds to the application task context, orange is the GAPRole task context and red corresponds to the protocol stack context.

Generic Attribute Profile (GATT)

Just as the GAP layer handles most connection-related functionality, the GATT layer of the Bluetooth low energy protocol stack is used by the application for data communication between two connected devices. Data is passed and stored in the form of characteristics which are stored in memory on the Bluetooth low energy device. From a GATT standpoint, when two devices are connected they are each in one of two roles.

  • The GATT server
    the device containing the characteristic database that is being read or written by a GATT client.
  • The GATT client
    the device that is reading or writing data from or to the GATT server.

Figure 37. shows this relationship in a sample Bluetooth low energy connection where the peripheral device (that is, a CC2640R2 Launchpad) is the GATT server and the central device (that is, a smart phone) is the GATT client.

../_images/gatt_client_server.jpg

Figure 37. GATT Client and Server Interaction Overview

The GATT roles of client and server are independent from the GAP roles of peripheral and central. A peripheral can be either a GATT client or a GATT server, and a central can be either a GATT client or a GATT server. A peripheral can act as both a GATT client and a GATT server. For a hands-on review of GATT services and characteristics, see SimpleLink Academy.

GATT Characteristics and Attributes

While characteristics and attributes are sometimes used interchangeably when referring to Bluetooth low energy, consider characteristics as groups of information called attributes. Attributes are the information actually transferred between devices. Characteristics organize and use attributes as data values, properties, and configuration information. A typical characteristic is composed of the following attributes.

  • Characteristic Value
    data value of the characteristic
  • Characteristic Declaration
    descriptor storing the properties, location, and type of the characteristic value
  • Client Characteristic Configuration
    a configuration that allows the GATT server to configure the characteristic to be notified (send message asynchronously) or indicated (send message asynchronously with acknowledgment)
  • Characteristic User Description
    an ASCII string describing the characteristic

These attributes are stored in the GATT server in an attribute table. In addition to the value, the following properties are associated with each attribute.

  • Handle
    the index of the attribute in the table (Every attribute has a unique handle.)
  • Type
    indicates what the attribute data represents (referred to as a UUID [universal unique identifier]. Some of these are Bluetooth SIG-defined and some are custom.)
  • Permissions
    enforces if and how a GATT client device can access the value of an attribute

GATT Client Abstraction

Like the GAP layer, the GATT layer is also abstracted. This abstraction depends on whether the device is acting as a GATT client or a GATT server. As defined by the Bluetooth Specification, the GATT layer is an abstraction of the ATT layer.

GATT clients do not have attribute tables or profiles as they are gathering, not serving, information. Most of the interfacing with the GATT layer occurs directly from the application.

../_images/gatt_client_abstract.jpg

Figure 38. Visualization of GATT Client Abstraction.

GATT Server Abstraction

As a GATT server, most of the GATT functionality is handled by the individual GATT profiles. These profiles use the GATTServApp (a configurable module that stores and manages the attribute table). Figure 39. shows this abstraction hierarchy.

../_images/gatt_server_abstract.jpg

Figure 39. Visualization of GATT Server abstraction.

The design process involves creating GATT profiles that configure the GATTServApp module and use its API to interface with the GATT layer. In this case of a GATT server, direct calls to GATT layer functions are unnecessary. The application then interfaces with the profiles.

GATT Services and Profile

A GATT service is a collection of characteristics. For example, the heart rate service contains a heart rate measurement characteristic and a body location characteristic, among others. Multiple services can be grouped together to form a profile. Many profiles only implement one service so the two terms are sometimes used interchangeably.

Note

TI intends this section as an introduction to the attribute table by using simple_peripheral as an example. For information on how this profile is implemented within the stack, see GATT Server Abstraction.

There are four GATT profiles defined in the simple_peripheral example application project.

  • GAP GATT Service (GGS)

    This service contains device and access information such as the device name, vendor identification, and product identification.

    The following characteristics are defined for this service:

    • Device name
    • Appearance
    • Peripheral preferred connection parameters

    Note

    See Bluetooth Core_v4.2 specification (vol. 3 Part C, Ch. 12) for more information on these characteristics.

  • Generic Attribute Service

    This service contains information about the GATT server, is a part of the Bluetooth low energy protocol stack, and is required for every GATT server device as per the Bluetooth low energy specification.

  • Device Info Service

    This service exposes information about the device such as the hardware, software version, firmware version, regulatory information, compliance information, and manufacturer name. The Device Info Service is part of the Bluetooth low energy protocol stack and configured by the application.

  • simple_gatt_profile Service

    This service is a sample profile for testing and for demonstration. The full source code is provided in the simple_gatt_profile.c and simple_gatt_profile.h files.

Figure 40. shows the attribute table in the simple_peripheral project.

../_images/sbp_attr_table.jpg

Figure 40. Simple GATT Profile Characteristic Table taken with BTool. Red indicates a Profile declaration, Yellow indicates character declaration, and White indicates Attributes related to a particular characteristic declaration.

The simple_gatt_profile contains the following characteristics:

  • SIMPLEPROFILE_CHAR1
    1-byte value that can be read or written from a GATT client device
  • SIMPLEPROFILE_CHAR2
    1-byte value that can be read from a GATT client device but cannot be written
  • SIMPLEPROFILE_CHAR3
    1-byte value that can be written from a GATT client device but cannot be read
  • SIMPLEPROFILE_CHAR4
    1-byte value that cannot be directly read or written from a GATT client device (This value is notifiable: This value can be configured for notifications to be sent to a GATT client device.)
  • SIMPLEPROFILE_CHAR5
    5-byte value that can be read (but not written) from a GATT client device

The following is a line-by-line description of the simple profile attribute table, referenced by the following handle.

  • 0x001C is the simple_gatt_profile service declaration.

    This declaration has a UUID of 0x2800 (Bluetooth-defined GATT_PRIMARY_SERVICE_UUID). The value of this declaration is the UUID of the simple_gatt_profile (custom-defined).

  • 0x001D is the SimpleProfileChar1 characteristic declaration.

    This declaration can be thought of as a pointer to the SimpleProfileChar1 value. The declaration has a UUID of 0x2803 (Bluetooth-defined GATT_CHARACTER_UUID). The value of the declaration characteristic, as well as all other characteristic declarations, is a 5-byte value explained here (from MSB to LSB):

    • Byte 0 is the properties of the SimpleProfileChar1 as defined in the Bluetooth specification (The following are some of the relevant properties.)
      • 0x02: permits reads of the characteristic value
      • 0x04: permits writes of the characteristic value (without a response)
      • 0x08: permits writes of the characteristic value (with a response)
      • 0x10: permits of notifications of the characteristic value (without acknowledgment)
      • 0x20: permits notifications of the characteristic value (with acknowledgment)

    The value of 0x0A means the characteristic is readable (0x02) and writeable (0x08).

    • Bytes 1-2: the byte-reversed handle where the SimpleProfileChar1 svalue is (handle 0x0021)
    • Bytes 3-4: the UUID of the SimpleProfileChar1 value (custom-defined 0xFFF1)
  • 0x001E is the SimpleProfileChar1 Characteristic Value

    This value has a UUID of 0xFFF1 (custom-defined). This value is the actual payload data of the characteristic. As indicated by its characteristic declaration (handle 0x0020), this value is readable and writable.

  • 0x001F is the SimpleProfileChar1 Characteristic User Description

    This description has a UUID of 0x2901 (Bluetooth-defined). The value of this description is a user-readable string describing the characteristic.

  • 0x0020 - 0x002C

    are attributes that follow the same structure as the simpleProfileChar1 described previously with regard to the remaining four characteristics. The only different attribute, handle 0x0028, is described as follows.

    0x0028 is the SimpleProfileChar4 Client Characteristic Configuration. This configuration has a UUID of 0x2902 (Bluetooth-defined). By writing to this attribute, a GATT server can configure the SimpleProfileChar4 for notifications (writing 0x0001) or indications (writing 0x0002). Writing 0x0000 to this attribute disable notifications and indications.

GATT Security

As described in GATT Server Abstraction, the GATT server may define permissions independently for each characteristic. The server may allow some characteristics to be accessed by any client, while limiting access to other characteristics to only authenticated or authorized clients. These permissions are usually defined as part of a higher level profile specification. For custom profiles, the user may select the permissions as they see fit. For more information about the GATT Security, refer to the Bluetooth Core_v4.2 specification (Vol 3, Part G, section 8).

Authentication

Characteristics that require authentication cannot be accessed until the client has gone through an authenticated pairing method. This verification is performed within the stack, with no processing required by the application. The only requirement is for the characteristic to be registered properly with the GATT server.

For example, characteristic 5 of the simple_gatt_profile allows on authenticated reads.

// Characteristic Value 5
{
   { ATT_BT_UUID_SIZE, simpleProfilechar5UUID },
   GATT_PERMIT_AUTHEN_READ,
   0,
   simpleProfileChar5
},

When an un-authenticated client attempts to read this value, the GATT server automatically rejects it with ERROR_INSUFFICIENT_AUTHEN (0x41), without invoking the simpleProfile_ReadAttrCB(). See an example of this in Sniffer Capture Example.

../_images/image134.jpeg

Figure 41. Sniffer Capture Example

After the client has successfully authenticated, read/write requests are forwarded to the profiles read/write callback. See the code below for a simple_gatt_profile example:

case SIMPLEPROFILE_CHAR5_UUID:
*pLen = SIMPLEPROFILE_CHAR5_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR5_LEN );
break;
Authorization

Authorization is a layer of security provided in addition to what BLE already implements. Because applications are required to define their own requirements for authorization, the stack forwards read/write requests on these characteristics to the application layer of the profile.

For the profile to register for authorization information from the GATT server, it must define an authorization callback with the stack. The simple_gatt_profile does not do this by default, but below is an example of how it could be modified to do this.

  1. Register profile level authorization callback.
CONST gattServiceCBs_t simpleProfileCBs =
{
   simpleProfile_ReadAttrCB,      // Read callback function pointer
   simpleProfile_WriteAttrCB,     // Write callback function pointer
   simpleProfile_authorizationCB  // Authorization callback function pointer
};
  1. Implement authorization callback code.
static bStatus_t simpleProfile_authorizationCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 opcode )
{
   //This is just an example implementation, normal use cases would require
   //more complex logic to determine that the device is authorized

   if(clientIsAuthorized)
      return SUCCESS;
   else
      return ATT_ERR_INSUFFICIENT_AUTHOR;
}

The authorization callback executes in the stack context, thus intensive processing should not be performed in this function. The implementation is left up to the developer; the above callback should be treated as a shell. The return value should be either SUCCESS if the client is authorized to access the characteristic, or ATT_ERR_INSUFFICIENT_AUTHOR if they have not yet obtained proper authorization. Authorization requires the connection to be authenticated beforehand, or ATT_ERR_INSUFFICIENT_AUTHEN will be sent as an error response.

If a characteristic that requires authorization is registered with the GATT server, but no application level authorization callback is defined, the stack will return ATT_ERR_UNLIKELY. Because this error can be cryptic, TI recommends using an authorization callback.

Using the GATT Layer Directly

This section describes how to use the GATT layer in the application. The functionality of the GATT layer is implemented in the library but header functions can be found in the gatt.h file. ATT_GATT has the complete API for the GATT layer. The Bluetooth Core_v4.2 specification provides more information on the functionality of these commands. These functions are used primarily for GATT client applications. A few server-specific functions are described in the API. Most of the GATT functions returns ATT events to the application, review ATT_GATT for details.

The general procedure to use the GATT layer when functioning as a GATT client (that is, in the simple_central project) is as follows:

  1. Initialize the GATT client.

    VOID GATT_InitClient();
    
  2. Register to receive incoming ATT indications and notifications.

    GATT_RegisterForInd(selfEntity);
    
  3. Perform a GATT client procedure.

    Note

    The example uses GATT_WriteCharValue(), which is triggered when modifying a characteristic value in the simple_central example application.

    ../_images/gatt_write_char.jpg

    Figure 42. Context Diagram of the Application utilizing GATT_WriteCharValue(). Green corresponds to the application task context and Red corresponds to the protocol stack context.

  4. Receive and handle the response to the GATT client procedure in the application. In Figure 43., the application receives an ATT_WRITE_RSP event from the stack.

    ../_images/gatt_attr_write_rsp_event.jpg

    Figure 43. Context diagram of stack sending a ATT_WRITE_RSP event. Green corresponds to the application task context and red corresponds to the protocol stack context.

    Note

    Even though the event sent to the application is an ATT event, it is sent as a GATT protocol stack message (GATT_MSG_EVENT).

  5. A GATT client may also receive asynchronous data from the GATT server as indications or notifications other than receiving responses to its own commands. Registering to receive these ATT notifications and indications is required in Step 2. These notifications and indications are also be sent as ATT events in GATT messages to the application. These must be handled as described in GATT Services and Profile.

GAP GATT Service (GGS)

The GAP GATT Service (GGS) is required for low-energy devices that implement the central or peripheral role. Multirole devices that implement either of these roles must also contain the GGS. The purpose of the GGS is to aide in the device discovery and connection initiation process.

Note

For more information about the GGS, refer to the Bluetooth Core_v4.2 specification (Vol 3, Part C, section 12).

Using the GGS

This section describes what the application must do to configure, start, and use the GAP Gatt Service. The GGS is implemented as part of the Bluetooth Low Energy library code, the API can be found in GATTServApp. ATT_GATT describes the full API, including commands, configurable parameters, events, and callbacks.

  1. Include header
#include "gapgattserver.h"
  1. Initialize GGS parameters.
1
2
3
4
// GAP GATT Attributes
static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "Simple BLE Peripheral";

GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName);
  1. Initialize application callbacks with GGS (optional). This notifies the application when any of the characteristics in the GGS have changed.
GGS_RegisterAppCBs(&appGGSCBs);
  1. Add the GGS to the GATT server.
bStatus_t GGS_AddService(GATT_ALL_SERVICES);

Generic Attribute Profile Service (GATT Service)

The Generic Attribute Profile (GATT) Service provides information about the GATT services registered with a device.

Note

For more information on GATT, refer to Bluetooth Core_v4.2 specification (Vol 3, Part G, section 7).

The service changed characteristic is used to inform bonded devices that services have changed on the server upon reconnection. Service changed updates are sent in the form of GATT indications, and the service changed characteristic is not writeable or readable. In the TI Bluetooth low energy stack, the service changed characteristic is implemented as part of the gattservapp, which is part of library code.

Per the TI Bluetooth low energy stack spec, it is safe for server devices whose characteristic tables do not change over their lifetime to exclude the service changed characteristic. Support for indications from this characteristic must be supported on GATT client devices.

Using the GATT Service

This section describes what the user must do to enable the GATT service changed feature in the Bluetooth low energy stack. Once the service changed feature is enabled, the GAPBondMgr will handle sending the service changed indication to clients who have enabled it using the CCCD.

  1. Use a supported build config for the stack; only stack libraries with v4.1 features and L2CAP connection-oriented channels will support the service changed characteristic.

    Enable this feature with the project’s build_config.opt file, by uncommenting the following line:

    -DBLE_V41_FEATURES=L2CAP_COC_CFG+V41_CTRL_CFG
    
  2. From this point, the GAPBondMgr handles sending an indication to connected clients when the service has changed and the CCCD is enabled. If the feature is enabled, the peripheral role invokes the necessary APIs to send the indication through the GAPBondMgr.

  3. On the client side, service changed indications can be registered using the same method as registering for other indications.

GATTServApp Module

The GATTServApp stores and manages the application-wide attribute table. Various profiles use this module to add their characteristics to the attribute table. The Bluetooth low energy stack uses this module to respond to discovery requests from a GATT client. For example, a GATT client may send a Discover all Primary Characteristics message. The Bluetooth low energy stack on the GATT server receives this message and uses the GATTServApp to find and send over-the-air all of the primary characteristics stored in the attribute table. This type of functionality is beyond the scope of this document and is implemented in the library code. The GATTServApp functions accessible from the profiles are defined in gattservapp_util.c and described in the API in GATTServApp. These functions include finding specific attributes and reading or modifying client characteristic configurations. See Figure 44. for an example of how GATTServApp functions in an application.

../_images/gatt_att_table_init.jpg

Figure 44. Attribute Table Initialization Diagram.

Building up the Attribute Table

Upon power-on or reset, the application builds the GATT table by using the GATTServApp to add services. Each service consists of a list of attributes with UUIDs, values, permissions, and read and write call-backs. As Figure 44. shows, all of this information is passed through the GATTServApp to GATT and stored in the stack.

Attribute table initialization must occur in the application initialization function, that is, simple_peripheral_init().

// Initialize GATT attributes
GGS_AddService(GATT_ALL_SERVICES);         // GAP
GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes

Implementing Profiles in Attributes Table

This section describes the general architecture for implementing profiles and provides specific functional examples in relation to the simple_gatt_profile in the simple_peripheral project. See GATT Services and Profile for an overview of the simple_gatt_profile.

Attribute Table Definition

Each service or group of GATT attributes must define a fixed size attribute table that gets passed into GATT. This table in simple_gatt_profile.c is defined as the following.

static gattAttribute_t simpleProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED]

Each attribute in this table is of the following type.

typedef struct attAttribute_t
{
   gattAttrType_t type; //!< Attribute type (2 or 16 octet UUIDs)
   uint8 permissions; //!< Attribute permissions

   uint16 handle; //!< Attribute handle - assigned internally by attribute server

   uint8* const pValue; //!< Attribute value - encoding of the octet array is defined in
                        //!< the applicable profile. The maximum length of an attribute
                        //!< value shall be 512 octets.
} gattAttribute_t;

When utilizing gattAttribute_t, the various fields have specific meanings.

  • gattAttrType_t type

    type is the UUID associated with the attribute being placed into the table. gattAttrType_t itself is defined as:

    typedef struct
    {
       uint8 len;         //!< Length of UUID (2 or 16)
       const uint8 *uuid; //!<Pointer to UUID
    } gattAttrType_t;
    

    Where length can be either ATT_BT_UUID_SIZE (2 bytes), or ATT_UUID_SIZE (16 bytes). The *uuid is a pointer to a number either reserved by Bluetooth SIG (defined in gatt_uuid.c) or a custom UUID defined in the profile.

  • uint8 permissions

    enforces how and if a GATT client device can access the value of the attribute. Possible permissions are defined in gatt.h as follows:

    #define GATT_PERMIT_READ 0x01 //!< Attribute is Readable
    #define GATT_PERMIT_WRITE 0x02 //!< Attribute is Writable
    #define GATT_PERMIT_AUTHEN_READ 0x04 //!< Read requires Authentication
    #define GATT_PERMIT_AUTHEN_WRITE 0x08 //!< Write requires Authentication
    #define GATT_PERMIT_AUTHOR_READ 0x10 //!< Read requires Authorization
    #define GATT_PERMIT_AUTHOR_WRITE 0x20 //!< Write requires Authorization
    #define GATT_PERMIT_ENCRYPT_READ 0x40 //!< Read requires Encryption
    #define GATT_PERMIT_ENCRYPT_WRITE 0x80 //!< Write requires Encryption

    Allocating Memory for GATT Procedures further describes authentication, authorization, and encryption.

  • uint16 handle

    handle is a placeholder in the table where GATTServApp assigns a handle. This placeholder is not customizable. Handles are assigned sequentially.

  • uint8* const pValue

    pValue is a pointer to the attribute value. The size is unable to change after initialization. The maximum size is 512 octets.

Service Declaration

Consider the following simple_gatt_profile service declaration attribute:

// Simple Profile Service
{
   { ATT_BT_UUID_SIZE, primaryServiceUUID },    // type
   GATT_PERMIT_READ,                            // permissions
   0,                                           // handle
   (uint8 *)&simpleProfileService               // pValue
}

The type is set to the Bluetooth SIG-defined primary service UUID (0x2800). A GATT client is permitted to read this service with the permission is set to GATT_PERMIT_READ. The pValue is a pointer to the UUID of the service, custom-defined as 0xFFF0. SimpleProfileService itself is defined to reference the profile’s UUID.

// Simple Profile Service attribute
static CONST gattAttrType_t simpleProfileService =
   {
      ATT_BT_UUID_SIZE,
      simpleProfileServUUID
   };
Characteristic Declaration

Consider the following simple_gatt_profile simpleProfileCharacteristic1 declaration.

// Characteristic 1 Declaration
{
   { ATT_BT_UUID_SIZE, characterUUID },
   GATT_PERMIT_READ,
   0,
   &simpleProfileChar1Props
},

The type is set to the Bluetooth SIG-defined characteristic UUID (0x2803). This makes simpleProfileCharacteristic1 read only to the GATT client with the permissions set to GATT_PERMIT_READ.

For functional purposes, the only information required to be passed to the GATTServApp in pValue is a pointer to the properties of the characteristic value. The GATTServApp adds the UUID and the handle of the value. These properties are defined to allow this particular characteristic in this service to be read and written to.

// Simple Profile Characteristic 1 Properties
static uint8 simpleProfileChar1Props = GATT_PROP_READ | GATT_PROP_WRITE;

Note

An important distinction exists between these properties and the GATT permissions of the characteristic value. These properties are visible to the GATT client stating the properties of the characteristic value. The GATT permissions of the characteristic value affect its functionality in the protocol stack. These properties must match that of the GATT permissions of the characteristic value.

Characteristic Value

Consider the simple_gatt_profile simpleProfileCharacteristic1 value.

// Characteristic Value 1
{
{ ATT_BT_UUID_SIZE, simpleProfilechar1UUID },
  GATT_PERMIT_READ | GATT_PERMIT_WRITE,
  0,
  &simpleProfileChar1
},

The type is set to the custom-defined simpleProfilechar1 UUID (0xFFF1). The properties of the characteristic value in the attribute table must match the properties from the characteristic value declaration. The pValue is a pointer to the location of the actual value, statically defined in the profile as follows.

// Characteristic 1 Value
static uint8 simpleProfileChar1 = 0;
Client Characteristic Configuration

Consider the simple_gatt_profile simpleProfileCharacteristic4 configuration.

// Characteristic 4 configuration
{
{ ATT_BT_UUID_SIZE, clientCharCfgUuID },
  GATT_PERMIT_READ | GATT_PERMIT_WRITE,
  0,
  (uint8 *)&simpleProfileChar4Config
 },

The type is set to the Bluetooth SIG-defined client characteristic configuration UUID (0x2902) GATT clients must read and write to this attribute so the GATT permissions are set to readable and writable. The pValue is a pointer to the location of the client characteristic configuration array, defined in the profile as follows.

Listing 47. simple_gatt_profile characteristic 4 pValue pointer define. :linenos:
static gattCharCfg_t *simpleProfileChar4Config;

Note

Client characteristic configuration is represented as an array because this value must be cached for each connection. The catching of the client characteristic configuration is described in more detail in Add Service Function.

Add Service Function

As described in GATTServApp Module, when an application starts up it requires adding the GATT services it supports. Each profile needs a global AddService function that can be called from the application. Some of these services are defined in the protocol stack, such as GAP GATT Service and GATT Service. User-defined services must expose their own AddService function that the application can call for profile initialization. Using SimpleProfile_AddService() as an example, these functions should do as follows.

  • Allocate space for the client characteristic configuration (CCC) arrays. As an example, a pointer to one of these arrays was initialized in the profile as described in ref:client_characteristic_configuration.

    In the AddService function, the supported connections is declared and memory is allocated for each array. Only one CCC is defined in the imple_gatt_profile but there can be multiple CCCs.

// Allocate Client Characteristic Configuration table
simpleProfileChar4Config = (gattCharCfg_t *)ICall_malloc(sizeof(gattCharCfg_t) * linkDBNumConns );

if ( simpleProfileChar4Config == NULL )
{
return ( bleMemAllocError );
}
  • Initialize the CCC arrays. CCC values are persistent between power downs and between bonded device connections. For each CCC in the profile, the GATTServApp_InitCharCfg() function must be called. This function tries to initialize the CCCs with information from a previously bonded connection and set the initial values to default values if not found.
1
GATTServApp_InitCharCfg( INVALID_CONHANDLE, simpleProfileChar4Config );
  • Register the profile with the GATTServApp. This function passes the attribute table of the profile to the GATTServApp so that the attributes of the profile are added to the application-wide attribute table managed by the protocol stack and handles are assigned for each attribute. This also passes pointers to the callbacks of the profile to the stack to initiate communication between the GATTServApp and the profile.
// Register GATT attribute list and CBs with GATT Server App
status = GATTServApp_RegisterService( simpleProfileAttrTbl,
                                      GATT_NUM_ATTRS( simpleProfileAttrTbl ),
                                      GATT_MAX_ENCRYPT_KEY_SIZE,
                                      &simpleProfileCBs );

Register Application Callback Function

Profiles can relay messages to the application using callbacks. In the simple_peripheral project, the simple_gatt_profile calls an application callback whenever the GATT client writes a characteristic value. For these application callbacks to be used, the profile must define a Register Application Callback function that the application uses to set up callbacks during its initialization. The register application callback function fo the simple_gatt_profile is the following.

Listing 48. Register application callbacks.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
bStatus_t SimpleProfile_RegisterAppCBs( simpleProfileCBs_t *appCallbacks )
{
   if ( appCallbacks )
   {
      simpleProfile_AppCBs = appCallbacks;
      return ( SUCCESS );
   }

      else
      {
         return ( bleAlreadyInRequestedMode );
   }
}

Where the callback typedef is defined as the following.

typedef struct
{
   simpleProfileChange_t pfnSimpleProfileChange; // Called when characteristic value changes
} simpleProfileCBs_t;

The application must then define a callback of this type and pass it to the simple_gatt_profile with the SimpleProfile_RegisterAppCBs() function. This occurs in simple_peripheral.c as follows.

//Simple GATT Profile Callbacks
#ifndef FEATURE_OAD_ONCHIP
static simpleProfileCBs_t SimpleBLEPeripheral_simpleProfileCBs =
{
   SimpleBLEPeripheral_charValueChangeCB // Characteristic value change callback
};
#endif //!FEATURE_OAD_ONCHIP

//...

//Register callback with SimpleGATTprofile
SimpleProfile_RegisterAppCBs(&SimpleBLEPeripheral_simpleProfileCBs);

See Read and Write Callback Functions for further information on how this callback is used.

Read and Write Callback Functions

The profile must define Read and Write callback functions which the protocol stack calls when one of the attributes of the profile are written to or read from. The callbacks must be registered with GATTServApp as mentioned in Add Service Function. These callbacks perform the characteristic read or write and other processing (possibly calling an application callback) as defined by the specific profile.

Read Request from Client

When a read request from a GATT Client is received for a given attribute, the protocol stack checks the permissions of the attribute and, if the attribute is readable, call the read call-back profile. The profile copies in the value, performs any profile-specific processing, and notifies the application if desired. This procedure is illustrated in Figure 45. for a read of simpleprofileChar1 in the simple_gatt_profile. Red corresponds to processing in the protocol stack context.

../_images/gatt_cb.jpg

Figure 45. Callback flow diagram.

The processing is in the context of the protocol stack. If any intensive profile-related processing that must be done in the case of an attribute read, this should be split up and done in the context of the Application task. See the Write Request from Client for more information.

Write Request from Client

When a write request from a GATT client is received for a given attribute, the protocol stack checks the permissions of the attribute and, if the attribute is write, call the write callback of the profile. The profile stores the value to be written, performs any profile-specific processing, and notifies the application if desired. Figure 46. shows this procedure for a write of simpleprofileChar3 in the simple_gatt_profile. Red corresponds to processing in the protocol stack context and green is processing in the application context.

../_images/gatt_write_req_from_client.jpg

Figure 46. Write request from client flow diagram.

Important

Minimizing the processing in protocol stack context is important. In this example, additional processing beyond storing the attribute write value in the profile (that is, writing to the LCD) occurs in the application context by enqueuing a message in the queue of the application.

Get and Set Functions

The profile containing the characteristics shall provide set and get abstraction functions for the application to read and write a characteristic of the profile. The set parameter function also includes logic to check for and implement notifications and indications if the relevant characteristic has notify or indicate properties. Figure 47. and the following code show this example for setting simpleProfileChacteristic4 in the simple_gatt_profile.

../_images/image133.jpeg

Figure 47. Get and Set Profile Parameter.

For example, the application initializes simpleProfileCharacteristic4 to 0 in simple_peripheral.c through the following.

uint8_t charValue4 = 0;
SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, sizeof(uint8_t), &charValue4);

The code for this function is displayed in the following code snippet (from simple_gatt_profile.c). Besides setting the value of the static simpleProfileChar4, this function also calls GATTServApp_ProcessCharCfg() because it has notify properties. This action forces GATTServApp to check if notifications have been enabled by the GATT Client. If so, the GATTServApp sends a notification of this attribute to the GATT Client.

Listing 49. Set characteristic 4 in SimpleProfile_SetParameter(). :linenos:
bStatus_t SimpleProfile_SetParameter( uint8 param, uint8 len, void *value )
{
bStatus_t ret = SUCCESS switch ( param )
{
case SIMPLEPROFILE_CHAR4:
if ( len == sizeof ( uint8 ) )
{
simpleProfileChar4 = *((uint8*)value);

// See if Notification has been enabled
GATTServApp_ProcessCharCfg( simpleProfileChar4Config, &simpleProfileChar4, FALSE,
                            simpleProfileAttrTbl, GATT_NUM_ATTRS( simpleProfileAttrTbl ),
                            INVALID_TASK_ID, simpleProfile_ReadAttrCB );
}

Queued Writes

Prepare Write commands allows a GATT server to send more payload data by queuing up multiple write requests. The default queue size is 5. With a default MTU of 23 and payload of 18 bytes, up to 90 bytes of payload can be sent. Refer to Bluetooth Core_v4.2 specification (Vol 3, Part F, Section 3.4.6) for more information on queued writes.

Adjust the Prepare Write queue with GATTServApp_SetParameter() with parameter GATT_PARAM_NUM_PREPARE_WRITES. There is no specified limit, but it is bounded by the available HEAPMGR space.

Allocating Memory for GATT Procedures

To support fragmentation, GATT and ATT payload structures must be dynamically allocated for commands sent wirelessly. For example, a buffer must be allocated when sending a GATT_Notification. The stack does this allocation if the preferred method to send a GATT notification or indication is used: calling a SetParameter function of the profile (that is, SimpleProfile_SetParameter()) and calling GATTServApp_ProcessCharCfg() as described in Get and Set Functions.

If using GATT_Notification() or GATT_Indication() directly, memory management must be added as follows.

  1. Try to allocate memory for the notification or indication payload using GATT_bm_alloc().
  2. Send notification or indication using GATT_Notification() or GATT_Indication() if the allocation succeeds.

Note

If the return value of the notification or indication is SUCCESS (0x00), the stack freed the memory.

  1. Use GATT_bm_free() to free the memory if the return value is something other than SUCCESS (for example, blePending).

    The following is an example of this in the GATTServApp_SendNotiInd function in the gattservapp_util.c file.

Listing 50. gattServApp_SendNotiInd().
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
noti.pValue = (uint8 *)GATT_bm_alloc( connHandle, ATT_HANDLE_VALUE_NOTI, GATT_MAX_MTU, &len );

if ( noti.pValue != NULL )
{
   status = (*pfnReadAttrCB)( connHandle, pAttr, noti.pValue, &noti.len, 0, len, GATT_LOCAL_READ );

   if ( status == SUCCESS )
   {
      noti.handle = pAttr->handle;

      if ( cccValue & GATT_CLIENT_CFG_NOTIFY )
      {
         status = GATT_Notification( connHandle, &noti, authenticated );
      }

      else // GATT_CLIENT_CFG_INDICATE
      {
         status = GATT_Indication( connHandle, (attHandleValueInd_t *)&noti, authenticated, taskId );
      }
   }

   if ( status != SUCCESS )
   {
      GATT_bm_free( (gattMsg_t *)&noti, ATT_HANDLE_VALUE_NOTI );
   }
}

else
{
   status = bleNoResources;
}

For other GATT procedures, take similar steps as noted in ATT_GATT.

Registering to Receive Additional GATT Events in the Application

Using GATT_RegisterForMsgs(), receiving additional GATT messages to handle certain corner cases is possible. This possibility can be seen in simple_peripheral_processGATTMsg(). The following three cases are currently handled.

  • GATT server in the stack was unable to send an ATT response (due to lack of available HCI buffers): Attempt to transmit on the next connection interval. Additionally, a status of bleTimeout is sent if the ATT transaction is not completed within 30 seconds, as specified in the core spec.
Listing 51. See if GATT server was unable to transmit an ATT response. :linenos:
// See if GATT server was unable to transmit an ATT response
if (pMsg->hdr.status == blePending)
{
//No HCI buffer was available. Let's try to retransmit the response
//on the next connection event.

if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity, SBP_CONN_EVT_END_EVT) == SUCCESS)
{
//First free any pending response
 SimpleBLEPeripheral_freeAttRsp(FAILURE);

 //Hold on to the response message for retransmission
 pAttRsp = pMsg;

 //Don't free the response message yet
 return (FALSE);
 }
 }
  • An ATT flow control violation: The application is notified that the connected device has violated the ATT flow control specification, such as sending a Read Request before an Indication Confirm is sent.

    No more ATT requests or indications can be sent wirelessly during the connection. The application may want to terminate the connection due to this violation. As an example in simple_peripheral, the LCD is updated.

Listing 52. ATT flow control violation.
1
2
3
4
5
6
7
8
9
else if (pMsg->method == ATT_FLOW_CTRL_VIOLATED_EVENT)
{
  //ATT request-response or indication-confirmation flow control is
  //violated. All subsequent ATT requests or indications will be dropped.
  //The app is informed in case it wants to drop the connection.

  //Display the opcode of the message that caused the violation.
  DISPLAY_WRITE_STRING_VALUE("FC Violated: %d", pMsg->msg.flowCtrlEvt.opcode, LCD_PAGE5);
}
  • An ATT MTU size is updated: The application is notified in case this affects its processing in any way. See Maximum Transmission Unit (MTU) for more information on the MTU. As an example in simple_peripheral, the LCD is updated.
Listing 53. MTU updated.
1
2
3
4
5
else if (pMsg->method == ATT_MTU_UPDATED_EVENT)
{
    // MTU size updated
    DISPLAY_WRITE_STRING_VALUE("MTU Size: $d", pMsg->msg.mtuEvt.MTU, LCD_PAGE5);
}

GAP Bond Manager and LE Secure Connections

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

Table 11. GAP Bond Manager Terminology
Term Description
Pairing The process of exchanging keys
Encryption Data is encrypted after pairing, or re-encryption (a subsequent connection where keys are looked up from nonvolatile memory).
Authentication The pairing process complete with MITM (Man in the Middle) protection.
Bonding Storing the keys in nonvolatile memory to use for the next encryption sequence.
Authorization An additional application level key exchange in addition to authentication
OOB Out of Band. Keys are not exchanged over the air, but rather over some other source such as serial port or NFC. This also provides MITM protection.
MITM Man in the Middle protection. This prevents an attacker from listening to the keys transferred over the air to break the encryption.
Just Works Pairing method where keys are transferred over the air without MITM

The general process that the GAPBondMgr uses is as follows.

  1. The pairing process exchanges keys through the following methods described in Selection of Pairing Mode.
  2. Encrypt the link with keys from Step 1.
  3. The bonding process stores keys in secure flash (SNV).
  4. Use the keys stored in SNV to encrypt the link when reconnecting.

Tip

Performing all of these steps is not necessary. For example, two devices may choose to pair but not bond.

Selection of Pairing Mode

Version 4.2 of the Bluetooth Spec introduces a Secure Connections feature to upgrade BLE pairing. For a detailed description of the algorithms used for Secure Connections, see Bluetooth Core_v4.2 specification (Section 5.1 of Vol 1, Part A). The previous pairing methods used in the 4.1 and 4.0 Bluetooth Specs are still available, and are now defined as LE legacy pairing. The main difference is that Secure Connection uses Elliptic Curve Diffie-Hellman cryptography, while LE legacy pairing does not.

There are four types of pairing models, each of which are described in detail in GAPBondMgr Examples for Different Pairing Modes:

  • just works (Secure Connections or LE Legacy)
  • passkey entry (Secure Connections or LE Legacy)
  • numeric comparison (Secure Connections)
  • Out of Band (Secure Connections or LE Legacy)

The selection of the association model and whether or not pairing will succeed is based upon the following parameters (all tables from Section 2.3.5.1 of Vol 3, Part H of the Bluetooth Core_v4.2 specification). The GAPBondMgr parameters, as they map to the table parameters below are listed here. For more information on these parameters, see GAPBondMgr.

  • GAPBOND_LOCAL_OOB_SC_ENABLED: OOB Set / Not Set

  • GAPBOND_MITM_PROTECTION: MITM Set / Not Set

  • GAPBOND_IO_CAPABILITIES: IO Capabilities

  • GAPBOND_SECURE_CONNECTION: secure connections supported / not supported

    Beyond what the spec defines, this parameter also affects whether or not pairing succeeds, as described in GAPBondMgr.

If both devices support secure connections, use Figure 48. to decide upon the next step.

../_images/image136.jpeg

Figure 48. Parameters With Secure Connections.

If at least one device does not support secure connections, use Figure 49. to decide upon the next step.

../_images/image137.jpeg

Figure 49. Parameters Without Secure Connections.

If, based on one of the previous tables, IO capabilities are to be used to determine the association model, use Figure 50.

../_images/image138.jpeg

Figure 50. Parameters With IO Capabilities

Using GAPBondMgr

This section describes what the application must do to configure, start, and use the GAPBondMgr. The GAPRole handles some of the GAPBondMgr functionality. The GAPBondMgr is defined in gapbondmgr.c and gapbondmgr.h. GAPBondMgr describes the full API including commands, configurable parameters, events, and callbacks. The project being considered here is the security_examples_central project available from the BLE Examples page in TI SimpleLink GitHub Code Examples. The general steps to use the GAPBondMgr module are as follows:

  1. Configure the stack to include GAPBondMgr functionality and, if desired, secure connections. Define the following in build_config.opt in the stack project:
    • DGAP_BOND_MGR
    • DBLE_V42_FEATURES=SECURE_CONNS_CFG
  2. The stack must also be configured to use 1 or 2 SNV pages, by defining OSAL_SNV=1 or OSAL_SNV=2 as a preprocessor-defined symbol in the stack project.
  3. If using Secure Connections, the PDU size must be >= 69. This can be set by defining the following preprocessor symbol in the application project: MAX_PDU_SIZE=69. Also, the minimum heap size that can be used with Secure Connections is 3690.
  4. Configure the GAPBondMgr by initializing its parameters as desired. See ATT_GATT for a complete list of parameters with functionality described. There are examples of this for the various pairing / bonding modes in GAPBondMgr Examples for Different Pairing Modes.
  5. Register application callbacks with the GAPBondMgr, so that the application can communicate with the GAPBondMgr and be notified of events. See an example of this in the simple_central project and ATT_GATT for the callback definitions.
// Register with bond manager after starting device
GAPBondMgr_Register(&security_examples_central_bondCB);

Note

This should also occur in the application initialization function after the GAPRole profile is started (that is, GAPCentralRole_StartDevice()).

  1. Once the GAPBondMgr is configured, it operates mostly autonomously from the perspective of the application. When a connection is established, it initiates pairing and bonding, depending on the configuration parameters set during initialization, and communicates with the application as needed through the defined callbacks.

    A few parameters can be set and functions called asynchronously at any time from the application. See GAPBondMgr for more information.

    Most communication between the GAPBondMgr and the application at this point occurs through the callbacks which were registered in Step 2. Figure 51. is a flow diagram example from simple_central of the GAPBondMgr notifying the application that pairing has completed. The same method occurs for various other events and will be expanded upon in the following section. In this diagram, red corresponds to processing in the protocol stack context and green to the application context.

../_images/gapbondmgr_flow_diagram.jpg

Figure 51. Flow Diagram Example.

GAPBondMgr Examples for Different Pairing Modes

This section provides message diagrams for the types of security that can be implemented. These modes assume acceptable I/O capabilities are available for the security mode, and that the selection of whether or not to support Secure Connections allows for the pairing mode. See the Selection of Pairing Mode on how these parameters affect pairing. These examples only consider the pairing aspect. Bonding can be added to each type of pairing in the same manner and is shown in the next section.

Caution

The code snippets here are not complete functioning examples, and are only intended for illustration purposes. See the security_examples for a complete example.

Pairing Disabled

With pairing set to FALSE, the BLE stack automatically rejects any attempt at pairing. Configure the GAPBondMgr as follows to disable pairing:

uint8 pairMode = GAPBOND_PAIRING_MODE_NO_PAIRING;
GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &pairMode);
Just Works Pairing

Just Works pairing allows encryption without MITM authentication and is vulnerable to MITM attacks. Just Works pairing can be LE Legacy or Secure Connections pairing. The GAPBondMgr does not need any additional input from the application for just works pairing. Configure the GAPBondMgr for Just Works pairing as follows.

uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;
uint8_t mitm = FALSE;
GAPBondMgr_SetParameter( GAPBOND_PAIRING_MODE, sizeof (uint8_t), &pairMode);
GAPBondMgr_SetParameter( GAPBOND_MITM_PROTECTION, sizeof (uint8_t), &mitm);

Figure 52. describes the interaction between the GAPBondMgr and the application for Just Works pairing. As shown, the application receives a GAPBOND_PAIRING_STATE_STARTED event once the pairing request has been sent, and a GAPBOND_PAIRING_STATE_COMPLETE event once the pairing process has completed. At this time, the link is encrypted.

../_images/gap_justworks_fig.jpg

Figure 52. Just Works Pairing.

Passcode Entry

Passkey entry is a type of authenticated pairing that can prevent MITM attacks. It can be either LE Legacy pairing or Secure Connections pairing. In this pairing method, one device displays a 6-digit passcode, and the other device enters the passcode. As described in Selection of Pairing Mode, the IO capabilities decide which device performs which role. The passcode callback registered with the GAPBondMgr when it was started is used to enter or display the passcode. The following is an example of initiating Passcode Entry pairing where the passcode is displayed.

  1. Define passcode callback
Listing 54. Define passcode callback.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Bond Manager Callbacks
static gapBondCBs_t security_examples_central_bondCB =
{
  (pfnPasscodeCB_t)security_examples_central_passcodeCB, //Passcode callback
  security_examples_central_pairStateCB                  //Pairing state callback
};

static void security_examples_central_passcodeCB(uint8_t *deviceAddr, uint16_t connHandle, uint8_t uiInputs, uint8_t uiOutputs, uint32_t numComparison)
{
  gapPasskeyNeededEvent_t *pData; // Allocate space for the passcode event.

  if ((pData = ICall_malloc(sizeof(gapPasskeyNeededEvent_t))))
  {
    memcpy(pData->deviceAddr, deviceAddr, B_ADDR_LEN);
    pData->connectionHandle = connHandle;
    pData->uiInputs = uiInputs;
    pData->uiOutputs = uiOutputs;

    // Enqueue the event.
    security_examples_central_enqueueMsg(SEC_PASSCODE_NEEDED_EVT, 0, (uint8_t *) pData);
  }
}
  1. Configure GAPBondMgr
Listing 55. Configure GAPBondMgr.
1
2
3
4
5
uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;
uint8_t mitm = TRUE;
GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;
uint8_t mitm = TRUE;
GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &mitm);
  1. Process passcode callback and send response to stack
Listing 56. Process passcode and send the response.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
static void security_examples_central_processPasscode(uint16_t connectionHandle, gapPasskeyNeededEvent_t *pData)
{

  if (pData->uiInputs) // if we are to enter passkey
  {
    passcode = 111111;
    // Send passcode response
    GAPBondMgr_PasscodeRsp(connectionHandle, SUCCESS, passcode);
  }

  else if (pData->uiOutputs) // if we are to display passkey
  {
    passcode = 111111;
    DISPLAY_WRITE_STRING_VALUE("Passcode: %d", passcode, LCD_PAGE4);

    // Send passcode response
    GAPBondMgr_PasscodeRsp(connectionHandle, SUCCESS, passcode);
  }
}

Depending on what the uiInputs and uiOutputs returned from the GAPBondMgr, the passcode must either be displayed or entered. The passcode is then sent to the GAPBondMgr using GAPBondMgr_PasscodeRsp(), so that pairing can continue. In this case, the password is statically set to 111111. In a real product, the password will likely be randomly generated, and the device must expose a way for the user to enter the passcode, then send it to the GAPBondMgr using GAPBondMgr_PasscodeRsp(). There is an example of this in the security_examples projects. The complete interaction between the GAPBondMgr and the application is shown in Figure 53..

../_images/gap_passcode_exchange_fig.jpg

Figure 53. Interaction Between the GAPBondMgr and the Application.

Numeric Comparison

Numeric comparison is a type of authenticated pairing that protects from MITM attacks. It is only possible as a Secure Connections pairing; not LE legacy. For numeric comparison pairing, both devices display a 6-digit code. Each device must then indicate, through a button press or some other yes-no input, whether the codes match. The passcode callback registered with the GAPBondMgr when it was started is used to display the 6-digit code. The following is an example of initiating Numeric Comparison pairing where the passcode is displayed. The IO capabilities must be set appropriately to select numeric comparison (that is, Display/Yes-No on both sides).

  1. Define passcode callback to display code.
Listing 57. Define passcode callback to display code.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Bond Manager Callbacks
static gapBondCBs_t SimpleBLECentral_bondCB =
{
  (pfnPasscodeCB_t)SimpleBLECentral_passcodeCB, //Passcode callback
  SimpleBLECentral_pairStateCB                  //Pairing state callback
};

static void SimpleBLECentral_passcodeCB (uint8_t *deviceAddr, uint16_t connHandle, uint8_t uiInputs, uint8_t uiOutputs, uint32_t numComparison)
{
  gapPasskeyNeededEvent_t *pData;

  // Allocate space for the passcode event.
  if ((pData = ICall_malloc(sizeof(gapPasskeyNeededEvent_t))))
  {
    memcpy(pData->deviceAddr, deviceAddr, B_ADDR_LEN);
    pData->connectionHandle = connHandle;
    pData->numComparison = numComparison;

    // Enqueue the event.
    security_examples_central_enqueueMsg(SEC_PASSCODE_NEEDED_EVT, 0, (uint8_t *) pData);
  1. Configure GAPBondMgr
Listing 58. Configure GAPBondMgr.
1
2
3
4
5
6
7
8
9
uint8_t pairMode = GAPBOND_PAIRING_MODE_INITIATE;
uint8_t scMode = GAPBOND_SECURE_CONNECTION_ONLY;
uint8_t mitm = TRUE;
uint8_t ioCap = GAPBOND_IO_CAP_DISPLAY_YES_NO;

GAPBondMgr_SetParameter(GAPBOND_IO_CAPABILITIES, sizeof(uint8_t), &ioCap);
GAPBondMgr_SetParameter(GAPBOND_PAIRING_MODE, sizeof(uint8_t), &pairMode);
GAPBondMgr_SetParameter(GAPBOND_MITM_PROTECTION, sizeof(uint8_t), &mitm);
GAPBondMgr_SetParameter(GAPBOND_SECURE_CONNECTION, sizeof(uint8_t), &scMode);
  1. Process passcode callback and display code.
Listing 59. Process passcode callback and display code.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void SimpleBLECentral_processPasscode (uint16_t connectionHandle, gapPasskeyNeededEvent_t *pData)
{

  if (pData->numComparison) //numeric comparison
  {

    //Display passcode
    DISPLAY_WRITE_STRING_VALUE("Num Cmp: %d", pData->numComparison, LCD_PAGE4);
  }
}
  1. Accept Yes-No input from user and send response to GAPBondMgr.
Listing 60. Accept Yes-No input from user and send response to GAPBondMgr.
1
2
3
4
5
6
if (keys & KEY_RIGHT)
{
  GAPBondMgr_PasscodeRsp(connHandle, SUCCESS, TRUE);
  DISPLAY_WRITE_STRING("Codes Match!", LCD_PAGE5);
  return;
}

In this case, the third parameter of GAPBondMgr_PasscodeRsp, which usually accepts a passcode, is overloaded to send TRUE to the stack to indicate that the codes match and to continue with pairing.

../_images/gap_numeric_comparison_fig.jpg

Figure 54. Numeric Comparison.

GAPBondMgr Example With Bonding Enabled

Bonding can enabled or disabled for any type of pairing through the GAPBOND_BONDING_ENABLED parameter, and occurs after the pairing process is complete. To enable bonding, configure the GAPBondMgr as follows:

uint8_t bonding = TRUE;
GAPBondMgr_SetParameter(GAPBOND_BONDING_ENABLED, sizeof(uint8_t), &bonding);

With bonding enabled, the GAPBondMgr stores the long-term key transferred during the pairing process to SNV. See GAPBondMgr and SNV for more information. After this is completed, the application is notified through the GAPBOND_PAIRING_STATE_COMPLETE event. GAPBOND_PAIRING_STATE_COMPLETE is only passed to the application pair state callback when initially connecting, pairing, and bonding. For future connections to a bonded device, the security keys are loaded from flash, thus skipping the pairing process. In this case, only GAPBOND_PAIRING_STATE_BONDED is passed to the application pair state callback. This is illustrated in GAPBondMgr Example With Bonding Enabled.

../_images/gapbondmgr_bonding_enabled_fig.jpg

Figure 55. GAPBondMgr Example With Bonding Enabled.

GAPBondMgr and SNV

This section describes how the GAPBondMgr uses the SNV flash area for storing bonding information. For more information on SNV itself, see Flash. The amount of bonds that can be stored is set by the GAP_BONDINGS_MAX definition, which is set to 10 by default in gapbondmgr.h. The functionality of the GAPBondMgr when there are no more available bonds varies based on whether the “least recently used” scheme is enabled. See GAPBondMgr for more information on the GAPBOND_LRU_BOND_REPLACEMENT parameter. If this parameter is set to false, it is not possible to add any more bonds without manually deleting a bond. If the parameter is set to true, the least recently used bond is deleted to make room for the new bond.

The following components comprise one bonding entry:

  1. Bond Record: this consists of the peer’s address, address type, privacy reconnection address, and state flags. This comprises 14 bytes and is defined as such:
typedef struct
{
  uint8   publicAddr[B_ADDR_LEN];     // Peer's address
  uint8   publicAddrType;             // Peer's address type
  uint8   reconnectAddr[B_ADDR_LEN];  // Privacy Reconnection Address
  uint8   stateFlags;                 // State flags: @ref GAP_BONDED_STATE_FLAGS
} gapBondRec_t;
  1. Client Characteristic Configurations (CCC): the amount of CCCs stored in each entry are set by the GAP_CHAR_CFG_MAX define. This is set to 4 by default. Each CCC is comprised of 4-bytes and is defined as follows:
typedef struct
{
  uint16 attrHandle;  // attribute handle
  uint8  value;       // attribute value for this device
} gapBondCharCfg_t;
  1. Local Long Term Key (LTK) info: this stores the local device’s encryption information. This comprises 28 bytes and is composed as such:
typedef struct
{
  uint8   LTK[KEYLEN];              // Long Term Key (LTK)
  uint16  div;  //lint -e754        // LTK eDiv
  uint8   rand[B_RANDOM_NUM_SIZE];  // LTK random number
  uint8   keySize;                  // LTK key size
} gapBondLTK_t;
  1. Connected Device Long Term Key Info: this stores the connected device’s encryption information. This is also a gapBondLTK_t and comprises 28 bytes.
  2. Connected Device Identity Resolving Key (IRK): this stores the IRK generated during pairing. This is a 16-byte array.
  3. Connected Device Sign Resolving Key (SRK): this stores the SRK generated during pairing. This is a 16-byte array.
  4. Connected Device Sign counter: this stores the sign counter generated during pairing. This is a 4-byte word.

LE Privacy 1.2

Summary

BLE-Stack 3.00.00 and newer support the privacy feature that reduces the ability to track an LE device over a period of time, by changing the Bluetooth device address on a frequent basis. LE Privacy 1.2 extends privacy to the controller by allowing the controller to both resolve peer and generate local resolvable private addresses (RPAs). It is used during connection mode and connection procedures. In BLE-Stack 3.00.00, Privacy 1.2 is always enabled. Table 12. lists the definition of terms related to the privacy feature.

Table 12. Definition of Privacy Feature Terms
Term Definition
Resolvable address A resolvable address is one that can potentially be resolved. Resolvable address Specifically, it is a device address that is a random resolvable private address (RPA).
Resolving list (RL) One or more entries of local/peer IRK pairs associated with an identity address (public or random static).
Device address A 48-bit value used to identify a device. A device address can be public or random. A device may use at least one, and can use both.
Identity (ID) address An RPA is resolved with an identity resolving key (IRK) and is associated with a public address or a random static address, known as the identity (ID) address.
Non-resolvable address A non-resolvable address is one that can never be resolved. Specifically, it is a device address that is a public address, a random static address, or a non-resolvable private address.

Theory of Operation

For a device using the privacy feature to reconnect to known devices, the device address, referred to as the private address, must be resolvable by the other device. The private address is generated using the device’s resolving identity key (IRK) exchanged during the bonding procedure.

With LE Privacy 1.2, the host is able to populate a resolving list in the controller. The resolving list consists of a set of records, where each record contains a pair of IRKs, one for local and one for peer, as well as the identity address of the peer device. A identity address of the peer device should be the public or static address of that device, which is obtained during phase 3 of pairing. The controller, which now contains all of the IRKs for previously bonded devices, is able to resolve private addresses into identity addresses of peers. These addresses are then able to be passed to the controller white list for further action, as shown in Figure 56.

../_images/resolvinglist.png

Figure 56. Resolving List.

If the controller is unable to resolve the peer RPAs, or the white list takes no actions for the incoming address, the address is still passed to the host. If the local device or peer device wishes, it can initiate a bonding sequence to exchange IRKs as well as device identity addresses. If these are exchanged, the host can use those parameters to update the controller’s resolving list, and even update the white list, so that the controller can automatically form a connection with the peer during a future connection attempt.

New HCI Commands

The following new HCI commands are now supported in the controller:

  • LE Add Device to Resolving List Command
  • LE Remove Device to Resolving List Command
  • LE Clear Resolving List Command
  • LE Read Resolving List Size Command
  • LE Read Peer Resolvable Address Command
  • LE Read Local Resolvable Address Command
  • LE Set Address Resolution Enable Command
  • LE Set Random Private Address Timeout Command

For additional details, please see Bluetooth Core_v4.2 specification (Vol 2, Part E, Section 7.8 for the commands, and Section 7.7 for the event).

Privacy and White List

Enabling Auto Sync of White List

The stack can automatically add devices to the white list after bonding. Use the following code to enable this syncing of the white list.

Listing 61. Enable syncing of white list.
1
2
uint8_t autoSyncWhiteList = TRUE;
GAPBondMgr_SetParameter(GAPBOND_AUTO_SYNC_WL, sizeof(uint8_t), &autoSyncWhiteList);
Using Resolvable Private Addresses

The device also can be configured to use a random address. Use GAP_ConfigDeviceAddr() to use random address. This API must be called after the GAP layer is started but cannot be called during any BLE activity. In the function gapRole_processGAPMsg() add the code below after gapRole_state = GAPROLE_STARTED:

//set address type to resolvable private address
status = GAP_ConfigDeviceAddr(ADDRMODE_PRIVATE_RESOLVE, NULL);

if (status != SUCCESS)
{
    System_abort("Error!");
}

It can be verified with a sniffer that the address changes when advertising. The default timeout value between private (resolvable) address changes is 15 minutes. This can be modified by GAP_SetParamValue() after calling GAP_ConfigDeviceAddr():

//Set timeout value to 5 minute
GAP_SetParamValue( TGAP_PRIVATE_ADDR_INT , 5);

// Update the advertising data
...
Testing Privacy with White List

The following steps can be made to test the privacy with white list feature:

  1. Connect a iOS device to the CC2640R2F both supporting Privacy 1.2.
  2. Pair with the device with the default passcode: 000000.
  3. The iOS devices should be automatically added to the white list.
  4. Disconnect and wait for the iOS device address to change.
  5. Reconnect to the CC2640R2F.

LE Data Length Extension

Summary

The data length extension feature allows the LE controller to send data channel packet data units (PDUs) with payloads of up to 251 bytes of application data, while in the connected state. Furthermore, a new PDU size can be negotiated by either side at any time during a connection. Previously, the controller’s largest data channel payload was 27 bytes. This increases the data rate by around 2.5*, compared to Bluetooth Core Specification version 4.0 and 4.1 devices (if both devices support extended packet length and are configured properly).

Data Length Update Procedure

Once a connection is formed, the LE controllers of the device can use the LL_LENGTH_REQ and LL_LENGTH_RSP control PDUs to negotiate a larger payload size for data channel PDUs. A data length update may be initiated by the host or performed autonomously by the controller. Either the master or the slave can initiate the procedure.

After the data length update procedure is complete, both controllers select a new data length based on two parameters: PDU size and time. The largest size supported by both local and remote controller is selected; time is taken into account to support different data rates. These parameters are defined below:

  • PDU size
    The largest application data payload size supported by the controller. This size does not include packet overhead, such as access address or preamble.
  • Time
    The maximum number of microseconds that the device takes to transmit or receive a PDU at the PHY rate. This parameter uses units of microseconds (us).

Note

Reference the Bluetooth Core Specification version 4.2 ([Vol 6], Part B, Section 5.1.9, Section 6.14) for more information about the data length update procedure.

See Table 14. for reference to the maximum sizes and times supported. The CC2640R2F supports these maximum values.

Table 14. LE And PDU Size and Transmit Time.
LE Data Packet Length Extensions Feature Supported PDU Size in Octets PDU Transmit Time (us)
Minimum Maximum Minimum Maximum
No 27 27 328 328
Yes 27 251 328 2120

Initial Values

The controller defaults to using PDU sizes compatible with 4.0 and 4.1 devices. It uses 27 bytes as its initial maximum size, and 328 us as the maximum time. The application can update the data length in two ways. First, the application can set the connection initial max octets to cause the controller to request a larger size for every connection. Second, the controller can initialize the connection with the default values of 27 octets and 328 us, then dynamically negotiate the data length at a later time in the connection.

For maximum throughput, high layer protocols such as the BLE host should also use a larger PDU size (see Maximum Transmission Unit (MTU)). Figure 60. illustrates various PDU sizes in the stack.

../_images/l2cap_pdu_sizes.jpg

Figure 60. Various PDUs within the Stack

Data Length Extension HCI Commands and Events

The following HCI commands can be used to interact with the controller related to the data length extension feature:

  • LE Read Suggested Default Data Length Command
  • LE Write Suggested Default Data Length Command
  • LE Read Maximum Data Length Command
  • LE Set Data Length Command

The above commands may generate:

  • LE Data Length Change Event

Note

For more information about these HCI commands and their fields, see Bluetooth Core Specification version 4.2 ([Vol 2], Part E, Section 7.7-7.8). Additionally, the APIs for these new commands are documented under HCI.

Enabling LE Extended Packet Length Feature

This section reviews how to enable and use data length extension in an application. This document uses simple_peripheral on the CC2640R2 Launchpad as an example. The same principles should apply to other projects.

Enabling the Feature in the BLE-Stack

In the c2650lp_stack project, open the build_config.opt file. There is a list of BluetoothCore Specification version 4.2 features:

1
2
3
4
5
6
7
8
/* BLE v4.2 Features */
/* -DBLE_V42_FEATURES=SECURE_CONNS_CFG+PRIVACY_1_2_CFG+EXT_DATA_LEN_CFG */
/* -DBLE_V42_FEATURES=SECURE_CONNS_CFG+PRIVACY_1_2_CFG */
/* -DBLE_V42_FEATURES=PRIVACY_1_2_CFG+EXT_DATA_LEN_CFG */
/* -DBLE_V42_FEATURES=SECURE_CONNS_CFG+EXT_DATA_LEN_CFG */
/* -DBLE_V42_FEATURES=SECURE_CONNS_CFG */
/* -DBLE_V42_FEATURES=PRIVACY_1_2_CFG */
-DBLE_V42_FEATURES=EXT_DATA_LEN_CFG

Uncomment a configuration that fits your needs; to use data length extension, a configuration with EXT_DATA_LEN_CFG must be selected.

Enable the Feature at Run Time

As discussed in Initial Values, the LE controller initially uses packet length values compatible with 4.0 and 4.1 devices in new connections. Update these to trigger the controller to automatically attempt to negotiate a higher data length at the beginning of every new connection. To enable this feature, add the following call to the application task’s initialization routine (such as simple_peripheral_init). Use valid values as shown in Table 14., otherwise the controller will reject this call.

1
2
3
4
5
6
#define APP_SUGGESTED_PDU_SIZE 251
#define APP_SUGGESTED_TX_TIME 2120

//This API is documented in hci.h
HCI_LE_WriteSuggestedDefaultDataLenCmd(APP_SUGGESTED_PDU_SIZE ,
APP_SUGGESTED_TX_TIME)

Once a connection is formed, the controller handles negotiating a packet size with the peer device. If both devices are set up to use data length extension, a throughput increase is observed.

Set Packet Length in a Connection

Packet length can also be changed dynamically in a connection using the below API snippet. The application can determine when this must occur based on any logic, such as sensor data or button presses.

uint16_t cxnHandle; //Request max supported size
uint16_t requestedPDUSize = 251;
uint16_t requestedTxTime = 2120;

GAPRole_GetParameter(GAPROLE_CONNHANDLE, &cxnHandle); //This API is documented in hci.h

if (SUCCESS != HCI_LE_SetDataLenCmd(cxnHandle, requestedPDUSize, requestedTxTime))
   DISPLAY_WRITE_STRING("Data length update failed", LCD_PAGE0);

Host Controller Interface (HCI)

The host controller interface (HCI) layer is a thin layer which transports commands and events between the host and controller elements of the Bluetooth protocol stack. In a pure network processor application (that is, the host_test project), the HCI layer is implemented through a transport protocol such as SPI or UART. In embedded wireless MCU projects such as simple_peripheral project, the HCI layer is implemented through function calls and callbacks within the wireless MCU. All of the commands and events discussed, such as ATT, GAP, and so forth, pass from the upper layers of the protocol stack through the HCI layer to the controller. Likewise, the controller sends received data and events to the host and upper layers through HCI.

As well as standard Bluetooth LE HCI commands, a number of HCI extension vendor-specific commands are available which extend some of the functionality of the controller for use by the application. See HCI for a description of available HCI and HCI extension commands.

The BLE-Stack supports a network processor configuration (host_test) that allows an application to running on an external MCU to interface to the BLE-Stack. The network processor can accept all LE HCI commands; however, because the BLE host and controller both reside on the wireless MCU, some HCI commands will have their corresponding events consumed by the TI BLE host. Thus, it is not possible to interface an external, off-chip Bluetooth host to the CC2640 wireless MCU using standard HCI LE commands. Network processor configurations should use both HCI and TI vendor-specific HCI commands to implement an external Bluetooth application.

Using HCI and HCI Vendor-Specific Commands in the Application

Follow these steps to use these commands and receive their respective events in the application:

  1. Include the HCI transport layer header file.
1
#include "hci_tl.h"
  1. Register with GAP for HCI/Host messages. This should be done in the application initialization function.
1
2
// Register with GAP for HCI/Host messages
GAP_RegisterForMsgs(selfEntity);
  1. Call any HCI or HCI vendor-specific command from the application.
  2. HCI events are returned as inter-task messages as a HCI_GAP_EVENT_EVENT. See the simple_peripheral project for an example of this.

The following sections consider receiving HCI events and HCI vendor-specific events.

Standard LE HCI Commands and Events

These commands are documented in Volume 2, Part E, Section 7 of the 4.2 Core Spec. The mechanism to use these commands is the same for any command in this section of the core spec, including HCI LE commands. The example below demonstrates how to use the core spec to implement an HCI command in the application. The command considered is Read RSSI Command.

Sending an HCI Command
  1. Find the command in the core spec:
../_images/hci_ble_core_rssi_snippet.jpg

Figure 61. RSSI Command from the BLE Core Specifications.

  1. Find mapping to BLE stack command. Using the HCI, shows that this command maps to HCI_ReadRssiCmd().
  2. Using the API from Step 1, fill in the parameters and call the command from somewhere in the application. This specific command should be called after a connection is formed. There is only command parameter here: a 2-byte connection handle. In the case of this example, the connection handle is 0x0000:
1
HCI_ReadRssiCmd(0x0000);
Receiving HCI Events
  1. Look at the core spec to see the format of the returned event:

    ../_images/hci_ble_core_rssi_rtnparams.jpg

    Figure 62. RSSI Return information with Event from the BLE Core Specifications.

  2. This command returns a Command Complete event, so add this as a case in the processing of HCI_GAP_EVENT_EVENT:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    static uint8_t SimpleBLEPeripheral_processStackMsg(ICall_Hdr* pMsg)
    {
       uint8_t safeToDealloc = TRUE;
       switch (pMsg->event)
       {
          case HCI_GAP_EVENT_EVENT:
          {
             // Process HCI message switch(pMsg->status)
          {
    
          // Process HCI Command Complete Event case
          HCI_COMMAND_COMPLETE_EVENT_CODE:
          {
             // Parse Command Complete Event for opcode and status
             hciEvt_CmdComplete_t* command_complete = (hciEvt_CmdComplete_t*) pMsg;
             uint8_t status = command_complete->pReturnParam[0];
    
             //find which command this command complete is for
             switch (command_complete->cmdOpcode)
             {
                case HCI_READ_RSSI:
                {
                   if (status == SUCCESS)
                   {
                      uint16_t handle = BUILD_UINT16( command_complete->pReturnParam[2], command_complete->pReturnParam[1]);
    
                      //check handle
                      if (handle == 0x00)
                      {
                         //store RSSI
                         uint8_t rssi = command_complete->pReturnParam[3];
    

First, the status of the stack message is checked to see what type of HCI event it is. In this case, it is an HCI_COMMAND_COMPLETE_EVENT_CODE (0x0E). Then the event returned from the stack as a message (pMsg) is cast to an (hciEvt_CmdComplete_t*), which is defined as:

1
2
3
4
5
6
7
// Command Complete Event typedef struct
{
   osal_event_hdr_t hdr;
   uint8 numHciCmdPkt;
   uint16 cmdOpcode;
   uint8* pReturnParam;
} hciEvt_CmdComplete_t;

Next, the cmdOpcode is checked and it is found that it matches HCI_READ_RSSI (0x1405). Then the status of the event is checked. The core spec API from above states that the first byte of the return parameters is the Status.

Then, check to see if this RSSI value is for the correct handle. The core spec API states that the second and third bytes of the return parameters are the Handle.

Finally, the RSSI value is stored. The core spec API states that the fourth byte of the return parameter is the RSSI.

HCI Vendor-Specific Commands

These commands are documented in the TI Vendor Specific HCI Guide. The mechanism to use these commands is the same for all vendor-specific commands. The example below demonstrates how to use the core spec to implement an HCI command in the application. The command considered is Read RSSI Command.

Sending HCI Vendor-Specific Command
  1. Find the command in the TI BLE vendor-specific guide:

    ../_images/hci_vendor_per_cmd.jpg

    Figure 63. Packet Error Rate Cmd from HCI Vendor-Specific Commands Guide.

  2. The BLE Stack function that implements this command is found under the Command column: HCI_EXT_PacketErrorRateCmd.

  3. Using the API from Step 1, fill in the parameters and call the command from somewhere in the application. The first parameter is a 2-byte connHandle, which is 0x00 for this example. The second parameter is a 1-byte command, which is 0x01, to read the counters. Therefore, use:

1
HCI_EXT_PacketErrorRateCmd( 0, HCI_EXT_PER_READ );
Receiving HCI Vendor-Specific Events
  1. Find the corresponding event in the TI BLE Vendor-Specific HCI Guide:

    ../_images/hci_vendor_per_event.jpg

    Figure 64. Packet Error Rate Event from HCI Vendor-Specific Commands Guide.

  2. As stated in the Events Generated section of the command API, this command returns a Command Complete event; thus add this as a case in the processing of HCI_GAP_EVENT_EVENT:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    static uint8_t SimpleBLEPeripheral_processStackMsg(ICall_Hdr* pMsg)
    {
       uint8_t safeToDealloc = TRUE;
       switch (pMsg->event)
       {
          case HCI_GAP_EVENT_EVENT:
          {
             // Process HCI message
             switch(pMsg->status)
             {
                // Process HCI Vendor Specific Command Complete Event
                case HCI_VE_EVENT_CODE:
                   {
                      // Parse Command Complete Event for opcode and status
                      hciEvt_VSCmdComplete_t* command_complete = (hciEvt_VSCmdComplete_t*)pMsg;
    
                      // Find which command this command complete is for
                      switch(command_complete->cmdOpcode)
                      {
                         case HCI_EXT_PER:
                         {
                            uint8_t status = command_complete->pEventParam[2];
                            if (status == SUCCESS)
                            {
                               uint8_t cmdVal = command_complete->pEventParam[3];
    
                               if (cmdVal == 1) //if we were reading packet error rate
                               {
                                  uint16_t numPkts = BUILD_UINT16(command_complete->pEventParam[5], command_complete->pEventParam[4]);
                                  uint16_t numCrcErr = BUILD_UINT16(command_complete->pEventParam[7], command_complete->pEventParam[6]);
                                  uint16_t numEvents = BUILD_UINT16(command_complete->pEventParam[9], command_complete->pEventParam[8]);
                                  uint16_t numMissedEvents = BUILD_UINT16(command_complete->pEventParam[11], command_complete->pEventParam[10]);
    

    First, the status of the stack message is checked to see what type of HCI event it is. In this case, it is an HCI_VE_EVENT_CODE (0xFF).

    Next, the event returned from the stack as a message (pMsg) is cast to an (hciEvt_VSCmdComplete_t*), which is defined as:

    1
    2
    3
    4
    5
    typedef struct
    {
       osal_event_hdr_t hdr; uint8 length;
       uint16 cmdOpcode; uint8* pEventParam;
    } hciEvt_VSCmdComplete_t;
    

    The opcode is checked by reading command_complete->cmdOpcode, and found that it matches HCI_EXT_PER (0xFC14).

    Next, the *pEventParam is parsed to extract the parameters defined in the event API. The first two bytes (shown in red in Figure 65.) are the event opcode (0x1404). The third byte is the Status. This is the case for all vendor-specific events.

    From the fourth byte of pEventParam on, the event API from the TI BLE Vendor-Specific Guide is used for parsing, starting at the third parameter. This is the case for all vendor-specific events. For this example, the fourth byte of pEventParam corresponds to the cmdVal parameter. This is shown in memory and explained further below.

    ../_images/hci_vendor_per_memory.jpg

    Figure 65. RSSI Commend from the BLE Core Specifications.

    The status is checked by reading the third byte of the event parameters (command_complete->pEventParam[2]). This is shown in yellow in Figure 65..

    Starting from the fourth byte of the event parameters (command_complete->pEventParam[3]), the event API states that the next parameter is a one-byte cmdVal. This is checked to verify that this event corresponds to a read of the PER counters. This is shown in pink.

    Continuing parsing using the event API, the next parameter is a two-byte numPkts. This is found by building a uint16_t out of the fifth and sixth bytes of the event parameters. This is shown in blue. In a similar fashion, numCrcErr is found from the seventh and eight bytes of the event parameters (shown in green).

    Next, numEvents is found from the ninth and tenth bytes of the event parameters (shown in orange). Finally, numMissedEvents is found from the eleventh and twelfth bytes of the event parameters (shown in purple).

Creating a Custom Bluetooth low energy Application

A system designer must have a firm grasp on the general system architecture, application, and Bluetooth low energy stack framework to implement a custom Bluetooth low energy application. This section provides indications and guidance on where and how to start implementing a custom application based on information presented in the previous sections (The Application and The Stack) as well as knowledge of TI RTOS and CC2640R2F.

Decide what role and purpose the custom application should have. If an application is tied to a specific service or profile, start with that sample application. An example is the heart rate sensor project, which implements the heart rate adopted profile. Otherwise, base your project on one of the following sample applications that implement one of the fundamental GAP roles:

  • simple_central
  • simple_peripheral
  • simple_broadcaster
  • simple_observer

Adaption of Board Files

TI-RTOS drivers rely on “board files” for their configuration parameters. The convention is to name these files based on the development kits provided by TI in addition to a generic Board.h with Board_* definitions to map the proper pins defined by ioc.h.

For example, for the simple_peripheral project, the following comprise the set of board files.:

Board.h
CC2640R2_LAUNCHXL.h
CC2640R2_LAUNCHXL.c

Note

simple_peripheral board files are located in <SDK_INSTALL_DIR>\examples\rtos\CC2640R2_LAUNCHXL\blestack\boards\CC2640R2_LAUNCHXL

TI recommends to start with theses existing set of board files when porting board files to custom development boards. When modifying or porting these board files, user should consult with TI Driver API Reference.

Tip

Board files provided by TI include TI-RTOS driver configuration data structures for various drivers. If the application does not use the associated TI-RTOS drivers, the linker will simply omit them from the application image.

The following steps provide guidance on customizing board files for a BLE-Stack 3.00.00 project.

  1. Duplicate existing board files from existing CC2640R2_LAUNCHXL board files.

    • These files can be found at: <SDK_INSTALL_DIR>\examples\rtos\CC2640R2_LAUNCHXL\blestack\boards\CC2640R2_LAUNCHXL
    • Create a copy of the CC2640R2_LAUNCHXL directory and give it a unique meaningful name for your development platform. In this example, we call it MYBOARD.

    Caution

    Simplelink CC2640R2 SDK also contains board files for TI-RTOS kernel and driver examples. For BLE-Stack 3.00.00 projects, use the files specified above for reference from simple_peripheral.

    • In the MYBOARD directory, rename CC2640R2_LAUNCHXL.c and CC2640R2_LAUNCHXL.h to their MYBOARD.c and MYBOARD.h respectively.
    • Search and replace all references of CC2640R2_LAUNCHXL with MYBOARD in Board.h, MYBOARD.c and MYBOARD.h.
  2. Add a new preprocessor define in your project’s board.c and board.h files.

    Continuing with the MYBOARD example, modify board.c and board.h in <SDK_INSTALL_DIR>\examples\rtos\CC2640R2_LAUNCHXL\blestack\target

    • Replace CC2640R2_LAUNCHXL by MYBOARD in your project’s pre-defined symbols (See Accessing Preprocessor Symbols or Accessing Preprocessor Symbols)

    • In board.h, add the highlighted lines shown below:

      1
      2
      3
      4
      #elif defined(CC2640R2_LAUNCHXL)
          #include "./cc2640r2lp/cc2640r2lp_board.h"
      #elif defined(MYBOARD)
          #include "../../boards/MYBOARD/MYBOARD.h"
      
    • In board.c, add the highlighted lines shown below:

      1
      2
      3
      4
      5
      #elif defined(CC2640R2_LAUNCHXL)
          #include "./cc2640r2lp/cc2640r2lp_board.c"
      #elif defined(MYBOARD)
          #include "../../boards/MYBOARD/Board.h"
          #include "../../boards/MYBOARD/MYBOARD.c"
      
    • Explicit references to CC2640R2_LAUNCHXL.h need to be replaced by MYBOARD.h

  3. Modify board files to match application requirements

Configuring RF Front-Ends and Antennas for Custom Hardware

RF Front-Ends must be configured correctly in software to achieve optimal sensitivity. Incorrect configurations can result in unreliable RF performance. Configuration of the Front-End is done within the board file.

For example, within the simple_peripheral project, the Front-End and antenna configuration is defined in CC2640R2_LAUNCHXL.h with:

1
#define CC2650EM_7ID

The define CC2650EM_7ID which gets processed in ble_user_config.h to define RF_FE_MODE_AND_BIAS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#define CC2650EM_7ID

// RF Front End Mode and Bias configuration

#if defined( CC26XX )

#if defined( CC2650EM_7ID )

#define RF_FE_MODE_AND_BIAS           ( RF_FE_DIFFERENTIAL |\
                                        RF_FE_INT_BIAS)

This configures the project to use a differential antenna with internal bias. Other configurations can also be found in ble_user_config.h, select the configuration appropriate to your project.

For information on Front-Ends and Antenna configurations, see CC26xx RF FrontEnds and Antennas.

For information on other hardware considerations, see TI Bluetooth Low Energy Wiki.

Configuring Parameters for Custom Hardware

  1. Set parameters, such as the sleep clock accuracy of the 32.768-kHz crystal.
  2. Define the CCFG parameters in ccfg_app_ble.c to enable or disable the ROM serial bootloader, JTAG access (DAP), flash protection, and so forth.

Note

For a description of CCFG configuration parameters, see TI CC26xx Technical Reference Manual (SWCU117).

Defining Application Behavior

The Sample Applications will often contain simple RTOS tasks with a barebones messaging system between tasks. For more information on how the application tasks works in general, review The Application.

To add drivers for other peripherals, see Drivers for more information.

Creating Additional Tasks

Many designs can benefit from the multi-threaded RTOS environment, by adding additional tasks to handle application-specific functionality. If the system design requires the addition of an additional RTOS task, see Initializing a Task for guidance on adding a task.

When adding a new task that makes protocol stack API calls, including calls to OSAL SNV, the task must register with ICall as described in ICall Initialization and Registration. Additionally, project preprocessor settings for OSAL_MAX_NUM_PROXY_TASKS, ICALL_MAX_NUM_TASKS, and CALL_MAX_NUM_ENTITIES may need to be increased based on the number of added tasks (see Debugging Memory Problems).

Directed Advertisements as GATT Server

In BLE-Stack 3.00.00, Privacy 1.2 is always enabled. Most of the privacy features are handled by the GAP bond manager in the stack. To conserve flash memory, by default, the GAP bond manager does not enable GATT client features. The implication of these disabled GATT client features is that the GAP bond manager will not query the Central Address Resolution characteristic of the remote device.

In order to perform a directed advertisement when the initiator’s address is set to Private Resolvable Address, the peripheral device must read the Central Address Resolution characteristic of its remote device to make sure address resolution is supported. Failure to do so before sending directed advertisements violates the 4.2 Bluetooth specification.

If you require the use of directed advertisements, you can add this functionality by commenting out the #define GBM_GATT_NO_CLIENT preprocessor option in gapbondmgr.c as shown below:

Listing 62. Compiling using GATT_NO_CLIENT
  /*
   * When GATT_NO_CLIENT is used, the use of GATT Client API is compiled out under
   * GBM_GATT_NO_CLIENT.  This means that, in the context of Privacy 1.2, the Bond
   * Manager of this device will not read the Central Address Resolution
   * Characteristic of the remote device.  If it is desired that this device uses
   * a Private Resolvable Address for Directed Advertisements, comment out the
   * pre-processor logic below.
   */
  #ifdef GATT_NO_CLIENT
    #ifndef GBM_GATT_NO_CLIENT
      #define GBM_GATT_NO_CLIENT
    #endif // !GBM_GATT_NO_CLIENT
  #endif // GATT_NO_CLIENT

Optimizing Bluetooth low energy Stack Memory Usage

Configuration of the Bluetooth low energy protocol stack is essential for maximizing the amount of RAM and flash memory available for the application. Refer to Stack Configurations to configure parameters that impact runtime RAM usage, such as the maximum allowable size and number of PDUs. The TI Bluetooth low energy protocol stack is implemented to use a small RAM footprint, and allow the application to control the behavior of the stack by using the runtime ICall heap. For example, an application that only sends one GATT notification per connection event must store only one PDU in the heap, whereas as an application that must send multiple notifications must enqueue multiple PDUs in the heap.

To increase the available flash memory allocated to the application project, minimize the flash usage of the protocol stack by including only Bluetooth low energy features required to implement the defined role of the device. The available protocol stack configurable features are described in Stack Configurations. Adding additional features to the protocol stack has the net effect of reducing the amount of flash memory to the application.

Additional Memory Configuration Options

The following tips can be used to minimize RAM and flash usage by the protocol stack:

  1. Verify that your application uses the optimize for flash size compiler optimization settings (default for TI projects).
  2. Use only one page of SNV or do not use any NV pages if the GAP bond manager is not required. Set the NO_OSAL_SNV stack preprocessor option. See Using Simple NV for Flash Storage for a description of SNV.
  3. Exclude the GATT client functionality by defining the GATT_NO_CLIENT predefined symbol in the stack project for peripheral devices. (Peripheral devices do not typically implement the GATT client role.)
  4. Remove or exclude debug DISPLAY drivers from the application project (see Dynamic Allocation Errors).
  5. Exclude Bluetooth low energy features from the Bluetooth low energy stack that are not used.

See Check System Flash and RAM Usage With Map File for the procedure to check the size of the configured protocol stack.

Defining Bluetooth low energy Behavior

This step involves using Bluetooth low energy protocol stack APIs to define the system behavior and adding profiles, defining the GATT database, configuring the security model, and so forth. Use the concepts explained in The Stack as well as the Bluetooth low energy API reference in BLE-Stack API Reference.

Stack Configurations

Configuring Bluetooth Low Energy Protocol Stack Features

The Bluetooth Low Energy protocol stack can be configured to include or exclude certain Bluetooth Low Energy features by changing the library configuration in the stack project. The available Bluetooth Low Energy features are defined in the build_config.opt file in the Tools folder of the stack project within the IDE. Based on the features selected in the build_config.opt file, the lib_search.exe tool selects the respective precompiled library during the build process of the stack project.

Table 15. lists a summary of configurable features. See the build_config.opt file for additional details and supported configurations. Some features in the BLE-Stack 3.00.00 cannot be removed and are always enabled, these features are:

  • V41_CTRL_CFG
  • EXT_DATA_LEN_CFG
  • PRIVACY_1_2_CFG
Table 15. Summary of TI Bluetooth Low Energy Protocol Stack Features.
Feature Description
HOST_CONFIG
This option configures the stack’s host layer based on its targeted GAP role.
These combo roles are supported:
- PERIPHERAL_CFG+OBSERVER_CFG
- CENTRAL_CFG+BROADCASTER_CFG
- PERIPHERAL_CFG+CENTRAL_CFG
- BROADCASTER_CFG+OBSERVER_CFG
BLE_V41_FEATURES
Features supported from the Bluetooth Low Energy core specification v4.1.
These features include:
- Ping (V41_CTRL_CFG)
- Slave feature exchange (V41_CTRL_CFG)
- Connection parameter update request (V41_CTRL_CFG)
- Multirole connections (V41_CTRL_CFG)
- L2CAP Connection Oriented Channels (L2CAP_COC_CFG)
BLE_V42_FEATURES
Features supported from the Bluetooth Low Energy core specification v4.2.
These include:
- Data Length Extension (EXT_DATA_LEN_CFG)
- Secure Connections (SECURE_CONNS_CFG)
- Privacy 1.2 (PRIVACY_1_2_CFG)
HCI_TL_xxxx Include HCI Transport Layer (FULL, PTM or NONE).

Note

Selecting the correct stack configuration is essential in optimizing the amount of flash memory available to the application. To conserve memory, exclude certain Bluetooth Low Energy protocol stack features that may not be required.

Run-Time Bluetooth low energy Protocol Stack Configuration

The Bluetooth low energy protocol stack can be configured with various parameters that control its runtime behavior and RF antenna layout. The available configuration parameters are described in the ble_user_config.h file in the ICallBLE IDE folder of the application. During initialization, these parameters are supplied to the Bluetooth Low Energy protocol stack by the user0Cfg structure, declared in main.c.

#include "ble_user_config.h"

// BLE user defined configuration
bleUserCfg_t user0Cfg = BLE_USER_CFG;

Because the ble_user_config.h file is shared with projects within the SDK, TI recommends defining the configuration parameters in the preprocessor symbols of the application when using a non default value. For example, to change the maximum PDU size from the default 27 to 162, set the preprocessor symbol MAX_PDU_SIZE=162 in the preprocessor symbols for the application project. Increasing certain parameters may increase heap memory use by the protocol stack; adjust the HEAPMGR_SIZE as required (if not using auto sized heap). Table 16. lists the available configuration parameters.

Table 16. Bluetooth Low Energy Stack Configuration Parameters
Parameter Description
MAX_NUM_BLE_CONNS Maximum number of simultaneous Bluetooth low energy connections. Default is 1 for Peripheral and Central roles. Maximum value is based on GAPRole.
MAX_NUM_PDU Maximum number of Bluetooth low energy HCI PDUs. Default is 5. If the maximum number of connections is set to 0, then this number should also be set to 0.
MAX_PDU_SIZE Maximum size in bytes of the Bluetooth low energy HCI PDU. Default is 27. Valid range is 27 to 255. The maximum ATT_MTU is MAX_PDU_SIZE - 4. See Configuring for Larger MTU Values.
L2CAP_NUM_PSM Maximum number of L2CAP Protocol/Service Multiplexers (PSM). Default is 3.
L2CAP_NUM_CO_CHANNELS Maximum number of L2CAP Connection-Oriented (CO) Channels. Default is 3.
PM_STARTUP_MARGIN Defines time in microseconds (us) the system will wake up before the start of the connection event. Default is 300. This value is optimized for the example projects.
RF_FE_MODE_AND_BIAS Defines the RF antenna front end and bias configuration. Set this value to match the actual hardware antenna layout. This value can be set directly, or through Board.h. For more information see Configuring RF Front-Ends and Antennas for Custom Hardware
ICALL_MAX_NUM_TASKS (App) OSAL_MAX_NUM_PROXY_TASKS (Stack) Defines the max number of ICall enabled tasks. These defines, although on different sides, (app and stack), must be the same. OSAL_MAX_NUM_PROXY_TASKS defaults to 2. ICALL_MAX_NUM_Tasks defaults to 2.

Warning

TI Recommends limiting the number of ICall enabled tasks due to resource usage. For more information on creating ICall enabled tasks, see Adding ICall RTOS Tasks