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:
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.
Different SPP applications shall use different SPP UUIDs.
Feature Set
The functions provided by SPP APIs 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.
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 |
---|---|---|
bt_spp.h |
Connect SPP, send and receive data etc. |
Functions
SPP Init
bool bt_spp_init()
Initialize SPP Profile.
bool bt_spp_service_register()
Register SPP service using SPP service UUID and local server channel.
SPP Connect
bool bt_spp_connect_req()
Send an SPP connection request.
bool bt_spp_connect_cfm()
Send an SPP connection confirmation.
Send an SPP disconnection request.
bool bt_spp_disconnect_all_req()
Send a request to disconnect all SPP connections.
SPP Command
bool bt_spp_registered_uuid_check()
Check whether the UUID is registered and bond the local server channel with UUID.
Set SPP ERTM mode.
SPP Data Transfer
bool bt_spp_data_send()
Send SPP data to remote device.
Give SPP credits to remote device.
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 num 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 BR/EDR 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_EVENT_SPP_CREDIT_RCVD. AnyWay, if SPP link credits is not zero, SPP could send the SPP data.
SPP data is received when receiving BT_EVENT_SPP_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.