5.1.2.7 BLE Custom Service

This section explains how to create a peripheral device with custom profile and control. The RGB LED and user button 2 on the PIC32-BZ6 Curiosity board shall be used to enable or disable the LED status. The peripheral device will be the PIC32-BZ6 Curiosity board and the central device can either be a smartphone with a Light Blue app or another PIC32-BZ6 Curiosity board. The instructions mentioned below are applicable for a BLE peripheral device.

Users can choose to either run the precompiled Application Example hex file provided on the PIC32-BZ6 Curiosity Board or follow the steps to develop the application from scratch.

It is recommended to follow the examples in sequence to understand the basic concepts before progressing to the advanced topics.

Hardware Requirement

Table 5-22. Hardware Prerequisites
S. No.

Tool

Quantity

1PIC32-BZ6 Curiosity Board

1

2Micro USB Cable

1

3Android/iOS Smart phone

1

SDK Setup

Refer to Getting Started with Software Development from Related Links.

Software

To install Tera Term tool, refer to the Tera Term web page in Reference Documentation from Related Links.

Smartphone App

Light Blue iOS/Android app available in stores

Programming the Precompiled Hex File or Application Example

Using MPLAB® X IPE:

  1. Import and program the precompiled hex file: <Harmony Content Path>\wireless_apps_pic32_bz6\apps\ble\building_blocks\peripheral\profiles_services\custom_service\precompiled_hex\custom_service.X.production.signed.hex.
  2. For detailed steps, refer to Programming a Device in MPLAB® IPE in Reference Documentation from Related Links.
    Note: Ensure to choose the correct Device and Tool information.

Using MPLAB® X IDE:

  1. Perform the following the steps mentioned in Running a Precompiled Example. For more information, refer to Running a Precompiled Application Example from Related Links.
  2. Open and program the application <Harmony Content Path>\wireless_apps_pic32_bz6\apps\ble\building_blocks\peripheral\profiles_services\custom_service\firmware\custom_service.X.
  3. For more details on how to find the Harmony Content Path, refer to Installing the MCC Plugin from Related Links.

Demo Description

This application enables users to control a RGB LED on the PIC32-BZ6 Curiosity board and read its status using a central device. On reset, demo will print “[BLE] Device Initialized. Ready for connection” which denotes the start of advertisements and then “[BLE] Connected to Peer Device”: when connection is established. Application data to be sent to the connected central device (smartphone or another PIC32-BZ6 Curiosity board) is entered in a terminal emulator like Tera Term.

Testing

  1. Using a micro USB cable, connect the Debug USB on the Curiosity board to a PC.
  2. Program the precompiled hex file or application example as mentioned.
  3. Open TeraTerm and configure as mentioned below:
    Terminal Settings:
    • Baud Rate/Speed – 115200 (as configured in SERCOM configuration)
    • Parity – None
    • Data Bits – 8
    • Stop Bits – 1
    • Flow Control – None
    For more details on how to set the “Serial Port” and “Speed”, refer to COM Port Setup in Running a Precompiled Application Example from Related Links.
  4. Press the NMCLR button on the Curiosity board. This must be displayed in Tera Term.
    Figure 5-113. Tera Term
  5. Launch the Light Blue mobile app and search for the device name “pic32cx-bz6” and press Connect. Users with an iOS device may see the device name as “Microchip”.
    Figure 5-114. LightBlue® App
    Figure 5-115. Tera Term
  6. Once connected, basic information like Advertisement Data and Device Information will be available.

    Figure 5-116. LightBlue® App
  7. Find the custom service with the UUID: 4d434850-5255-42d0-aef8-881facf4ceea. Two characteristics will be available with the custom service. The button characteristics with the Readable/Notify property and RGB LED characteristics with Readable/Writable property.
    Figure 5-117. LightBlue® App
  8. Click on the button characteristics and press subscribe. Press the User button 2 on the curiosity board. The Read Value in the app will give its current status. Pressing the button will also toggle the RGB LED.
    Figure 5-118. 
    Figure 5-119. 
  9. Go back and click on the RGB LED characteristics which has a read/write option. The write option changes the RGB LED Colour by writing a hex value in the form of XXXXXX. A value of 000000 will turn OFF the LED and value of FFFFFF turns the Red, Green and Blue LED of RGB LED ON. The read option will give the current value stored for the RGB LED.
    Figure 5-120. 
    Figure 5-121. 
  10. Press back two times to disconnect the BLE connection. A disconnected device will advertise again.
    Figure 5-122. Tera Term

Developing the Application from Scratch using MCC

