BR/EDR SPP

The Serial Port Profile defines the protocols and procedures that shall be used by devices using Bluetooth for RS232 (or similar) serial cable emulation. The scenario covered by this profile deals with legacy applications using Bluetooth as a cable replacement, through a virtual serial port abstraction (which in itself is operating system-dependent). The purpose of this document is to give an overview of the Serial Port Profile and demo application. The SPP demo project implements a simple SPP application and can be used as a framework for developing SPP applications.

Overview

SPP Role Features

SPP roles are typically defined as follows.

  • Device A - This is the device that takes initiative to form a connection to another device.

    • SPP client

  • Device B - This is the device that waits for another device to take initiative to connect.

    • SPP server

SPP Profile Stack

Protocols and entities used in SPP profile are shown below.

  • The Baseband LMP and L2CAP are the OSI layer 1 and 2 Bluetooth protocols.

  • RFCOMM is the Bluetooth adaptation of GSM TS 07.10 , providing a transport protocol for serial port emulation.

  • SDP is the Bluetooth Service Discovery Protocol.

SPP UUID

  • Universally Unique Identifier(UUID) is a universally unique identifier that is guaranteed to be unique across all space and all time. UUIDs can be independently created in a distributed fashion. No central registry of assigned UUIDs is required. A UUID is a 128-bit value.

  • The full 128-bit value of a 16-bit or 32-bit UUID may be computed by a simple arithmetic operation.

    • 128_bit_value = 16_bit_value * 296 + Bluetooth_Base_UUID

    • 128_bit_value = 32_bit_value * 296 + Bluetooth_Base_UUID

    • The Base UUID(Bluetooth_Base_UUID) is used for calculating 128-bit UUIDs from ‘short UUIDs’ (16-bit UUID and 32-bit UUID) as described in the SDP Specification 00000000-0000-1000-8000-00805F9B34FB.

    • A 16-bit UUID may be converted to 32-bit UUID format by zero-extending the 16-bit value to 32-bits. An equivalent method is to add the 16-bit UUID value toa zero-valued 32-bit UUID.

  • Blutetooth SDP UUID numbers are defined in https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/.

  • SPP UUID in SPP server and SPP client should be the same and registered using bt_spp_service_register.

  • Different SPP applications shall use different SPP UUIDs.

Requirements

The sample supports the following development kits:

Hardware Platforms

Board Name

Build Target

RTL87x3E HDK

RTL87x3E EVB

bt_spp_demo_4M_bank0
bt_spp_demo_16M_bank0

RTL87x3D HDK

RTL87x3D EVB

bt_spp_demo_16M_bank0

This sample project can be found under board\evb\bt_spp_demo in SDK folder structure. Users can choose the project according to the Board Name and choose the Build Target according to the flash map.

When built for an xxx_4M_xxx build target, the sample is configured to build and run with a 4M flash map.

When built for an xxx_8M_xxx build target, the sample is configured to build and run with an 8M flash map.

When built for an xxx_16M_xxx build target, the sample is configured to build and run with a 16M flash map.

The sample also requires another EVB board. SPP connection can be established and data can be transferred between two EVB boards.

To quickly set up the development environment, please refer to the detailed instructions provided in Quick Start.

Configurations

  1. UART

    UART receiver/transmitter PINMUX is configured by the macro definition SPP_DEMO_UART_RX and SPP_DEMO_UART_TX in the file sdk\src\sample\bt_spp_demo\app_bt_spp_demo_main.c.

  2. SPP Role

    SPP role is configured by the macro definition SPP_DEMO_SERVER_ROLE in the file sdk\src\sample\bt_spp_demo\app_flag.h. One EVB board should configure the macro as 0 which is the client. Another EVB board should configure the macro as 1 which is the server.

Building and Downloading

Take the project rtl87x3e_spp_demo.uvprojx and target bt_spp_demo_4M_bank0 as an example, to build and run the sample with Keil development environment, follow the steps listed below:

  1. Open rtl87x3e_spp_demo.uvprojx.

  2. Choose the build target bt_spp_demo_4M_bank0.

  3. Configure the macro definition SPP_DEMO_SERVER_ROLE.

  4. Build the target:
    After a successful build, the APP bin file bt_spp_demo_bank0_MP-v0.0.0.0-xxx.bin will be generated in the directory bin\rtl87x3e\flash_4M_dualbank\bank0.

  5. Download APP bin into EVB board.

  6. Configure the macro definition SPP_DEMO_SERVER_ROLE as another role, build and download APP bin into another EVB board.

  7. Press reset button in the two EVB boards.

Experimental Verification

