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.

CCS/LAUNCHXL-CC1352R1: Multi_role receive characteristic value with GetParameter

Part Number: LAUNCHXL-CC1352R1

Tool/software: Code Composer Studio

Hi,

I'm using CCS v8 and simplelink_cc13x2_sdk_1_60_00_29.

I'm trying to communicate with the simple_peripheral and multi_role via an ADC Service created in Bluetooth Developer Studio with the TI plugin but I only get weird values. But I do get the same values every time. Do you have any idea what can be wrong and what I have to change to receive the right data? I've tried to send shorter values and strings with the same result. 

The code used in simple_peripheral to set the values is shown below and is in the SimplePeripheral_init function: 

  uint32_t Sensor1 = 1234;
  uint32_t Sensor2 = 656;
  uint32_t Sensor3 = 2529;
  uint32_t Sensor4 = 999;
  uint32_t Sensor5 = 1952;

  // Send data over BLE
  AdcService_SetParameter(AS_SENSOR_1_ID, sizeof(uint32_t), &Sensor1);
  AdcService_SetParameter(AS_SENSOR_2_ID, sizeof(uint32_t), &Sensor2);
  AdcService_SetParameter(AS_SENSOR_3_ID, sizeof(uint32_t), &Sensor3);
  AdcService_SetParameter(AS_SENSOR_4_ID, sizeof(uint32_t), &Sensor4);
  AdcService_SetParameter(AS_SENSOR_5_ID, sizeof(uint32_t), &Sensor5);

where AdcService_SetParameter is:

bStatus_t AdcService_SetParameter( uint8_t param, uint16_t len, void *value )
{
  bStatus_t ret = SUCCESS;
  uint8_t  *pAttrVal;
  uint16_t *pValLen;
  uint16_t valMinLen;
  uint16_t valMaxLen;

  switch ( param )
  {
    case AS_SENSOR_1_ID:
      pAttrVal  =  as_Sensor_1Val;
      pValLen   = &as_Sensor_1ValLen;
      valMinLen =  AS_SENSOR_1_LEN_MIN;
      valMaxLen =  AS_SENSOR_1_LEN;
      Log_info2("SetParameter : %s len: %d", (IArg)"Sensor_1", (IArg)len);
      break;

    case AS_SENSOR_2_ID:
      pAttrVal  =  as_Sensor_2Val;
      pValLen   = &as_Sensor_2ValLen;
      valMinLen =  AS_SENSOR_2_LEN_MIN;
      valMaxLen =  AS_SENSOR_2_LEN;
      Log_info2("SetParameter : %s len: %d", (IArg)"Sensor_2", (IArg)len);
      break;

    case AS_SENSOR_3_ID:
      pAttrVal  =  as_Sensor_3Val;
      pValLen   = &as_Sensor_3ValLen;
      valMinLen =  AS_SENSOR_3_LEN_MIN;
      valMaxLen =  AS_SENSOR_3_LEN;
      Log_info2("SetParameter : %s len: %d", (IArg)"Sensor_3", (IArg)len);
      break;

    case AS_SENSOR_4_ID:
      pAttrVal  =  as_Sensor_4Val;
      pValLen   = &as_Sensor_4ValLen;
      valMinLen =  AS_SENSOR_4_LEN_MIN;
      valMaxLen =  AS_SENSOR_4_LEN;
      Log_info2("SetParameter : %s len: %d", (IArg)"Sensor_4", (IArg)len);
      break;

    case AS_SENSOR_5_ID:
      pAttrVal  =  as_Sensor_5Val;
      pValLen   = &as_Sensor_5ValLen;
      valMinLen =  AS_SENSOR_5_LEN_MIN;
      valMaxLen =  AS_SENSOR_5_LEN;
      Log_info2("SetParameter : %s len: %d", (IArg)"Sensor_5", (IArg)len);
      break;

    default:
      Log_error1("SetParameter: Parameter #%d not valid.", (IArg)param);
      return INVALIDPARAMETER;
  }

  // Check bounds, update value and send notification or indication if possible.
  if ( len <= valMaxLen && len >= valMinLen )
  {
    memcpy(pAttrVal, value, len);
    *pValLen = len; // Update length for read and get.
  }
  else
  {
    Log_error3("Length outside bounds: Len: %d MinLen: %d MaxLen: %d.", (IArg)len, (IArg)valMinLen, (IArg)valMaxLen);
    ret = bleInvalidRange;
  }

  return ret;
}

