4.1.3 Bluetooth®LE Multirole Multilink Transparent UART

This section helps users create a multirole multilink device and send/receive characters between connected Bluetooth Low Energy devices over Microchip proprietary Transparent UART Profile. The multilink central enables users to connect multiple peripheral devices to a central device. The multilink central device acts as peripheral device and is connected to an another central device (MBD mobile application). The central is MBD application and peripheral devices in this demo is PIC32WM-BW1 devices.

Hardware Requirement

Table 4-14. Hardware Requirement
ToolQty
PIC32WM-BW1 Curiosity Board3 (min)
USB Type-C cable3

Software Requirement

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

Programming the Precompiled hex File or Application Example

Programming the hex file using MPLABX IPE

  1. Precompiled Hex file is located in “<Harmony Content Path>\wireless_apps_pic32_bw1\apps\ble\multirole\multilink\hex” folder
  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.

Programming the application using MPLABX 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 example “mr_ml_trp_uart.x” located in “<Harmony Content Path>\wireless_apps_pic32_bw1\apps\ble\multirole\multilink\firmware” using MPLABX IDE

Testing

  1. Users must use 3–4 PIC32WM-BW1 Curiosity Boards where 3 boards are peripheral devices and 1 configured as multirole device.
  2. Program 3 peripheral devices with “peripheral_trp_uart” application. For demo testing with multiple links, user needs to configure the peripheral devices with different Public addresses as following. The public address can be changed from file app_ble.c, APP_BleConfigBasic() in struct devAddr.
    1. Board 1:{0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6}
    2. Board 2: {0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6}
    3. Board 3: {0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6}
    Figure 4-92. app_ble.c
  3. For respective peripheral device open Tera Term configured with following settings through Terminal Settings:
    1. Baud rate/ Speed – 115200
    2. Parity – None
    3. Data bits – 8
    4. Stop bits – 1
    5. Flow Control – None
  4. Reset the board. Upon reset, “BW1-Advertising” will be displayed. This indicates a peripheral device is advertising itself.
  5. Program PIC32WM-BW1 Curiosity board 4 with “mr_ml_trp_uart.X” application. Open Tera Term configured with following settings through Terminal Settings:
    1. Baud rate/ Speed – 115200
    2. Parity – None
    3. Data bits – 8
    4. Stop bits – 1
    5. Flow Control – None
  6. Reset the board. Upon reset, “Scanning” will be displayed. This indicates multirole device is looking for nearby peripheral devices to connect. It scans for 100 seconds.
  7. Upon finding peripheral device with any public address message “Found Peer Node” will be displayed and a connection request will be initiated. The user will see message “Initiating Connection” followed by “Connected” after successful connection.
  8. During the scan time if more devices are available, which will be true in this case, multirole multilink device will keep initiating connections with the new peer nodes.
  9. After connection establishment, all connected peripheral devices will display “Connected!” on respective terminal windows.
  10. Users can now start sending data through peripheral devices to the multirole device. Characters entered on any peripheral device (Board 1,2,3) terminal emulator will appear on central board’s (board 4) terminal emulator.
    Figure 4-93. Tera Term Logs
  11. After scanning is completed, multirole device will start advertising with message “Scan Completed! Advertising” to enable the user to connect a central device (like mobile) to multirole device.
  12. Open LightBlue Bluetooth LE application on mobile device and connect to the multirole device advertising with name “pic32wm-bw1”.
  13. To send message, select 0x49535343-8841-43F4-A8D4-ECBE34729BB3. Change the text string to UTF-8 through option on top right corner and select on Write new value to enter your text.
    Note: When connecting through the LightBlue app on Android, select the following properties: Write and Write Without Response.
  14. The entered text will be displayed on multirole device emulator as “Server Data:”.
    Figure 4-94. LightBlue App
  15. Characters entered on multirole multilink device terminal emulator will appear on peripheral devices emulator in a round-robin fashion without priority. For example, to send character “a” to all peripheral devices “aaa” needs to be entered on terminal emulator of central device.

MCC Project Graph

This section explains how MCC Project graph should look like and component configurations.

  1. Verify if the Project Graph window has all the expected configuration as illustrated in the following figure.
    Figure 4-95. Project Graph

