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.

CC2642R: BLE L2CAP CoC (Connection Oriented Channels) example

Part Number: CC2642R
Other Parts Discussed in Thread: LAUNCHXL-CC26X2R1, CC1352R, CC1352P, CC2652RB, SYSCONFIG, CC2640R2F

Hardware

This example has been written and tested on LAUNCHXL-CC26X2R1. It can be easily ported to CC2652RB, CC1352R, CC1352P devices.

Software

This example has been written for SDK 4.40. It should be easily ported to other SDK versions. Please consult the porting guide / migration guide of your SDK for additional information.

Peripheral

  1. Open CCS
  2. Import the simple_peripheral example
  3. Open simple_peripheral.syscfg with SysConfig. Then in RF Stacks > BLE enable "L2CAP Connection Oriented Channels". Save and close the file.
  4. Replace the whole content of the file simple_peripheral.c with the content of this file: SDK_4_40_simple_peripheral_l2cap.c. To help understanding, here is a diff file (vs the out-of-the-box simple_peripheral.c file): 
    simple_peripheral_diff_file.diff
     // Spin if the expression is not true
     #define SIMPLEPERIPHERAL_ASSERT(expr) if (!(expr)) simple_peripheral_spin();
     
    +// Hard coded PSM for passing data between central and peripheral
    +#define SBP_PSM             0x0080
    +
     /*********************************************************************
      * TYPEDEFS
      */
    @@ -325,6 +328,9 @@ static GAP_Addr_Modes_t addrMode = DEFAULT_ADDRESS_MODE;
     // Current Random Private Address
     static uint8 rpa[B_ADDR_LEN] = {0};
     
    +// Unique CoC CID
    +uint16_t cocCID;
    +
     /*********************************************************************
      * LOCAL FUNCTIONS
      */
    @@ -375,6 +381,10 @@ void simple_peripheral_handleNPIRxInterceptEvent(uint8_t *pMsg);  // Declaration
     static void simple_peripheral_sendToNPI(uint8_t *buf, uint16_t len);  // Declaration
     #endif // PTM_MODE
     
    +static uint8_t SimplePeripheral_processL2CAPMsg(l2capSignalEvent_t *pMsg);
    +static void SimplePeripheral_processL2CAPDataEvent(l2capDataEvent_t *pMsg);
    +static void SimplePeripheral_sendL2CAPData(uint8_t *params, uint16_t paramLen);
    +
     /*********************************************************************
      * EXTERN FUNCTIONS
      */
    @@ -548,6 +558,25 @@ static void SimplePeripheral_init(void)
       DevInfo_AddService();                        // Device Information Service
       SimpleProfile_AddService(GATT_ALL_SERVICES); // Simple GATT Profile
     
    +  l2capPsm_t psm;
    +
    +  psm.psm = SBP_PSM; // connHandle is PSM here
    +  psm.mtu = MAX_PDU_SIZE;
    +  psm.initPeerCredits = 0xFFFF;
    +  psm.peerCreditThreshold = 0;
    +  psm.maxNumChannels = 1;
    +  psm.pfnVerifySecCB = NULL;
    +  // Need to translate ICall task entity to OSAL for registration with stack
    +  psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);
    +
    +  bStatus_t registerStat = L2CAP_RegisterPsm(&psm);
    +
    +  if(registerStat != SUCCESS)
    +  {
    +    Display_printf(dispHandle, 8, 0, "Failed to register PSM with stat: %d",
    +                    registerStat);
    +  }
    +
       // Setup the SimpleProfile Characteristic Values
       // For more information, see the GATT and GATTServApp sections in the User's Guide:
       // http://software-dl.ti.com/lprf/ble5stack-latest/
    @@ -638,6 +667,10 @@ static void SimplePeripheral_init(void)
       // Inform Stack to Initialize PTM
       HCI_EXT_EnablePTMCmd();
     #endif // PTM_MODE
    +
    +  // Register for L2CAP Flow Control Events
    +  L2CAP_RegisterFlowCtrlTask(selfEntity);
    +
     }
     
     /*********************************************************************
    @@ -823,6 +856,16 @@ static uint8_t SimplePeripheral_processStackMsg(ICall_Hdr *pMsg)
           break;
         }
     
    +    case L2CAP_SIGNAL_EVENT:
    +      // Process L2CAP signal
    +      safeToDealloc = SimplePeripheral_processL2CAPMsg((l2capSignalEvent_t *)pMsg);
    +      break;
    +
    +    case L2CAP_DATA_EVENT:
    +      // Process L2CAP data
    +      SimplePeripheral_processL2CAPDataEvent((l2capDataEvent_t *)pMsg);
    +      break;
    +
         default:
           // do nothing
           break;
    @@ -1343,6 +1386,10 @@ static void SimplePeripheral_performPeriodicTask(void)
         SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, sizeof(uint8_t),
                                    &valueToCopy);
       }
    +
    +  static uint32_t l2capCounter = 0;
    +  SimplePeripheral_sendL2CAPData((uint8_t *)(&l2capCounter), sizeof(l2capCounter));
    +  l2capCounter++;
     }
     
     /*********************************************************************
    @@ -2547,5 +2594,107 @@ static void SimplePeripheral_menuSwitchCb(tbmMenuObj_t* pMenuObjCurr,
         Display_clearLine(dispHandle, SP_ROW_CONNECTION);
       }
     }
    +
    +/*********************************************************************
    + * @fn      SimplePeripheral_processL2CAPMsg
    + *
    + * @brief   Process L2CAP messages and events.
    + *
    + * @return  TRUE if safe to deallocate incoming message, FALSE otherwise.
    + */
    +static uint8_t SimplePeripheral_processL2CAPMsg(l2capSignalEvent_t *pMsg)
    +{
    +  uint8_t safeToDealloc = TRUE;
    +
    +  switch (pMsg->opcode)
    +  {
    +      case L2CAP_CHANNEL_ESTABLISHED_EVT:
    +      {
    +        l2capChannelEstEvt_t *pEstEvt = &(pMsg->cmd.channelEstEvt);
    +
    +        cocCID = pEstEvt->CID;
    +
    +        // Give max credits to the other side
    +        L2CAP_FlowCtrlCredit(pEstEvt->CID, 0xFFFF);
    +      }
    +      break;
    +
    +      case L2CAP_SEND_SDU_DONE_EVT:
    +      {
    +          if (pMsg->hdr.status == SUCCESS)
    +          {
    +            // Handle Success
    +            Display_printf(dispHandle, 16, 0, "SDU sent");
    +          }
    +          else
    +          {
    +            // Handle fail
    +            Display_printf(dispHandle, 16, 0, "SDU send failed");
    +          }
    +      }
    +      break;
    +  }
    +
    +  return (safeToDealloc);
    +}
    +
    +/*********************************************************************
    + * @fn      SimplePeripheral_processL2CAPDataEvent
    + *
    + * @brief   Handles incoming L2CAP data
    + *
    + * @param   pMsg - pointer to the signal that was received
    + *
    + * @return  none
    + */
    +static void SimplePeripheral_processL2CAPDataEvent(l2capDataEvent_t *pMsg)
    +{
    +  // This application doesn't care about other L2CAP data events other than CoC
    +  // It is possible to expand this function to support multiple COC CID's
    +  if (pMsg->pkt.CID == cocCID)
    +  {
    +
    +    // Clear RX data lines before printing
    +    Display_clearLines(dispHandle, 7,9);
    +
    +    uint32_t rxDataCtr = BUILD_UINT32(pMsg->pkt.pPayload[0],
    +                                      pMsg->pkt.pPayload[1],
    +                                      pMsg->pkt.pPayload[2],
    +                                      pMsg->pkt.pPayload[3]);
    +
    +    // Process data
    +    Display_printf(dispHandle, 15, 0, "L2CAP RX: %d", rxDataCtr);
    +  }
    +}
    +
    +/*********************************************************************
    + * @fn      SimplePeripheral_sendL2CAPData
    + *
    + * @brief   Send L2CAP data to the peer
    + *
    + * @param   params - data to send
    + * @param   paramLen - len of data
    + *
    + * @return  none
    + */
    +static void SimplePeripheral_sendL2CAPData(uint8_t *params, uint16_t paramLen)
    +{
    +  l2capPacket_t pkt;
    +  bStatus_t stat;
    +
    +  if (cocCID)
    +  {
    +    pkt.CID = cocCID;
    +    pkt.pPayload = params;
    +    pkt.len = paramLen;
    +
    +    stat = L2CAP_SendSDU(&pkt);
    +    if(stat != SUCCESS)
    +    {
    +      Display_printf(dispHandle, 17, 0, "L2CAP Data send error: %d", stat);
    +    }
    +  }
    +}
    +
     /*********************************************************************
     *********************************************************************/
    
  5. Build the example and flash it on the device.