Follow the steps below to build the application manually:
Note: It is recommended for the new users of the MPLAB Code Configurator to refer MPLAB® Code Configurator (MCC) User’s Guide in Reference Documentation from Related Links.
  1. Create a new harmony project. For more details, see Creating a New MCC Harmony Project from Related Links.
  2. To setup the basic components and configuration required to develop this application, import component configuration: <Harmony Content Path>\wireless_apps_pic32_bz6\apps\ble\building_blocks\peripheral\profiles_services\custom_service\firmware\custom_service.X\custom_service.mc.
    Note: Import and Export functionality of the Harmony component configuration will help users to start from a known working setup of the MCC configuration.
  3. Accept dependencies or satisfiers when prompted.
  4. Verify if the project graph window has all the expected configuration.
    Figure 5-123. Project Graph

Verify Custom Service Configuration

  1. Select BLE Stack component the Project Graph and configure the following in the Configuration Options panel.

    Note: If users cannot see the Configuration Options panel in the right-hand side of the MPLAB X IDE, it might be minimized. Hover the cursor towards the Configuration Options side tab and click the “dot” on the top right-hand corner to pin it (see the following figure).
    Figure 5-124. Configuration Panel
    .
  2. Select the FreeRTOS component in the Project Graph and configure the following.
    Figure 5-125. FreeRTOS Configuration
  3. Select the SERCOM0 component in the Project Graph and configure the following.
    Figure 5-126. SERCOM0 Configuration
  4. Select the System component in the Project Graph and configure the following.
    Figure 5-127. System Configuration
  5. Select the EIC component in the Project Graph and configure the following.
    Figure 5-128. EIC Configuration
  6. Select the Customized Service component in the Project Graph and configure the following.
    Figure 5-129. Customized Service Configuration
    Figure 5-130. Customized Service Configuration
    Figure 5-131. Customized Service Configuration

Files and Routines Automatically generated by the MCC

After generating the program source from the MCC interface by clicking Generate Code, the BLE configuration source and header files can then be found in the following project directories.
Figure 5-132. Project Files

Initialization routines for OSAL, RF System, and BLE System are auto-generated by the MCC. See OSAL Libraries Help in Reference Documentation from Related Links. Initialization routine executed during program initialization can be found in the project file.

Figure 5-133. initialization.c

The BLE stack initialization routine executed during Application Initialization can be found in project files. This initialization routine is automatically generated by the MCC. This call initializes and configures the GAP, GATT, SMP, L2CAP and BLE middleware layers.

Figure 5-134. 
Autogenerated, Advertisement Data Format
Figure 5-135. AD Structures and Types
Table 5-23. Source Files
Source FilesUsage
app.cApplication State machine, includes calls for Initialization of all BLE stack (GAP,GATT, SMP, L2CAP) related component configurations
app_ble\app_ble.cSource code for the BLE stack related component configurations, code related to function calls from app.c
app_ble\app_ble_handler.cAll GAP, GATT, SMP and L2CAP event handlers
ble_button_led_svc.cAll custom service button GREEN LED configurations and functions
Note: app.c is autogenerated and has a state machine-based application code. Users can use this template to develop their own application.

User Application Development

Add the code to enable advertisement, call the button external interrupt callback functions and initialize custom service in APP_STATE_INIT of the function APP_Tasks() in app.c