Verify Scan, Advertisement and Transparent Profile Configuration

  1. Select BLE Stack component in project graph.
    Figure 4-96. BLE Stack Configuration
    Figure 4-97. BLE Stack Configuration
    Note: Advertising Interval Min and Max can be modified. Advertisement payload can be configured by user here.
  2. Select Transparent Profile configuration.
    Figure 4-98. Transparent Profile Configuration

Generating a Code

For more details on code generation, refer to MPLAB Code Configurator (MCC) Code Generation section from Related Links.

After generating the program source from MCC interface by clicking Generate Code, the Bluetooth Low Energy configuration can be found in the following project directories.
Figure 4-99. Project File Structure
The OSAL, RF System, BLE System initialization routine executed during program initialization can be found in the project files. This initialization routine is automatically generated by the MCC.
Figure 4-100. initialization.c

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

During system sleep, clock (system PLL) will be disabled and system tick will be turned OFF. FreeRTOS timer needs to be compensated for the time spent in sleep. RTC timer which works in the sleep mode is used to accomplish this. RTC timer will be initialized after BLE stack initialization.
Table 4-15. Source Files
Source FilesUsage
app.cApplication State machine, includes calls for Initialization of all Bluetooth Low Energy stack (GAP,GATT, SMP, L2CAP) related component configurations
app_ble.cSource Code for the Bluetooth Low Energy stack related component configurations, code related to function calls from app.c
app_ble_handler.cAll GAP, GATT, SMP and L2CAP Event handlers
app_trspc_handler.cAll Transparent UART Client related Event handlers
app_trsps_handler.cAll Transparent UART Server related Event handlers
ble_trspc.cAll Transparent Client Functions for user application
ble_trsps.cAll Transparent Server Functions for user application
Note: app.c is auto generated and has a state machine based Application code sample and users can use this template to develop their application.

Header Files

  • ble_gap.h: This header file contains BLE GAP functions and is automatically included in the app.c file
  • ble_trspc.h: This is the header file associated with API’s and structures related to BLE Transparent Client functions for application user
  • ble_trsps.h: This is the header file associated with API’s and structures related to BLE Transparent Server functions for application user

Function Calls

MCC Framework generates and adds the code to initialize the Bluetooth Low Energy Stack GAP, GATT, L2CAP and SMP in APP_BleStackInit() function
  • APP_BleStackInit() is the API that will be called inside the Applications Initial State (APP_STATE_INIT) in app.c

