nRF5 SDK v14.0.0
UART/Serial Port Emulation over BLE
This example requires one of the following SoftDevices: S132, S140

Important: Before you run this example, make sure to program the SoftDevice.

The Nordic UART Service (NUS) Application is an example that emulates a serial port over BLE. In the example, Nordic Semiconductor's development board serves as a peer to the phone application "nRF UART", which is available for iOS from App Store and for Android from Google Play. In addition, the example demonstrates how to use a proprietary (vendor-specific) service and characteristics with the SoftDevice.

The application includes one service: the Nordic UART Service. The 128-bit vendor-specific UUID of the Nordic UART Service is 6E400001-B5A3-F393-E0A9-E50E24DCCA9E (16-bit offset: 0x0001).

This service exposes two characteristics: one for transmitting and one for receiving (as seen from the peer).

Important code lines

The following sections describe some important parts of the example code.

Adding proprietary service and characteristics

The initialization of the proprietary service and its characteristics is done in ble_nus.c.

The Nordic UART Service is added to the SoftDevice as follows:

// Add a custom base UUID.
err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
VERIFY_SUCCESS(err_code);
ble_uuid.type = p_nus->uuid_type;
ble_uuid.uuid = BLE_UUID_NUS_SERVICE;
// Add the service.
&ble_uuid,
&p_nus->service_handle);

The TX characteristic is added to the SoftDevice as shown in the code below. The read and write permissions of the characteristic and its CCCD are set as open, which means that there are no security restrictions on this characteristic. The type of the UUID (ble_uuid.type) is the value that was returned in the call to sd_ble_uuid_vs_add(). The RX characteristic is added in a similar way.

ble_gatts_attr_t attr_char_value;
ble_uuid_t ble_uuid;
memset(&cccd_md, 0, sizeof(cccd_md));
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.notify = 1;
char_md.p_char_user_desc = NULL;
char_md.p_char_pf = NULL;
char_md.p_user_desc_md = NULL;
char_md.p_cccd_md = &cccd_md;
char_md.p_sccd_md = NULL;
ble_uuid.type = p_nus->uuid_type;
memset(&attr_md, 0, sizeof(attr_md));
attr_md.rd_auth = 0;
attr_md.wr_auth = 0;
attr_md.vlen = 1;
memset(&attr_char_value, 0, sizeof(attr_char_value));
attr_char_value.p_uuid = &ble_uuid;
attr_char_value.p_attr_md = &attr_md;
attr_char_value.init_len = sizeof(uint8_t);
attr_char_value.init_offs = 0;
attr_char_value.max_len = BLE_NUS_MAX_TX_CHAR_LEN;
return sd_ble_gatts_characteristic_add(p_nus->service_handle,
&char_md,
&attr_char_value,
&p_nus->tx_handles);

Initializing UART

The initialization of the application and the handling of data sent and received through BLE and UART are done in main.c.

The UART initialization is done as shown in the code below. The code segment uses the UART module that is provided in the SDK to perform the UART configuration. Note that app_uart_comm_params_t configures the application to use hardware flow control. Therefore, RTS_PIN_NUMBER and CTS_PIN_NUMBER are used as Ready-to-Send and Clear-to-Send pins, respectively.

static void uart_init(void)
{
uint32_t err_code;
app_uart_comm_params_t const comm_params =
{
.rx_pin_no = RX_PIN_NUMBER,
.tx_pin_no = TX_PIN_NUMBER,
.rts_pin_no = RTS_PIN_NUMBER,
.cts_pin_no = CTS_PIN_NUMBER,
.use_parity = false,
.baud_rate = UART_BAUDRATE_BAUDRATE_Baud115200
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_event_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);
APP_ERROR_CHECK(err_code);
}

Handling data received over BLE

When initializing the service in the services_init() function, the application passes the function nus_data_handler, which is used for handling the received data. When the Nordic UART Service indicates that data has been received over BLE from the peer, the same data is relayed to the UART. The nus_data_handler function is implemented as follows:

