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
}