This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2652R: Fail To Write From Client To Server

Part Number: CC2652R

Hello,

I have created two custom Bluetooth applications. One is based on the simple_peripheral example and the other is based on the simple_central example. Right now, I'm having trouble getting the central device to write to a characteristic value on the peripheral device.

I am running these applications simultaneously on two CC2652R1F Launchpads. I am using version 4.20.00.35 of the SimpleLink SDK.

At this point, I believe my peripheral application is working exactly how I intend. It maintains a custom profile/service with two characteristics. One characteristic is setup as read-only with notification functionality and the other is simply write-only. I have tested the peripheral application by connecting to it via an app on my phone and doing reads / subscribing to notifications / doing writes and everything seems to be working as intended.

The issue lies in my central application. I have attached the file which contains the main functionality of the central device for reference.

/******************************************************************************

 @file  simple_central.c

 @brief This file contains the Simple Central sample application for use
        with the CC2650 Bluetooth Low Energy Protocol Stack.

 Group: WCS, BTS
 Target Device: cc13x2_26x2
 
 *****************************************************************************/

/*********************************************************************
 * INCLUDES
 */
#include <string.h>

#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/knl/Clock.h>
#include <ti/sysbios/knl/Event.h>
#include <ti/sysbios/knl/Queue.h>

#include "bcomdef.h"

#include <icall.h>
#include "util.h"
#include "icall_ble_api.h"
#include "osal_list.h"
#include <ti_drivers_config.h>
#include "ti_ble_config.h"
#include "ble_user_config.h"
#include "custom_profile.h"
#include "simple_central.h"


/*********************************************************************
 * CONSTANTS
 */

// Application events
#define SC_EVT_SCAN_DISABLED       0x03
#define SC_EVT_SVC_DISC            0x05
#define SC_EVT_INIT_CONN           0x06
#define SC_EVT_START_WRITING       0x07
#define SC_EVT_DEVICE_NOT_FND      0x08

// Simple Central Task Events
#define SC_ICALL_EVT                         ICALL_MSG_EVENT_ID  // Event_Id_31
#define SC_QUEUE_EVT                         UTIL_QUEUE_EVENT_ID // Event_Id_30

#define SC_ALL_EVENTS                        (SC_ICALL_EVT | SC_QUEUE_EVT)

// Task configuration
#define SC_TASK_PRIORITY                     1

// Task stack size
#define SC_TASK_STACK_SIZE                   1024

// Spin if the expression is not true
#define SIMPLECENTRAL_ASSERT(expr) if (!(expr)) SimpleCentral_spin();


/*********************************************************************
 * TYPEDEFS
 */

// Discovery states
enum
{
  BLE_DISC_STATE_IDLE,                // Idle
  BLE_DISC_STATE_MTU,                 // Exchange ATT MTU size
  BLE_DISC_STATE_SVC,                 // Service discovery
  BLE_DISC_STATE_CHAR                 // Characteristic discovery
};

// App event passed from profiles.
typedef struct
{
  appEvtHdr_t hdr; // event header
  uint8_t *pData;  // event data
} scEvt_t;

// Scanned device information record
typedef struct
{
  uint8_t addrType;         // Peer Device's Address Type
  uint8_t addr[B_ADDR_LEN]; // Peer Device Address
} scanRec_t;

// Connected device information
typedef struct
{
  uint16_t connHandle;        // Connection Handle
  uint16_t char1Handle;       // Characteristic 1 Handle
  uint16_t char2Handle;       // Characteristic 2 Handle
  uint8_t  addr[B_ADDR_LEN];  // Peer Device Address
} connRec_t;

typedef struct  
{
	osal_list_elem elem;
	uint8_t  addr[B_ADDR_LEN];  // member's BDADDR
	uint8_t  addrType;          // member's Address Type
	uint16_t connHandle;        // member's connection handle
	uint8_t  status;            // bitwise status flag 
} groupListElem_t;


/*********************************************************************
 * LOCAL VARIABLES
 */

// Entity ID globally used to check for source and/or destination of messages
static ICall_EntityID selfEntity;

// Event globally used to post local events and pend on system and
// local events.
static ICall_SyncHandle syncEvent;

// Queue object used for app messages
static Queue_Struct appMsg;
static Queue_Handle appMsgQueue;

// Task configuration
Task_Struct scTask;
#if defined __TI_COMPILER_VERSION__
#pragma DATA_ALIGN(scTaskStack, 8)
#else
#pragma data_alignment=8
#endif
uint8_t scTaskStack[SC_TASK_STACK_SIZE];

// Number of connected devices
static uint8_t numConn = 0;

// List of connections
static connRec_t connList[MAX_NUM_BLE_CONNS];