static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA)
{
uint32_t err_code;
NRF_LOG_DEBUG("Received data from BLE NUS. Writing data on UART.");
NRF_LOG_HEXDUMP_DEBUG(p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
for (uint32_t i = 0; i < p_evt->params.rx_data.length; i++)
{
do
{
err_code = app_uart_put(p_evt->params.rx_data.p_data[i]);
if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_BUSY))
{
NRF_LOG_ERROR("Failed receiving NUS message. Error 0x%x. ", err_code);
APP_ERROR_CHECK(err_code);
}
} while (err_code == NRF_ERROR_BUSY);
}
if (p_evt->params.rx_data.p_data[p_evt->params.rx_data.length-1] == '\r')
{
while (app_uart_put('\n') == NRF_ERROR_BUSY);
}
}
}

Handling data received over UART

Data that is received from the UART undergoes certain checks before it is relayed to the BLE peer using the Nordic UART Service. The code shown below is part of the app_uart event handler, which is called every time a character is received over the UART. Received characters are buffered into a string until a newline character is received or the size of the string matches the current MTU size. When one of these two conditions is met, the string is sent over BLE using the ble_nus_string_send function.

The implementation of the example requires the newline character to be '\n'. Some terminals use different characters to trigger a newline, for example '\r'. Therefore, you might need to configure your terminal to interpret only '\n' as a newline.

void uart_event_handle(app_uart_evt_t * p_event)
{
static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];
static uint8_t index = 0;
uint32_t err_code;
switch (p_event->evt_type)
{
UNUSED_VARIABLE(app_uart_get(&data_array[index]));
index++;
if ((data_array[index - 1] == '\n') || (index >= (m_ble_nus_max_data_len)))
{
NRF_LOG_DEBUG("Ready to send data over BLE NUS");
NRF_LOG_HEXDUMP_DEBUG(data_array, index);
do
{
uint16_t length = (uint16_t)index;
err_code = ble_nus_string_send(&m_nus, data_array, &length);
if ( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_BUSY) )
{
APP_ERROR_CHECK(err_code);
}
} while (err_code == NRF_ERROR_BUSY);
index = 0;
}
break;
break;
APP_ERROR_HANDLER(p_event->data.error_code);
break;
default:
break;
}
}

Setup

You can find the source code and the project file of the example in the following folder: <InstallFolder>\examples\ble_peripheral\ble_app_uart

LED assignments:

Button assignments: BSP BLE Button Assignments

Testing

Test the UART Application with the nRF UART app, which is available for iOS (App Store) and Android (Google Play).

You can also test the application with nRF Connect by performing the following steps:

  1. Connect the board to the computer using a USB cable. The board is assigned a COM port, which is visible in the Device Manager.
  2. Start a terminal emulator like PuTTY and connect to the used COM port with the following UART settings:
    • Baud rate: 115.200
    • 8 data bits
    • 1 stop bit
    • No parity
    • HW flow control: None
  3. Compile and program the application. Observe that the BSP_INDICATE_ADVERTISING state is indicated, and that the device is advertising with the device name "Nordic_UART".
  4. Observe that the text "UART Start!" is printed on the COM listener running on the computer.
  5. Connect to the device from nRF Connect. Observe that the BSP_INDICATE_CONNECTED state is indicated.
  6. Observe that the services are shown in the connected device.
  7. Select the UART RX characteristic value in nRF Connect. You can write hexadecimal ASCII values to the UART RX and get the text displayed on the COM listener.
  8. Write '30 31 32 33 34 35 36 37 38 39' (the hexadecimal value for the string "0123456789") and click 'write'. Verify that the text "0123456789" is displayed on the COM listener.
  9. For sending data from the device to nRF Connect, enter any text, for example, "Hello", on the COM listener. Observe that a notification with the corresponding ASCII values is sent to the peer on handle 0x12. For the string "Hello", the notification is '48 65 6C 6C 6F'.
  10. Disconnect the device in nRF Connect. Observe that the BSP_INDICATE_ADVERTISING state is indicated.
  11. If no peer connects to the application within 180 seconds (APP_ADV_TIMEOUT_IN_SECONDS), the application will put the chip into system-off mode.

Documentation feedback | Developer Zone | Subscribe | Updated