User Application Development

  1. Include
    • ble_trspc.h in app.c, Bluetooth Low Energy Transparent UART client related API’s are available here.
    • ble_trsps.h in app.c, Bluetooth Low Energy Transparent UART Server related API’s are available here
    • osal/osal_freertos_extend.h in app_trsps_handler.c, OSAL related API’s are available here
    • definitions.h in all the files where UART will be used to print debug information
      Note: definitions.h is not specific to just UART peripheral, instead it must be included in all application source files where peripheral functionality will be exercised
    • Include the user action. For more information, refer to User Action from Related Links.
  2. Start Scanning in app.c
    // Scanning Enabled
    BLE_GAP_SetScanningEnable(true, BLE_GAP_SCAN_FD_ENABLE, BLE_GAP_SCAN_MODE_OBSERVER, 1000);
    // Output the status string to UART
    SERCOM0_USART_Write((uint8_t *)"Scanning \r\n", 11);
    

    This API is called in the Applications Initial state (APP_STATE_INIT) in app.c. Scan duration is 100 seconds.

  3. Scan results and Initiating a BLE Connection in app_ble_handler.c

    • Connection handle associated with the peer peripheral device needs to be saved for data exchange after a BLE connection.
    • p_event->eventField.evtConnect.connHandle has this information.
    • In Multilink Application, unique connection handler's will be generated for all the peripheral links
      #include "peripheral/sercom/usart/plib_sercom0_usart.h"
      
      extern uint16_t conn_hdl[3];// connection handle info captured @BLE_GAP_EVT_CONNECTED event
      extern uint8_t no_of_links;// No of connected peripheral devices
      
    • In APP_BleGapEvtHandler, case BLE_GAP_EVT_ADV_REPORT:
      /* TODO: implement your application code.*/
      // code snippet to filter scan results and initiate connection
      // Filter Devices based of Address, for this example address checking only 2 bytes
      if ((p_event->eventField.evtAdvReport.addr.addr[0] == 0xA1 && p_event->eventField.evtAdvReport.addr.addr[1] == 0xA2) ||
                      (p_event->eventField.evtAdvReport.addr.addr[0] == 0xB1 && p_event->eventField.evtAdvReport.addr.addr[1] == 0xB2) ||
                      (p_event->eventField.evtAdvReport.addr.addr[0] == 0xC1 && p_event->eventField.evtAdvReport.addr.addr[1] == 0xC2))
      {
      SERCOM0_USART_Write((uint8_t *)"Found Peer Node\r\n", 17);
      BLE_GAP_CreateConnParams_T createConnParam_t;
      createConnParam_t.scanInterval = 0x3C; // 37.5 ms
      createConnParam_t.scanWindow = 0x1E; // 18.75 ms
      createConnParam_t.filterPolicy = BLE_GAP_SCAN_FP_ACCEPT_ALL;
      createConnParam_t.peerAddr.addrType = p_event->eventField.evtAdvReport.addr.addrType;
      memcpy(createConnParam_t.peerAddr.addr, p_event->eventField.evtAdvReport.addr.addr, GAP_MAX_BD_ADDRESS_LEN);
      createConnParam_t.connParams.intervalMin = 0x10;
      createConnParam_t.connParams.intervalMax = 0x10;
      createConnParam_t.connParams.latency = 0;
      createConnParam_t.connParams.supervisionTimeout = 0x48;
      SERCOM0_USART_Write((uint8_t *)"Initiating Connection\r\n", 23);
      BLE_GAP_CreateConnection(&createConnParam_t);
      }
      
    Figure 4-101. app_ble_handler.c
  4. Connected and Disconnected Events in app_ble_handler.c

    • In APP_BleGapEvtHandler, case BLE_GAP_EVT_CONNECTED:
      /* TODO: implement your application code.*/
      SERCOM0_USART_Write((uint8_t *)"Connected!\r\n", 12);
      conn_hdl[no_of_links]=p_event->eventField.evtConnect.connHandle;
      
      no_of_links++;
      
    • In APP_BleGapEvtHandler, case BLE_GAP_EVT_DISCONNECTED:
      /* TODO: implement your application code.*/
      SERCOM0_USART_Write((uint8_t *)"Disconnected\r\n", 15);
      
      Figure 4-102. app_ble_handler.c
  5. Scan Timeout Handler

    • The initiated scan operation will provide scan timeout event, user can start the advertisement to connect with another central device.
    • In APP_BleGapEvtHandler, case BLE_GAP_EVT_SCAN_TIMEOUT:
    /* TODO: implement your application code.*/
    SERCOM0_USART_Write((uint8_t *)"Scan Completed \r\n", 17);
    // Start Advertisement
    BLE_GAP_SetAdvEnable(0x01, 0x00);
    SERCOM0_USART_Write((uint8_t *)"Advertising\r\n",13);
    

Transmit Data

  • Add APP_MSG_UART_CB in APP_MsgId_T enum in app.h as illustrated in the following figure
    Figure 4-103. app.h
  • BLE_TRSPC_SendData(conn_hdl[i], 1, &uart_data); is the API to be used for sending data towards the client device
  • BLE_TRSPS_SendData(conn_hdl[i], 1, &uart_data); is the API to be used for sending data towards the Server device
Note: The precompiled application example uses a UART callback to initiate the data transmission upon receiving a character on UART

Example Implementation for Transmitting the received data over UART using the BLE_TRSPC_SendData API in app.c

#include "peripheral/sercom/usart/plib_sercom0_usart.h"
#include "config/default/ble/profile_ble/ble_trspc/ble_trspc.h"
#include "config/default/ble/profile_ble/ble_trsps/ble_trsps.h"

uint16_t conn_hdl[3];// connection handle info captured @BLE_GAP_EVT_CONNECTED event
uint8_t uart_data;
uint8_t no_of_links;// No of connected peripheral devices
uint8_t i = 0;// link index
void uart_cb(SERCOM_USART_EVENT event, uintptr_t context)
{
  APP_Msg_T   appMsg;   
  // If RX data from UART reached threshold (previously set to 1)
  if( event == SERCOM_USART_EVENT_READ_THRESHOLD_REACHED )
  {
    // Read 1 byte data from UART
    SERCOM0_USART_Read(&uart_data, 1);

    appMsg.msgId = APP_MSG_UART_CB;
    OSAL_QUEUE_Send(&appData.appQueue, &appMsg, 0);     
  }
}

