4. Code Overview

This section provides the code blocks needed to successfully execute this tutorial.

4.1. Custom Ble Service Header File

Create a new header file, for instance my_custom_service.h, and add the following code:

#include <stdint.h>
#include <ble_service.h>


/* User-defined callback functions - Prototyping */
typedef void (* mcs_get_char_value_cb_t) (ble_service_t *svc, uint16_t conn_idx);

typedef void (* mcs_set_char_value_cb_t) (ble_service_t *svc, uint16_t conn_idx, const uint8_t *value);



/* User-defined callback functions */
typedef struct {

        /* Handler for read requests - Triggered on application context */
        mcs_get_char_value_cb_t get_characteristic_value;

        /* Handler for write requests - Triggered on application context */
        mcs_set_char_value_cb_t set_characteristic_value;

} my_custom_service_cb_t;


/*
 * \brief This function creates the custom BLE service and registers it in BLE framework
 *
 * \param [in] variable_value Default characteristic value
 * \param [in] cb             Application callback functions
 *
 * \return service handle
 *
 */
ble_service_t *mcs_init(const uint8_t *variable_value, const my_custom_service_cb_t *cb);



/*
 * This function should be called by the application as a response to a read request
 *
 * \param[in] svc       service instance
 * \param[in] conn_idx  connection index
 * \param[in] status    ATT error
 * \param[in] value     attribute value
 */
void mcs_get_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status, const uint8_t *value);


/*
 * This function should be called by the application as a response to a write request
 *
 * \param[in] svc       service instance
 * \param[in] conn_idx  connection index
 * \param[in] status    ATT error
 */
void mcs_set_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status);



/*
 * Notify all the connected peer devices that characteristic value has been updated.
 *
 * \param[in] svc       service instance
 * \param[in] value     updated characteristic value
 */
void mcs_notify_char_value_all(ble_service_t *svc, const uint8_t *value);

4.2. Custom BLE Service Source File

Code snippet of custom BLE service implementation. Create a new source file, for instance my_custom_service.c, and add the following code:

#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "osal.h"
#include "ble_att.h"
#include "ble_bufops.h"
#include "ble_common.h"
#include "ble_gatt.h"
#include "ble_gatts.h"
#include "ble_storage.h"
#include "ble_uuid.h"
#include "my_custom_service.h"


#define UUID_GATT_CLIENT_CHAR_CONFIGURATION (0x2902)

static const char char_user_descriptor_val[]  = "Switch ON/OFF LED D2 on DevKit";


void mcs_notify_char_value(ble_service_t *svc, uint16_t conn_idx, const uint8_t *value);

/* Service related variables */
typedef struct {
        ble_service_t svc;

        // User-defined callback functions
        const my_custom_service_cb_t *cb;

        // Attribute handles of BLE service
        uint16_t mc_char_value_h;
        uint16_t mc_char_value_ccc_h;

} mc_service_t;




/* This function is called upon write requests to characteristic attribute value */
static att_error_t do_char_value_write(mc_service_t *mcs, uint16_t conn_idx, uint16_t offset, uint16_t length, const uint8_t *value)
{
        uint8_t ct;

        if (offset) {
                return ATT_ERROR_ATTRIBUTE_NOT_LONG;
        }


        /* Check if the length of the envoy data exceed the maximum permitted */
        if (length != 1) {
                return ATT_ERROR_INVALID_VALUE_LENGTH;
        }

        /*
         * Check whether the application has defined a callback function
         * for handling the event.
         */
        if (!mcs->cb || !mcs->cb->set_characteristic_value) {
                return ATT_ERROR_WRITE_NOT_PERMITTED;
        }

        ct = get_u8(value);

        /*
         * The application should get the data written by the peer device.
         */
        mcs->cb->set_characteristic_value(&mcs->svc, conn_idx, &ct);

        return ATT_ERROR_OK;

}


/* This function is called upon write requests to CCC attribute value */
static att_error_t do_char_value_ccc_write(mc_service_t *mcs, uint16_t conn_idx, uint16_t offset, uint16_t length, const uint8_t *value)
{
        uint16_t ccc;

        if (offset) {
                return ATT_ERROR_ATTRIBUTE_NOT_LONG;
        }

        if (length != sizeof(ccc)) {
                return ATT_ERROR_INVALID_VALUE_LENGTH;
        }

        ccc = get_u16(value);

        /* Store the envoy CCC value to the ble storage */
        ble_storage_put_u32(conn_idx, mcs->mc_char_value_ccc_h, ccc, true);

        return ATT_ERROR_OK;
}