// Connection handle of current connection
static uint16_t scConnHandle = LINKDB_CONNHANDLE_INVALID;

// Discovery state
static uint8_t discState = BLE_DISC_STATE_IDLE;

// Discovered service start and end handle
static uint16_t svcStartHdl = 0;
static uint16_t svcEndHdl = 0;

// Maximum PDU size (default = 27 octets)
static uint16_t scMaxPduSize;

// Address mode
static GAP_Addr_Modes_t addrMode = DEFAULT_ADDRESS_MODE;

// Index of custom peripheral in the advertisement report, returned once scanning is done.
static uint8_t indexOfPeriphInAdvReport = 0;

// The BDA (Bluetooth Device Address) of the target peripheral to connect to.
// This address is hard coded in the custom peripheral application.
static uint8_t customPeriphBDA[B_ADDR_LEN] = {0x06, 0x05, 0x04, 0x03, 0x02, 0xC1};

// A general pointer to a uint8_t that does not point to any data.
// This pointer is used when we want to trigger things on application events without
//    needing to pass any data.
static uint8_t *noData = NULL;

// A debug counter.
static uint16_t debugCntr = 0;


/*********************************************************************
 * LOCAL FUNCTIONS
 */
static void SimpleCentral_init(void);
static void SimpleCentral_taskFxn(uintptr_t a0, uintptr_t a1);

static uint8_t SimpleCentral_processStackMsg(ICall_Hdr *pMsg);
static void SimpleCentral_processGapMsg(gapEventHdr_t *pMsg);
static void SimpleCentral_processGATTMsg(gattMsgEvent_t *pMsg);
static void SimpleCentral_processAppMsg(scEvt_t *pMsg);
static void SimpleCentral_processGATTDiscEvent(gattMsgEvent_t *pMsg);
static void SimpleCentral_startSvcDiscovery(void);

static uint8_t SimpleCentral_addConnInfo(uint16_t connHandle, uint8_t *pAddr);
static uint8_t SimpleCentral_getConnIndex(uint16_t connHandle);

static status_t SimpleCentral_enqueueMsg(uint8_t event, uint8_t status, uint8_t *pData);

static void SimpleCentral_scanCb(uint32_t evt, void* msg, uintptr_t arg);


/*********************************************************************
 * PUBLIC FUNCTIONS
 */


/*********************************************************************
 * @fn      SimpleCentral_spin
 *
 * @brief   Spin forever
 *
 * @param   none
 */
static void SimpleCentral_spin(void)
{
  volatile uint8_t x;

  while(1)
  {
    x++;
  }
}

/*********************************************************************
 * @fn      SimpleCentral_createTask
 *
 * @brief   Task creation function for the Simple Central.
 *
 * @param   none
 *
 * @return  none
 */
void SimpleCentral_createTask(void)
{
  Task_Params taskParams;

  // Configure task
  Task_Params_init(&taskParams);
  taskParams.stack = scTaskStack;
  taskParams.stackSize = SC_TASK_STACK_SIZE;
  taskParams.priority = SC_TASK_PRIORITY;

  Task_construct(&scTask, SimpleCentral_taskFxn, &taskParams, NULL);
}

/*********************************************************************
 * @fn      SimpleCentral_Init
 *
 * @brief   Initialization function for the Simple Central App Task.
 *          This is called during initialization and should contain
 *          any application specific initialization (ie. hardware
 *          initialization/setup, table initialization, power up
 *          notification).
 *
 * @param   none
 *
 * @return  none
 */