Central

  1. (If not already done) Open CCS
  2. Import the simple_central example
  3. Open simple_peripheral.syscfg with SysConfig. Then in RF Stacks > BLE enable "L2CAP Connection Oriented Channels". Save and close the file.
  4. Replace the whole content of the file simple_central.c with the content of this file: SDK_4_40_simple_central_l2cap.c. To help understanding, here is a diff file (vs the out-of-the-box simple_central.c file): 
    simple_central_diff_file.diff
    @@ -150,6 +150,8 @@
     //Default connection handle which is set when group member is created
     #define GROUP_INITIALIZED_CONNECTION_HANDLE  0xFFFF
     
    +// Hard coded PSM for passing data between central and peripheral
    +#define SBC_PSM             0x0080
     
     /*********************************************************************
      * TYPEDEFS
    @@ -337,6 +339,9 @@ static uint8_t numGroupMembers = 0;
     //Connection in progress to avoid double initiate
     static groupListElem_t *memberInProg;
     
    +// Unique CoC CID
    +uint16_t cocCID;
    +
     /*********************************************************************
      * LOCAL FUNCTIONS
      */
    @@ -388,6 +393,11 @@ static void SimpleCentral_scanCb(uint32_t evt, void* msg, uintptr_t arg);
     static void SimpleCentral_menuSwitchCb(tbmMenuObj_t* pMenuObjCurr,
                                            tbmMenuObj_t* pMenuObjNext);
     
    +static bStatus_t SimpleCentral_openL2CAPChanCoc(uint16_t psm_id);
    +static void SimpleCentral_processL2CAPDataEvent(l2capDataEvent_t *pMsg);
    +static void SimpleCentral_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg);
    +static void SimpleCentral_sendL2CAPData(uint8_t *params, uint16_t paramLen);
    +
     /*********************************************************************
      * EXTERN FUNCTIONS
      */
    @@ -789,7 +799,11 @@ static uint8_t SimpleCentral_processStackMsg(ICall_Hdr *pMsg)
         }
     
         case L2CAP_SIGNAL_EVENT:
    -      // place holder for L2CAP Connection Parameter Reply
    +      SimpleCentral_processL2CAPSignalEvent((l2capSignalEvent_t *)pMsg);
    +      break;
    +
    +    case L2CAP_DATA_EVENT:
    +      SimpleCentral_processL2CAPDataEvent((l2capDataEvent_t *)pMsg);
           break;
     
         default:
    @@ -1501,6 +1515,8 @@ static void SimpleCentral_handleKeys(uint8_t keys)
      */
     static void SimpleCentral_processGATTMsg(gattMsgEvent_t *pMsg)
     {
    +  static bool l2capChannelOpen = false;
    +
       if (linkDB_Up(pMsg->connHandle))
       {
         // See if GATT server was unable to transmit an ATT response
    @@ -1522,9 +1538,25 @@ static void SimpleCentral_processGATTMsg(gattMsgEvent_t *pMsg)
           }
           else
           {
    -        // After a successful read, display the read value
    -        Display_printf(dispHandle, SC_ROW_CUR_CONN, 0,
    -                       "Read rsp: 0x%02x", pMsg->msg.readRsp.pValue[0]);
    +          uint16_t psm = 0x0080; // Should be the value set for SBP_PSM in simple_peripheral
    +
    +           // Print the L2CAP PSM read from GATT
    +           Display_print1(dispHandle, 16, 0, "L2CAP PSM: %d", psm);
    +
    +           if(!l2capChannelOpen)
    +           {
    +             bStatus_t stat = SimpleCentral_openL2CAPChanCoc(psm);
    +             if(stat != SUCCESS)
    +             {
    +               Display_printf(dispHandle, 17, 0, "L2CAP err: %d", stat);
    +             }
    +             else
    +             {
    +               l2capChannelOpen = true;
    +               Display_printf(dispHandle, 17, 0, "L2CAP CoC opened with PSM: %d",
    +                               psm);
    +             }
    +           }
           }
         }
         else if ((pMsg->method == ATT_WRITE_RSP)  ||
    @@ -2840,6 +2872,139 @@ static void SimpleCentral_menuSwitchCb(tbmMenuObj_t* pMenuObjCurr,
       }
     }
     
    +/*********************************************************************
    + * @fn      SimpleCentral_openL2CAPChanCoc
    + *
    + * @brief   Opens a communication channel between Master/Slave
    + *
    + * @return  none
    + */
    +static bStatus_t SimpleCentral_openL2CAPChanCoc(uint16_t psm_id)
    +{
    +  l2capPsm_t psm;
    +  bStatus_t ret;
    +
    +  psm.initPeerCredits = 0xFFFF;
    +  psm.maxNumChannels = 1;
    +  psm.mtu = MAX_PDU_SIZE;
    +  psm.peerCreditThreshold = 0;
    +  psm.pfnVerifySecCB = NULL;
    +  psm.psm = psm_id;
    +  psm.taskId = ICall_getLocalMsgEntityId(ICALL_SERVICE_CLASS_BLE_MSG, selfEntity);
    +
    +  ret = L2CAP_RegisterPsm(&psm);
    +
    +  if (ret == SUCCESS)
    +  {
    +    L2CAP_ConnectReq(scConnHandle, SBC_PSM, SBC_PSM);
    +  }
    +
    +  return (ret);
    +}
    +
    +
    +/*********************************************************************
    + * @fn      SimpleCentral_processL2CAPSignalEvent
    + *
    + * @brief   Handle L2CAP signal events
    + *
    + * @param   pMsg - pointer to the signal that was received
    + *
    + * @return  none
    + */
    +static void SimpleCentral_processL2CAPSignalEvent(l2capSignalEvent_t *pMsg)
    +{
    +    switch (pMsg->opcode)
    +    {
    +        case L2CAP_CHANNEL_ESTABLISHED_EVT:
    +        {
    +          l2capChannelEstEvt_t *pEstEvt = &(pMsg->cmd.channelEstEvt);
    +
    +          cocCID = pEstEvt->CID;
    +
    +          // Give max credits to the other side
    +          L2CAP_FlowCtrlCredit(pEstEvt->CID, 0xFFFF);
    +        }
    +        break;
    +
    +        case L2CAP_SEND_SDU_DONE_EVT:
    +        {
    +            if (pMsg->hdr.status == SUCCESS)
    +            {
    +              // Handle Success
    +              Display_printf(dispHandle, 20, 0, "SDU sent");
    +            }
    +            else
    +            {
    +              // Handle fail
    +              Display_printf(dispHandle, 20, 0, "SDU send failed");
    +            }
    +        }
    +        break;
    +    }
    +}
    +
    +/*********************************************************************
    + * @fn      SimpleCentral_sendL2CAPData
    + *
    + * @brief   Send L2CAP data to the peer
    + *
    + * @param   params - data to send
    + * @param   paramLen - len of data
    + *
    + * @return  none
    + */
    +static void SimpleCentral_sendL2CAPData(uint8_t *params, uint16_t paramLen)
    +{
    +  l2capPacket_t pkt;
    +  bStatus_t stat;
    +
    +  if (cocCID)
    +  {
    +    pkt.CID = cocCID;
    +    pkt.pPayload = params;
    +    pkt.len = paramLen;
    +
    +    stat = L2CAP_SendSDU(&pkt);
    +    if(stat != SUCCESS)
    +    {
    +      Display_printf(dispHandle, 19, 0, "L2CAP Data send error: %d", stat);
    +    }
    +  }
    +}
    +
    +/*********************************************************************
    + * @fn      SimpleCentral_processL2CAPDataEvent
    + *
    + * @brief   Handles incoming L2CAP data
    + *
    + * @param   pMsg - pointer to the signal that was received
    + *
    + * @return  none
    + */
    +static void SimpleCentral_processL2CAPDataEvent(l2capDataEvent_t *pMsg)
    +{
    +  static uint32_t cocDummyInfo = 0xFFFFFFFF;
    +  // This application doesn't care about other L2CAP data events other than CoC
    +  // It is possible to expand this function to support multiple COC CID's
    +  if (pMsg->pkt.CID == cocCID)
    +  {
    +    // Clear RX data lines before printing
    +    Display_clearLines(dispHandle, 7,9);
    +
    +    uint32_t rxData = BUILD_UINT32(pMsg->pkt.pPayload[0],
    +                                   pMsg->pkt.pPayload[1],
    +                                   pMsg->pkt.pPayload[2],
    +                                   pMsg->pkt.pPayload[3]);
    +
    +    // Process data
    +    Display_printf(dispHandle, 18, 0, "L2CAP RX: %d", rxData);
    +
    +    // Send a response, to show bidirectional L2CAP activity
    +    SimpleCentral_sendL2CAPData((uint8_t *)(&cocDummyInfo), sizeof(cocDummyInfo));
    +    cocDummyInfo--;
    +  }
    +}
     
     /*********************************************************************
     *********************************************************************/
    
  5. In the file simple_central_menu.c replace the line
    MENU_ITEM_ACTION(scMenuPerConn,0,"GATT Read",          SimpleCentral_doGattRead)

    By:

    MENU_ITEM_ACTION(scMenuPerConn,0,"Read L2CAP PSM from GATT",          SimpleCentral_doGattRead)

    This is because we are going to re-purpose the usual GATT read command into the L2CAP COC establishment.

    Rebuild the project and flash it on the device.

Example usage

Once the two examples are flashed, I recommend to open two serial terminals to display the log of both of them (UART settings are the same as the out-of-the box examples).

For these examples, you only have to use the two-button-menu (i.e. the two buttons of the launchpad) of the central device.

  1. Click "Discover device".
  2. Click "Connect to"
  3. Select the proper device address (use the log of the peripheral device to find its address)
  4. Click "Work with"
  5. Select the proper device
  6. Wait for encryption to finish and GATT discovery to complete
  7. The menu "Read L2CAP PSM from GATT" will then appear
  8. Click "Read L2CAP PSM from GATT" ("Read L2CAP PSM from GATT" might be called "GATT Read" if you have not modified the simple_central_menu.c file)

This is going to open the L2CAP-COC. The devices will then use it to send messages to each other. (Note: we use a clock already defined in the out-of-the-box example to trigger the message send - this can be done differently if needed).

For the moment only one connection is supported and the PSM are hard coded.

Other resources

The user's guide is probably the most valuable resource.

In addition, the examples project_zero, simple_peripheral_oad_offchip and simple_peripheral_oad_onchip implements an example of L2CAP COC connection.

For CC2640R2F devices, this thread provides examples.