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 provide an overview of the Serial Port Profile. SPP is used to send data through Bluetooth between two devices.
Terminology and Concepts
SPP Roles
The following roles are defined for SPP Profile:
Device A (DevA) - This is the device that takes initiative to form a connection to another device.
Device B (DevB) - This is the device that waits for another device to take initiative to connect.
Note
For purposes of mapping the Serial Port Profile to the conventional serial port architecture, both DevA and DevB can be either a Data Circuit Endpoint (DCE) or a Data Terminal Endpoint (DTE).
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
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.
\(128bit value = 16bit value * 296 + Bluetooth Base UUID\)
\(128bit value = 32bit 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.
Different SPP applications shall use different SPP UUIDs.
Feature Set
The functions provided by SPP API are as below:
Connecting and Disconnecting: Including functions to connect/disconnect SPP.
Sending and Receiving Data: Including functions to send data through SPP between devices.
Provided APIs
This figure shows the relationship between applications, SPP and Bluetooth Protocol Stack. Above the horizon line is developed by the applications. Below the horizon line is developed by Realtek.

SPP Struct
The table below shows a simple description of SPP APIs. SPP related APIs are provided in the sdk\inc\framework\bt\bt_spp.h
.
Header File |
Description |
API Reference |
---|---|---|
|
Connect SPP, send and receive data etc. |
Functions
SPP Init
SPP Connect

SPP Connect

SPP Disconnect
SPP Command
SPP Data Transfer

SPP Data TRX Flow
Source Code Overview
Initialization
Initialize SPP, configure supported service number. Register SPP service using
SPP service UUID and local server channel. Register SPP Profile callback handler with bt_mgr_cback_register()
.
void spp_demo_app_init(void)
{
spp_demo_int();
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);
}
SPP SDP Record
SPP SDP local record should be registered by
bt_sdp_record_add()
. SPP service number may be different in different SPP application.For Serial Port Profile, 16-bit UUID 0x1101
UUID_SERIAL_PORT
is reserved, meanwhile, for vendor serial port, 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,
0x03,
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"
};
#ifdef RTK_VENDOR_SPP
const uint8_t rtk_vendor_spp_sdp_record[] =
{
SDP_DATA_ELEM_SEQ_HDR,
0x4E,
//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,
0x11,
SDP_UUID128_HDR,
0x6a, 0x24, 0xee, 0xab, 0x4b, 0x65, 0x46, 0x93, 0x98, 0x6b, 0x3c, 0x26, 0xc3, 0x52, 0x26, 0x4f,
//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_VENDOR_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_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,
'r', 't', 'k', '_', 'v', 'n', 'd', '_', 's', 'p', 'p'
};
#endif
SPP Profile Callback Handler
Callback function is used to handle SPP related events.
SPP Profile connection is completed when receiving
BT_EVENT_SPP_CONN_CMPL
.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 credit information when recieving
BT_SPP_EVENT_CREDIT_RCVD
. Anyway, if SPP link credits is not zero, SPP could send the SPP data.
SPP data is received when receiving
BT_SPP_EVENT_DATA_IND
.
static void app_spp_bt_cback(T_BT_EVENT event_type, void *event_buf, uint16_t buf_len)
{
T_BT_EVENT_PARAM *param = event_buf;
T_APP_BR_LINK *p_link;
bool handle = true;
switch (event_type)
{
case BT_EVENT_SPP_CONN_CMPL:
{
T_APP_BR_LINK *p_link;
p_link = app_find_br_link(param->spp_conn_cmpl.bd_addr);
if (p_link != NULL)
{
p_link->rfc_credit = param->spp_conn_cmpl.link_credit;
p_link->rfc_frame_size = param->spp_conn_cmpl.frame_size;
}
}
break;
case BT_EVENT_SPP_CREDIT_RCVD:
{
p_link = app_find_br_link(param->spp_credit_rcvd.bd_addr);
if (p_link == NULL)
{
APP_PRINT_ERROR0("app_spp_bt_cback: no acl link found");
return;
}
if ((p_link->rfc_credit == 0) && (param->spp_credit_rcvd.link_credit))
{
app_pop_data_transfer_queue(CMD_PATH_SPP, false);
}
p_link->rfc_credit = param->spp_credit_rcvd.link_credit;
}
break;
case BT_EVENT_SPP_DATA_IND:
{
uint8_t *p_data;
uint16_t len;
uint8_t app_idx;
uint16_t data_len;
uint16_t total_len;
p_link = app_find_br_link(param->spp_data_ind.bd_addr);
if (p_link == NULL)
{
APP_PRINT_ERROR0("app_spp_bt_cback: no acl link found");
return;
}
app_idx = p_link->id;
p_data = param->spp_data_ind.data;
len = param->spp_data_ind.len;
data_len = len;
bt_spp_credits_give(app_db.br_link[app_idx].bd_addr, param->spp_data_ind.local_server_chann, 1);
}
break;
case BT_EVENT_SPP_CONN_IND:
{
p_link = app_find_br_link(param->spp_conn_ind.bd_addr);
if (p_link == NULL)
{
APP_PRINT_ERROR0("app_spp_bt_cback: no acl link found");
return;
}
uint8_t local_server_chann = param->spp_conn_ind.local_server_chann;
uint16_t frame_size = param->spp_conn_ind.frame_size;
bt_spp_connect_cfm(p_link->bd_addr, local_server_chann, true, frame_size, 7);
}
break;
case BT_EVENT_SPP_DISCONN_CMPL:
{
p_link = app_find_br_link(param->spp_disconn_cmpl.bd_addr);
if (p_link != NULL)
{
p_link->rfc_credit = 0;
}
app_transfer_queue_reset(CMD_PATH_SPP);
}
break;
default:
handle = false;
break;
}
if (handle == true)
{
APP_PRINT_INFO1("app_spp_bt_cback: event_type 0x%04x", event_type);
}
}
Sample Projects
SDK provides a corresponding demo application for developers’ reference in development. The BR/EDR SPP gives a simple example on how to use SPP.