static void SimpleCentral_init(void)
{
  // ******************************************************************
  // N0 STACK API CALLS CAN OCCUR BEFORE THIS CALL TO ICall_registerApp
  // ******************************************************************
  // Register the current thread as an ICall dispatcher application
  //    so that the application can send and receive messages.
  ICall_registerApp(&selfEntity, &syncEvent);

  // Create an RTOS queue for message from profile to be sent to app.
  appMsgQueue = Util_constructQueue(&appMsg);

  // Initialize the connection list.
  uint8_t i;
  for (i = 0; i < MAX_NUM_BLE_CONNS; i++)
  {
    connList[i].connHandle = LINKDB_CONNHANDLE_INVALID;
  }

  // Set the device name.
  GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, (void *)attDeviceName);

  // Set default values for rx/tx packet sizes and rx/tx times.
  // Extended Data Length Feature is enabled by default in build_config.opt in stack project.
  // The data length extension feature allows us to define packet sizes greater than 27 bytes long.
  {
    #define APP_SUGGESTED_RX_PDU_SIZE   251     // Default is 251 octets (RX).
    #define APP_SUGGESTED_RX_TIME      3000     // Default is 17000us (RX).
    #define APP_SUGGESTED_TX_PDU_SIZE   251     // Default is 27 octets (TX).
    #define APP_SUGGESTED_TX_TIME      3000     // Default is 328us (TX).

    // Set the maximum values. Note that setting the maximum values does not actually set the PDU sizes/times.
    // The PDU size for a particular connection is set when the
    HCI_EXT_SetMaxDataLenCmd(APP_SUGGESTED_TX_PDU_SIZE, APP_SUGGESTED_TX_TIME, APP_SUGGESTED_RX_PDU_SIZE, APP_SUGGESTED_RX_TIME);
  }

  // Initialize GATT Client
  GATT_InitClient();

  // Register to receive incoming ATT Indications/Notifications.
  GATT_RegisterForInd(selfEntity);

  // Initialize GATT attributes.
  GGS_AddService(GATT_ALL_SERVICES);         // GAP
  GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes

  // Register for GATT local events and ATT Responses pending for transmission
  GATT_RegisterForMsgs(selfEntity);

  // Reject all parameter update requests
  GAP_SetParamValue(GAP_PARAM_LINK_UPDATE_DECISION, GAP_UPDATE_REQ_DENY_ALL);

  // Initialize GAP layer for Central role and register to receive GAP events
  GAP_DeviceInit(GAP_PROFILE_CENTRAL, selfEntity, addrMode, &pRandomAddress);
}

/*********************************************************************
 * @fn      SimpleCentral_taskFxn
 *
 * @brief   Application task entry point for the Simple Central.
 *
 * @param   none
 *
 * @return  events not processed
 */