/* This function is called upon read requests to characteristic attribue value */
static void do_char_value_read(mc_service_t *mcs, const ble_evt_gatts_read_req_t *evt)
{
        /*
         * Check whether the application has defined a callback function
         * for handling the event.
         */
        if (!mcs->cb || !mcs->cb->get_characteristic_value) {
                ble_gatts_read_cfm(evt->conn_idx, evt->handle, ATT_ERROR_READ_NOT_PERMITTED, 0, NULL);
                return;
        }

        /*
         * The application should provide the requested data to the peer device.
         */
        mcs->cb->get_characteristic_value(&mcs->svc, evt->conn_idx);

        // callback executed properly

}

/*---------------------------------------------------------------------------------------------------------------------------------------------------------------*/



/* Notify all the connected peer devices that characteristic attribute value has been updated */
void mcs_notify_char_value_all(ble_service_t *svc, const uint8_t *value)
{
        uint8_t num_conn;
        uint16_t *conn_idx;

        ble_gap_get_connected(&num_conn, &conn_idx);

        while (num_conn--) {
                mcs_notify_char_value(svc, conn_idx[num_conn], value);
        }

        if (conn_idx) {
                OS_FREE(conn_idx);
        }
}



/* Notify the peer device that characteristic attribute value has been updated */
void mcs_notify_char_value(ble_service_t *svc, uint16_t conn_idx, const uint8_t *value)
{
        mc_service_t *mcs = (mc_service_t *) svc;

        uint16_t ccc = 0x0000;
        uint8_t pdu[1];

        ble_storage_get_u16(conn_idx, mcs->mc_char_value_ccc_h, &ccc);


        /* Check if the notifications are enabled from the peer device, otherwise don't send anything. */
        if (!(ccc & GATT_CCC_NOTIFICATIONS)) {
                return;
        }

        pdu[0] = *((uint8_t *)value);

        ble_gatts_send_event(conn_idx, mcs->mc_char_value_h, GATT_EVENT_NOTIFICATION, sizeof(pdu), pdu);
}




/*
 * This function should be called by the application as a response to read requests
 */
void mcs_get_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status, const uint8_t *value)
{
        mc_service_t *mcs = (mc_service_t *) svc;
        uint8_t pdu[1];

        pdu[0] = *value;

        /* This function should be used as a response for every read request */
        ble_gatts_read_cfm(conn_idx, mcs->mc_char_value_h, ATT_ERROR_OK, sizeof(pdu), pdu);
}


/*
 * This function should be called by the application as a response to write requests
 */
void mcs_set_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status)
{
        mc_service_t *mcs = (mc_service_t *) svc;

        /* This function should be used as a response for every write request */
        ble_gatts_write_cfm(conn_idx, mcs->mc_char_value_h, status);
}


/* Handler for read requests, that is BLE_EVT_GATTS_READ_REQ */
static void handle_read_req(ble_service_t *svc, const ble_evt_gatts_read_req_t *evt)
{
        mc_service_t *mcs = (mc_service_t *) svc;

        /*
         * Identify for which attribute handle the read request has been sent to
         * and call the appropriate function.
         */

        if (evt->handle == mcs->mc_char_value_h) {
                do_char_value_read(mcs, evt);
        } else if (evt->handle == mcs->mc_char_value_ccc_h) {
                uint16_t ccc = 0x0000;

                /* Extract the CCC value from the ble storage */
                ble_storage_get_u16(evt->conn_idx, mcs->mc_char_value_ccc_h, &ccc);

                // We're little-endian - OK to write directly from uint16_t
                ble_gatts_read_cfm(evt->conn_idx, evt->handle, ATT_ERROR_OK, sizeof(ccc), &ccc);

        /* Otherwise read operations are not permitted */
        } else {
                ble_gatts_read_cfm(evt->conn_idx, evt->handle, ATT_ERROR_READ_NOT_PERMITTED, 0, NULL);
        }

}


/* Handler for write requests, that is BLE_EVT_GATTS_WRITE_REQ */
static void handle_write_req(ble_service_t *svc, const ble_evt_gatts_write_req_t *evt)
{
        mc_service_t *mcs = (mc_service_t *) svc;
        att_error_t status = ATT_ERROR_WRITE_NOT_PERMITTED;

        /*
         * Identify for which attribute handle the write request has been sent to
         * and call the appropriate function.
         */

        if (evt->handle == mcs->mc_char_value_h) {
                status = do_char_value_write(mcs, evt->conn_idx, evt->offset, evt->length, evt->value);
                goto done;
        } else if (evt->handle == mcs->mc_char_value_ccc_h) {
                status = do_char_value_ccc_write(mcs, evt->conn_idx, evt->offset, evt->length, evt->value);
                goto done;
        }

done:
                if (status == ((att_error_t) - 1)) {
                        // Write handler executed properly, will be replied by cfm call
                        return;
                }

                /* Otherwise write operations are not permitted */
                ble_gatts_write_cfm(evt->conn_idx, evt->handle, status);
}



