Pairing Processes Example


This code example has a related User's Guide, here: Pairing Processes


This code example is created to demonstrate different Bluetooth pairing processes and help implement firmware, which is able to securely connect to other Bluetooth devices. Read the related User's Guide first to get a better understanding of the processes.

Setting up

  1. Create a new SoC-Empty application project with Bluetooth SDK version 2.12.x or above.

  2. Click on the *.isc file in your project tree, select the Custom BLE GATT field on the upper right side of the GATT configurator, and select Import GATT from .bgproj file from the bottom icon on the right side.

  3. Select the gatt.xml provided here, click Save, and press Generate. You should now have a new Secure Service and three characteristics within it.

  4. Copy the following files to your project:

    • app.c
    • lcd_support.bat
  5. Run the lcd_support.bat batch file, which will copy the necessary files to use the LCD screen and button interrupts from the SDK directories to your project in the workspace. To run the file, double-click from the project tree within Simplicity IDE.

  6. Add the following to the include paths. For example, for GCC: right-click on the project -> Properties -> "C/C++ Build" -> Settings -> "GNU ARM C Compiler" -> Includes): "$"

  7. Enable SPI display in hal-config.h:

    #define HAL_SPIDISPLAY_ENABLE            (1)
  8. Add the following line to hal-config.h:

    #define HAL_SPIDISPLAY_FREQUENCY   (1000000)
  9. [Optional] Enable logging to UART by setting DEBUG_LEVEL to 1 in app.h

  10. Configure macros IO_CAPABILITY and MITM_PROTECTION to DISPLAYONLY and 0x00 respectively.

  11. Build and flash the project to two Wireless Starter Kits (WSTK).


The attached demo code enables testing all pairing combinations shown in the table of Pairing Processes. It implements both the initiator and responder roles in the same firmware. The roles are configured by pressing and holding down PB0 on the starter kit during reset and they are shown on the LCD screen. Pressing the button during reset puts the device into responder role. Leaving the button released during reset puts the device into initiator role.

A mobile phone with EFR Connect App can also be used as the responder, which covers the bottom row of the table.

I/O capabilities and Man In The Middle protection are configurable with two preprocessor macros IO_CAPABILITY and MITM_PROTECTION in app.c.


#define IO_CAPABILITY (KEYBOARDONLY)  // Choose IO capabilities.
#define MITM_PROTECTION (0x01)        // 0=JustWorks, 1=PasskeyEntry or NumericComparison

At boot and after connection closed event, all bondings are deleted. Then the security manager (sm) is configured with the flags and capabilities chosen above.

gecko_cmd_sm_configure(MITM_PROTECTION, IO_CAPABILITY);

The passkey, for those methods that need one, is generated from the device address or is hard coded in the "KEYBOARDONLY" situation (both input), when, for example, it is shipped with the product.

Finally, bondings are enabled and the devices start scanning (responder) or advertising (initiator) for connection. Scanning device looks for advertisements of the "Secure Service" and connects. On connection, the initiator calls sm_increase_security to initiate the pairing process. The security related events are handled as follows:

      case gecko_evt_le_connection_opened_id:
        connection = evt->data.evt_le_connection_opened.connection;
        if (is_initiator) {

      case gecko_evt_sm_passkey_display_id:
        // Display passkey
        printLog("Passkey: %4lu\r\n", evt->data.evt_sm_passkey_display.passkey);
        passkey = evt->data.evt_sm_passkey_display.passkey;
        state = DISPLAY_PASSKEY;

      case gecko_evt_sm_passkey_request_id:
        printLog("Passkey request\r\n");

      case gecko_evt_sm_confirm_passkey_id:
        printLog("Passkey confirm\r\n");
        passkey = evt->data.evt_sm_confirm_passkey.passkey;
        state = PROMPT_YESNO;

      case gecko_evt_sm_confirm_bonding_id:
        printLog("Bonding confirm\r\n");
        gecko_cmd_sm_bonding_confirm(evt->data.evt_sm_confirm_bonding.connection, 1);

      case gecko_evt_sm_bonded_id:
        printLog("Bond success\r\n");
        state = IDLE;

      case gecko_evt_sm_bonding_failed_id:
        printLog("Bonding failed, reason 0x%2X\r\n", evt->data.evt_sm_bonding_failed.reason);

The state is used to track which external signals (REJECT_COMPARISON, ACCEPT_COMPARISON, REENTER_PASSKEY, SUBMIT_PASSKEY) each pushbutton emits and what is shown on the LCD screen at each step of the interactive processes. See button_cb and refresh_display functions for more details.

Processes that require interaction show either

Prompts for YES/NO are accepted with PB0 and rejected with PB1.

For an editable passkey, the current digit is incremented with PB1 and PB0 is used to step the cursor to the next digit on the right. For passkey entries, the prompt for submitting the whole key is shown after the cursor loops to the beginning of the passkey field again.

If you enable printing to UART, you can also follow the processes with a terminal program, such as Tera Term. Figures below show connecting a BG13 device with the firmware built, as described earlier and mobile phone with EFR Connect App. The BG13 is the initiator with DISPLAYONLY capabilities and the phone acts as the responder with KEYBOARDDISPLAY capabilities. This leads to using passkey entry (initiator displays, responder inputs) as indicated by the table. The mobile phone will prompt to pair with the device on connection and asks to input the passkey formed from the public address of the BG13 which is shown on the WSTK screen. After the authentication is complete, the LCD screen will go back to showing the role and security level is printed to the terminal.

Initiator displays passkey

Terminal output SoC-Phone

To change the pairing process and try out other combinations, modify the macros as described earlier.