The code in multi_role for retrieving the data is set in multi_role_doGattRead function and shown below: 

    uint32_t length;
    uint32_t Sensor1;
    uint32_t Sensor2;
    uint32_t Sensor3;
    uint32_t Sensor4;
    uint32_t Sensor5;

    AdcService_GetParameter(AS_SENSOR_1_ID, &length, &Sensor1);
    AdcService_GetParameter(AS_SENSOR_2_ID, &length, &Sensor2);
    AdcService_GetParameter(AS_SENSOR_3_ID, &length, &Sensor3);
    AdcService_GetParameter(AS_SENSOR_4_ID, &length, &Sensor4);
    AdcService_GetParameter(AS_SENSOR_5_ID, &length, &Sensor5);

where 

bStatus_t AdcService_GetParameter( uint8_t param, uint16_t *len, void *value )
{
  bStatus_t ret = SUCCESS;
  switch ( param )
  {
    case AS_SENSOR_1_ID:
      *len = MIN(*len, as_Sensor_1ValLen);
      memcpy(value, as_Sensor_1Val, *len);
      Log_info2("GetParameter : %s returning %d bytes", (IArg)"Sensor_1", (IArg)*len);
      break;

    case AS_SENSOR_2_ID:
      *len = MIN(*len, as_Sensor_2ValLen);
      memcpy(value, as_Sensor_2Val, *len);
      Log_info2("GetParameter : %s returning %d bytes", (IArg)"Sensor_2", (IArg)*len);
      break;

    case AS_SENSOR_3_ID:
      *len = MIN(*len, as_Sensor_3ValLen);
      memcpy(value, as_Sensor_3Val, *len);
      Log_info2("GetParameter : %s returning %d bytes", (IArg)"Sensor_3", (IArg)*len);
      break;

    case AS_SENSOR_4_ID:
      *len = MIN(*len, as_Sensor_4ValLen);
      memcpy(value, as_Sensor_4Val, *len);
      Log_info2("GetParameter : %s returning %d bytes", (IArg)"Sensor_4", (IArg)*len);
      break;

    case AS_SENSOR_5_ID:
      *len = MIN(*len, as_Sensor_5ValLen);
      memcpy(value, as_Sensor_5Val, *len);
      Log_info2("GetParameter : %s returning %d bytes", (IArg)"Sensor_5", (IArg)*len);
      break;

    default:
      Log_error1("GetParameter: Parameter #%d not valid.", (IArg)param);
      ret = INVALIDPARAMETER;
      break;
  }
  return ret;
}

Thanks and regards,