void APP_UartCBHandler()
{
    // Send the data from UART to connected device through Transparent service
BLE_TRSPC_SendData(conn_hdl[i], 1, &uart_data);
    i++;
    if(i==no_of_links) i = 0; //reset link index    
}
Figure 4-104. app.c
  • When data is available on UART in APP_Tasks(), case APP_STATE_INIT:
    // Enable UART Read
    SERCOM0_USART_ReadNotificationEnable(true, true);
    // Set UART RX notification threshold to be 1
    SERCOM0_USART_ReadThresholdSet(1);
    // Register the UART RX callback function
    SERCOM0_USART_ReadCallbackRegister(uart_cb, (uintptr_t)NULL);
    
  • In APP_Tasks(), case APP_STATE_SERVICE_TASKS:
    else if(p_appMsg->msgId==APP_MSG_UART_CB)
    {
    // Transparent UART Client Data transfer Event
    APP_UartCBHandler();
    }
    
Figure 4-105. app.c
  • In app_trspc_handler.c
    #include <string.h>
    #include <stdint.h>
    #include "ble_trspc/ble_trspc.h"
    #include "definitions.h"
    
  • In APP_TrspcEvtHandler(), case BLE_TRSPC_EVT_RECEIVE_DATA:
    /* TODO: implement your application code.*/
    uint16_t data_len;
    uint8_t *data;
    // Retrieve received data length
    BLE_TRSPC_GetDataLength(p_event->eventField.onReceiveData.connHandle, &data_len);
    // Allocate memory according to data length
    data = OSAL_Malloc(data_len);
    if(data == NULL)
    break;
    // Retrieve received data
    BLE_TRSPC_GetData(p_event->eventField.onReceiveData.connHandle, data);
    // Output received data to UART
    SERCOM0_USART_Write((uint8_t *)"\r\nClient Data :", 15);
    SERCOM0_USART_Write(data, data_len);
    // Free memory
    OSAL_Free(data);
    

Receive Data

  • BLE_TRSPC_EVT_RECEIVE_DATA is the event generated when data is sent from peripheral device
  • Users need to use the BLE_TRSPC_GetDataLength(&data_len) and BLE_TRSPS_GetDataLength(&data_len) API to extract the length of application data received
  • BLE_TRSPC_GetData(&conn_hdl, data); and BLE_TRSPS_GetData(&conn_hdl, data); API is used to retrieve the data, conn_hdl is the value obtained from Connection Handler section
Note:
  • BLE_TRSPC_Event_T p_event structure stores the information about Bluetooth Low Energy transparent UART callback functions
  • BLE_TRSPS_Event_T p_event structure stores the information about Bluetooth Low Energy transparent UART callback functions

Example Implementation for printing the received data from peripheral device over UART

  • In app_trsps_handler.c
    #include <string.h>
    #include <stdint.h>
    #include "ble_trsps/ble_trsps.h"
    #include "ble_trspc/ble_trspc.h"
    #include "definitions.h"
    
    extern uint16_t conn_hdl[3];// connection handle info captured @BLE_GAP_EVT_CONNECTED event
    extern uint8_t no_of_links;// No of connected peripheral devices
    
  • In APP_TrspsEvtHandler(), case BLE_TRSPS_EVT_RECEIVE_DATA:
    /* TODO: implement your application code.*/
    uint16_t data_len;
    uint8_t *data;
    // Retrieve received data length
    BLE_TRSPS_GetDataLength(p_event->eventField.onReceiveData.connHandle, &data_len);
    // Allocate memory according to data length
    data = OSAL_Malloc(data_len);
    if(data == NULL)
    break;
    // Retrieve received data
    BLE_TRSPS_GetData(p_event->eventField.onReceiveData.connHandle, data);
    // Output received data to UART
    SERCOM0_USART_Write((uint8_t *)"\r\nServer Data :", 15);
    SERCOM0_USART_Write(data, data_len);
    // Send the data from UART to connected device through Transparent service
    for (int i=0;i<no_of_links;i++)
    {
    BLE_TRSPC_SendData(conn_hdl[i], data_len, data);
    }
    // Free memory
    OSAL_Free(data);