static void SimpleCentral_taskFxn(uintptr_t a0, uintptr_t a1)
{
  // Initialize application
  SimpleCentral_init();

  // Application main loop
  for (;;)
  {
    uint32_t events;

    events = Event_pend(syncEvent, Event_Id_NONE, SC_ALL_EVENTS,
                        ICALL_TIMEOUT_FOREVER);

    if (events)
    {
      ICall_EntityID dest;
      ICall_ServiceEnum src;
      ICall_HciExtEvt *pMsg = NULL;

      if (ICall_fetchServiceMsg(&src, &dest, (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
      {
        uint8 safeToDealloc = TRUE;

        if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
        {
          ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;

          // Check for BLE stack events first
          if (pEvt->signature != 0xffff)
          {
            // Process inter-task message
            safeToDealloc = SimpleCentral_processStackMsg((ICall_Hdr *)pMsg);
          }
        }

        if (pMsg && safeToDealloc)
        {
          ICall_freeMsg(pMsg);
        }
      }

      // If RTOS queue is not empty, process app message
      if (events & SC_QUEUE_EVT)
      {
        scEvt_t *pMsg;
        while (pMsg = (scEvt_t *)Util_dequeueMsg(appMsgQueue))
        {
          // Process message
          SimpleCentral_processAppMsg(pMsg);

          // Free the space from the message
          ICall_free(pMsg);
        }
      }
    }
  }
}

/*********************************************************************
 * @fn      SimpleCentral_processStackMsg
 *
 * @brief   Process an incoming task message.
 *
 * @param   pMsg - message to process
 *
 * @return  TRUE if safe to deallocate incoming message, FALSE otherwise.
 */
static uint8_t SimpleCentral_processStackMsg(ICall_Hdr *pMsg)
{
  uint8_t safeToDealloc = TRUE;

  switch (pMsg->event)
  {
    case GAP_MSG_EVENT:
      SimpleCentral_processGapMsg((gapEventHdr_t*) pMsg);
      break;

    case GATT_MSG_EVENT:
      SimpleCentral_processGATTMsg((gattMsgEvent_t *)pMsg);
      break;

    default:
      break;
  }

  return (safeToDealloc);
}

/*********************************************************************
 * @fn      SimpleCentral_processAppMsg
 *
 * @brief   Scanner application event processing function.
 *
 * @param   pMsg - pointer to event structure
 *
 * @return  none
 */
static void SimpleCentral_processAppMsg(scEvt_t *pMsg)
{
  bool safeToDealloc = TRUE;

  switch (pMsg->hdr.event)
  {

    // Process all advertising reports. Since we are trying to connect to specifically the
    //    hw5_custom_peripheral, and we know its hard-coded device address, we must find the
    //    index of the advertising report which has this address and record it.
    case SC_EVT_SCAN_DISABLED:
    {
      uint8_t numReport;
      uint8_t i;
      uint8_t j;
      bool isAddrEqual;
      GapScan_Evt_AdvRpt_t advRpt;

      // The total number of advertising reports. Note that the upper bound on this number is
      //    DEFAULT_MAX_SCAN_RES.
      numReport = ((GapScan_Evt_End_t*) (pMsg->pData))->numReport;

      // Now loop through all advertising reports until we find the report which has the BDA we
      //    are looking for i.e. the BDA of the custom peripheral. When we find that, record the
      //    index.
      // This index will be used to connect to that particular device.
      for (i = 0; i < numReport; i++)
      {
         // This method assumes there is exactly one matching address in the advertisement report
         //    list.
         isAddrEqual = true;
         GapScan_getAdvReport(i, &advRpt);
         for (j = 0; j < B_ADDR_LEN; j++)
         {
             if (advRpt.addr[j] != customPeriphBDA[j])
             {
                 isAddrEqual = false;
             }
         }

         // If this particular address report contains the address of the custom peripheral, save the
         //    index and stop examining the advertising reports.
         if (isAddrEqual == true)
         {
             indexOfPeriphInAdvReport = i;
             break;
         }
      }

      if (isAddrEqual == false)
      {
          // The target device was not found.  No data to pass with this event.
          SimpleCentral_enqueueMsg(SC_EVT_DEVICE_NOT_FND, 0, noData);
      }
      else
      {
          // The target device was found. Include a pointer to the index in the message.
          uint8_t *pIndexOfPeriphInAdvReport = &indexOfPeriphInAdvReport;
          SimpleCentral_enqueueMsg(SC_EVT_INIT_CONN, 0, pIndexOfPeriphInAdvReport);
      }
      break;
    }

    // The custom peripheral was not found during scanning, so spin.
    case SC_EVT_DEVICE_NOT_FND:
    {
      while(1);
    }

    // Initiate the connection process by passing the index of the advertisement
    //    report corresponding to the target peripheral device.
    case SC_EVT_INIT_CONN:
    {
      SimpleCentral_doConnect(*(pMsg->pData));
      break;
    }

    // Start the service discovery process. This will allow us to do characteristic reads and writes.
    case SC_EVT_SVC_DISC:
    {
      SimpleCentral_startSvcDiscovery();
      break;
    }

    // We have discovered the handle of the characteristic value we want to write to. Now do a write.
    case SC_EVT_START_WRITING:
    {
      SimpleCentral_doGattWrite();
      break;
    }

    default:
      break;
  }

  // Deallocate the data contained in the message. If there is no data in the message, don't deallocate anything.
  // Note that deallocation of the event header is done at the task function level.
  if ((safeToDealloc == TRUE) && (pMsg->pData != NULL))
  {
    ICall_free(pMsg->pData);
  }
}

/*********************************************************************
 * @fn      SimpleCentral_processGapMsg
 *
 * @brief   GAP message processing function.
 *
 * @param   pMsg - pointer to event message structure
 *
 * @return  none
 */
static void SimpleCentral_processGapMsg(gapEventHdr_t *pMsg)
{
  switch (pMsg->opcode)
  {
    case GAP_DEVICE_INIT_DONE_EVENT:
    {
      uint8_t temp8;
      uint16_t temp16;
      gapDeviceInitDoneEvent_t *pPkt = (gapDeviceInitDoneEvent_t *)pMsg;

      // Register callback to process Scanner events.
      // When one of the scan events specified via the GapScan_setEventMask happens,
      //    this callback function will be called.
      GapScan_registerCb(SimpleCentral_scanCb, NULL);

      // Set Scanner Event Mask.
      // The list of GAP events that trigger callbacks to the registered callback function.
      GapScan_setEventMask(GAP_EVT_SCAN_DISABLED);

      // Set Scan PHY parameters.
      // Passive scanning looks for non-scannable advertisements i.e. advertisements belonging
      //    to peripheral devices which are not set up to be able to do handle scan requests and
      //    issue scan responses.
      GapScan_setPhyParams(DEFAULT_SCAN_PHY, SCAN_TYPE_PASSIVE, DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_WINDOW);

      // Set Advertising report fields to keep.
      // We are only interested in the address and address type of the advertising device. The advertising packet
      //    contains no other data of interest to us.
      temp16 = ADV_RPT_FIELDS;
      GapScan_setParam(SCAN_PARAM_RPT_FIELDS, &temp16);

      // Set Scanning Primary PHY.
      temp8 = DEFAULT_SCAN_PHY;
      GapScan_setParam(SCAN_PARAM_PRIM_PHYS, &temp8);

      // Set LL Duplicate Filter.
      // This filter out duplicate packets at the link layer.
      temp8 = SCAN_FLT_DUP_ENABLE;
      GapScan_setParam(SCAN_PARAM_FLT_DUP, &temp8);

      // Set PDU filter.
      // Only connectable and complete packets are desired.
      // It doesn't matter if received packets are Scannable or Non-Scannable,
      //    Directed or Undirected, Scan Responses or Advertisements, and
      //    Legacy or Extended.
      // This prevents useless packets from being processed.
      temp16 = SCAN_FLT_PDU_CONNECTABLE_ONLY | SCAN_FLT_PDU_COMPLETE_ONLY;
      GapScan_setParam(SCAN_PARAM_FLT_PDU_TYPE, &temp16);

	  // Set PHY parameters.
      // The connection interval is the maximum time period that can elapse before a Bluetooth
      //    connection disconnects. Packets must be exchanged over Bluetooth even when data does
      //    not necessarily need to shared between client and server.
      GapInit_setPhyParam(DEFAULT_INIT_PHY, INIT_PHYPARAM_CONN_INT_MIN, INIT_PHYPARAM_MIN_CONN_INT);
	  GapInit_setPhyParam(DEFAULT_INIT_PHY, INIT_PHYPARAM_CONN_INT_MAX, INIT_PHYPARAM_MAX_CONN_INT);

	  // Set the maximum PDU size.
      scMaxPduSize = pPkt->dataPktLen;

      // Now start scanning for available connections.
      // Scanning for DEFAULT_SCAN_DURATION x 10 ms.
      // Let the stack record the advertising reports as many as up to DEFAULT_MAX_SCAN_RES.
      GapScan_enable(0, DEFAULT_SCAN_DURATION, DEFAULT_MAX_SCAN_RES);

      break;
    }

    case GAP_CONNECTING_CANCELLED_EVENT:
    {
      // The connection attempt failed, spin.
      while(1){}
      break;
    }

    case GAP_LINK_ESTABLISHED_EVENT:
    {
      uint16_t connHandle = ((gapEstLinkReqEvent_t*) pMsg)->connectionHandle;
      uint8_t* pAddr = ((gapEstLinkReqEvent_t*) pMsg)->devAddr;
      uint8_t  connIndex;

      // Update the current connection handle.
      // The connection handle is used to identify a particular connection in the connection list.
      scConnHandle = connHandle;

      // Add this connection info to the list of current connections.
      connIndex = SimpleCentral_addConnInfo(connHandle, pAddr);

      // connIndex cannot be equal to or greater than MAX_NUM_BLE_CONNS
      SIMPLECENTRAL_ASSERT(connIndex < MAX_NUM_BLE_CONNS);

      // For this new connection, the handles of the characteristics of interest
      //    are unknown.
      // Once we do service discovery of this connection, these values will be
      //    updated with the actual handles of the characteristic values of interest.
      // Note that the "characteristic handle" refers to the handle of the characteristic
      //    value of interest.
      connList[connIndex].char1Handle = 0;
      connList[connIndex].char2Handle = 0;

      // Now start service discovery.
      SimpleCentral_startSvcDiscovery();
      break;
    }

    default:
      break;
  }
}


/*********************************************************************
 * @fn      SimpleCentral_processGATTMsg
 *
 * @brief   Process GATT messages and events.
 *
 */
static void SimpleCentral_processGATTMsg(gattMsgEvent_t *pMsg)
{
  // Check the discovery state of the device. If the device is not in the idle
  //    state, pass off processing.
  if (discState != BLE_DISC_STATE_IDLE)
  {
    SimpleCentral_processGATTDiscEvent(pMsg);
  }

  // Needed only for ATT Protocol messages
  GATT_bm_free(&pMsg->msg, pMsg->method);
}


/*********************************************************************
 * @fn      SimpleCentral_startSvcDiscovery
 *
 * @brief   Start service discovery.
 *
 */
static void SimpleCentral_startSvcDiscovery(void)
{
  attExchangeMTUReq_t req;

  // Update the global discovery state.
  discState = BLE_DISC_STATE_MTU;

  // Discover GATT Server's Rx MTU size.
  req.clientRxMTU = scMaxPduSize - L2CAP_HDR_SIZE;

  // ATT MTU size should be set to the minimum of the Client Rx MTU
  //    and Server Rx MTU values.
  // Note that when this function successfully finishes executing, a ATT_EXCHANGE_MTU_RSP
  //    event is issued. Otherwise an ATT_ERROR_RSP event is issued.
  // I believe an ATT_ERROR_RSP is issued only if the set PDU sizes are incompatible i.e.
  //    the client is trying to transmit more bytes to the server in a single packet than
  //    it can handle.
  GATT_ExchangeMTU(scConnHandle, &req, selfEntity);
}


/*********************************************************************
 * @fn      SimpleCentral_processGATTDiscEvent
 *
 * @brief   Process GATT discovery event
 *
 */
static void SimpleCentral_processGATTDiscEvent(gattMsgEvent_t *pMsg)
{
  if (discState == BLE_DISC_STATE_MTU)
  {
    // MTU size response received, discover the primary service of the peripheral device.
    if (pMsg->method == ATT_EXCHANGE_MTU_RSP)
    {
      uint8_t uuid[ATT_BT_UUID_SIZE] = {LO_UINT16(CUSTOMPROFILE_SERV_UUID), HI_UINT16(CUSTOMPROFILE_SERV_UUID)};

      // Update the global discovery state.
      discState = BLE_DISC_STATE_SVC;

      // Search for the specified service. When found, the stack issues the ATT_FIND_BY_TYPE_VALUE_RSP event.
      // If an error occurs on the server, the stack will issue an ATT_ERROR_RSP event.
      GATT_DiscPrimaryServiceByUUID(pMsg->connHandle, uuid, ATT_BT_UUID_SIZE, selfEntity);
    }
  }
  else if (discState == BLE_DISC_STATE_SVC)
  {
    // Service found, store handles.
    if (pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP && pMsg->msg.findByTypeValueRsp.numInfo > 0)
    {
      // Get the starting and ending handle of the primary service.
      svcStartHdl = ATT_ATTR_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
      svcEndHdl = ATT_GRP_END_HANDLE(pMsg->msg.findByTypeValueRsp.pHandlesInfo, 0);
    }

    // If the primary service was properly discovered on the server, attempt to discover the characteristics within
    //    the service.
    if ((pMsg->method == ATT_FIND_BY_TYPE_VALUE_RSP) && (pMsg->hdr.status == bleProcedureComplete))
    {
      if (svcStartHdl != 0)
      {
        attReadByTypeReq_t req;

        // Update the global discovery state.
        discState = BLE_DISC_STATE_CHAR;

        // Attempt to discover CUSTOM_PROFILE_CHAR2 on the server.
        req.startHandle = svcStartHdl;
        req.endHandle = svcEndHdl;
        req.type.len = ATT_BT_UUID_SIZE;
        req.type.uuid[0] = LO_UINT16(CUSTOMPROFILE_CHAR2_UUID);
        req.type.uuid[1] = HI_UINT16(CUSTOMPROFILE_CHAR2_UUID);

        // In order to write to a characteristic value, we need to discover the handle
        //    of the characteristic value.
        // This function issues a ATT_READ_BY_TYPE_RSP event or a ATT_ERROR_RSP event when
        //    it completes.
        uint8_t status = GATT_DiscCharsByUUID(pMsg->connHandle, &req, selfEntity);
      }
    }
  }
  else if (discState == BLE_DISC_STATE_CHAR)
  {
    // Characteristic found, store handle.
    if (pMsg->method == ATT_READ_BY_TYPE_RSP)
    {
      // Get the index of the current connection.
      // Note that there is only ever one connection.
      uint8_t connIndex = SimpleCentral_getConnIndex(scConnHandle);

 	  // Store the handle of the custom profile characteristic 2 value in the connection list.
      connList[connIndex].char2Handle = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3], pMsg->msg.readByTypeRsp.pDataList[4]);
    }

    // Characteristic discovery is done.
    discState = BLE_DISC_STATE_IDLE;

    // Now that we have discovered the characteristic we want to write to, enqueue an application message to do a
    //    characteristic value write.
    if( SimpleCentral_enqueueMsg( SC_EVT_START_WRITING, 0, NULL ) != SUCCESS )
    {
       ICall_free(pMsg);
    }

  }
}

/*********************************************************************
 * @fn      SimpleCentral_addConnInfo
 *
 * @brief   Add a device to the connected device list
 *
 * @return  Index of the connected device list entry where the new connection
 *          info is put in.
 *          If there is no room, MAX_NUM_BLE_CONNS will be returned.
 */
static uint8_t SimpleCentral_addConnInfo(uint16_t connHandle, uint8_t *pAddr)
{
  uint8_t i;

  for (i = 0; i < MAX_NUM_BLE_CONNS; i++)
  {
    if (connList[i].connHandle == LINKDB_CONNHANDLE_INVALID)
    {
      // Found available entry to put a new connection info in
      connList[i].connHandle = connHandle;
      memcpy(connList[i].addr, pAddr, B_ADDR_LEN);
      numConn++;

      break;
    }
  }

  return i;
}

/*********************************************************************
 * @fn      SimpleCentral_getConnIndex
 *
 * @brief   Find index in the connected device list by connHandle
 *
 * @return  the index of the entry that has the given connection handle.
 *          if there is no match, MAX_NUM_BLE_CONNS will be returned.
 */
static uint8_t SimpleCentral_getConnIndex(uint16_t connHandle)
{
  uint8_t i;

  for (i = 0; i < MAX_NUM_BLE_CONNS; i++)
  {
    if (connList[i].connHandle == connHandle)
    {
      break;
    }
  }

  return i;
}

/*********************************************************************
 * @fn      SimpleCentral_enqueueMsg
 *
 * @brief   Creates a message and puts the message in RTOS queue.
 *
 * @param   event - message event.
 * @param   state - message state.
 * @param   pData - message data pointer.
 *
 * @return  TRUE or FALSE
 */
static status_t SimpleCentral_enqueueMsg(uint8_t event, uint8_t state, uint8_t *pData)
{
  uint8_t success;
  scEvt_t *pMsg = ICall_malloc(sizeof(scEvt_t));

  // Create dynamic pointer to message.
  if (pMsg)
  {
    pMsg->hdr.event = event;
    pMsg->hdr.state = state;
    pMsg->pData = pData;

    // Enqueue the message.
    success = Util_enqueueMsg(appMsgQueue, syncEvent, (uint8_t *)pMsg);
    return (success) ? SUCCESS : FAILURE;
  }

  return(bleMemAllocError);
}

/*********************************************************************
 * @fn      SimpleCentral_scanCb
 *
 * @brief   Callback called by GapScan module while scanning is enabled.
 *          Various events are passed back to this callback function, which
 *          are then enqueued in the RTOS message queue, for later processing
 *          by the application.
 *
 * @param   evt - event
 * @param   msg - message coming with the event
 * @param   arg - user argument
 *
 * @return  none
 */
void SimpleCentral_scanCb(uint32_t evt, void* pMsg, uintptr_t arg)
{
  uint8_t event;

  if (evt & GAP_EVT_SCAN_DISABLED)
  {
    event = SC_EVT_SCAN_DISABLED;
  }
  else
  {
    return;
  }

  if(SimpleCentral_enqueueMsg(event, SUCCESS, pMsg) != SUCCESS)
  {
    ICall_free(pMsg);
  }
}

/*********************************************************************
 * @fn      SimpleCentral_doConnect
 *
 * @brief   Establish a connection to a peer device.
 *
 * @param   index - The index of the advertisement report corresponding to
 *                  the targeted peripheral device.
 *
 */
void SimpleCentral_doConnect(uint8_t index)
{
  GapScan_Evt_AdvRpt_t advRpt;

  // Get the advertisement report corresponding to the desired peripheral
  //    connection. We need this in order to connect to the correct peripheral.
  GapScan_getAdvReport(index, &advRpt);

  // Initiate the connection with the desired peripheral.
  // Continue trying to initiate a connection until GapInit_cancelConnect() is called.
  // When the connection is successfully formed, a GAP_LINK_ESTABLISHED_EVENT is issued.
  GapInit_connect(advRpt.addrType & MASK_ADDRTYPE_ID, advRpt.addr, DEFAULT_INIT_PHY, 0);

}

/*********************************************************************
 * @fn      SimpleCentral_doGattWrite
 *
 * @brief   GATT Write
 *
 */
void SimpleCentral_doGattWrite()
{

  status_t status;

  // Corresponds to writing "HI" at row 1, column 0
  uint8_t charVals[5] = {0x01, 0x00, 0x48, 0x49, 0x00};

  attWriteReq_t req;

  req.pValue = GATT_bm_alloc(scConnHandle, ATT_WRITE_REQ, 5, NULL);

  if ( req.pValue != NULL )
  {
    // Get the connection index of the current connection.
    // Note that there is never more than one connection.
    uint8_t connIndex = SimpleCentral_getConnIndex(scConnHandle);

    // Fill in the write request.
    req.handle = connList[connIndex].char2Handle;
    req.len = 5;
    req.pValue[0] = charVals[0];
    req.pValue[1] = charVals[1];
    req.pValue[2] = charVals[2];
    req.pValue[3] = charVals[3];
    req.pValue[4] = charVals[4];
    req.sig = 0;
    req.cmd = 0;

    status = GATT_WriteCharValue(scConnHandle, &req, selfEntity);

    if ( status != SUCCESS )
    {
      GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
    }
  }

}


/*********************************************************************
*********************************************************************/

Just like the simple_central example, my application starts by scanning (only once!) for advertisement packets. When scanning finishes, it looks through the advertisement report for a Bluetooth device with a specific device address. If the target device is found, a connection is established with it. Once the connection is formed, the service discovery process starts. As a first step, the client and server exchange MTU values. Then the client attempts to discover the service by looking for my custom service UUID in the attribute table (i.e. GATT_DiscPrimaryServiceByUUID). Once the starting handle of the service is retrieved, an attempt is made to discover one of the characteristics within the service. Once the handle of the characteristic value of interest is discovered, a write is attempted from client to server. At this point, things go awry.

Up until line 757 of the attached code, things seem to be working as I would expect. The central device always finds the target peripheral device and successfully connects to it. The starting handle of the discovered custom service matches what I see with BTool, and the handle of the characteristic value that is discovered also matches what I see with BTool. However, once SimpleCentral_enqueueMsg( SC_EVT_START_WRITING, 0, NULL ) is executed and SimpleCentral_doGattWrite() is called (at some point later in time), things stop making sense to me. The actual write within SimpleCentral_doGattWrite() is done via GATT_WriteCharValue(), and the status returned from this function call is always 0x16 - which means blePending. This implies that the client is busy doing something else at the moment the write is attempted, but I cannot figure out what it's doing. Another interesting symptom I've noticed is that at the moment the SC_EVT_START_WRITING application event is processed, the svcStartHdl and svcEndHdl seem to have been overwritten from their originally correct values. Since these values are only written in the service discovery procedure, this seems to imply that service discovery is happening multiple times in error. I tested against this hypothesis by simply counting the number of times the various stages of service discovery happen - and it turns out each only happens once, as expected. I've also tried adding an extended task sleep delay before doing the client-to-server write to give the BLE stack time to process anything that it might need to, but that doesn't seem to resolve the issue either.

Anyone have any idea what might be going wrong here?

Thank you in advance for the help.

  • Hi Andrew,

    Thank you for the detailed description! Do you have access to a packet sniffer to monitor the BLE traffic so we can see what's actually being sent back and forth? I'd also recommend reviewing the solutions described in this thread to see if they help you.

    https://e2e.ti.com/support/wireless-connectivity/bluetooth-group/bluetooth/f/bluetooth-forum/448338/cc2650-gatt_readcharvalue-returning-blepending

    Best,

    Nate

  • Hey Nathan,

    Unfortunately I don't currently have access to a packet sniffer.

    I read through the thread you linked, but it didn't really give me a new theory about what's going on.

    For some reason, when my characteristic discovery process finishes a bunch of statically allocated variables (such as svcStartHandle and svcEndHandle) seem to get corrupted. This seems to point to some memory corruption, but I can't quite figure out what is going wrong.

  • Hi Andrew,

    If you have another launchpad on hand, you can consider using it as a sniffer with the following instructions

    http://software-dl.ti.com/lprf/packet_sniffer_2/docs/user_guide/html/users_guide.html

    With respect to your current error - Can you compare your code with the unmodified out-of-box simple_central code to see where they differ? Note that you can't place breakpoints in your code while it's running because they will interfere with the connection interval of the devices. Do you get reasonable values for these variables in the unmodified Simple_Central example?

  • Hey Nathan,

    I compared my application with the out-of-box simple central code to try and find any differences. I was not able to find any substantial differences. They both follow the generic pattern: initialize the device in the central role, do scanning, process advertisement reports, connect to a device, exchange MTUs, do service discovery, and then write to a characteristic value.

    In fact, I was able to slightly modify the out-of-box simple central code to write to the attribute I cannot write to with my custom application. This tells me that my custom peripheral application code is working properly, but there is definitely something wrong with my custom central application.

    I did make one interesting discovery: By removing my call to SimpleCentral_enqueueMsg( SC_EVT_START_WRITING, 0, NULL ) I was able to get rid of the memory corruption problem I was seeing before. I have no idea why this call causes memory corruption to happen, and it's probably only a symptom of some larger problem I have yet to identify. Enqueueing a message in the application task's message queue, from the application task itself, should not cause a memory corruption.

    However, even after removing this and just calling SimpleCentral_doGattWrite() directly, GATT_WriteCharValue() still seems to be returning 0x16 indicating blePending. So I'm still dead in the water...

    Would you mind looking at the code I attached previously and sanity-checking it for anything I might have missed?

  • Hi Andrew,

    Unfortunately we cannot parse your code as a whole. If you have snippets of code you'd like to ask us about then we can help you there, but it's simply not feasible or sustainable to go through your entire code.

    Have you tried to just send one char in your call to SimpleCentral_doGattWrite like Simple_Central does? Does that still fail?

  • Hi Andrew,

    After a device gets connected, many operations occur behind the scene. As a consequence, the BLE stack may be busy with something else if you attempt the GATT write to quickly.

    Generally speaking, I recommend to check the value returned by GATT_WriteCharValue(). If the status is "blePending", then re-attempt the write later (you can use a clock to trigger the new write). Generally speaking, the time to wait depends on the connection interval and on the amount of operations going on behind the scene.

    I hope this will help,

    Best regards,