In order to demo SPP application, user needs to do step by step as follows:

  1. Prepare 2 sets of Evolution Boards, EVB 1 and EVB 2.

  2. Build and Download the SPP demo application binary to the EVBs.

    1. Generate SPP server demo bin and download to EVB 1:
      Set the SPP_DEMO_SERVER_ROLE in app_flag.h to 1, build and generate spp_demo_bank0_MP-xxx.bin, download it to EVB 1.

    2. Generate SPP client demo bin and download to EVB 2:
      Set the SPP_DEMO_SERVER_ROLE in app_flag.h to 0, build and generate spp_demo_bank0_MP-xxx.bin, download it to EVB 2.

  3. Reset and power on two EVBs.

  4. Input command spp connect to connect SPP between EVB 1 and EVB 2. The console will print SPP Connected: informing user that SPP is connected.

    UART Command to Input

    Output of UART Response

    Description

    spp connect 0x11 0x22 0x55 0x77 0x88 0x99

    SPP Connected: addr 0x112255667788, local_server_chann 0x03

    Connect SPP with remote demo EVB.

  5. After UART prints SPP Connected:, data TRX can be tested through SPP.

  6. Input command spp send_data to send SPP data. The console will print SPP send data to addr informing user that SPP is sent. When the remote device received data, the console will print SPP receive data from addr.

    UART Command to Input

    Output of UART Response

    Description

    spp send_data 0x11 0x22 0x55 0x77 0x88 0x99 0x12

    SPP receive data from addr 0x112255667788, local_server_chann 0x03

    Send SPP data to remote demo EVB.

Code Overview

This section describes some parts of the source codes used in application of this project.

Source Code Directory

  • Project directory: sdk\board\evb\bt_spp_demo.

  • Project source code directory: sdk\src\sample\bt_spp_demo.

Source files in the sample project are currently categorized into several groups as below.

└── Project: bt_spp_demo_16M_bank0
    ├── include                         ROM UUID header files. Users do not need to modify it.
    ├── lib                             Includes all binary symbol files that user application is built on.
    ├── cmsis                           The cmsis source code. Users do not need to modify it.
    └── app                             The application source code.
        ├── app_bt_spp_demo_app.c
        ├── app_bt_spp_demo_gap.c 
        ├── app_bt_spp_demo_link.c
        ├── app_bt_spp_demo_main.c
        ├── app_bt_spp_demo_sdp.c
        ├── app_console_msg.c
        ├── app_io_msg.c
        ├── app_bt_spp_demo_console.c
        ├── console_uart.c
        ├── app_dlps.c

Initialization

Main function is invoked when the application is powered on or the chip resets and performs the following initialization functions.

int main(void)
{
    board_init();
    driver_init();
    task_init();
    framework_init();
    app_bt_gap_init();
    spp_demo_gap_init();
    spp_demo_sdp_init();
    spp_demo_app_init();
    app_spp_demo_cmd_register();

    os_sched_start();

    return 0;
}

SPP profile could be initialized as follows.

  • SPP server/client should support the same SPP service at the same time by registering related UUID with bt_spp_service_register.

  • SPP server/client may process the corresponding event by registering SPP profile callback handler with bt_mgr_cback_register.

void spp_demo_app_init(void)
{
    spp_demo_init();
    bt_spp_init();
    bt_spp_service_register((uint8_t *)spp_demo_service_class_uuid128, SPP_DEMO_RFC_SPP_CHANN_NUM);
    bt_mgr_cback_register(spp_demo_app_bt_cback);
    bt_spp_ertm_mode_set(false);
}

SPP SDP Record

  • SPP SDP local record should be registered by bt_sdp_record_add, SPP SDP local record includes the SPP service num. Moreover, SPP service num may be different in different SPP application.

  • For SPP, 16-bit UUID 0x1101 is reserved, meanwhile, for vendor Serial Port, a 128-bit vendor UUID may be defined and used by the user.

  • SPP server may support SPP service, vendor SPP service or more services at the same time by registering the related SDP record.

