Merging two BLE examples together

This is a follow-up to my previous blog post; adding custom commands to a BLE example.

I hope it was interesting to see an example of adding the vendor specific Nordic UART service (NUS) into another project.

For completeness, I wanted to share steps needed to keep all the functionality of the BLE UART example (ble_app_uart), instead of just using part of the service functionality like we did in the previous blog post.

This leads to a few gotchas that we will explore now. As with the previous blog post, I uploaded the files to GitHub. Looking at the diff of the original ble_app_hts and the merged result ble_app_hts_uart is probably just as educational as reading the points below.

The following steps assume you have completed the steps in the previous blog post. We can continue with the same ble_app_hts main.c and sdk_config.h file.

1. Restore nus_data_handler() to how it looks in ble_app_uart.

Posted below for convenience.

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);
        }
    }

}

2. Changes to sdk_config.h to add UART.

UART uses app_uart and app_fifo but these are actually missing in the sdk_config used by ble_app_hts. we have to add them.

// <e> APP_UART_ENABLED - app_uart - UART driver
//==========================================================
#ifndef APP_UART_ENABLED
#define APP_UART_ENABLED 1
#endif
// <o> APP_UART_DRIVER_INSTANCE  - UART instance used
 
// <0=> 0 

#ifndef APP_UART_DRIVER_INSTANCE
#define APP_UART_DRIVER_INSTANCE 0
#endif

//==========================================================
// <q> APP_FIFO_ENABLED  - app_fifo - Software FIFO implementation
 

#ifndef APP_FIFO_ENABLED
#define APP_FIFO_ENABLED 1
#endif

// </e>

ble_app_hts was using the UART to print logs from the app.
ble_app_uart, on the other hand, was using RTT to print these logs since the UART was (fittingly enough) being used to print UART data coming over BLE from NUS.
We want that same behavior as the ble_app_uart example. Change NRF_LOG_BACKEND_UART_ENABLED form 1 to 0, and NRF_LOG_BACKEND_RTT_ENABLED from 0 to 1.

NRF_LOG_BACKEND_UART_ENABLED 0
NRF_LOG_BACKEND_RTT_ENABLED 1


3. Changes to project and main.c to add uart

Include app_uart module and the nrf_uart driver.

#include "app_uart.h"
#include "nrf_uart.h" //or #include "nrf_uarte.h" to use EasyDMA

Add app_uart_fifo and app_fifo source file to you project and the header file to your include path.
(app_uart.h and app_uart_fifo.c are located in <SDK>/components/libraries/uart)
(app_fifo.h and app_fifo.h are located in <SDK>/components/libraries/fifo)

Copy the entire uart_init() and uart_event_handle() from ble_app_uart. Posted below for convenience.

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,
        .flow_control = APP_UART_FLOW_CONTROL_DISABLED,
        .use_parity   = false,
#if defined (UART_PRESENT)
        .baud_rate    = NRF_UART_BAUDRATE_115200
#else
        .baud_rate    = NRF_UARTE_BAUDRATE_115200
#endif
    };

    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);
}

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)
    {
        case APP_UART_DATA_READY:
            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_data_send(&m_nus, data_array, &length, m_conn_handle);
                    if ( (err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_BUSY) &&
                         (err_code != NRF_ERROR_NOT_FOUND) )
                    {
                        APP_ERROR_CHECK(err_code);
                    }
                } while (err_code == NRF_ERROR_BUSY);

                index = 0;
            }
            break;

        case APP_UART_COMMUNICATION_ERROR:
            APP_ERROR_HANDLER(p_event->data.error_communication);
            break;

        case APP_UART_FIFO_ERROR:
            APP_ERROR_HANDLER(p_event->data.error_code);
            break;

        default:
            break;
    }
}

Add uart_init(); to your main function.

Add buffer defines for UART close to the top of your main file.

#define UART_TX_BUF_SIZE 256
#define UART_RX_BUF_SIZE 256

4. (optional) Enable long MTU

uart_event_handle() uses m_ble_nus_max_data_len. We have to add it near the top of main next to the other global defines.

static uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3;

Replace gatt_init() of ble_app_hts with the gatt_init() found in ble_app_uart.

void gatt_init(void)
{
    ret_code_t err_code;

    err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
    APP_ERROR_CHECK(err_code);
}

In sdk_config.h, change the NRF_SDH_BLE_GATT_MAX_MTU_SIZE from 23 to 247.

NRF_SDH_BLE_GATT_MAX_MTU_SIZE 247

5. Advertising data

By now the application should be able to run. You can connect to it using nRF Toolbox on iOS or Android or nRF Connect. Notice that logs are printed on the RTT and whatever you send with the UART app in nRF Toolbox is printed over UART on our pc.
The app connects to nRF toolbox without problems, but if you observe the nRF Connect app, you notice that it does not advertise with the NUS UUID. Let's add it.

If we try to add it to the m_adv_uuids, we will get the error NRF_ERROR_DATA_SIZE returned from advertising_init() because we don't have enough bytes left in our 29 bytes of advertising data to fit the 16 bytes of NUS UUID.
But we can add it to the scan response instead since that lets us send 29 additional payload bytes to any active scanner.

We do have space to change the advertising name:

#define DEVICE_NAME "Nordic_HTS_UART"

Add the UUID define from ble_app_uart, and the static list of scan response UUIDs.

#define NUS_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN

static ble_uuid_t m_sr_uuids[] =
{
    {BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE},
}

in advertising_init() add

init.srdata.uuids_complete.uuid_cnt = sizeof(m_sr_uuids) / sizeof(m_sr_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_sr_uuids;

Thats it! You should be able to test your application according to the steps described for ble_app_hts and ble_app_uart. Especially the the uart should be interesting to test since it should now work the same way as in ble_app_uart, with respect to input and output of UART data over terminal (unlike my last blog post).