Figure 5-136. app.c
 /* Application's initial state. */
 case APP_STATE_INIT:
 {  
    bool appInitialized = true;
    //appData.appQueue = xQueueCreate( 10, sizeof(APP_Msg_T) );
    APP_BleStackInit();
    BLE_GAP_SetAdvEnable(0x01, 0); //Enable BLE Advertisement
    BLE_BUTTON_LED_Add();
    APP_Button_Init();
    SYS_CONSOLE_PRINT("[BLE] Device Initialized. Ready for connection. \r\n");
Add the GREEN LED and button handlers to the function APP_Tasks() in app.c
Figure 5-137. app.c
 case APP_STATE_SERVICE_TASKS:
 { 
    if (OSAL_QUEUE_Receive(&appData.appQueue, &appMsg, OSAL_WAIT_FOREVER))
    {
     switch (p_appMsg->msgId)
     {
        case APP_MSG_BLE_STACK_EVT:
        {
            APP_BleStackEvtHandler((STACK_Event_T *)p_appMsg->msgData);
         }
         break;
         case APP_MSG_BLE_CS_LED_EVT:
         {
             APP_CustomService_RGB_Handler((uint8_t *)((STACK_Event_T *)p_appMsg->msgData));
          }
          break;
          case APP_MSG_BLE_CS_BUTTON_EVT:
          {
              APP_CustomService_Button_Handler();
          }
          break;
       }
   }
  break;
 }

Add the message IDs for the GREEN LED and button events in app.h

Figure 5-138. app.h
 typedef enum APP_MsgId_T
 {
     APP_MSG_BLE_STACK_EVT,
     APP_MSG_ZB_STACK_EVT,
     APP_MSG_ZB_STACK_CB,
     APP_MSG_BLE_CS_LED_EVT,
     APP_MSG_BLE_CS_BUTTON_EVT,
     APP_MSG_STACK_END
 } APP_MsgId_T;

Add the custom service source and header files “<Harmony Content Path>\wireless_apps_pic32_bz6\apps\ble\building_blocks\peripheral\profiles_services\custom_service\firmware\src\app_ble_custom_service.c and app_ble_custom_service.h and app_ble_custom_service.h which has the supporting functions for the button LED custom service file.

Figure 5-139. Project Files
Add the required variables, functions and callback handlers for the GATT read/write response in app_ble_handler.c along with the connect and disconnect message information.
Figure 5-140. app_ble_handler.c
Figure 5-141. app_ble_handler.c
Figure 5-142. app_ble_handler.c
Figure 5-143. app_ble_handler.c
void APP_BleGapEvtHandler(BLE_GAP_Event_T *p_event)
{ 
    switch(p_event->eventId)
    {
        case BLE_GAP_EVT_CONNECTED:
        {
        /* TODO: implement your application code.*/
        SYS_CONSOLE_PRINT("[BLE] Connected to Peer Device: 0x");
        for(int8_t idx=(GAP_MAX_BD_ADDRESS_LEN-1); idx>=0; idx--)
        {
            SYS_CONSOLE_PRINT("%02x", p_event->eventField.evtConnect.remoteAddr.addr[idx]);
        }
        SYS_CONSOLE_PRINT("\n\r[BLE] Connection Handle: %d\n\r",p_event->eventField.evtConnect.connHandle);
        G_ConnHandle = p_event->eventField.evtConnect.connHandle;
        }
        break;
        case BLE_GAP_EVT_DISCONNECTED:
        {
           /* TODO: implement your application code.*/
           SYS_CONSOLE_PRINT("[BLE] Disconnected Handle: %d, Reason: 0x%X\n\r",p_event->eventField.evtDisconnect.connHandle, p_event->eventField.evtDisconnect.reason);
           G_ConnHandle = 0;
           BLE_GAP_SetAdvEnable(0x01, 0); //Enable BLE Advertisement          
        }
        break;

Add the functions for the GATT read/write handlers which will act on the received GATT request in app_ble_handler.c.

Figure 5-144. app_ble_handler.c
Figure 5-145. app_ble_handler.c
void APP_GattSEvtReadHandler(GATT_EvtRead_T p_event)
{

    uint8_t error = 0;
    uint16_t status;
    SYS_CONSOLE_PRINT("[BLE] GATT Read ATTR Handle 0x%X \r\n",p_event.attrHandle);
    
    if ((p_event.attrHandle <= BUTTON_LED_START_HDL) ||
        (p_event.attrHandle > BUTTON_LED_END_HDL))
    {
        /* Not BLE Custom Service characteristic. */
        return;
    }

        switch(p_event.attrHandle)
        {
            case BUTTON_LED_HDL_CHAR_0:                            /**< Handle of characteristic 0. */
            case BUTTON_LED_HDL_CCCD_0:                            /**< Handle of characteristic 0 CCCD . */
            case BUTTON_LED_HDL_CHAR_1:                            /**< Handle of characteristic 1. */
            case BUTTON_LED_HDL_CHARVAL_1:                         /**< Handle of characteristic 1 value. */
                error = ATT_ERR_APPLICATION_ERROR;
                break;
            case BUTTON_LED_HDL_CHARVAL_0:                         /**< Handle of characteristic 0 value. */                
//                SYS_CONSOLE_PRINT(" ATTR Handle Read 0x%X \r\n",p_event.attrHandle);
                break;
        }

    if ((p_event.readType == ATT_READ_REQ)
    || (p_event.readType == ATT_READ_BLOB_REQ))
    {
        if (!error)
        {
            sp_trsReadRespParams = (GATTS_SendReadRespParams_T *)OSAL_Malloc(sizeof(GATTS_SendReadRespParams_T));
            if (sp_trsReadRespParams == NULL)
            {
                return;
            }
            trsRespErrConnHandle = p_event.connHandle;
            sp_trsReadRespParams->responseType = ATT_READ_RSP;
            sp_trsReadRespParams->attrLength = 0x01;
            sp_trsReadRespParams->attrValue[0]= bleCSdata.rgbOnOffStatus;            
            status = GATTS_SendReadResponse(p_event.connHandle, sp_trsReadRespParams);
            if (status == MBA_RES_SUCCESS)
            {
                OSAL_Free(sp_trsReadRespParams);
                sp_trsReadRespParams = NULL;
            }
        }
        else
        {
            sp_trsErrParams = (GATTS_SendErrRespParams_T *)OSAL_Malloc(sizeof(GATTS_SendErrRespParams_T));
            if (sp_trsErrParams == NULL)
            {
                return;
            }
            trsRespErrConnHandle = p_event.connHandle;
            sp_trsErrParams->reqOpcode = p_event.readType;
            sp_trsErrParams->attrHandle = p_event.attrHandle;
            sp_trsErrParams->errorCode = error;
            status = GATTS_SendErrorResponse(p_event.connHandle, sp_trsErrParams);
            if (status == MBA_RES_SUCCESS)
            {
                OSAL_Free(sp_trsErrParams);
                sp_trsErrParams = NULL;
            }
        }
    }   
}
void APP_GattSEvtWriteHandler(GATT_EvtWrite_T p_event)
{
    uint8_t error = 0;
    uint16_t status;
    SYS_CONSOLE_PRINT("[BLE] GATT Write ATTR Handle 0x%X \r\n",p_event.attrHandle);
    
    if ((p_event.attrHandle <= BUTTON_LED_START_HDL) ||
        (p_event.attrHandle > BUTTON_LED_END_HDL))
    {
        /* Not BLE Custom Service characteristic. */
        error = ATT_ERR_INVALID_HANDLE;
        return;
    }
    
        switch(p_event.attrHandle)
        {
            case BUTTON_LED_HDL_CHAR_0:                            /**< Handle of characteristic 0. */
            case BUTTON_LED_HDL_CHARVAL_0:                         /**< Handle of characteristic 0 value. */
            case BUTTON_LED_HDL_CCCD_0:                            /**< Handle of characteristic 0 CCCD . */
            case BUTTON_LED_HDL_CHAR_1:                            /**< Handle of characteristic 1. */
                error = ATT_ERR_APPLICATION_ERROR;
                break;
            case BUTTON_LED_HDL_CHARVAL_1:                         /**< Handle of characteristic 1 value. */
//                SYS_CONSOLE_PRINT(" ATTR Handle %d \r\n",p_event.attrHandle);
                APP_CustomService_RGB_Callback(p_event.writeValue);
                break;
        }

    if ((p_event.writeType == ATT_WRITE_REQ)
    || (p_event.writeType == ATT_PREPARE_WRITE_REQ))
    {
        if (!error)
        {
            sp_trsRespParams = (GATTS_SendWriteRespParams_T *)OSAL_Malloc(sizeof(GATTS_SendWriteRespParams_T));
            if (sp_trsRespParams == NULL)
            {
                return;
            }
            trsRespErrConnHandle = p_event.connHandle;
            sp_trsRespParams->responseType = ATT_WRITE_RSP;
            status = GATTS_SendWriteResponse(p_event.connHandle, sp_trsRespParams);
            if (status == MBA_RES_SUCCESS)
            {
                OSAL_Free(sp_trsRespParams);
                sp_trsRespParams = NULL;
            }
        }
        else
        {
            sp_trsErrParams = (GATTS_SendErrRespParams_T *)OSAL_Malloc(sizeof(GATTS_SendErrRespParams_T));
            if (sp_trsErrParams == NULL)
            {
                return;
            }
            trsRespErrConnHandle = p_event.connHandle;
            sp_trsErrParams->reqOpcode = p_event.writeType;
            sp_trsErrParams->attrHandle = p_event.attrHandle;
            sp_trsErrParams->errorCode = error;
            status = GATTS_SendErrorResponse(p_event.connHandle, sp_trsErrParams);
            if (status == MBA_RES_SUCCESS)
            {
                OSAL_Free(sp_trsErrParams);
                sp_trsErrParams = NULL;
            }
        }
    }
}
Note: Manual read/write responses, permissions and other standard events like GAP and GATT can be executed in app_ble_handler.c
Note: Users can explore more BLE Advertisement functionalities using the BLE Stack APIs. For more information, refer to BLE Stack in Reference Documentation from Related Links.