const uint8_t spp_demo_sdp_record[] =
{
    SDP_DATA_ELEM_SEQ_HDR,
    0x4C,
    //attribute SDP_ATTR_SRV_CLASS_ID_LIST
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_ATTR_SRV_CLASS_ID_LIST >> 8),
    (uint8_t)SDP_ATTR_SRV_CLASS_ID_LIST,
    SDP_DATA_ELEM_SEQ_HDR,
    0x03,
    SDP_UUID16_HDR,
    (uint8_t)(UUID_SERIAL_PORT >> 8),
    (uint8_t)(UUID_SERIAL_PORT),

    //attribute SDP_ATTR_PROTO_DESC_LIST
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_ATTR_PROTO_DESC_LIST >> 8),
    (uint8_t)SDP_ATTR_PROTO_DESC_LIST,
    SDP_DATA_ELEM_SEQ_HDR,
    0x0c,
    SDP_DATA_ELEM_SEQ_HDR,
    03,
    SDP_UUID16_HDR,
    (uint8_t)(UUID_L2CAP >> 8),
    (uint8_t)UUID_L2CAP,
    SDP_DATA_ELEM_SEQ_HDR,
    0x05,
    SDP_UUID16_HDR,
    (uint8_t)(UUID_RFCOMM >> 8),
    (uint8_t)UUID_RFCOMM,
    SDP_UNSIGNED_ONE_BYTE,
    SPP_DEMO_RFC_SPP_CHANN_NUM,

    //attribute SDP_ATTR_BROWSE_GROUP_LIST
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_ATTR_BROWSE_GROUP_LIST >> 8),
    (uint8_t)SDP_ATTR_BROWSE_GROUP_LIST,
    SDP_DATA_ELEM_SEQ_HDR,
    0x03,
    SDP_UUID16_HDR,
    (uint8_t)(UUID_PUBLIC_BROWSE_GROUP >> 8),
    (uint8_t)UUID_PUBLIC_BROWSE_GROUP,

    //attribute SDP_ATTR_LANG_BASE_ATTR_ID_LIST
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_ATTR_LANG_BASE_ATTR_ID_LIST >> 8),
    (uint8_t)SDP_ATTR_LANG_BASE_ATTR_ID_LIST,
    SDP_DATA_ELEM_SEQ_HDR,
    0x09,
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_LANG_ENGLISH >> 8),
    (uint8_t)SDP_LANG_ENGLISH,
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_CHARACTER_UTF8 >> 8),
    (uint8_t)SDP_CHARACTER_UTF8,
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_BASE_LANG_OFFSET >> 8),
    (uint8_t)SDP_BASE_LANG_OFFSET,

    //attribute SDP_ATTR_PROFILE_DESC_LIST
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)(SDP_ATTR_PROFILE_DESC_LIST >> 8),
    (uint8_t)SDP_ATTR_PROFILE_DESC_LIST,
    SDP_DATA_ELEM_SEQ_HDR,
    0x08,
    SDP_DATA_ELEM_SEQ_HDR,
    0x06,
    SDP_UUID16_HDR,
    (uint8_t)(UUID_SERIAL_PORT >> 8),
    (uint8_t)UUID_SERIAL_PORT,
    SDP_UNSIGNED_TWO_BYTE,
    0x01,//version 1.2
    0x02,

    //attribute SDP_ATTR_SRV_NAME
    SDP_UNSIGNED_TWO_BYTE,
    (uint8_t)((SDP_ATTR_SRV_NAME + SDP_BASE_LANG_OFFSET) >> 8),
    (uint8_t)(SDP_ATTR_SRV_NAME + SDP_BASE_LANG_OFFSET),
    SDP_STRING_HDR,
    0x0B,
    0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x72, 0x74 //"serial port"
};

void spp_demo_sdp_init(void)
{
    bt_sdp_record_add((void *)spp_demo_sdp_record);
}

GAP Callback Handler

  • The callback function below is used to handle Bluetooth GAP related events.

  • The callback function will call bt_spp_registered_uuid_check to check if remote device support SPP profile by UUID registered or not and get local SPP service number when received SDP information.

  • For SPP, 16-bit UUID 0x1101 is reserved, meanwhile, for vendor Serial Port, 128-bit vendor UUID may be defined and used by the user.

  • SPP may support SPP service, vendor SPP service or more services at the same time by registering the related UUID.

static const uint8_t spp_demo_service_class_uuid128[16] =
{
    0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb
};

SPP Callback Handler

  • SPP link credit means the SPP TX transport window size and SPP frame size means the SPP TRX transport MTU size.

  • If SPP link credit is zero, then, it will be set according to remote device credit. Anyway, if SPP link credits are not zero, APP could send the data.