Elin 

  • Hi Elin,

    In setting the characteristic value, you will need to break it up by each byte and store it into a byte array. For example:

    uint8_t Sensor1[SENSOR1_CHAR_LEN] = {0xD2, 0x04, 0x00, 0x00}; // 1234 in hex 0x04D2
                                                                  // SENSOR1_CHAR_LEN is the length of your characteristic 
                                                                  // since you had it as a uint32_t it would be 4 or sizeof(uint32_t)
    
    AdcService_SetParameter(AS_SENSOR_1_ID, SENSOR1_CHAR_LEN, &Sensor1);

    If you haven't yet, you will also need to make changes in your ReadAttrCB function in your profile to something similar to this:

          case AS_SENSOR_1_ID:
            *pLen = SENSOR1_CHAR_LEN;
            VOID memcpy( pValue, pAttr->pValue, SENSOR1_CHAR_LEN);
            break;

    I would highly suggest you look at how SIMPLEPROFILE_CHAR5 is setup in simple_gatt_profile.c (simplelink_cc13x2_sdk_1_60_00_29\source\ti\ble5stack\profiles\simple_profile\cc26xx) and simple_gatt_profile.h (simplelink_cc13x2_sdk_1_60_00_29\source\ti\ble5stack\profiles\simple_profile). That characteristic is setup to send 5 bytes. You will basically want to copy that structure for your characteristics when it is more than a byte.

    Now when using multi_role to read the characteristic from simple peripheral, you can't use AdcService_GetParameter. All that is doing is retrieving the characteristic values from multi_role's own GATT table. Instead you will need to use GATT_ReadCharValue as originally used in the multi_role_doGattRead function. When a readRsp is received in multi_role_processGATTMsg, that's where you'll need to process the read data. The following is what I am referring to in the multi_role_processGATTMsg function. I've added an example of what change you'd have to do to receive a characteristic of more than a byte:

    static uint8_t multi_role_processGATTMsg(gattMsgEvent_t *pMsg)
    {
     //...
     // Messages from GATT server
      if (linkDB_Up(pMsg->connHandle))
      {
        if ((pMsg->method == ATT_READ_RSP)   ||
            ((pMsg->method == ATT_ERROR_RSP) &&
             (pMsg->msg.errorRsp.reqOpcode == ATT_READ_REQ)))
        {
     //...
          else
          {
            uint32_t charReadValue = 0;
            // After a successful read, display the read value        
            if(pMsg->msg.readRsp.len == sizeof(uint32_t)){                   
              charReadValue = BUILD_UINT32(pMsg->msg.readRsp.pValue[0], pMsg->msg.readRsp.pValue[1], 
                              pMsg->msg.readRsp.pValue[2], pMsg->msg.readRsp.pValue[3]);
                             // Since the characteristic is sent per byte, you will need to reconstruct it to be 4 bytes
            }
            
            Display_printf(dispHandle, MR_ROW_CUR_CONN, 0, "Read rsp: %d", charReadValue);
          }
    //...
    

    I'd also highly suggest going through the Custom Profile SimpleLink Academy training. This will go through exercise that will help you understand this process: http://dev.ti.com/tirex/#/?link=Software%2FSimpleLink%20CC13x2%20SDK%2FSimpleLink%20Academy%2FBluetooth%205%2FCustom%20Profile

    -Sy Su

  • Hi,

    Thanks for the reply.

    I’ve checked both the simple_gatt_profile.h/c several times and the Custom Profile without understanding how to solve my issue.

    What’s the point of changing the code in ReadAttrCB? This is the code generated in Bluetooth Developer Studio:

        case AS_SENSOR_1_ID:
          valueLen = as_Sensor_1ValLen;
    
          Log_info4("ReadAttrCB : %s connHandle: %d offset: %d method: 0x%02x",
                     (IArg)"Sensor_1",
                     (IArg)connHandle,
                     (IArg)offset,
                     (IArg)method);
          /* Other considerations for Sensor 1 can be inserted here */
          break;
    

    So do I call ReadAttrCB because I never enter it? And how do I call GATT_ReadCharValue? I don’t see how to link that function to my characteristics.

    BR

    Elin

  • Elin,

    You will want to make sure that your application's characteristics is defined and implemented the same way as how SIMPLEPROFILE_CHAR5 is defined and implemented in simple_gatt_profile.c/simple_gatt_profile.h.

    The ReadAttrCB is not something you explicitly call. It is a callback function that occurs whenever the attribute is read. It is registered in SimpleProfile_AddService as follows:

        // Register GATT attribute list and CBs with GATT Server App
        status = GATTServApp_RegisterService( simpleProfileAttrTbl,
                                              GATT_NUM_ATTRS( simpleProfileAttrTbl ),
                                              GATT_MAX_ENCRYPT_KEY_SIZE,
                                              &simpleProfileCBs );

    Where simpleProfileCBs is defined as follows:

    CONST gattServiceCBs_t simpleProfileCBs =
    {
      simpleProfile_ReadAttrCB,  // Read callback function pointer
      simpleProfile_WriteAttrCB, // Write callback function pointer
      NULL                       // Authorization callback function pointer
    };

    For reference, the documentation for GATTServApp_RegisterService is found here: http://software-dl.ti.com/lprf/simplelink_cc2640r2_latest/docs/blestack/ble_user_guide/doxygen/ble/html/group___g_a_t_t_serv_app.html#gab03b16f9f82020807d82ee44d2479acf

    The point of changing the code in ReadAttrCB, is that whenever a read occurs you specify the appropriate action for the profile to take based on what characteristic is read from. If read permissions are enabled for the specific characteristic you will want to store the characteristic's value (pAttr->pValue) into the value that will be sent back to the client device (pValue). A read from SIMPLEPROFILE_CHAR5 is handled as follows (in simpleProfile_ReadAttrCB) and should be the same as how you should handle a read for your characteristic:

          case SIMPLEPROFILE_CHAR5_UUID:
            *pLen = SIMPLEPROFILE_CHAR5_LEN;
            VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR5_LEN );
            break;

    Now GATT_ReadCharValue is to be called from the client device. Which in your current situation is the multi role device. This is already being called in multi_role_doGattRead before you changed it:

    bool multi_role_doGattRead(uint8_t index)
    {
      attReadReq_t req;
      uint8_t connIndex = multi_role_getConnIndex(mrConnHandle);
    
      // connIndex cannot be equal to or greater than MAX_NUM_BLE_CONNS
      MULTIROLE_ASSERT(connIndex < MAX_NUM_BLE_CONNS);
    
      req.handle = connList[connIndex].charHandle;
      GATT_ReadCharValue(mrConnHandle, &req, selfEntity);
    
      return (true);
    }

    You will need to make sure that the charHandle (req.handle) is setup to read from the characteristic handle that you want to read from. I believe it is currently set to read from Characteristic 1 so that will need to change based on what characteristic you want to read from.

    -Sy Su

  • Hi Sy Su,

    Can I send you my workspace in a private message?

    I don't understand how to solve the problem and it doesn't help when I try and change my service files.

    BR
    Elin
  • Hi Elin,

    Have you already gone through all of the Bluetooth training modules in SimpleLink Academy to get an understanding of Bluetooth and the stack?
    dev.ti.com/.../overview.html

    In addition to that, reading through the Developing a Bluetooth Low Energy Application section in the stack's User's Guide: dev.ti.com/.../index-cc26x2.html

    You are welcome to post your project so far in this thread.

    -Sy Su
  • Hi Sy Su,

    I have not gone through all training modules but I've read all of them and completed some of them.

    I do not wish to post my project publicly since this can cause problems for this project in the future.

    How is the charHandle changed to one of the sensors? Right now it isn't a handle to any of my characteristics since it does not enter "if(pMsg->msg.readRsp.len == sizeof(uint32_t))".

    How is the charReadValue reconstructed in multi_role_processGATTMsg?

    BR Elin 

  • Elin,

    To get the correct charHandle, you should take a look at where GATT_DiscCharsByUUID is called. Just before this is called the request parameters are setup which includes the specific UUID you are looking for to get it's handle:

            req.type.uuid[0] = LO_UINT16(SIMPLEPROFILE_CHAR1_UUID);
            req.type.uuid[1] = HI_UINT16(SIMPLEPROFILE_CHAR1_UUID);

    In your case you'll want to use the characteristic UUIDs for your sensor chars instead.

    Then when the request is complete, it is stored into charHandle as follows:

          // Store the handle of the simpleprofile characteristic 1 value
          connList[connIndex].charHandle
            = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3],
                           pMsg->msg.readByTypeRsp.pDataList[4]);

    For GATT_ReadCharValue, if read in the documentation for the function you will learn about the following:

    If the return status from this function is SUCCESS, the calling application task will receive a GATT_MSG_EVENT message with method:

    In the end after the events are processed, the read response is shown as the following:

    // After a successful read, display the read value
            Display_printf(dispHandle, MR_ROW_CUR_CONN, 0, "Read rsp: %d", pMsg->msg.readRsp.pValue[0]);


    Searching for the appropriate terms in the code and understanding the structure of it can go a long way here and help you find your answers.

    -Sy Su

  • Hi Sy Su, 

    When I change the UUIDs for the service and the characteristic, the option "Read GATT" and "Write GATT" disappears from the options when working with a device. Can you help with that? 

    Here is the project: ble5_multi_role_cc13x2r1lp_app.zip

    BR Elin 

  • Elin,

    The cause of this is occurring here in multi_role_doSelectConn: 

      if (connList[index].charHandle == 0)
      {
        // Initiate service discovery
        multi_role_enqueueMsg(MR_EVT_SVC_DISC, (void *)index);
    
        // Diable GATT Read/Write until simple service is found
        itemsToDisable = MR_ITEM_GATTREAD | MR_ITEM_GATTWRITE;
      }

    Can you confirm you are getting a valid charHandle for your characteristic AS_SENSOR_1 in here?

          connList[connIndex].charHandle
            = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3],
                           pMsg->msg.readByTypeRsp.pDataList[4]);

    -Sy Su

  • Okay, so by removing that it should work?

    Probably not since they are unchanged. How can I check that?

    Elin

  • Elin,

    You can check if they are changed or not by looking at my second question in my last post:

    Check to see if you are receiving a valid charHandle at this point in your code. Search for the following lines, set a breakpoint and check the value of it while debugging the code. If you look through the code, the charHandle is initialized as 0 so if it is still 0 after the charHandle is stored then it is invalid.

    connList[connIndex].charHandle
      = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3],
                     pMsg->msg.readByTypeRsp.pDataList[4]);

    -Sy Su