/* Function to be called after a cleanup event */
static void cleanup(ble_service_t *svc)
{
        mc_service_t *mcs = (mc_service_t *) svc;

        ble_storage_remove_all(mcs->mc_char_value_ccc_h);

        OS_FREE(mcs);
}


/* Initialization function for My Custom Service (mcs).*/
ble_service_t *mcs_init(const uint8_t *variable_value ,const my_custom_service_cb_t *cb)
{
        mc_service_t *mcs;

        uint16_t num_attr;
        att_uuid_t uuid;

        uint16_t char_user_descriptor_h;

        /* Allocate memory for the sevice hanle */
        mcs = (mc_service_t *)OS_MALLOC(sizeof(*mcs));
        memset(mcs, 0, sizeof(*mcs));


        /* Declare handlers for specific BLE events */
        mcs->svc.read_req  = handle_read_req;
        mcs->svc.write_req = handle_write_req;
        mcs->svc.cleanup   = cleanup;
        mcs->cb = cb;


        /*
         * 0 --> Number of Included Services
         * 1 --> Number of Characteristic Declarations
         * 2 --> Number of Descriptors
         */
        num_attr = ble_gatts_get_num_attr(0, 1, 2);


        /* Service declaration */
        ble_uuid_from_string("00000000-1111-2222-2222-333333333333", &uuid);
        ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr);


        /* Characteristic declaration */
        ble_uuid_from_string("11111111-0000-0000-0000-111111111111", &uuid);
        ble_gatts_add_characteristic(&uuid, GATT_PROP_READ | GATT_PROP_NOTIFY | GATT_PROP_WRITE,
                                ATT_PERM_RW, 1, GATTS_FLAG_CHAR_READ_REQ, NULL, &mcs->mc_char_value_h);


        /* Define descriptor of type Client Characteristic Configuration (CCC) */
        ble_uuid_create16(UUID_GATT_CLIENT_CHAR_CONFIGURATION, &uuid);
        ble_gatts_add_descriptor(&uuid, ATT_PERM_RW, 2, 0, &mcs->mc_char_value_ccc_h);

        /* Define descriptor of type Characteristic User Description (CUD) */
        ble_uuid_create16(UUID_GATT_CHAR_USER_DESCRIPTION, &uuid);
        ble_gatts_add_descriptor(&uuid, ATT_PERM_READ, sizeof(char_user_descriptor_val),
                                                              0, &char_user_descriptor_h);


        /*
         * Register all the attribute handles so that they can be updated
         * by the BLE manager automatically.
         */
        ble_gatts_register_service(&mcs->svc.start_h, &mcs->mc_char_value_h,
                         &mcs->mc_char_value_ccc_h, &char_user_descriptor_h ,0);


        /* Calculate the last attribute handle of the BLE service */
        mcs->svc.end_h = mcs->svc.start_h + num_attr;

        /* Set default attribute values */
        ble_gatts_set_value(mcs->mc_char_value_h, 1, variable_value);
        ble_gatts_set_value(char_user_descriptor_h,  sizeof(char_user_descriptor_val),
                                                               char_user_descriptor_val);

        /* Register the BLE service in BLE framework */
        ble_service_add(&mcs->svc);

        /* Return the service handle */
        return &mcs->svc;

}

4.3. Initializing the Custom BLE Service

In ble_peripheral_task.c, before ble_peripheral_task(), declare all the user-defined callback functions as well as variables for initializing the custom BLE service:

#if CFG_MY_CUSTOM_SERVICE
#include "my_custom_service.h"
#include "hw_gpio.h"


/* LED D2 status flag */
__RETAINED_RW volatile bool pin_status_flag = 0;

/* Characteristic value */
__RETAINED_RW uint8_t mcs_char_val = 0;

/* Handle of custom BLE service */
__RETAINED_RW ble_service_t *mcs = NULL;


/* Handler for read requests */
static void mcs_get_char_val_cb(ble_service_t *svc, uint16_t conn_idx)
{
        uint8_t var_value = mcs_char_val;

        /* Send the requested data to the peer device.  */
        mcs_get_char_value_cfm(svc, conn_idx, ATT_ERROR_OK, &var_value);
}