static void spp_demo_app_bt_cback(T_BT_EVENT event_type, void *event_buf, uint16_t buf_len)
{
    ......
    switch (event_type)
    {
    case BT_EVENT_SPP_CONN_CMPL:
        {
            p_link = spp_demo_find_link(param->spp_conn_cmpl.bd_addr);
            if (p_link != NULL)
            {
                service = spp_demo_find_service(param->spp_conn_cmpl.bd_addr,
                                                param->spp_conn_cmpl.local_server_chann);
                if (service == NULL)
                {
                    service = spp_demo_alloc_service(param->spp_conn_cmpl.bd_addr,
                                                     param->spp_conn_cmpl.local_server_chann);
                    if (service != NULL)
                    {
                        char console_buf[80];

                        service->credit = param->spp_conn_cmpl.link_credit;
                        service->frame_size = param->spp_conn_cmpl.frame_size;

                        sprintf((char *)console_buf, "SPP Connected: addr 0x%x%x%x%x%x%x, local_server_chann 0x%x \r\n", 
                                param->spp_conn_cmpl.bd_addr[0],
                                param->spp_conn_cmpl.bd_addr[1],
                                param->spp_conn_cmpl.bd_addr[2],
                                param->spp_conn_cmpl.bd_addr[3],
                                param->spp_conn_cmpl.bd_addr[4],
                                param->spp_conn_cmpl.bd_addr[5],
                                param->spp_conn_cmpl.local_server_chann);

                        console_write((uint8_t *)console_buf, strlen(console_buf));
                    }
                }

                bt_device_mode_set(BT_DEVICE_MODE_DISCOVERABLE_CONNECTABLE);
            }
        }
        break;

    case BT_EVENT_SPP_CREDIT_RCVD:
        {
            service = spp_demo_find_service(param->spp_credit_rcvd.bd_addr,
                                            param->spp_credit_rcvd.local_server_chann);
            if (service != NULL)
            {
                service->credit = param->spp_credit_rcvd.link_credit;
            }
        }
        break;

    case BT_EVENT_SPP_DATA_IND:
        {
            service = spp_demo_find_service(param->spp_data_ind.bd_addr,
                                            param->spp_data_ind.local_server_chann);
            if (service != NULL)
            {
                char console_buf[90];

                bt_spp_credits_give(param->spp_data_ind.bd_addr, param->spp_data_ind.local_server_chann, 1);
                
                sprintf((char *)console_buf, "SPP receive data from addr 0x%x%x%x%x%x%x, local_server_chann 0x%x \r\n", 
                        param->spp_data_ind.bd_addr[0],
                        param->spp_data_ind.bd_addr[1],
                        param->spp_data_ind.bd_addr[2],
                        param->spp_data_ind.bd_addr[3],
                        param->spp_data_ind.bd_addr[4],
                        param->spp_data_ind.bd_addr[5],
                        param->spp_data_ind.local_server_chann);

                console_write((uint8_t *)console_buf, strlen(console_buf));
                console_write(param->spp_data_ind.data, param->spp_data_ind.len);
            }
        }
        break;

    case BT_EVENT_SPP_CONN_IND:
        {
            p_link = spp_demo_find_link(param->spp_conn_ind.bd_addr);
            if (p_link != NULL)
            {
                service = spp_demo_find_service(param->spp_conn_ind.bd_addr,
                                                param->spp_conn_ind.local_server_chann);

                if (service == NULL)
                {
                    bt_spp_connect_cfm(p_link->bd_addr, 
                                       param->spp_conn_ind.local_server_chann, 
                                       true, 
                                       param->spp_conn_ind.frame_size,
                                       SPP_DEMO_SPP_DEFAULT_CREDITS);
                }
                else
                {
                    bt_spp_connect_cfm(p_link->bd_addr, 
                                       param->spp_conn_ind.local_server_chann, 
                                       false, 
                                       param->spp_conn_ind.frame_size,
                                       SPP_DEMO_SPP_DEFAULT_CREDITS);
                }
            }
        }
        break;
    ......

User Command

This section provides a detailed description of the usage of user commands for the SPP demo.

The SPP demo requires inputting commands through the Data UART in the host terminal to perform interaction. This allows various actions to be executed and the application to be controlled.

SPP Connect

The flow of the spp connect command is to connect SPP with a remote device. User can input command below to connect SPP. The address in the command is the remote Bluetooth device.

spp connect [remote_device_bluetooth_address]

The console will print SPP Connected: informing user that SPP is connected.

Information

Description

Command

spp connect

Parameters

Remote Bluetooth address

Usage

Connect SPP with remote device.

Example

spp connect 11 22 33 44 55 66

The SPP connect flow is shown below.

SPP Send Data

The flow of the spp send_data command is to send data through SPP to a remote device. User can input command below to send data. The address in the command is the remote Bluetooth device.

spp send_data [remote_device_bluetooth_address] [data]

The console will print SPP send data to addr informing user that SPP has been sent. When the remote device receives data, the console will print SPP receive data from addr.

Information

Description

Command

spp send_data

Parameters

Remote Bluetooth address and data

Usage

Send SPP data to remote device.

Example

spp send_data 11 22 33 44 55 66 99

The SPP data transfer and receive flow is shown below.