/* Handler for write requests */
static void mcs_set_char_val_cb(ble_service_t *svc, uint16_t conn_idx, const uint8_t *value)
{
        mcs_char_val = *value;

      /*
       * Check the written value and if it is equal to 0x01
       * then turn on LED D2 on DevKit.
       */
      if (mcs_char_val == 0x01) {
              hw_gpio_set_active(HW_GPIO_PORT_1, HW_GPIO_PIN_5);
              pin_status_flag = 1; // Turn on LED D2
      } else {
              hw_gpio_set_inactive(HW_GPIO_PORT_1, HW_GPIO_PIN_5);
              pin_status_flag = 0; // Turn off LED D2
      }

      /* Send an ACK to the peer device as a response to the write request */
      mcs_set_char_value_cfm(svc, conn_idx, ATT_ERROR_OK);

      /*
       * Notify all the connected peer devices that characteristic value
       * has been changed.
       */
      mcs_notify_char_value_all(mcs, &mcs_char_val);
}

/* Declare callback functions for specific BLE events */
static const my_custom_service_cb_t mcs_callbacks = {
        .get_characteristic_value = mcs_get_char_val_cb,
        .set_characteristic_value = mcs_set_char_val_cb,
};
#endif

In ble_peripheral_task.c, inside ble_peripheral_task() and before starting advertising, register the custom BLE service in Dialog BLE framework:

#if CFG_MY_CUSTOM_SERVICE
        /* Initialize the custom BLE service */
        mcs = mcs_init(&mcs_char_val, &mcs_callbacks);
#endif

Optionally, in ble_peripheral_task.c source file change the advertising data:

/*
 * BLE peripheral advertising data
 */
static const uint8_t adv_data[] = {
        0x12, GAP_DATA_TYPE_LOCAL_NAME,
        'M', 'y', ' ', 'C', 'u', 's', 't', 'o', 'm', ' ', 'S', 'e', 'r', 'v', 'i', 'c', 'e'
};

4.4. Macro Definitions

In config/ble_peripheral_config.h, disable all the predefined services and add/enable the custom service as provided by this tutorial.

// enable debug service (see readme.txt for details)
#define CFG_DEBUG_SERVICE      (0)

#define CFG_BAS                (0)     // register BAS service
#define CFG_BAS_MULTIPLE       (0)     // add 2 instances of BAS service
#define CFG_CTS                (0)     // register CTS
#define CFG_DIS                (0)     // register DIS
#define CFG_DIS_FULL           (0)     // add all possible characteristics to DIS
#define CFG_SCPS               (0)     // register ScPS
#define CFG_USER_SERVICE       (0)     // register custom service (using 128-bit UUIDs)

#define CFG_MY_CUSTOM_SERVICE  (1)

4.5. Hardware Initialization

In main.c, replace prvSetupHardware() with the following code to configure pins after a power-up/wake-up cycle. Please note that every time the system enters sleep, it loses all its pin configurations.

__RETAINED_RW extern volatile bool pin_status_flag;

/**
 * @brief Initialize the peripherals domain after power-up.
 *
 */
static void periph_init(void)
{
        hw_gpio_configure_pin(HW_GPIO_PORT_1, HW_GPIO_PIN_5, HW_GPIO_MODE_OUTPUT,
                                                   HW_GPIO_FUNC_GPIO, pin_status_flag);
}

/**
 * \brief Initialize the peripherals domain after power-up.
 *
 */

static void prvSetupHardware( void )
{
#if mainCHECK_INTERRUPT_STACK == 1
        extern unsigned long _vStackTop[], _pvHeapStart[];
        unsigned long ulInterruptStackSize;
#endif

        /* Init hardware */
        pm_system_init(periph_init);

#if mainCHECK_INTERRUPT_STACK == 1
        /* The size of the stack used by main and interrupts is not defined in
           the linker, but just uses whatever RAM is left.  Calculate the amount of
           RAM available for the main/interrupt/system stack, and check it against
           a reasonable number.  If this assert is hit then it is likely you don't
           have enough stack to start the kernel, or to allow interrupts to nest.
           Note - this is separate to the stacks that are used by tasks.  The stacks
           that are used by tasks are automatically checked if
           configCHECK_FOR_STACK_OVERFLOW is not 0 in FreeRTOSConfig.h - but the stack
           used by interrupts is not.  Reducing the conifgTOTAL_HEAP_SIZE setting will
           increase the stack available to main() and interrupts. */
        ulInterruptStackSize = ( ( unsigned long ) _vStackTop ) - ( ( unsigned long ) _pvHeapStart );
        OS_ASSERT( ulInterruptStackSize > 350UL );

        /* Fill the stack used by main() and interrupts to a known value, so its
           use can be manually checked. */
        memcpy( ( void * ) _pvHeapStart, ucExpectedInterruptStackValues, sizeof( ucExpectedInterruptStackValues ) );
#endif
}